Skip to main content

Overview

Fills an array of orders with the caller (msg.sender) as the taker. This is a batch version of fillOrder that allows operators to execute multiple orders efficiently in a single transaction.
function fillOrders(Order[] memory orders, uint256[] memory fillAmounts) external nonReentrant onlyOperator notPaused

Parameters

orders
Order[]
required
Array of orders to be filled. See Order Structure for details.Each order must:
  • Have a valid signature from the maker
  • Not be expired (if expiration is set)
  • Not be cancelled or fully filled
  • Have a valid nonce
  • Have a fee rate below the maximum allowed
  • Reference a registered token ID
fillAmounts
uint256[]
required
Array of amounts to fill for each corresponding order, always denominated in terms of the maker amount.Requirements:
  • Array length must match the orders array length
  • Each fill amount must be less than or equal to the corresponding order’s remaining unfilled amount

Requirements

  • Caller must be an authorized operator (onlyOperator)
  • Trading must not be paused (notPaused)
  • Function must not be re-entered (nonReentrant)
  • orders and fillAmounts arrays must have the same length
  • Each order must pass all validation checks
  • Caller must have sufficient balance of all required taker assets
  • Order makers must have sufficient balance of their respective maker assets

Behavior

Execution Flow

The function iterates through the arrays and calls the internal _fillOrder function for each order-amount pair. All fills are executed atomically - if any order fails, the entire transaction reverts.

Order Processing

For each order in the array:
  1. Order is validated (signature, expiration, nonce, etc.)
  2. Fees are calculated based on the order’s feeRateBps
  3. Assets are transferred between the operator and order maker
  4. Order status is updated on-chain
  5. Events are emitted

Gas Optimization

This function is more gas-efficient than calling fillOrder multiple times because:
  • Only one transaction is submitted
  • Shared setup costs are paid once
  • Reentrancy guard is checked once

Events

OrderFilled
event
Emitted for each order that is successfully filled.
event OrderFilled(
    bytes32 indexed orderHash,
    address indexed maker,
    address indexed taker,
    uint256 makerAssetId,
    uint256 takerAssetId,
    uint256 makerAmountFilled,
    uint256 takerAmountFilled,
    uint256 fee
)
See fillOrder for parameter details.
FeeCharged
event
Emitted for each order where a fee is charged (if fee > 0).
event FeeCharged(
    address indexed receiver,
    uint256 indexed tokenId,
    uint256 amount
)

Errors

All errors from fillOrder can be thrown for any order in the array:
NotOperator
error
Caller is not an authorized operator
Paused
error
Trading is currently paused
OrderExpired
error
One or more orders have passed their expiration timestamp
OrderFilledOrCancelled
error
One or more orders have already been fully filled or cancelled
InvalidSignature
error
One or more order signatures are invalid
InvalidNonce
error
One or more order nonces are invalid
FeeTooHigh
error
One or more order fee rates exceed the maximum allowed
MakingGtRemaining
error
One or more fill amounts exceed the remaining unfilled amount
NotTaker
error
One or more orders specify a specific taker and caller is not that address

Example Usage

// Create multiple orders
Order[] memory orders = new Order[](3);

// Order 1: BUY YES at 50c with 1% fee
orders[0] = Order({
    salt: 1,
    maker: makerAddress1,
    signer: makerAddress1,
    taker: address(0),
    tokenId: yesTokenId,
    makerAmount: 50_000_000,
    takerAmount: 100_000_000,
    expiration: 0,
    nonce: 0,
    feeRateBps: 100,
    side: Side.BUY,
    signatureType: SignatureType.EOA,
    signature: signature1
});

// Order 2: BUY NO at 50c with 1% fee
orders[1] = Order({
    salt: 2,
    maker: makerAddress1,
    signer: makerAddress1,
    taker: address(0),
    tokenId: noTokenId,
    makerAmount: 50_000_000,
    takerAmount: 100_000_000,
    expiration: 0,
    nonce: 0,
    feeRateBps: 100,
    side: Side.BUY,
    signatureType: SignatureType.EOA,
    signature: signature2
});

// Order 3: SELL YES at 60c with 1% fee
orders[2] = Order({
    salt: 3,
    maker: makerAddress1,
    signer: makerAddress1,
    taker: address(0),
    tokenId: yesTokenId,
    makerAmount: 100_000_000,
    takerAmount: 60_000_000,
    expiration: 0,
    nonce: 0,
    feeRateBps: 100,
    side: Side.SELL,
    signatureType: SignatureType.EOA,
    signature: signature3
});

// Specify fill amounts for each order
uint256[] memory fillAmounts = new uint256[](3);
fillAmounts[0] = 50_000_000;  // Fill order 1 completely
fillAmounts[1] = 50_000_000;  // Fill order 2 completely
fillAmounts[2] = 100_000_000; // Fill order 3 completely

// Fill all orders in one transaction
exchange.fillOrders(orders, fillAmounts);

// Result:
// - All three orders are filled atomically
// - Operator provides YES tokens and USDC to makers
// - Operator receives USDC and YES tokens from makers
// - Fees are collected by the operator

Use Cases

Multi-Order Fills

Perfect for filling multiple limit orders from different makers in a single transaction, reducing gas costs and ensuring atomic execution.

Batch Processing

Operators can efficiently process batches of orders without worrying about partial failures - either all orders fill or none do.

Order Book Sweeping

Fill multiple levels of the order book in one transaction to execute large trades efficiently.

See Also