When you swap tokens on most DEX aggregators, you specify how much you want to send and accept whatever comes back. That model works fine for traders. It breaks for payments.
If someone wants to pay exactly 100 USDC for a product, “approximately 100 USDC” is a different thing — and the merchant’s settlement system will reject it.
This is the problem we solved in KanaAggregator with exact-output swaps.
The Standard Model
Most AMMs use the constant-product formula:
x * y = k
Given input Δx, the output is:
Δy = y - k / (x + Δx * (1 - fee))
That’s easy to compute forward. You know what you put in, you calculate what you get out.
Reversing the Math
For exact-output, we want to find Δx given a target Δy. Rearranging:
Δx = (k / (y - Δy) - x) / (1 - fee)
In Move, this looks like:
public fun get_amount_in(
amount_out: u64,
reserve_in: u64,
reserve_out: u64,
fee_bps: u64,
): u64 {
let numerator = (reserve_in as u128) * (amount_out as u128) * 10000;
let denominator = ((reserve_out - amount_out) as u128) * (10000 - (fee_bps as u128));
((numerator / denominator) as u64) + 1 // ceiling division
}
The + 1 is important. Integer division truncates, which means without the ceiling you’d calculate an input that’s one unit short — the swap would fail on-chain.
Multi-Path Complexity
Single-hop exact-output is straightforward. The complexity comes with multi-path routing. If a swap routes through three pools to get from token A to token D:
A → B → C → D (target: exactly Δd)
You have to work backwards:
- Find
Δcneeded to produceΔdin the C→D pool - Find
Δbneeded to produceΔcin the B→C pool - Find
Δaneeded to produceΔbin the A→B pool
Each step introduces ceiling rounding, so the final Δa is slightly overstated. The contract sends back the unused portion as a refund to the caller.
Why Move Makes This Cleaner
Move’s resource model helps here in a subtle way. When you hold an intermediate token inside a transaction, it’s a first-class resource — the compiler guarantees you can’t accidentally drop it or double-spend it. In Solidity you’d need careful balance checks at each step; in Move the type system enforces it.
The abort semantics also give you cleaner all-or-nothing guarantees. If any leg of the path can’t produce the required output, the entire transaction reverts with a typed error code, not a generic revert.
Production Results
After shipping exact-output swaps in KanaAggregator, our payment flows saw:
- Zero settlement mismatches (previously ~0.3% of payment transactions had rounding issues)
- Slightly higher gas (the reverse calculation is more expensive), offset by removing retry logic on the payment layer
If you’re building payment rails on Aptos, exact-output is worth the extra computation. Certainty at settlement time is not optional.