Why Exact-Output Swaps Leave Dust in Treasury Contracts
Exact-output swaps cap input and fix the amount received; wrappers that keep the unused balance can turn a routine refund into treasury dust.
The Blockchain Brief Desk5 min read
Exact-output swaps can leave treasury dust when a wrapper supplies more input than the pools consume and does not return the difference. The swap fixes the amount of output; the input amount varies with pool state, subject to a maximum. Any surplus stays wherever the wrapper left it. It does not become a protocol fee or enter the liquidity pool.
That distinction matters for treasury accounting. A contract may collect an explicit fee from the input actually spent, while a separate, untracked balance accumulates because each call moved in a maximum amount and failed to settle its remainder. The remedy starts with tracing token balances and transfers through the wrapper, router and recipient. For the separate mechanics of building a route across chains, see QuickSwap Ethereum-to-Solana routing details.
How does an exact-output swap leave tokens behind?
An exact-output call specifies the desired output and a ceiling for input. In Uniswap v3, for example, a wrapper can transfer the full amountInMaximum from the caller, approve the router for that amount, and call exactOutputSingle. The router executes against the pool and returns the actual input amount. If that amount is below the ceiling, the wrapper must return the difference and clear the remaining approval.
In a multi-hop exact-output route, the router works backward from the required final output. Each pool determines how much of its input token is needed to produce the preceding hop’s output. The route succeeds only if total input stays within the maximum. The pool receives what the swap owes it; the unused prefunded balance remains with the wrapper unless the wrapper transfers it elsewhere.
That balance is the source of the dust. Suppose a treasury contract supplies a maximum of a token, but the swap consumes less. If the code records only the fee and output while omitting the refund, the leftover token remains on the contract. Later calls can add more. The balance may be small per transaction, but it is still treasury property if the treasury funded it.
Is unused input the same as a protocol fee?
No. A fee is an amount the contract deliberately charges under its fee rules. Unused input is the difference between the amount made available and the amount the swap consumed. It belongs to whoever funded the call unless a separate, explicit rule assigns it elsewhere.
Mixing those categories creates accounting errors. If the wrapper calculates a fee from the maximum rather than the actual input, it may charge against tokens that never entered the swap. If it sweeps the contract’s entire token balance as revenue, it may also collect leftovers from earlier calls or funds deposited for another purpose. A balance held by the contract is not proof that the current caller supplied it.
Exact-input swaps have a different input rule: the caller specifies how much to sell, while the output varies subject to a minimum. That usually avoids an input ceiling refund, though partial fills, route design, or token behavior can still leave balances in a wrapper. The useful distinction is not the interface label alone; it is whether the contract tracks what entered, what the router consumed, what it owes as fees, and what it must return.
How should a treasury wrapper account for the remainder?
Use per-call balance changes to separate current funds from pre-existing balances. Record the input-token balance before pulling funds, then measure the amount received and the balance after the router returns. Refund the unused portion to the original funder, and clear the router allowance. Keep the fee calculation tied to the documented fee base, such as actual input spent, rather than to the maximum merely approved or transferred.
- Check that the router’s reported input does not exceed the authorized maximum.
- Refund the unused input to the address that funded the swap.
- Reset any residual allowance after execution, including after code paths that can return without spending it.
- Account for fees separately from refunds, using the contract’s stated fee basis.
Balance-delta accounting also needs token assumptions. Fee-on-transfer tokens can make the amount received differ from the amount requested. Rebasing tokens can change balances independently of transfers. A wrapper that assumes ordinary ERC-20 behavior may miscalculate the refund or fee for either case. It should reject unsupported tokens or measure actual balance changes at each transfer boundary.
A treasury sweep function needs the same care. A function that transfers the contract’s whole balance can sweep old dust, but it cannot establish who owns that dust or which transaction created it. Restrict the destination and caller, emit records for recovered balances, and avoid presenting recovery as a substitute for refund logic. If the contract is meant to retain surplus, define that rule explicitly and include it in treasury accounting.
What should an operator check when dust appears?
Start with the wrapper’s call path: identify who transfers the maximum, who approves the router, which address receives output, and what happens after the router returns its actual input. Compare token balance changes around the call with the reported input and any fee transfer. Then check whether residual approvals or later sweep functions can move the balance.
The practical rule is simple: treat amountInMaximum as a spending limit, not as the amount spent or earned. The wrapper should settle the actual input, the explicit fee, and the refund as separate amounts. If code does not show where each amount goes, treasury dust is an accounting gap, not an unavoidable feature of exact-output swaps.