Skip to main content
TWAP (Time-Weighted Average Price) orders allow you to split a large order into multiple smaller parts that execute at fixed intervals. This helps minimize price impact and achieve better average execution prices.

How It Works

A TWAP order divides your total trade into n equal parts, each executing at a frequency of t seconds. Each part has a validity window defined by the span parameter.
// Example: Sell 1000 tokens in 10 parts, one every hour
partSellAmount = 100 tokens  // Each part sells 100 tokens
n = 10                        // Total 10 parts
t = 3600                      // 1 hour between parts
span = 300                    // Each part valid for 5 minutes

Data Structure

The TWAP order is defined by the following struct:
struct Data {
    IERC20 sellToken;
    IERC20 buyToken;
    address receiver;
    uint256 partSellAmount;  // amount to sell in each part
    uint256 minPartLimit;    // minimum buy amount per part
    uint256 t0;              // start time (or 0 for dynamic)
    uint256 n;               // number of parts
    uint256 t;               // frequency (seconds between parts)
    uint256 span;            // validity window for each part (seconds)
    bytes32 appData;
}

Parameters

sellToken
IERC20
required
The token to sell in the TWAP order
buyToken
IERC20
required
The token to receive in exchange
receiver
address
required
The address that will receive the bought tokens
partSellAmount
uint256
required
The amount of sellToken to sell in each individual part of the TWAP. Must be greater than 0.Example: If you want to sell 1000 tokens total in 10 parts, set this to 100.
minPartLimit
uint256
required
The minimum amount of buyToken to receive for each part. Acts as a limit price. Must be greater than 0.Example: If each part sells 100 USDC and you want at least 0.05 ETH, set this to 50000000000000000 (0.05 * 10^18).
t0
uint256
required
The UNIX timestamp when the TWAP order starts. Set to 0 to use dynamic start time from the context (set when the order is first created).Must be less than type(uint32).max (year 2106).
n
uint256
required
The total number of parts to split the order into. Must be greater than 1 and at most type(uint32).max.Total trade size = partSellAmount * n
t
uint256
required
The frequency in seconds between each part. Must be greater than 0 and at most 365 days.Examples:
  • 300 = 5 minutes
  • 3600 = 1 hour
  • 86400 = 1 day
span
uint256
required
The validity window for each part in seconds. Must be less than or equal to t.
  • Set to 0 for the part to be valid for the entire frequency period
  • Set to a smaller value to create a specific validity window
Example: If t = 3600 (1 hour) and span = 300 (5 minutes), each part is only valid for the first 5 minutes of its hour.
appData
bytes32
required
The IPFS hash of the appData associated with the order. Can be used to attach metadata.

Validation Rules

The TWAP order validates the following conditions:
  • sellToken and buyToken must be different tokens
  • Both tokens must have non-zero addresses
  • partSellAmount must be greater than 0
  • minPartLimit must be greater than 0
  • t0 must be less than type(uint32).max
  • n must be greater than 1 and at most type(uint32).max
  • t must be greater than 0 and at most 365 days
  • span must be less than or equal to t
The order will revert with OrderNotValid if any validation rule is violated.

Behavior

Part Calculation

The current part is calculated as:
part = (block.timestamp - t0) / t
Each part has a unique validTo timestamp, which ensures a unique orderUid and prevents double execution.

Validity Window

The validTo timestamp for each part is calculated as:
  • If span = 0: validTo = t0 + (part + 1) * t - 1 (valid until the next part starts)
  • If span > 0: validTo = t0 + part * t + span - 1 (valid for the span duration)

Dynamic Start Time

If t0 is set to 0, the start time is dynamically determined from the ComposableCoW cabinet storage when the order is first queried. This allows the TWAP to start when it’s first activated rather than at a predetermined time.

Error Messages

BEFORE_TWAP_START
error
The current time is before the TWAP start time (t0). The order will poll again at t0.
AFTER_TWAP_FINISH
error
All parts of the TWAP have completed. The order is no longer valid.
NOT_WITHIN_SPAN
error
The current time is outside the validity window for the current part.

Example Usage

Example 1: Basic TWAP

Sell 1000 USDC for ETH in 10 parts over 10 hours:
TWAPOrder.Data memory twapOrder = TWAPOrder.Data({
    sellToken: IERC20(USDC_ADDRESS),
    buyToken: IERC20(WETH_ADDRESS),
    receiver: msg.sender,
    partSellAmount: 100e6,        // 100 USDC per part
    minPartLimit: 0.05e18,        // At least 0.05 ETH per part
    t0: block.timestamp,          // Start now
    n: 10,                        // 10 parts total
    t: 3600,                      // 1 hour between parts
    span: 0,                      // Valid for entire hour
    appData: bytes32(0)
});

bytes memory staticInput = abi.encode(twapOrder);

Example 2: TWAP with Narrow Validity Window

Sell 5000 DAI for USDC in 20 parts, each valid for only 10 minutes:
TWAPOrder.Data memory twapOrder = TWAPOrder.Data({
    sellToken: IERC20(DAI_ADDRESS),
    buyToken: IERC20(USDC_ADDRESS),
    receiver: msg.sender,
    partSellAmount: 250e18,       // 250 DAI per part
    minPartLimit: 249e6,          // At least 249 USDC per part (1% slippage)
    t0: 0,                        // Dynamic start time
    n: 20,                        // 20 parts total
    t: 1800,                      // 30 minutes between parts
    span: 600,                    // Each part valid for 10 minutes
    appData: bytes32(0)
});

bytes memory staticInput = abi.encode(twapOrder);

Implementation Details

Contract Address

The TWAP conditional order type is deployed at /src/types/twap/TWAP.sol.

Key Functions

function getTradeableOrder(
    address owner,
    address,
    bytes32 ctx,
    bytes calldata staticInput,
    bytes calldata
) public view override returns (GPv2Order.Data memory order) {
    TWAPOrder.Data memory twap = abi.decode(staticInput, (TWAPOrder.Data));
    
    // If t0 is 0, get start time from context
    if (twap.t0 == 0) {
        twap.t0 = uint256(composableCow.cabinet(owner, ctx));
    }
    
    order = TWAPOrder.orderFor(twap);
    
    // Revert if order is outside the TWAP bundle's span
    if (!(block.timestamp <= order.validTo)) {
        revert IConditionalOrder.OrderNotValid(NOT_WITHIN_SPAN);
    }
}
Each part of a TWAP order generates a unique orderUid based on its validTo timestamp. This ensures CoW Protocol only executes each part once, even if the order is repeatedly queried.

Build docs developers (and LLMs) love