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¶
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:
- Validate the Intent signature and check UUID hasn't been used
- Pull
initialDepositAmountfrom taker's wallet (with optional permit) - For each leg:
- Match taker order against a maker order via
Sera.settleRoutedLeg() - Hold intermediate outputs in transient balance
- Match taker order against a maker order via
- Verify envelope constraints:
- Total input ≤
maxInputAmount - Total output ≥
minOutputAmount - All intermediate balances consumed (no dust)
- Total input ≤
Example route: GBP → SGD via USD
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.