Skip to main content

Destination Override Approval

Sending swap output to an address other than your own contract needs more than that address being authorized on the pair. Your contract must also grant itself a per-destination approval on the pool, and that approval carries a budget and a deadline.

This applies only when you name a third-party destination. Swapping to your own contract needs none of it.

Why your contract has to grant it, not you​

The pool keys the approval on msg.sender. In a contract-to-contract swap your contract is msg.sender, so the approval has to be granted by the contract itself. Calling the pool from your owner wallet writes a record the swap will never read.

The approval is keyed on the exact four-part route:

(trader, fromToken, toToken, destination)

Change any one of the four and it is a different record.

The errors​

ErrorMeaning
never grantedno approval record has ever existed for this exact route
expireda record exists but its deadline has passed
budget exhausteda record exists and is in date, but the remaining budget is smaller than the amount you asked for

These three are mutually exclusive and they tell you different things — read them as a state, not as one failure.

Reading the record before you swap​

getDestinationOverrideApproval(trader, fromToken, toToken, destination)
→ (deadline, remainingAmount)

Two details are worth getting right, because either one returns (0, 0) — the same result as an approval that was never granted:

  • trader is the first argument, not the third.
  • The return is (deadline, remainingAmount) — deadline first.

Checking the result against a value you can predict is a quick way to confirm you have both right.

deadlineremainingAmountState
00never granted
in the pastanythingexpired
in the future0drained — the budget is spent
in the future> 0usable, up to remainingAmount

Granting an approval​

Two errors come from the grant itself rather than the swap:

ErrorMeaning
a budget of 0not accepted — grant a real amount
a deadline earlier than the current blocknot accepted — the deadline may be the current block or later

The current block itself is accepted: an approval granted that way is usable in that block and expired from the next. So you cannot create an approval that was already expired when granted — to see the expiry behavior you have to grant a short deadline and let it pass.

A block number you read off-chain is already stale by the time your transaction mines. Add a margin rather than passing the value you read unchanged, or the grant can land expired — or be rejected outright if the chain has moved past it.

Spending is one-way​

Consuming an approval reduces remainingAmount but never clears deadline. So once a route has been granted even once, it can never again be reported as never granted — only as expired or exhausted.

Re-granting resets both fields, so an exhausted route is reusable. Partial spends work as you would expect: a 1.0 budget spent 0.4 leaves 0.6, and a subsequent request for 0.8 is rejected while 0.6 succeeds.

Grant and swap together, or separately​

Granting and swapping in one transaction is the safer pattern, and the one to default to. The approval cannot be consumed by anything else in between, and it is left drained and past its deadline as soon as the block closes. The caller contract template does exactly this, and offers no way to do otherwise.

Splitting them is legitimate — you might want to grant a budget once and spend it across several swaps. Two things to know before you design for it. Your contract needs a function that grants without swapping, and a swap path that does not re-grant, otherwise the next swap simply overwrites the budget you set. And the deadline is then real elapsed blocks, so an approval you granted earlier may have expired before you use it.

What is checked before any of this​

Two things are settled before your approval is even looked at: whether the output token may go to a third party at all, and whether the recipient is authorized on the pair. If the output is a stablecoin, no approval of any kind will help — stablecoin output can only go to the account that did the swap. Deliver to your own contract and transfer onward. See When a swap is refused.

Your own contract may also apply its own destination checks before it ever calls the pool. Those are yours, not the pool's, and the pool's errors say nothing about them.