Skip to content

SeraSOR.sol

SeraSOR executes multi-leg atomic routes. The taker signs a single routed intent and the executor chooses the exact route at execution time.

Mainnet: 0xa7A0cf7cd6f043fCA23f29d8ae5aae6b46e11c18 Source: src/SeraSOR.sol

Constants

uint256 public constant MAX_ROUTE_LEGS = 20;

Data Structures

IntentParams

struct IntentParams {
    address taker;                 // wallet that signs and funds the route
    address inputToken;            // Initial wallet funding token
    address outputToken;           // Final output token
    uint256 maxInputAmount;        // Max total spend across all legs
    uint256 minOutputAmount;       // Min total output
    address recipient;             // Final output recipient
    uint256 initialDepositAmount;  // Amount to pull from wallet
    uint256 uuid;                  // Replay protection
    uint48 deadline;               // Signature deadline
}

The taker field is part of the signed payload. Older docs that omitted it are incorrect for the live contracts and public API.

Functions

executeIntent

Execute a multi-leg atomic route based on a taker's signed Intent.

function executeIntent(
    MatchData[] calldata matches,
    bytes calldata intentSignature,
    IntentParams calldata intent,
    uint8 uniqueTokenCount,
    uint256 permitDeadline,
    bytes calldata permitSignature
) external
Parameter Type Description
matches MatchData[] Array of order match pairs (one per leg)
intentSignature bytes Taker's EIP-712 Intent signature
intent IntentParams Signed routing parameters
uniqueTokenCount uint8 Optimization hint for transient balance tracking
permitDeadline uint256 ERC-2612 permit deadline (0 if not using permit)
permitSignature bytes ERC-2612 permit signature (empty if not using permit)

Flow:

  1. Validate the Intent signature and check UUID hasn't been used
  2. Pull initialDepositAmount from taker's wallet (with optional permit)
  3. For each leg:
    • Match taker order against a maker order via Sera.settleRoutedLeg()
    • Hold intermediate outputs in transient balance
  4. Verify envelope constraints:
    • Total input ≤ maxInputAmount
    • Total output ≥ minOutputAmount
    • All intermediate balances consumed (no dust)

Example route: GBP → SGD via USD

Leg 1: GBP → USD (taker sells GBP, receives USD)
Leg 2: USD → SGD (taker sells USD, receives SGD)

The USD is held transiently between legs and never touches the taker's vault.

Requirements:

  • ≥1 and ≤20 legs
  • Deadline not expired
  • UUID not previously used
  • All legs belong to the same taker
  • Final leg delivers to the signed recipient
  • All transient balances fully consumed

Events:

event IntentMatched(bytes32 indexed intentHash, address indexed taker, uint256 legCount);
event IntentLegMatched(bytes32 indexed intentHash, uint256 indexed legIndex, bytes32 takerOrderHash, bytes32 makerOrderHash);

EIP-712 Type Hash

bytes32 constant INTENT_TYPEHASH = keccak256(
    "Intent(address taker,address inputToken,address outputToken,uint256 maxInputAmount,uint256 minOutputAmount,address recipient,uint256 initialDepositAmount,uint256 uuid,uint48 deadline)"
);

State Variables

Replay protection is centralized in Sera. SeraSOR calls sera.consumeIntentUuid(taker, uuid) instead of keeping an isolated router-local registry.

Errors

Error Cause
EmptyRoute No legs provided
TooManyLegs More than 20 legs
InvalidRoute Route validation failed (token mismatch, wrong recipient, etc.)
TransientBalanceNotZero Intermediate tokens not fully consumed
InsufficientOutput Final output below minOutputAmount
ExcessiveInput Total input exceeds maxInputAmount

Replay protection is centralized in Sera: executeIntent calls sera.consumeIntentUuid(taker, uuid), which reverts with UuidAlreadyUsed if the UUID was already consumed.