TWAP / DCA

Time-Weighted Average Price (TWAP) and Dollar-Cost Averaging (DCA) are order archetypes that release liquidity over a fixed schedule. They are authored as SwapVM programs (SwapVM is the Aqua swap engine) and execute on the limit-order router, not on the Aqua swap router. Both are implemented by the _twap instruction, which combines a linear unlock schedule with a Dutch auction decay on each interval's sub-order.

A TWAP sells a fixed total amount evenly over duration. DCA is the same program executed periodically, where each execution fills the currently unlocked tranche.

The maker's tokens stay in their own wallet under a revocable, per-chain, per-token allowance and are pulled per tranche only as fills occur. Fills are not guaranteed: a tranche can be skipped when it is illiquid, and the schedule expresses intended pacing, not a promised outcome.


Core instruction

Instruction Role
_twap Manages the unlock schedule, the Dutch auction per tranche, and internal invalidation

_twap is a terminal instruction. It replaces the balance-and-swap formula combination used in ordinary limit orders.


Program

Solidity
1
2
3
4
5
6
7
8
9
10
11
Program memory program = ProgramBuilder.init(_opcodes());
bytes memory bytecode = bytes.concat(
    program.build(_twap, TWAPArgsBuilder.build(TwapArgs({
        balanceIn:                 totalAmountIn,   // Total input to sell over duration
        balanceOut:                totalAmountOut,  // Total output expected
        startTime:                 startTime,       // uint40 — start of schedule
        duration:                  duration,        // uint40 — total schedule length (seconds)
        priceBumpAfterIlliquidity: bumpBps,         // uint64 — price improvement after a missed interval
        minTradeAmountOut:         minOut           // uint256 — minimum output per tranche (dust guard)
    })))
);

_twap encodes args via abi.encode (6 × 32 = 192 bytes).


Args (from TWAPSwap)

Field Type Description
balanceIn uint256 Total input amount over full duration
balanceOut uint256 Total expected output
startTime uint40 Schedule start (unix seconds)
duration uint40 Total schedule length in seconds
priceBumpAfterIlliquidity uint64 BPS rate improvement after a missed slot (BPS = 1e9)
minTradeAmountOut uint256 Minimum output per tranche; reverts if the tranche is too small

How it works

  1. At execution time, _twap computes the currently unlocked fraction of balanceIn and balanceOut based on elapsed time.

  2. It runs a Dutch auction within the current tranche. The price improves within the slot while no fill has occurred.

  3. priceBumpAfterIlliquidity makes the next tranche more attractive when the previous one was skipped.

  4. Internal invalidation tracks cumulative fills; no separate invalidator instruction is needed.


DCA usage

To implement periodic DCA:

  • Set duration to the total campaign length (for example, 30 days).

  • Each execution fills the tranche for the current time slot.

  • The maker signs once; the program handles scheduling automatically.


Optional modifiers

Goal Add
Expiry _deadline before _twap
Holder-gated execution _onlyTakerTokenBalanceNonZero before _twap

  • TWAPSwap: instruction args and internal schedule math

  • Limit Order: simpler fixed-rate pattern

  • Dutch Auction: time-decay on a single tranche

Did you find what you need?