TWAPSwap

TWAPSwap is a time-weighted average price (TWAP) selling instruction. It combines linear liquidity unlocking, exponential Dutch-auction price decay, and automatic price improvement after periods of insufficient liquidity.

TWAPSwap belongs to the limit-order router's opcode set (alongside instructions such as Invalidators), not the Aqua opcode set. Like every 1inch swap instruction, it runs on SwapVM, the shared swap engine.

Source: src/instructions/TWAPSwap.sol

Inherits LimitSwap and calls _limitSwap1D internally for per-chunk price computation.


Args encoding

Encoded with abi.encode(TwapArgs) (ABI-encoded struct, not packed):

Field ABI slot Description
balanceIn slot 0 (32 bytes) Expected tokenIn amount that defines the initial exchange rate
balanceOut slot 1 (32 bytes) Total tokenOut available for the full TWAP duration
startTime slot 2 (32 bytes) TWAP start timestamp (Unix seconds)
duration slot 3 (32 bytes) Total duration in seconds
priceBumpAfterIlliquidity slot 4 (32 bytes) Price improvement factor in 1e18 scale after an illiquidity period (e.g. 1.1e18 = +10%)
minTradeAmountOut slot 5 (32 bytes) Minimum output per swap; enforced during the TWAP window

Total: 192 bytes. Build with TWAPSwapArgsBuilder.build(TwapArgs memory args).


Instructions

_twap

Solidity
1
function _twap(Context memory ctx, bytes calldata argsData) internal

Behavior

  1. Linear unlocking: unlocked = balanceOut × min(elapsed, duration) / duration; available = unlocked − totalSold
  2. Auction parameters for this chunk: on the first swap, uses (balanceIn, balanceOut, startTime) as base. On subsequent swaps, uses (lastSwap.amountIn, lastSwap.amountOut, lastSwap.timestamp).
  3. Illiquidity bump: If the previous swap interval had insufficient liquidity to cover minTradeAmountOut, a proportional bump is applied to baseAmountIn (makes the price better for the next taker) and the effective auctionStartTime is adjusted.
  4. Dutch auction decay: decay = 0.9999e18 ^ (block.timestamp − auctionStartTime) (hardcoded 0.01% per second decay). Sets balanceIn = baseAmountIn and balanceOut = baseAmountOut × decay / 1e18, then calls ctx.runLoop() which invokes the nested _limitSwap1D.
  5. Validation:
  • During TWAP window: amountOut >= minTradeAmountOut
  • Always: amountOut <= available
  1. State update (swap mode only): stores (amountIn, amountOut, block.timestamp, totalSold + amountOut) in twapLastSwaps[orderHash].

Quote/swap divergence: In quote mode, last swap data is read but not updated. Another fill between quote and swap may cause the swap to revert. Do not use backward jumps to this instruction.

Errors

Error Condition
TWAPSwapMinTradeAmountNotReached(amountIn, minAmount) During TWAP window: amountOut < minTradeAmountOut
TWAPSwapTradeAmountExceedLiquidity(amountIn, available) amountOut > available

Public state

Solidity
1
2
3
4
5
6
7
8
struct LastSwap {
    uint256 amountIn;
    uint256 amountOut;
    uint256 timestamp;
    uint256 totalSold;
}

mapping(bytes32 orderHash => LastSwap) public twapLastSwaps;

Configuration guidelines

minTradeAmountOut should be 1000× the expected gas cost in output token value:

  • Ethereum mainnet (~$50 gas) → set equivalent of $50,000+
  • Arbitrum/Optimism (~$0.50 gas) → set equivalent of $500+

priceBumpAfterIlliquidity compensates for the mandatory wait between chunks:

minTradeAmountOut as % of balanceOut Recommended bump
0.1% 1.05–1.10 × 1e18
1% 1.10–1.20 × 1e18
5% 1.30–1.50 × 1e18
10% 1.50–2.00 × 1e18

  • LimitSwap: provides the per-chunk price logic.
  • DutchAuction: standalone Dutch auction without linear unlocking.
  • Invalidators: alternative partial fill tracking (also in the limit-order opcode set).

Did you find what you need?