Skip to content

Sera.sol

Sera is the core settlement contract for deposits, matching, replay protection, and withdrawals.

Mainnet: 0xB5C50C5D5f038404F85970b7f5B7259C4AC0E198 Source: src/Sera.sol

Constants

string public constant NAME = "Sera";
string public constant VERSION = "1";
uint32 public constant WITHDRAW_DELAY_BLOCKS = 7200;
uint32 public constant WITHDRAW_EXPIRATION_BLOCKS = 14400;
uint256 public constant MAX_EXPIRATION = 365 days;
uint256 constant BPS_DENOMINATOR = 100_000_000_000_000;

Note that BPS_DENOMINATOR is 100_000_000_000_000, not 10_000 — the contract uses a higher-precision denominator inside uint48, so any percentage value stored against it must be scaled accordingly.

Data Structures

Order

struct Order {
    address user;
    uint48 expiration;
    uint48 feeBps;
    address recipient;
    address fromToken;
    address toToken;
    uint256 fromAmount;
    uint256 toAmount;
    uint256 initialDepositAmount;
    uint256 uuid;
}
  • recipient = address(0) means proceeds stay in the user's vault ledger.
  • initialDepositAmount is used by routed swaps and can be zero for normal resting orders.
  • uuid is the on-chain composite uint256, not the human-readable UUID string used by clients.

MatchData

struct MatchData {
    Order order0;          // First order
    bytes signature0;      // EIP-712 signature for order0
    uint256 matchAmount0;  // Amount of order0.fromToken to fill
    Order order1;          // Second order
    bytes signature1;      // EIP-712 signature for order1
    uint256 matchAmount1;  // Amount of order1.fromToken to fill
}

WithdrawIntent

struct WithdrawIntent {
    address user;          // Withdrawing user
    address[] tokens;      // Tokens to withdraw (1-20)
    uint256[] amounts;     // Amounts per token
    address recipient;     // Withdrawal destination
    uint256 deadline;      // Signature deadline
    uint256 uuid;          // Replay protection
}

Deposit Functions

depositFund

Deposit tokens from your wallet into the vault.

function depositFund(address _token, address _owner, uint256 _value) public
Parameter Type Description
_token address ERC-20 token to deposit
_owner address Must be msg.sender
_value uint256 Amount to deposit

Requirements:

  • Caller must be _owner
  • Token must be whitelisted
  • Contract must not be paused

depositFundWithPermit

Deposit with ERC-2612 permit (approve + deposit in one transaction).

function depositFundWithPermit(
    address _token,
    address _owner,
    uint256 _permitAmount,
    uint256 _depositAmount,
    uint256 _deadline,
    bytes calldata _sig
) public
Parameter Type Description
_token address ERC-20 token
_owner address Depositor (must be msg.sender)
_permitAmount uint256 Amount in the permit signature
_depositAmount uint256 Actual deposit amount (must be ≤ _permitAmount)
_deadline uint256 Permit deadline
_sig bytes ERC-2612 permit signature (64 or 65 bytes)

Withdrawal Functions

emergencyWithdraw

Two-step delayed withdrawal for when the API is unavailable.

function emergencyWithdraw(address token, uint256 amount) public

Flow:

  1. First call — Initiates a withdrawal request, recording the block number
  2. Wait — At least 7,200 blocks (~24 hours) must pass
  3. Second call — Executes the withdrawal with the same parameters

Requirements:

  • Amount must be > 0
  • Execution must happen within 14,400 blocks (~48 hours) of the request
  • Execution amount must match the request amount

Events:

event WithdrawRequested(address indexed user, address indexed token, uint256 amount, uint256 indexed requestBlock);
event Withdraw(address indexed token, address indexed to, uint256 amount);

Note

Frozen users can use emergency withdrawal. This ensures funds are always recoverable.

executeInstantWithdrawDualSig

Instant withdrawal with both user and executor signatures.

function executeInstantWithdrawDualSig(
    WithdrawIntent calldata intent,
    bytes calldata userSignature,
    address executor,
    bytes calldata executorSignature
) external
Parameter Type Description
intent WithdrawIntent Withdrawal parameters
userSignature bytes User's EIP-712 WithdrawIntent signature
executor address Co-signing executor; must hold EXECUTOR_ROLE
executorSignature bytes Executor's EIP-712 WithdrawIntent signature

Requirements:

  • Deadline not expired
  • UUID not previously used
  • 1-20 tokens per withdrawal
  • executor must have EXECUTOR_ROLE
  • Both signatures must be valid (EOA or ERC-1271)

Supports multi-token withdrawal in a single transaction. Replay protection is enforced per user via the signed uuid.

Events:

event InstantWithdraw(address indexed user, uint256 indexed uuid, address indexed token, uint256 amount, address recipient);

Order Matching

matchOrders

Settle a matched order pair. Called by authorized executors.

function matchOrders(MatchData calldata _match, uint256 deadline) external
Parameter Type Description
_match MatchData Both orders with signatures and fill amounts
deadline uint256 Unix timestamp; transaction reverts if expired

Validation:

  • Token symmetry: order0.fromToken == order1.toToken and vice versa
  • No self-matching (different order hashes)
  • No same-token matching
  • Both orders not expired
  • Both signatures valid
  • Sufficient vault balances
  • Fill amounts within order limits
  • feeBps <= BPS_DENOMINATOR
  • Token must be whitelisted and meet its minimum order amount on first fill

Events:

event OrderMatched(
    bytes32 indexed orderHash0, address indexed user0, address token0, uint256 amount0, uint256 protocolTake0,
    bytes32 indexed orderHash1, address user1, address token1, uint256 amount1, uint256 protocolTake1
);
event OrderFullyFilled(bytes32 indexed orderHash, address indexed user);

State Variables

mapping(bytes32 => uint256) public filledAmount;
mapping(address => mapping(uint256 => bool)) public isUuidExecuted;
mapping(address => mapping(uint256 => bool)) public isIntentUuidUsed;
mapping(address => mapping(address => WithdrawRequest)) public withdrawRequests;

isIntentUuidUsed is the shared replay registry that SeraSOR consumes through consumeIntentUuid(...).

EIP-712 Type Hashes

// Order signing
bytes32 constant ORDER_TYPEHASH = keccak256(
    "Order(address user,uint48 expiration,uint48 feeBps,address recipient,address fromToken,address toToken,uint256 fromAmount,uint256 toAmount,uint256 initialDepositAmount,uint256 uuid)"
);

// Withdrawal signing
bytes32 constant WITHDRAW_INTENT_TYPEHASH = keccak256(
    "WithdrawIntent(address user,address[] tokens,uint256[] amounts,address recipient,uint256 deadline,uint256 uuid)"
);

SOR Digest Helper

Sera also exposes getIntentDigest(...), which returns the EIP-712 digest used by SeraSOR for routed swap intents.

Admin Functions

Function Role Description
setTreasury(address) Admin Set the treasury address
setSlippageShares(...) Admin Configure on-chain slippage-share parameters
batchModifyWhitelistedTokens(...) Admin Whitelist/unwhitelist tokens
setTrustedRouter(address) Admin Set SeraSOR address
rescueToken(address, address) Admin Rescue accidentally sent tokens
pause() / unpause() Pauser Emergency pause/unpause

Errors

Error Cause
UserFrozen User account is frozen
AmountBelowMinimum Order below token's minimum amount
WithdrawNotReady Withdrawal delay period not elapsed
WithdrawExpired Withdrawal request expired
IntentExpired Signature deadline passed
UuidAlreadyUsed UUID replay detected
TokenMismatch Order token pair doesn't match
SelfMatch Attempting to match order against itself
OrderExpired Order expiration timestamp passed
OrderFilledAmountExceeded Fill would exceed remaining order amount
InvalidSignature Signature verification failed
InsufficientVaultBalance User doesn't have enough deposited