RFC-010: Nexus Escrow 智能合约规范
| 元数据 | 内容 |
|---|---|
| 标题 | Nexus Escrow 智能合约规范 |
| 版本 | 3.0.0 |
| 状态 | Standards Track (Draft) |
| 作者 | Cipher & Nexus Architect Team |
| 创建日期 | 2026-02-24 |
| 更新日期 | 2026-04-01 |
| 依赖 | RFC-005v4 (Payment Core), RFC-009 (Webhook Standard), RFC-001 (DID) |
| 目标链 | PlatON Devnet (chain_id: 20250407) |
| 支付币种 | ERC-3009 兼容代币 (XSGD, USDC) |
| 合约标准 | ERC-20, EIP-3009, EIP-712, OpenZeppelin v5, UUPS Upgradeable |
| Gas 模型 | Relayer 代付 (用户零 Gas) |
1. 摘要 (Abstract)
本 RFC 定义 Nexus Escrow 智能合约 v4.3.0 的完整规范。合约作为支付担保人,实现"用户签名 → Relayer 代付存入 → 商户履约 → 资金释放"的 Gasless 担保交易流程。
v3.0.0 核心变更 (相对于 RFC v2.0.0):
- UUPS Proxy: 采用可升级代理模式,支持合约逻辑在线升级
- Gasless Batch Deposit:
batchDepositGasless()— Relayer 代用户提交批量存款,用户零 Gas - Group Signature (Anti-MITM): EIP-712 签名验证批量交易参数完整性
- Cancel 机制:
cancelByOperator()— 运营/商户即时退款,无需等待超时 - Auto-Refund Unresolved Dispute:
refundUnresolvedDispute()— 仲裁超时自动退款 - Multi-Token:
setToken()— 支持切换 ERC-3009 兼容代币 - Fee Snapshot: 存入时快照手续费率,防止后续费率变更影响已有 escrow
- MAX_BATCH_SIZE: 单次批量最多 20 笔,防止 Gas griefing
2. 动机 (Motivation)
2.1 Escrow vs Direct Transfer
Direct Transfer (原始模式) Escrow Contract (本 RFC)
--------------------- ----------------------
User --transfer--> Merchant User --sign--> Relayer --deposit--> Contract --release--> Merchant
|
超时? --refund--> User
争议? --arbitrate--> 仲裁裁决
取消? --cancelByOperator--> 即时退款2.2 多维度对比
| 维度 | Direct Transfer | Escrow Contract | 评估 |
|---|---|---|---|
| 安全性 | 不可逆转,无追索权 | 超时退款 + 仲裁 + 即时取消 | Escrow 优 |
| 链上数据 | Transfer(from, to, value) 三字段 | 自定义事件含 paymentId, orderRef, merchantDid | Escrow 优 |
| Gas 成本 | ~65K (用户付) | ~220K (Relayer 付,用户零 Gas) | Escrow 优 (UX) |
| 纠纷处理 | 完全链下 | 链上仲裁 + 自动退款 | Escrow 优 |
| 跨链兼容 | 无 calldata | 合约地址 + calldata 对接 bridge | Escrow 优 |
3. 合约架构
3.1 UUPS Proxy 模式
┌─────────────────┐ ┌──────────────────────────────┐
│ ERC1967 Proxy │ ──► │ NexusEscrow Impl v4.3.0 │
│ (stable address)│ │ (upgradeable logic) │
│ 0xeB33a9C2... │ │ 0xF6ED311f... │
└─────────────────┘ └──────────────────────────────┘- Proxy 地址永久不变,所有交互通过 Proxy
- 升级通过
_authorizeUpgrade()(仅 owner) - 存储布局在 Proxy 中,逻辑在 Implementation 中
3.2 合约继承
solidity
contract NexusEscrow is
Initializable,
UUPSUpgradeable,
OwnableUpgradeable,
ReentrancyGuardUpgradeable4. 状态机
4.1 状态枚举
solidity
enum EscrowStatus {
NONE, // 0: 不存在
DEPOSITED, // 1: 资金已锁定,等待商户履约
RELEASED, // 2: 商户已提取 (扣除手续费)
REFUNDED, // 3: 已退款 (超时/取消/仲裁超时)
DISPUTED, // 4: 争议中,等待仲裁
RESOLVED_TO_MERCHANT, // 5: 仲裁归商户
RESOLVED_TO_PAYER, // 6: 仲裁归用户
RESOLVED_SPLIT // 7: 仲裁按比例分配 (v4.0.0 新增)
}4.2 状态转换图
┌──────────────────┐
│ DEPOSITED │ <-- deposit / batchDepositGasless
└────────┬─────────┘
│
┌──────────────┼──────────────┬───────────────┐
│ │ │ │
┌────────▼───────┐ ┌──▼──────────┐ ┌▼──────────┐ ┌▼───────────────┐
│ RELEASED │ │ REFUNDED │ │ REFUNDED │ │ DISPUTED │
│ (release) │ │ (超时退款) │ │(cancelBy │ │ (争议中) │
│ │ │ (refund) │ │ Operator) │ │ (dispute) │
└────────────────┘ └─────────────┘ └────────────┘ └───────┬────────┘
│
┌──────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌────▼────────┐ ┌──────▼─────────┐
│ RESOLVED_ │ │ RESOLVED_ │ │ RESOLVED_ │
│ TO_MERCHANT │ │ SPLIT │ │ TO_PAYER │
│ (≥50% 商户) │ │ (按比例) │ │ (<50% 商户) │
└─────────────┘ └─────────────┘ └────────────────┘
▲
│
refundUnresolvedDispute
(仲裁超时自动退款)4.3 转换规则
| 当前状态 | 目标状态 | 触发条件 | 调用者 |
|---|---|---|---|
| (无) | DEPOSITED | deposit / batchDepositGasless | Relayer / User |
| DEPOSITED | RELEASED | release() | Core operator / merchant |
| DEPOSITED | REFUNDED | refund() (超时后) | 任何人 (public) |
| DEPOSITED | REFUNDED | cancelByOperator() (即时) | Core operator / merchant |
| DEPOSITED | DISPUTED | dispute() (争议窗口内) | Payer |
| DISPUTED | RESOLVED_TO_MERCHANT | resolve(bps ≥ 5000) | Arbiter |
| DISPUTED | RESOLVED_TO_PAYER | resolve(bps < 5000) | Arbiter |
| DISPUTED | RESOLVED_SPLIT | resolve(0 < bps < 10000) | Arbiter |
| DISPUTED | RESOLVED_TO_PAYER | refundUnresolvedDispute() (仲裁超时) | 任何人 |
终态: RELEASED, REFUNDED, RESOLVED_TO_MERCHANT, RESOLVED_TO_PAYER, RESOLVED_SPLIT
5. 数据结构
5.1 Escrow Record
solidity
struct Escrow {
address payer; // 付款人地址
address merchant; // 商户收款地址
uint256 amount; // 代币金额
bytes32 orderRef; // 商户订单号 hash
bytes32 merchantDid; // 商户 DID hash
bytes32 contextHash; // 订单上下文 hash
uint256 releaseDeadline; // 履约截止时间 (ms, PlatON)
uint256 disputeDeadline; // 争议窗口截止时间 (ms)
EscrowStatus status; // 当前状态
uint16 feeBps; // 存入时快照的手续费率
}5.2 Batch Entry
solidity
struct BatchEntry {
bytes32 paymentId;
address merchant;
uint256 amount;
bytes32 orderRef;
bytes32 merchantDid;
bytes32 contextHash;
}5.3 常量
solidity
uint16 constant MAX_FEE_BPS = 500; // 5% 硬顶
uint256 constant MAX_BATCH_SIZE = 20; // 防止 Gas griefing6. 核心函数
6.1 存入函数
depositWithAuthorization — 单笔 EIP-3009 存入
solidity
function depositWithAuthorization(
bytes32 _paymentId,
address _from,
address _merchant,
uint256 _amount,
bytes32 _orderRef,
bytes32 _merchantDid,
bytes32 _contextHash,
uint256 _validAfter,
uint256 _validBefore,
bytes32 _nonce,
uint8 _v, bytes32 _r, bytes32 _s
) external nonReentrantRelayer 调用,使用用户的 EIP-3009 签名将代币从用户钱包转入合约。
batchDepositGasless — Relayer 代付批量存入 (v4.3.0 主入口)
solidity
function batchDepositGasless(
BatchEntry[] calldata entries,
uint256 totalAmount,
bytes32 groupId,
uint8 groupV, bytes32 groupR, bytes32 groupS,
address payer,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external nonReentrant这是 v4.3.0 的主要入口函数。 流程:
- 验证
entries.length <= MAX_BATCH_SIZE - 验证 Group Signature (EIP-712): 从
groupV/R/S恢复签名者,验证为 coreOperator - 调用
token.transferWithAuthorization(payer, address(this), totalAmount, ...)— 一次性转入总金额 - 为每个 entry 创建独立 Escrow record (feeBps 快照)
- Emit
Depositedevent for each entry +BatchDepositedevent
batchDepositWithGroupApproval — 用户提交批量存入
solidity
function batchDepositWithGroupApproval(
BatchEntry[] calldata entries,
uint256 totalAmount,
bytes32 groupId,
uint8 groupV, bytes32 groupR, bytes32 groupS,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external nonReentrant保留向后兼容。与 batchDepositGasless 的区别: payer = msg.sender (用户自己提交,自付 Gas)。
6.2 释放函数
release — 商户/运营释放资金
solidity
function release(bytes32 _paymentId) external nonReentrant- 调用者: merchant 或 coreOperator
- 前置条件: status == DEPOSITED
- 逻辑: 扣除 feeBps (快照值) 手续费,转给商户
- Emit:
PaymentReleased(paymentId, merchant, merchantAmount, fee)
6.3 退款函数
refund — 超时退款
solidity
function refund(bytes32 _paymentId) external nonReentrant- 调用者: 任何人 (public)
- 前置条件: status == DEPOSITED && block.timestamp > releaseDeadline
- Emit:
PaymentRefunded(paymentId, payer, amount)
cancelByOperator — 即时取消退款 (v4.1.0)
solidity
function cancelByOperator(bytes32 _paymentId) external nonReentrant- 调用者: coreOperator 或 merchant
- 前置条件: status == DEPOSITED (无需等待超时)
- Emit:
PaymentRefunded(paymentId, payer, amount)
区别: refund() 需等待 releaseDeadline 过期;cancelByOperator() 立即退款。
refundUnresolvedDispute — 仲裁超时退款 (v4.0.0)
solidity
function refundUnresolvedDispute(bytes32 _paymentId) external nonReentrant- 调用者: 任何人 (public)
- 前置条件: status == DISPUTED && block.timestamp > disputeDeadline + arbitrationTimeout
- 逻辑: 全额退还 payer
- Emit:
DisputeAutoResolved(paymentId, payer, amount)
6.4 争议函数
dispute — 发起争议
solidity
function dispute(bytes32 _paymentId, string calldata _reason) external nonReentrant- 调用者: payer
- 前置条件: status == DEPOSITED && block.timestamp <= disputeDeadline
- Emit:
PaymentDisputed(paymentId, payer, reason)
resolve — 仲裁裁决
solidity
function resolve(bytes32 _paymentId, uint16 _merchantBps) external nonReentrant- 调用者: arbiter
- 前置条件: status == DISPUTED
- 参数:
_merchantBps(0-10000),商户获得比例- 10000 = 全部归商户 → RESOLVED_TO_MERCHANT
- 0 = 全部归用户 → RESOLVED_TO_PAYER
- 其他 = 按比例分配 → RESOLVED_SPLIT (if 0 < bps < 10000)
- Emit:
DisputeResolved(paymentId, arbiter, toMerchant, merchantAmount, payerAmount)
6.5 查询函数
solidity
function getEscrow(bytes32 _paymentId) external view returns (Escrow memory)
function isRefundable(bytes32 _paymentId) external view returns (bool)
function isDisputable(bytes32 _paymentId) external view returns (bool)
function isGroupIdUsed(bytes32 _groupId) external view returns (bool)
function computeDomainSeparator() external view returns (bytes32)6.6 管理函数
| 函数 | 作用 | 调用者 |
|---|---|---|
setArbiter(address, bool) | 设置/移除仲裁人 | owner |
setCoreOperator(address, bool) | 设置/移除运营地址 | owner |
setDefaultReleaseTimeout(uint256) | 设置默认履约超时 | owner |
setDefaultDisputeWindow(uint256) | 设置默认争议窗口 | owner |
setArbitrationTimeout(uint256) | 设置仲裁超时 | owner |
setProtocolFeeBps(uint16) | 设置手续费率 (≤500) | owner |
setProtocolFeeRecipient(address) | 设置手续费收款地址 | owner |
setRequireGroupSig(bool) | 强制使用 Group Sig 函数 | owner |
setToken(address) | 切换 ERC-3009 代币 | owner |
7. Group Signature (Anti-MITM)
7.1 EIP-712 Domain
solidity
bytes32 constant NEXUS_GROUP_APPROVAL_TYPEHASH = keccak256(
"NexusGroupApproval(bytes32 groupId,bytes32 entriesHash,uint256 totalAmount)"
);
// Domain: Nexus, version 1, chainId, verifyingContract = escrow proxy7.2 签名流程
- Core 构建 BatchDepositInstruction,计算 entriesHash:
entriesHash = keccak256( abi.encode(entry0.paymentId, entry0.merchant, entry0.amount, ...) || abi.encode(entry1.paymentId, entry1.merchant, entry1.amount, ...) || ... ) - Core Operator 签署
NexusGroupApproval(groupId, entriesHash, totalAmount) - 签名附加到 BatchDepositInstruction 返回给前端
- 链上
batchDepositGasless()验证:ecrecover(digest, v, r, s) == coreOperator
7.3 防护目标
- 防止中间人篡改 batch entries (金额、商户地址)
- 防止重放 (usedGroupIds mapping)
- 确保 totalAmount == sum(entries.amount)
8. EIP-3009 集成
8.1 接口
solidity
interface IERC3009 is IERC20 {
function transferWithAuthorization(
address from, address to, uint256 value,
uint256 validAfter, uint256 validBefore, bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external;
function authorizationState(address authorizer, bytes32 nonce) external view returns (bool);
}8.2 签名参数
用户签署的 EIP-712 TypedData:
typescript
{
domain: {
name: "XSGD", // 代币合约 EIP-712 name
version: "2", // 代币合约 EIP-712 version
chainId: 20250407,
verifyingContract: tokenAddress
},
types: {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" }
]
},
message: {
from: payerAddress,
to: escrowProxyAddress,
value: totalAmountUint256,
validAfter: "0",
validBefore: Date.now() + 86400000, // +24h (毫秒, PlatON)
nonce: randomBytes32
}
}PlatON Devnet 注意:
block.timestamp返回毫秒。validBefore必须以毫秒为单位设置。
9. 事件定义
solidity
// 存入事件
event Deposited(bytes32 indexed paymentId, address indexed payer, address indexed merchant,
uint256 amount, bytes32 orderRef, bytes32 merchantDid, bytes32 contextHash,
uint256 releaseDeadline, uint256 disputeDeadline);
event BatchDeposited(bytes32 indexed groupId, address indexed payer,
uint256 totalAmount, uint256 count);
// 释放事件
event Released(bytes32 indexed paymentId, address indexed merchant,
uint256 merchantAmount, uint256 protocolFee);
// 退款事件
event Refunded(bytes32 indexed paymentId, address indexed payer, uint256 amount);
// 争议事件
event Disputed(bytes32 indexed paymentId, address indexed payer, string reason);
event Resolved(bytes32 indexed paymentId, address indexed arbiter,
uint16 merchantBps, uint256 merchantAmount, uint256 payerAmount);
event DisputeAutoResolved(bytes32 indexed paymentId, address indexed payer, uint256 amount);
// Group Sig 事件
event GroupSigVerified(bytes32 indexed groupId, address indexed signer);
// 管理事件
event ArbiterUpdated(address indexed arbiter, bool active);
event CoreOperatorUpdated(address indexed operator, bool active);
event ReleaseTimeoutUpdated(uint256 newTimeout);
event DisputeWindowUpdated(uint256 newWindow);
event ArbitrationTimeoutUpdated(uint256 newTimeout);
event ProtocolFeeUpdated(uint16 newBps);
event FeeRecipientUpdated(address indexed newRecipient);
event RequireGroupSigUpdated(bool required);
event TokenUpdated(address indexed newToken);10. 部署信息
10.1 当前部署 (PlatON Devnet, chain_id: 20250407)
| 合约 | 地址 | 类型 |
|---|---|---|
| NexusEscrow (Proxy) | 0xeB33a9C2b4c7D3F44Fd5514F90C355AF6bb79236 | UUPS Proxy |
| NexusEscrow (Impl v4.3.0) | 0xF6ED311f8ea594572872E78DB945277ab37ECE37 | Implementation |
| XSGD Token | 0x0Fd437613dE3d14F4dDaB8331DC0f2C0C543BdD0 | ERC-3009 |
| USDC Token (legacy) | 0xFF8dEe9983768D0399673014cf77826896F97e4d | ERC-3009 |
| Relayer / Core Operator | 0xf7EA5d3f0Bf8185c4f3C2F405D9a71009CF4D920 | EOA |
10.2 当前配置
| 参数 | 值 | 说明 |
|---|---|---|
| defaultReleaseTimeout | 86400000 (24h, ms) | 商户履约超时 |
| defaultDisputeWindow | 259200000 (72h, ms) | 用户争议窗口 |
| arbitrationTimeout | 604800000 (7d, ms) | 仲裁人裁决超时 |
| protocolFeeBps | 30 (0.3%) | 协议手续费 |
| requireGroupSig | false | Group Sig 强制开关 |
| Active token | XSGD | 当前活跃代币 |
10.3 版本历史
| 版本 | Impl 地址 | 日期 | 关键变更 |
|---|---|---|---|
| v2.0.0 | (non-upgradeable, deprecated) | 2026-02-24 | 初始 escrow (单笔 deposit) |
| v4.0.0 | 0x2EF4dB5E0021d074286c36821Cc897d2605e542E | 2026-02-26 | UUPS, batchDepositWithGroupApproval, RESOLVED_SPLIT, refundUnresolvedDispute, MAX_BATCH_SIZE, feeBps snapshot |
| v4.1.0 | 0x7aC2e9C7D655B352f142b679B22f4B86b23A36eC | 2026-03-12 | cancelByOperator (即时退款) |
| v4.3.0 | 0xF6ED311f8ea594572872E78DB945277ab37ECE37 | 2026-04-01 | batchDepositGasless (Relayer 代付), setToken (多代币) |
11. Gas 成本估算
| 操作 | Gas 消耗 | 用户承担 | Relayer 承担 |
|---|---|---|---|
| EIP-3009 签名 | 0 | 0 | 0 |
| batchDepositGasless (1 entry) | ~180,000 | 0 | 180,000 |
| batchDepositGasless (5 entries) | ~450,000 | 0 | 450,000 |
| release | ~80,000 | 0 | 80,000 |
| refund | ~75,000 | 0 | 75,000 |
| cancelByOperator | ~75,000 | 0 | 75,000 |
| dispute | ~60,000 | 60,000 | 0 |
| resolve | ~90,000 | 0 | 90,000 |
Escrow 模式下用户仅在主动发起争议 (dispute) 时需要 Gas。所有其他操作由 Relayer 承担。
12. 安全设计
12.1 访问控制
| 函数 | 权限 |
|---|---|
| deposit / batchDepositGasless | 任何人 (通常为 Relayer) |
| release | coreOperator 或 merchant |
| refund | 任何人 (超时后) |
| cancelByOperator | coreOperator 或 merchant |
| dispute | payer |
| resolve | arbiter |
| refundUnresolvedDispute | 任何人 (仲裁超时后) |
| 所有 set* 管理函数 | owner |
| _authorizeUpgrade | owner |
12.2 安全机制
- ReentrancyGuard: 所有状态变更函数添加
nonReentrant - Input Validation: 零地址、零金额、自付检查
- Fee Cap:
MAX_FEE_BPS = 500(5% 硬顶) - Batch Size Limit:
MAX_BATCH_SIZE = 20(防 Gas griefing) - Fee Snapshot: 存入时记录 feeBps,后续费率变更不影响已有 escrow
- Group ID Replay:
usedGroupIdsmapping 防止重放 - Group Sig Verification: 链上验证 Core Operator 签名
12.3 PlatON Devnet 特殊注意
block.timestamp返回毫秒 (非标准 EVM 行为)- 所有超时参数 (releaseTimeout, disputeWindow, arbitrationTimeout) 均以毫秒配置
- EIP-3009
validBefore也必须使用毫秒时间戳
13. 升级路径
| 当前 (v4.3.0) | 未来 |
|---|---|
| 单链 (PlatON Devnet) | 多链部署 (Base, Ethereum) |
| 单代币活跃 | 多代币并行 (per-payment token selection) |
| Operator 仲裁 | DAO 治理仲裁 |
| 固定费率 | 动态费率 (by volume, merchant tier) |
| UUPS owner 升级 | Timelock + multisig 升级治理 |
14. 版权
Copyright (c) 2026 Nexus Protocol. All Rights Reserved.