# The two entry points, contrasted by what each trusts from calldata

> Canonical: https://blazephoenix.xyz/learn/two-entry-points-calldata-trust
> License: CC BY 4.0 (attribution + link) · © 2026 BlazePhoenix
> Updated: 2026-08-13



The protocol offers two ways to swap, and the difference between them is not convenience. It is how much of the answer the contract works out for itself, which is the same as asking how much of calldata it has to believe. Everything else about the routing architecture follows from that split, so it is worth stating precisely and in both directions.

A protocol that offered only the solving path would be charging every user for a guarantee many of them do not want. One that offered only the calldata path would have nothing to distinguish it from the off-chain-solved architecture it was built to answer. Offering both is the honest arrangement provided the difference is documented — which is what this article is for.

## The calldata path: a complete route, trusted field by field

This path takes a complete route as an argument: the pools to use, the venue kinds, their parameters, and how the input is divided among them. The caller has solved the problem elsewhere and is asking the contract to carry it out. Three variants exist, differing only in how the input tokens arrive — a plain transfer, a signed permit, or native currency — and all three converge on the same execution core.

What the contract still does for itself on this path is what stops the arrangement from being blind obedience. It measures actual balance deltas rather than trusting declared amounts. It re-quotes each hop on-chain. It recomputes impact and derives its own output floor in-frame, anchored to a quote no caller supplies. And it rescales per hop when the amount that really arrived differs from the amount that was promised. The plan is trusted; the arithmetic is not.

The trusted surface is nonetheless large, and a reader deciding how much to trust an interface should start with the enumeration rather than with the reassurance. Used as given: the pool address that will be called, the expected output each leg is measured against, the venue kind and its parameters, the recipient, and the minimum. Everything in that list is an assumption about the caller.

## The on-chain-solving path: six scalars

This path takes the two tokens, the amount, a minimum, a recipient and a deadline, and works out the route itself, calling the solver in the same frame before any tokens move. It then hands the resulting plan to the same execution core the other path uses. Everything in the trusted-surface list above collapses into quantities the contract derived from public state during the transaction.

That hand-off has an unusual shape and a later section depends on it. A route computed in memory cannot be passed directly to a function expecting calldata, so the contract calls itself externally, converting memory to calldata across the call boundary. The inner function is guarded to accept only calls originating from the contract itself. Both paths rejoin at that point and share every line of code from there on — though not every behaviour.

```
                        calldata path              solving path
route comes from        the caller                 the contract, in-frame
trusted from calldata   pools, kinds, params,      six scalars only
                        splits, expectations
freshness of the plan   as stale as the caller's   same block, same transaction
                        search
gas                     lower                      higher
verifiable by the user  only its outcome           its derivation as well
```

## The same line, two behaviours: who the caller is

Sharing an execution core is usually described as a safety property, and mostly it is: one code path means one thing to audit. The exception is any line whose meaning depends on the calling frame, and there is at least one such line, which produced a real defect.

Inside the inner frame created by the self-call, the caller is the contract itself. The refund of unspent input — written, in the obvious way, as a transfer back to the caller — therefore becomes a transfer to the contract, and the user's change is stranded rather than returned, recoverable only through the administrative rescue path. The calldata path is unaffected, because there the caller is the user. Two paths that share every line do not share every behaviour when a shared line reads a value that differs between frames.

This was found while writing the architecture chapter. A fix exists — the payer is now passed explicitly rather than inferred from the calling frame — and at the time of writing it had not reached the published release. It is printed rather than held back because the alternative, publishing a paper that celebrates this entry point while knowing this, was not available to us.

## The protections that are not protocol-wide

The believability band and the capacity clamp live inside the solver. A route assembled by hand and submitted through the calldata path receives neither. That is not a bug — the caller of that path has explicitly taken the routing decision on themselves — but it means those protections are properties of one entry point, and describing them as properties of the protocol would be a category error that shows up in exactly the place a user would be harmed by it.

The boundaries that can enforce nothing are exactly the boundaries that leak, and this is the second instance of that corollary in the codebase. The first is the read-only preview surface. Both are the same structural fact seen twice: a guarantee restated somewhere it is not computed drifts, because nothing fails when it does.

What both paths do share is the enforcement layer, and it is worth being exact about which properties survive on either. Both refuse a swap submitted with no minimum output, at every entry point, where the reference implementation the industry copies accepts zero and lets the trade proceed unprotected. Both derive the protocol floor in-frame and combine it with the caller's limit by a maximum, so a caller can tighten and never loosen. Both measure the recipient's balance change rather than trusting a venue's report. Both emit the quote, the realised amount and the applied floor.

## Reentrancy, and two deliberate exceptions

Entry points that move value are guarded against reentrancy, and the guard is transient rather than stored, so it costs a fraction of the usual amount and cannot be left set by a failed transaction. Two places deliberately sit outside it, and both are the kind of exception that should be written down rather than discovered.

The internal hand-off between the solving entry point and the execution core is not guarded, because the outer call already holds the lock and a second guard would reject every legitimate route; it is instead restricted to calls originating from the contract itself. And the venue callbacks — which certain venue families require, since the pool calls back to collect its input — authenticate their caller and cap what may be pulled to the leg being executed.

The failure-signalling discipline is the same across both paths and is worth naming because it removes a whole class of second code path. Where an internal routine cannot produce an answer, it returns a value engineered to trip a guard the caller already has, rather than a plausible number that would pass unnoticed. The failure path reuses an existing check, so there is nothing new to get wrong.

## How to choose, and what neither path fixes

Take the calldata path if you have your own solver, want the cheaper execution, and are prepared to own the routing decision including the believability filtering the solver would otherwise have done for you. Take the solving path if what you want is a route no operator produced and you are willing to pay for it in gas and in route quality.

Neither path fixes the thing that actually bounds a user's downside, and the article would be dishonest without the sentence. The minimum is supplied by the caller on both paths, and it is the quantity every other bound falls back to. A compromised front end does not need to break any bound: it proposes a minimum that is non-zero but terrible, which passes every check the contract can perform, because the contract cannot know what the user would have chosen. Choosing the solving path removes the operator from the routing decision and leaves them in the minimum-setting decision, which is where the larger exposure lives.

**Verify it yourself:** compare the two entry points' signatures on-chain: one takes a route structure, the other takes six scalars — the size of that difference is the size of the trusted surface, and it is readable from the ABI without trusting any description of it

Related: https://blazephoenix.xyz/learn/control-split · https://blazephoenix.xyz/learn/invariant-decomposition-law · https://blazephoenix.xyz/learn/quotes-are-statements-about-the-past
