Skip to content
Go back

Exact-Output Swaps in Move: Reverse AMM Math on Aptos

Edit page

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:

  1. Find Δc needed to produce Δd in the C→D pool
  2. Find Δb needed to produce Δc in the B→C pool
  3. Find Δa needed to produce Δb in 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:

If you’re building payment rails on Aptos, exact-output is worth the extra computation. Certainty at settlement time is not optional.


Edit page
Share this post:

Previous Post
CCTP v2 in Production: Cross-Chain Payments Under 60 Seconds