Sera.sol¶
Sera is the core settlement contract for deposits, matching, replay protection, and withdrawals.
Mainnet: 0xB5C50C5D5f038404F85970b7f5B7259C4AC0E198 Sepolia: 0x83475A1bD98a8DC2DCd507A747e4DC85da241D6e Source: src/Sera.sol
Caller and Pause Requirements¶
| Operation | Caller and conditions |
|---|---|
depositFund | Sender is _owner; unpaused and whitelisted token |
depositFundWithPermit | Third-party sender allowed; unpaused and whitelisted token; allowance/permit and amount checks below |
matchOrders | Caller has Sera EXECUTOR_ROLE; unpaused; users must not be blacklisted |
executeInstantWithdrawDualSig | Caller may relay both valid signatures; the signed executor has Sera EXECUTOR_ROLE; unpaused; matching nonempty token/amount arrays and positive amounts |
emergencyWithdraw | Transaction sender withdraws its own balance; no pause or blacklist check on this withdrawal path; request rules below |
Read-only state access does not grant any of these write permissions.
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.initialDepositAmountis used by routed swaps and can be zero for normal resting orders.uuidis 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.
| 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 | Token owner and vault beneficiary; the caller may be a third party |
_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) |
When allowance to the Vault already covers _depositAmount, the contract skips permit validation. Otherwise it attempts permit, then the Vault pulls tokens from _owner. Public API helpers apply their own owner/signature checks; this contract rule does not relax them.
Withdrawal Functions¶
emergencyWithdraw¶
Two-step delayed withdrawal for when the API is unavailable.
Flow and requirements:
- Call with a nonzero token and a positive amount covered by your current Vault balance to create a request.
- Wait at least 7,200 blocks (about 24 hours at 12 seconds per block).
- At request block + 7,200 through + 14,400 inclusive, call for the same token with
0 < amount <= requested amount. The current balance must still cover that amount. - A successful withdrawal deletes the entire request, even if you withdraw less. Request again for any remainder.
- After request block + 14,400, a call creates a new request and restarts the delay; it does not execute the expired request or raise
WithdrawExpired.
Block intervals are approximate wall-clock times. The function does not require a user EIP-712 signature; the transaction sender identifies the withdrawing account.
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
Blacklisted users can use emergency withdrawal. Balance, delay and token-transfer requirements still apply.
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
executormust haveEXECUTOR_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.
| 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.toTokenand 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¶
These public view functions do not require an executor role and do not submit a trade:
function getIntentDigest(
address taker,
address inputToken, address outputToken,
uint256 maxInputAmount, uint256 minOutputAmount,
address recipient, uint256 initialDepositAmount,
uint256 uuid, uint48 deadline
) external view returns (bytes32);
function DOMAIN_SEPARATOR() public view returns (bytes32);
getIntentDigest returns the complete EIP-712 Intent digest, including taker and the Sera domain; it does not verify a signature or consume the UUID. DOMAIN_SEPARATOR() returns that deployment's domain separator. Use the target chain and the live Sera address from GET /config when constructing signatures.
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 |
|---|---|
BlacklistedUser | Vault blacklist blocks the trading action |
AmountBelowMinimum | Order below token's minimum amount |
WithdrawNotReady | Withdrawal delay period not elapsed |
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 |