Skip to content

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):

  1. UUPS Proxy: 采用可升级代理模式,支持合约逻辑在线升级
  2. Gasless Batch Deposit: batchDepositGasless() — Relayer 代用户提交批量存款,用户零 Gas
  3. Group Signature (Anti-MITM): EIP-712 签名验证批量交易参数完整性
  4. Cancel 机制: cancelByOperator() — 运营/商户即时退款,无需等待超时
  5. Auto-Refund Unresolved Dispute: refundUnresolvedDispute() — 仲裁超时自动退款
  6. Multi-Token: setToken() — 支持切换 ERC-3009 兼容代币
  7. Fee Snapshot: 存入时快照手续费率,防止后续费率变更影响已有 escrow
  8. 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 TransferEscrow Contract评估
安全性不可逆转,无追索权超时退款 + 仲裁 + 即时取消Escrow 优
链上数据Transfer(from, to, value) 三字段自定义事件含 paymentId, orderRef, merchantDidEscrow 优
Gas 成本~65K (用户付)~220K (Relayer 付,用户零 Gas)Escrow 优 (UX)
纠纷处理完全链下链上仲裁 + 自动退款Escrow 优
跨链兼容无 calldata合约地址 + calldata 对接 bridgeEscrow 优

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,
    ReentrancyGuardUpgradeable

4. 状态机

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 转换规则

当前状态目标状态触发条件调用者
(无)DEPOSITEDdeposit / batchDepositGaslessRelayer / User
DEPOSITEDRELEASEDrelease()Core operator / merchant
DEPOSITEDREFUNDEDrefund() (超时后)任何人 (public)
DEPOSITEDREFUNDEDcancelByOperator() (即时)Core operator / merchant
DEPOSITEDDISPUTEDdispute() (争议窗口内)Payer
DISPUTEDRESOLVED_TO_MERCHANTresolve(bps ≥ 5000)Arbiter
DISPUTEDRESOLVED_TO_PAYERresolve(bps < 5000)Arbiter
DISPUTEDRESOLVED_SPLITresolve(0 < bps < 10000)Arbiter
DISPUTEDRESOLVED_TO_PAYERrefundUnresolvedDispute() (仲裁超时)任何人

终态: 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 griefing

6. 核心函数

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 nonReentrant

Relayer 调用,使用用户的 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 的主要入口函数。 流程:

  1. 验证 entries.length <= MAX_BATCH_SIZE
  2. 验证 Group Signature (EIP-712): 从 groupV/R/S 恢复签名者,验证为 coreOperator
  3. 调用 token.transferWithAuthorization(payer, address(this), totalAmount, ...) — 一次性转入总金额
  4. 为每个 entry 创建独立 Escrow record (feeBps 快照)
  5. Emit Deposited event for each entry + BatchDeposited event

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 proxy

7.2 签名流程

  1. Core 构建 BatchDepositInstruction,计算 entriesHash:
    entriesHash = keccak256(
        abi.encode(entry0.paymentId, entry0.merchant, entry0.amount, ...) ||
        abi.encode(entry1.paymentId, entry1.merchant, entry1.amount, ...) ||
        ...
    )
  2. Core Operator 签署 NexusGroupApproval(groupId, entriesHash, totalAmount)
  3. 签名附加到 BatchDepositInstruction 返回给前端
  4. 链上 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)0xeB33a9C2b4c7D3F44Fd5514F90C355AF6bb79236UUPS Proxy
NexusEscrow (Impl v4.3.0)0xF6ED311f8ea594572872E78DB945277ab37ECE37Implementation
XSGD Token0x0Fd437613dE3d14F4dDaB8331DC0f2C0C543BdD0ERC-3009
USDC Token (legacy)0xFF8dEe9983768D0399673014cf77826896F97e4dERC-3009
Relayer / Core Operator0xf7EA5d3f0Bf8185c4f3C2F405D9a71009CF4D920EOA

10.2 当前配置

参数说明
defaultReleaseTimeout86400000 (24h, ms)商户履约超时
defaultDisputeWindow259200000 (72h, ms)用户争议窗口
arbitrationTimeout604800000 (7d, ms)仲裁人裁决超时
protocolFeeBps30 (0.3%)协议手续费
requireGroupSigfalseGroup Sig 强制开关
Active tokenXSGD当前活跃代币

10.3 版本历史

版本Impl 地址日期关键变更
v2.0.0(non-upgradeable, deprecated)2026-02-24初始 escrow (单笔 deposit)
v4.0.00x2EF4dB5E0021d074286c36821Cc897d2605e542E2026-02-26UUPS, batchDepositWithGroupApproval, RESOLVED_SPLIT, refundUnresolvedDispute, MAX_BATCH_SIZE, feeBps snapshot
v4.1.00x7aC2e9C7D655B352f142b679B22f4B86b23A36eC2026-03-12cancelByOperator (即时退款)
v4.3.00xF6ED311f8ea594572872E78DB945277ab37ECE372026-04-01batchDepositGasless (Relayer 代付), setToken (多代币)

11. Gas 成本估算

操作Gas 消耗用户承担Relayer 承担
EIP-3009 签名000
batchDepositGasless (1 entry)~180,0000180,000
batchDepositGasless (5 entries)~450,0000450,000
release~80,000080,000
refund~75,000075,000
cancelByOperator~75,000075,000
dispute~60,00060,0000
resolve~90,000090,000

Escrow 模式下用户仅在主动发起争议 (dispute) 时需要 Gas。所有其他操作由 Relayer 承担。


12. 安全设计

12.1 访问控制

函数权限
deposit / batchDepositGasless任何人 (通常为 Relayer)
releasecoreOperator 或 merchant
refund任何人 (超时后)
cancelByOperatorcoreOperator 或 merchant
disputepayer
resolvearbiter
refundUnresolvedDispute任何人 (仲裁超时后)
所有 set* 管理函数owner
_authorizeUpgradeowner

12.2 安全机制

  1. ReentrancyGuard: 所有状态变更函数添加 nonReentrant
  2. Input Validation: 零地址、零金额、自付检查
  3. Fee Cap: MAX_FEE_BPS = 500 (5% 硬顶)
  4. Batch Size Limit: MAX_BATCH_SIZE = 20 (防 Gas griefing)
  5. Fee Snapshot: 存入时记录 feeBps,后续费率变更不影响已有 escrow
  6. Group ID Replay: usedGroupIds mapping 防止重放
  7. 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.