Exact-in vs exact-out: routing a swap backwards
Selling an exact amount and buying an exact amount sound symmetric. Exact-out routing runs backwards, rounds the other way and settles differently.
Most swaps are exact-in: you sell a fixed amount and receive whatever the route pays. Sometimes you need the opposite. You owe exactly 1,000 USDT, a contract expects an exact payment, or you want precisely 0.5 BNB for gas and no more. That is an exact-out swap, which tirio.io calls buy mode: you fix the amount you receive and the route works out how little it can spend.
It sounds like the same problem reversed. In practice it changes how every pool is priced, how a route is searched, which way numbers round and how the transaction settles. Here is how.
The two questions
- Exact-in asks: if I put in
x, how much comes out? Information flows forward, from your token to the one you want. Each hop's output becomes the next hop's input. - Exact-out asks: to get exactly
yout, how much must go in? Information flows backward. You start at the token you want and work towards the one you hold.
Both have to be answered with the same integer math the pools use, but they use different functions and round in different directions.
Pricing a pool backwards
For a constant-product pool, the exact-out function is the inverse of the usual one. The Uniswap V2 library's getAmountIn computes
amountIn = reserveIn × amountOut × 1000 ÷ ((reserveOut − amountOut) × 997) + 1
with integer division, then adds one unit. That final + 1 matters. Integer division rounds down, and an input that is one unit too small would deliver slightly less than the target. Rounding up guarantees the pool pays at least what was asked.
A worked example with PancakeSwap V2's constants (9975 and 10000, a 0.25 % fee). Take a pool with 1,000 BNB and 770,000 USDT. To receive exactly 1,000 USDT:
getAmountIngives 1.303649240135449157 BNB.- Running that amount forward through the pool returns 1,000.000000000000000727 USDT, just over the target.
- One wei less would return 999.999999999999999961 USDT, just under it.
The inverse is exact to the last unit, and it errs on the side of delivering the full amount.
Concentrated-liquidity pools support exact output natively. In a Uniswap V3-style swap, a negative amount means "this much must come out," and the pool walks its ticks until it has computed the input required, rounding in its own favour. Our engine ports that exact-output path of the swap math and checks it against the official quoter's exact-output function.
Searching backwards
A multi-hop exact-out route is computed from the end. To deliver 1,000 USDT through BNB → BTCB → USDT, the engine first asks how much BTCB the last pool needs to pay out exactly 1,000 USDT, then how much BNB the first pool needs to pay out that much BTCB. Every hop's required input becomes the previous hop's required output.
Splitting works the other way round too. Exact-in splitting divides your input to maximise total output. Exact-out splitting divides the target output across paths to minimise the total input, gas included.
Settling an exact-out swap
The transaction has to deliver an exact output while spending an input that is only known approximately in advance, because the market can move before it is mined. That is solved with a ceiling.
- The minimum output is the target itself. If the recipient would receive less than the exact amount, the whole swap reverts.
- The input has a maximum. The quote's
maxInis the expected input plus the fee and your slippage tolerance. The Router may pull at most that much, and it pulls only what the route actually needs. - Pools are paid what they ask at execution time. The route pays each pool its real price at the moment it runs, not the price we estimated, and never more than the ceiling allows.
- Unused native BNB comes back. When you pay with BNB, the transaction carries
maxInas its value, and the Router refunds whatever the route did not use to you in the same transaction.
A real example
On 2026-10-01 we asked Tirio's public API for exactly 100 USDT, paid in BNB, with the default 0.5 % slippage. The simulated quote came back with:
| Field | Value |
|---|---|
mode | exactOut |
amountOut and minOut | 100 USDT, both |
amountIn (expected) | 0.129786 BNB |
maxIn (ceiling) | 0.130435 BNB |
tx.value | equal to maxIn |
| route | wrap to WBNB, then one PancakeSwap V3 pool |
The ceiling is exactly 0.5 % above the expected input. If the market had stayed put, the swap would have spent about 0.129786 BNB, delivered exactly 100 USDT and sent the remaining 0.00065 BNB back in the same transaction.
What changes for you
- Fees come from the input. On tirio.io the 3 bps Tirio fee is part of the expected input in buy mode. Because the output is fixed, there is no positive slippage to keep: any improvement shows up as a smaller input.
- You see a maximum paid, not a minimum received. The details list the most the swap can cost. Whatever the route does not need never leaves your wallet.
- The input token needs no transfer tax. An exact-out route has to know precisely what arrives in each pool, so it pays with tokens that move their full amount. BNB, the major stablecoins and other untaxed tokens qualify.
- Exact-out routes use pools with closed-form inverse math, such as constant-product and concentrated-liquidity pools, so that every required input can be computed to the unit.
When to use which
Use exact-in when you know what you want to spend: selling a position, swapping a balance, spending a budget. Use exact-out when you know what you need to end up with: repaying a precise amount, funding an exact payment, topping up gas. Both settle in one transaction with a hard bound on the side you did not fix.
The swapping guide shows buy mode in the app, and the API docs describe amountOut, maxIn and the refund for integrators.