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
function _twap(Context memory ctx, bytes calldata argsData) internal
Behavior
- Linear unlocking:
unlocked = balanceOut × min(elapsed, duration) / duration;available = unlocked − totalSold - 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). - Illiquidity bump: If the previous swap interval had insufficient liquidity to cover
minTradeAmountOut, a proportional bump is applied tobaseAmountIn(makes the price better for the next taker) and the effectiveauctionStartTimeis adjusted. - Dutch auction decay:
decay = 0.9999e18 ^ (block.timestamp − auctionStartTime)(hardcoded 0.01% per second decay). SetsbalanceIn = baseAmountInandbalanceOut = baseAmountOut × decay / 1e18, then callsctx.runLoop()which invokes the nested_limitSwap1D. - Validation:
- During TWAP window:
amountOut >= minTradeAmountOut - Always:
amountOut <= available
- State update (swap mode only): stores
(amountIn, amountOut, block.timestamp, totalSold + amountOut)intwapLastSwaps[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
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 |
Related
- 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).