Sera.sol¶
用于订单撮合、存款和提款的核心结算合约。
主网: 0xB5C50C5D5f038404F85970b7f5B7259C4AC0E198 Sepolia: 0x83475A1bD98a8DC2DCd507A747e4DC85da241D6e 源代码: src/Sera.sol
调用者与暂停要求¶
| 操作 | 调用者与条件 |
|---|---|
depositFund | 发送者为 _owner;未暂停、代币在白名单中 |
depositFundWithPermit | 允许第三方发送;未暂停、代币在白名单中;满足下述 allowance/permit 与金额检查 |
matchOrders | 调用者持有 Sera EXECUTOR_ROLE;未暂停;用户未被加入黑名单 |
executeInstantWithdrawDualSig | 调用者可转交两个有效签名;签名执行者持有 Sera EXECUTOR_ROLE;未暂停;代币/金额数组非空且等长、金额为正 |
emergencyWithdraw | 交易发送者提取自身余额;该提款路径不检查暂停或黑名单;仍须满足下述请求规则 |
只读状态查询不授予任何上述写入权限。
常量¶
string public constant NAME = "Sera";
string public constant VERSION = "1";
uint32 public constant WITHDRAW_DELAY_BLOCKS = 7200; // ~24 hours
uint32 public constant WITHDRAW_EXPIRATION_BLOCKS = 14400; // ~48 hours
uint256 public constant MAX_EXPIRATION = 365 days;
uint256 constant BPS_DENOMINATOR = 100_000_000_000_000;
feeBps 使用 BPS_DENOMINATOR = 10^14(100%),不是通常的 10,000;按此分母缩放费用。
数据结构¶
Order¶
struct Order {
address user; // Order originator
uint48 expiration; // Expiration timestamp
uint48 feeBps; // Fee in basis points
address recipient; // External recipient (address(0) = vault swap)
address fromToken; // Input token
address toToken; // Output token
uint256 fromAmount; // Input amount
uint256 toAmount; // Minimum output amount (price protection)
uint256 initialDepositAmount; // Wallet funding for SOR legs
uint256 uuid; // Unique identifier for replay protection
}
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
}
存款函数¶
depositFund¶
将代币从您的钱包存入 Vault。
| 参数 | 类型 | 说明 |
|---|---|---|
_token | address | 要存入的 ERC-20 代币 |
_owner | address | 必须为 msg.sender |
_value | uint256 | 存入金额 |
要求:
- 调用者必须为
_owner - 代币必须在白名单中
- 合约不能处于暂停状态
depositFundWithPermit¶
使用 ERC-2612 permit 存款(在一笔交易中完成授权 + 存款)。
function depositFundWithPermit(
address _token,
address _owner,
uint256 _permitAmount,
uint256 _depositAmount,
uint256 _deadline,
bytes calldata _sig
) public
| 参数 | 类型 | 说明 |
|---|---|---|
_token | address | ERC-20 代币 |
_owner | address | 代币所有者及 Vault 受益人;调用者可以是第三方 |
_permitAmount | uint256 | permit 签名中的金额 |
_depositAmount | uint256 | 实际存款金额(必须 ≤ _permitAmount) |
_deadline | uint256 | Permit 截止时间 |
_sig | bytes | ERC-2612 permit 签名(64 或 65 字节) |
如果对 Vault 的 allowance 已覆盖 _depositAmount,合约会跳过 permit 校验;否则先尝试 permit,再由 Vault 从 _owner 拉取代币。公共 API 辅助端点另有 owner/签名检查,不能据此放宽。
提款函数¶
emergencyWithdraw¶
当 API 不可用时的两步延迟提款。
流程与要求:
- 使用非零代币地址和正数金额申请,申请时 Vault 余额必须足够。
- 等待至少 7,200 个区块(按每区块 12 秒估算约 24 小时)。
- 在申请区块 + 7,200 至 + 14,400(含边界)期间,对同一代币调用,金额满足
0 < amount <= 申请金额,执行时余额也必须足够。 - 成功后整个申请被删除,即使少提也一样;剩余金额需要重新申请。
- 超过申请区块 + 14,400 后,再次调用会创建新申请并重新等待,不会执行过期申请,也不会抛
WithdrawExpired。
区块数对应的时间仅为估算。此函数通过交易发送者识别提款账户,不要求用户 EIP-712 消息签名。
事件:
event WithdrawRequested(address indexed user, address indexed token, uint256 amount, uint256 indexed requestBlock);
event Withdraw(address indexed token, address indexed to, uint256 amount);
Note
黑名单用户可以使用紧急提款,但仍需满足余额、等待期及代币转账条件。
executeInstantWithdrawDualSig¶
使用用户和执行者双重签名的即时提款。
function executeInstantWithdrawDualSig(
WithdrawIntent calldata intent,
bytes calldata userSignature,
address executor,
bytes calldata executorSignature
) external
| 参数 | 类型 | 说明 |
|---|---|---|
intent | WithdrawIntent | 提款参数 |
userSignature | bytes | 用户的 EIP-712 WithdrawIntent 签名 |
executor | address | 协同签名的执行者地址;必须持有 EXECUTOR_ROLE |
executorSignature | bytes | 执行者的 EIP-712 WithdrawIntent 签名 |
要求:
- 截止时间未过期
- UUID 未被使用过
- 每次提款 1-20 种代币
executor必须持有EXECUTOR_ROLE- 两个签名都必须有效(EOA 或 ERC-1271)
支持在单笔交易中进行多代币提款。
事件:
event InstantWithdraw(address indexed user, uint256 indexed uuid, address indexed token, uint256 amount, address recipient);
订单撮合¶
matchOrders¶
结算已匹配的订单对。由授权的执行者调用。
| 参数 | 类型 | 说明 |
|---|---|---|
_match | MatchData | 两个订单及其签名和成交金额 |
deadline | uint256 | Unix 时间戳;过期则交易回退 |
验证:
- 代币对称性:
order0.fromToken == order1.toToken,反之亦然 - 不能自我撮合(不同的订单哈希)
- 不能同币种撮合
- 两个订单都未过期
- 两个签名都有效
- Vault 余额充足
- 成交金额在订单限额范围内
事件:
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);
状态变量¶
mapping(bytes32 => uint256) public filledAmount; // Partial fill tracking per order hash
mapping(address => mapping(uint256 => bool)) public isIntentUuidUsed; // Shared SOR replay protection
mapping(address => mapping(uint256 => bool)) public isUuidExecuted; // Replay protection
mapping(address => mapping(address => WithdrawRequest)) public withdrawRequests; // Pending withdrawals
EIP-712 类型哈希¶
// 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 辅助函数¶
以下公开只读函数无需执行者角色,也不会提交交易:
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 返回完整 EIP-712 Intent 摘要,包括 taker 和 Sera 域;它不验证签名,也不消费 UUID。DOMAIN_SEPARATOR() 返回当前部署的域分隔符。构造签名时使用目标链及 GET /config 返回的 Sera 地址。
管理员函数¶
| 函数 | 角色 | 说明 |
|---|---|---|
setTreasury(address) | Admin | 设置金库地址 |
setSlippageShares(...) | Admin | 配置链上滑点分配参数 |
batchModifyWhitelistedTokens(...) | Admin | 白名单/取消白名单代币 |
setTrustedRouter(address) | Admin | 设置 SeraSOR 地址 |
rescueToken(address, address) | Admin | 救回误发的代币 |
pause() / unpause() | Pauser | 紧急暂停/恢复 |
错误¶
| 错误 | 原因 |
|---|---|
BlacklistedUser | Vault 黑名单阻止该交易操作 |
AmountBelowMinimum | 订单低于代币最低金额 |
WithdrawNotReady | 提款延迟期未满 |
IntentExpired | 签名截止时间已过 |
UuidAlreadyUsed | 检测到 UUID 重放 |
TokenMismatch | 订单代币对不匹配 |
SelfMatch | 尝试将订单与自身撮合 |
OrderExpired | 订单过期时间戳已过 |
OrderFilledAmountExceeded | 成交将超过订单剩余金额 |
InvalidSignature | 签名验证失败 |
InsufficientVaultBalance | 用户存款不足 |