RFC-005 v4: Nexus Payment Core Specification (Gasless Escrow Settlement)
| Metadata | Value |
|---|---|
| Title | Nexus Payment Core Specification |
| Version | 4.0.0 |
| Status | Standards Track (Draft) |
| Supersedes | RFC-005 v3.0.0 (Escrow Settlement) |
| Author | Cipher & Nexus Architect Team |
| Created | 2026-02-24 |
| Updated | 2026-04-01 |
| Scope | Orchestration, State Management, Gasless Escrow, Group Payments, AP2 Protocol, Cancel/Dispute, Multi-Token |
| Chain | PlatON Devnet (chain_id: 20250407) |
| Token | ERC-3009 compatible stablecoins (XSGD, USDC) |
1. Abstract
本 RFC 定义 Nexus Core 的完整实现规范。v4 在 v3 Escrow Settlement 基础上新增 Gasless Deposit、Cancel 机制、AP2 Credential Provider 和 Multi-Token 支持。
核心变更(v3 → v4):
- Gasless Deposit: 用户仅签署 EIP-3009 授权,Relayer 代付 Gas 提交
batchDepositGasless() - Cancel 机制: 商户/运营可在任何非终态取消支付,链上 ESCROWED 状态通过
cancelByOperator()即时退款 - AP2 Protocol: 作为 Credential Provider 为 Agent Protocol 2 签发 PaymentMandate Verifiable Credential
- Multi-Token: 通过
setToken()支持多种 ERC-3009 兼容代币 (当前: XSGD) - 13-State Machine: 新增 CANCELLED 状态
- Auto-Refund Late Deposits: 过期支付收到延迟链上存款时自动触发退款
2. Design Principles
- Escrow Settlement: 资金通过智能合约担保,商户履约后释放,超时自动退款
- Gasless UX: 用户只需签署 EIP-3009 授权,不持有任何原生代币;Relayer 代付全部 Gas
- Batch Efficiency: 多笔支付聚合为一组,共享一个链上交易
- MCP-First: 所有能力通过 MCP Protocol 暴露,同时提供 REST API
- Event Sourcing: 每次状态变更生成不可变事件记录
- Anti-MITM: Core Operator 签署 Group 指令,链上验证防止篡改
- Protocol Agnostic: 同时支持 NUPS 原生协议和 AP2 Agent Protocol
3. Architecture
UA --[MCP/REST]--> Nexus Core --[Webhook]--> MA
|
+-- Security Module (EIP-712, DID Resolver, Group Sig)
+-- Order State Machine (13 states)
+-- Group Manager (batch orchestration)
+-- AP2 Protocol Router (NUPS / AP2 detection)
+-- AP2 Credential Provider (PaymentMandate VC issuance)
+-- Chain Watcher (Escrow events: Deposited/Released/Refunded)
+-- Relayer (gasless deposit / release / refund / cancel tx submission)
+-- Webhook Notifier (HMAC signed, RFC-009)
+-- PostgreSQL (payments, payment_groups, events, merchants,
| webhook_logs, mandate_evidence)
|
PlatON Devnet (chain_id: 20250407)
XSGD/USDC (ERC-3009) + NexusEscrow (UUPS Proxy v4.3.0)4. Payment Flow
4.1 Happy Path (Gasless)
Step 1: UA obtains merchant Quote (NUPS v1.5, EIP-712 signed)
Step 2: UA calls Core nexus_orchestrate_payment(quotes[], payer_wallet)
Core verifies sigs -> DID resolves -> creates payment group
-> returns 402 with BatchDepositInstruction (user_action: "SIGN_ONLY")
Step 3: User signs EIP-3009 TypedData (via MetaMask / wallet) — 仅签名,不发送交易
Step 4: Checkout page POST /api/checkout/:groupId/relay-deposit { payer_address, v, r, s }
Core Relayer calls batchDepositGasless() on-chain — 用户零 Gas
Step 5: Core Chain Watcher detects Deposited events -> status = ESCROWED
Step 6: Core sends Webhook to MA: payment.escrowed
Step 7: MA confirms fulfillment -> Core Relayer calls release() on escrow
Step 8: Chain Watcher detects Released event -> status = SETTLED
Step 9: Core sends Webhook to MA: payment.settled
Step 10: MA confirms completion -> status = COMPLETED4.2 Checkout Page Flow (Browser Gasless)
Step 1: UA receives 402 response with checkout_url
Step 2: User opens checkout_url in browser with MetaMask
Step 3: Checkout page displays payment summary (SSE real-time updates)
Step 4: User clicks "Pay" → MetaMask prompts EIP-3009 signature (签名,非交易)
Step 5: Checkout page POST relay-deposit with signature components
Step 6: Relayer submits batchDepositGasless() on-chain
Step 7: Page polls /api/checkout/:token until receipt verified
Step 8: Receipt verified → ESCROWED transition4.3 AP2 Protocol Flow
当检测到 AP2 协议请求时 (body.protocol === "ap2" 或包含 cart_mandates):
Step 1: UA sends orchestrate request with CartMandate VCs
Step 2: Core validates CartMandate signatures, extracts payment intent
Step 3: Core creates payment group, returns 402 (same as NUPS)
Step 4: User completes gasless deposit (same as 4.1/4.2)
Step 5: On ESCROWED transition → Core issues PaymentMandate VC
(W3C Verifiable Credential, signed with ECDSA P-256 / JCS)
Step 6: PaymentMandate stored in mandate_evidence table
Step 7: Webhook includes mandate_vc in payload for AP2 orders4.4 Cancel Flow
Pre-Escrow (CREATED / AWAITING_TX):
-> nexus_cancel_payment → database status = CANCELLED
-> Webhook: payment.cancelled
ESCROWED:
-> nexus_cancel_payment → Relayer calls cancelByOperator() on-chain
-> Chain Watcher detects Refunded → status = CANCELLED
-> Webhook: payment.cancelled
Group-level:
-> nexus_cancel_order → cancels all child payments in group
-> Group status → GROUP_CANCELLED4.5 HTTP 402 Payment Required Response
interface PaymentRequired402 {
readonly nexus_version: string;
readonly group_id: string;
readonly status: "PAYMENT_REQUIRED";
readonly checkout_url: string;
readonly instruction: BatchDepositInstruction;
readonly nexus_group_sig: Hex;
readonly core_operator_address: Address;
}4.6 BatchDepositInstruction Schema
interface BatchDepositInstruction {
readonly group_id: string;
readonly chain_id: 20250407;
readonly chain_name: "PlatON Devnet";
readonly rpc_url: string;
readonly payment_method: "ESCROW_CONTRACT";
readonly escrow_contract: Address; // UUPS proxy address
readonly token_address: Address; // ERC-3009 token contract
readonly token_symbol: string; // "XSGD" | "USDC"
readonly token_decimals: 6;
readonly total_amount_uint256: string;
readonly total_amount_display: string;
readonly payments: readonly GroupPaymentDetail[];
readonly eip3009_sign_data: EIP3009SignData; // EIP-3009 TypedData for signing
readonly user_action: "SIGN_ONLY"; // v4: user only signs, relayer submits
readonly gas_paid_by: "RELAYER"; // v4: relayer pays all gas
readonly nexus_group_sig: Hex;
readonly core_operator_address: Address;
}v3 → v4 关键变更:
user_action:"SIGN_AND_SEND"→"SIGN_ONLY"gas_paid_by:"USER"→"RELAYER"token_symbol: 固定"USDC"→ 动态 (当前"XSGD")- 移除
deposit_tx字段 (用户不再直接发送交易)
4.7 GroupPaymentDetail Schema
interface GroupPaymentDetail {
readonly nexus_payment_id: string;
readonly merchant_did: string;
readonly merchant_order_ref: string;
readonly merchant_address: Address;
readonly amount_uint256: string;
readonly amount_display: string;
readonly summary: string;
readonly payment_id_bytes32: Hex; // keccak256(nexus_payment_id)
readonly order_ref_bytes32: Hex; // keccak256(merchant_order_ref)
readonly merchant_did_bytes32: Hex; // keccak256(merchant_did)
readonly context_hash: Hex; // keccak256(JSON.stringify(context))
}5. State Machine
5.1 States (13-state payment machine)
| Status | Description | Trigger |
|---|---|---|
| CREATED | Quote verified, payment created | orchestrate_payment success |
| AWAITING_TX | Legacy: UA has PaymentInstruction | Direct Transfer mode (deprecated) |
| BROADCASTED | Legacy: UA submitted tx_hash | Direct Transfer mode (deprecated) |
| ESCROWED | Funds deposited in escrow contract | Chain Watcher (Deposited event) |
| SETTLED | Escrow released to merchant | Chain Watcher (Released event) |
| COMPLETED | Merchant confirmed fulfillment | confirm_fulfillment call |
| EXPIRED | Payment timed out | Timeout Handler |
| TX_FAILED | On-chain transaction reverted | Chain Watcher |
| RISK_REJECTED | Security check failed | Security Module (future) |
| REFUNDED | Escrow refunded to payer | Chain Watcher (Refunded event) |
| DISPUTE_OPEN | Payer opened dispute | Chain Watcher (Disputed event) |
| DISPUTE_RESOLVED | Arbiter resolved dispute | Chain Watcher (Resolved event) |
| CANCELLED | Payment cancelled by merchant/operator | nexus_cancel_payment (v4 新增) |
5.2 Transition Rules
(none) -> CREATED [valid quote + signature verified]
CREATED -> ESCROWED [Chain Watcher: Deposited event]
CREATED -> EXPIRED [timeout]
CREATED -> RISK_REJECTED [security check failed]
CREATED -> CANCELLED [merchant/operator cancel]
ESCROWED -> SETTLED [Chain Watcher: Released event]
ESCROWED -> REFUNDED [Chain Watcher: Refunded event (timeout)]
ESCROWED -> DISPUTE_OPEN [Chain Watcher: Disputed event]
ESCROWED -> CANCELLED [cancelByOperator → Refunded on-chain]
SETTLED -> COMPLETED [merchant confirms fulfillment]
DISPUTE_OPEN -> DISPUTE_RESOLVED [Chain Watcher: Resolved event]
EXPIRED -> REFUNDED [auto-refund late deposits]
Legacy (Direct Transfer, deprecated):
CREATED -> AWAITING_TX [UA requests PaymentInstruction]
AWAITING_TX -> BROADCASTED [UA submits tx_hash]
AWAITING_TX -> CANCELLED [cancel]
BROADCASTED -> SETTLED [on-chain Transfer confirmed]
BROADCASTED -> TX_FAILED [on-chain revert]Terminal statuses: COMPLETED, EXPIRED, TX_FAILED, RISK_REJECTED, REFUNDED, DISPUTE_RESOLVED, CANCELLED
5.3 Group Statuses
| Group Status | Description |
|---|---|
| GROUP_CREATED | Group created, payments pending |
| GROUP_AWAITING_TX | 402 returned, waiting for on-chain tx |
| GROUP_DEPOSITED | On-chain deposit confirmed (receipt verified) |
| GROUP_ESCROWED | All child payments transitioned to ESCROWED |
| GROUP_SETTLED | All child payments released |
| GROUP_COMPLETED | All child payments completed |
| GROUP_EXPIRED | Group timed out |
| GROUP_PARTIAL | Mixed states (some settled, some disputed) |
| GROUP_CANCELLED | All child payments cancelled (v4 新增) |
5.4 Timeout Rules
| Scenario | Timeout | Action |
|---|---|---|
| Quote expiry | quote.expiry timestamp | CREATED -> EXPIRED |
| Awaiting deposit | 30 minutes | CREATED -> EXPIRED |
| Escrow release deadline | 24 hours (on-chain, ms) | refund() callable by anyone |
| Dispute window | 72 hours (on-chain, ms) | dispute() no longer callable |
| Arbitration timeout | 7 days (on-chain, ms) | refundUnresolvedDispute() callable |
PlatON Devnet 注意:
block.timestamp返回毫秒而非秒。所有链上超时参数均以毫秒为单位配置。
5.5 Auto-Refund Late Deposits
当 EXPIRED 状态的支付收到延迟的链上 Deposited 事件时:
- Chain Watcher 检测到 Deposited event
- 自动调用 Relayer.submitRefund() 触发链上退款
- 状态不变 (保持 EXPIRED),但 Refunded 事件被记录
- 防止用户资金被锁定在已过期的订单中
6. Security Specification
6.1 EIP-712 Quote Verification
const NEXUS_QUOTE_DOMAIN = {
name: "Nexus",
version: "1",
chainId: 20250407,
verifyingContract: "0x0000000000000000000000000000000000000000",
} as const;
const NEXUS_QUOTE_TYPES = {
NexusQuote: [
{ name: "merchant_did", type: "string" },
{ name: "merchant_order_ref", type: "string" },
{ name: "amount", type: "uint256" },
{ name: "currency", type: "string" },
{ name: "chain_id", type: "uint256" },
{ name: "expiry", type: "uint256" },
{ name: "context_hash", type: "bytes32" },
],
} as const;验证步骤:
- 从 quote 字段重建 EIP-712 TypedData
- 从 signature 恢复签名者地址
- 从 merchant_registry 解析 merchant_did 获取注册签名者
- 比对恢复地址与注册签名者
- 验证 quote 未过期
6.2 Group Signature (Anti-MITM)
Core 生成 BatchDepositInstruction 后,使用 Core Operator 密钥签署 EIP-712 GroupApproval:
const GROUP_APPROVAL_TYPES = {
NexusGroupApproval: [
{ name: "groupId", type: "bytes32" },
{ name: "entriesHash", type: "bytes32" },
{ name: "totalAmount", type: "uint256" },
],
} as const;链上 batchDepositGasless() 验证此签名,确保交易参数未被篡改。
6.3 Receipt Verification
Checkout 确认时,Core 验证链上交易回执:
- HTTP 200: receipt success → transition to ESCROWED
- HTTP 202: receipt not available yet → frontend polls (5s interval, max 24 attempts = 120s)
- HTTP 422: receipt reverted → error, no state change
6.4 DID Resolution (MVP)
MVP uses a local merchant_registry table:
merchant_did TEXT PRIMARY KEY
signer_address TEXT NOT NULL -- signing key address
payment_address TEXT NOT NULL -- receiving address (escrow release target)
webhook_url TEXT -- callback URL
webhook_secret TEXT -- HMAC key
ap2_did TEXT -- AP2 agent DID (optional)6.5 Payment Address Trust
CRITICAL: Core MUST resolve payment_address from the merchant_did registry. It MUST NOT trust any address passed in the quote or by the UA. This prevents payment redirection attacks.
7. Interface Specification
7.1 MCP Tools (11 tools)
| Tool | Caller | Description |
|---|---|---|
| nexus_orchestrate_payment | UA | Verify quotes, create payment group, return 402 with BatchDepositInstruction |
| nexus_get_payment_status | UA/MA | Query payment or group status |
| nexus_confirm_fulfillment | MA | Confirm merchant delivery, trigger escrow release |
| nexus_release_payment | MA/Core | Explicit escrow release |
| nexus_dispute_payment | Payer | Open dispute within dispute window |
| nexus_resolve_dispute | Arbiter | Resolve dispute with bps split |
| nexus_cancel_payment | MA | Cancel single payment (pre-escrow or on-chain) |
| nexus_cancel_order | MA | Cancel all payments in a group |
| nexus_get_merchant | UA | Get merchant profile + marketplace metadata |
| nexus_discover_agents | UA | Search agent catalog by keyword |
| nexus_get_agent_skill | UA | Fetch agent's skill.md |
7.2 REST API
| Method | Path | Description |
|---|---|---|
| POST | /api/orchestrate | Verify quotes, return 402 with payment instruction |
| GET | /api/checkout/:token | Get group instruction JSON |
| POST | /api/checkout/:token/relay-deposit | Gasless deposit (v4): submit EIP-3009 sig, relayer submits on-chain |
| POST | /api/checkout/:token/confirm | Legacy: confirm user-submitted tx_hash |
| GET | /api/payments | Query payments (by nexus_payment_id, merchant_order_ref, group_id) |
| GET | /api/payments/:id | Get payment details |
| GET | /api/merchant/payments | Merchant portal: list payments (filter by did, status, since) |
| POST | /api/merchant/confirm-fulfillment | Merchant confirms fulfillment (triggers release) |
| POST | /api/merchant/cancel-payment | Cancel single payment |
| POST | /api/merchant/cancel-order | Cancel all payments in group |
| GET | /api/agents | List all registered agents |
| GET | /api/agents/:did/skill | Fetch agent's skill.md |
7.3 Checkout Page
| Path | Description |
|---|---|
| /checkout/:groupId | Browser checkout page (MetaMask integration, Gasless) |
| /portal | Core management portal |
7.4 Rate Limiting
- 30 requests per minute per IP (token bucket)
- Headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
8. AP2 Protocol Integration
8.1 Protocol Detection
function detectProtocol(body): "ap2" | "nups" {
if (body.protocol === "ap2") return "ap2";
if (body.cart_mandates) return "ap2";
return "nups";
}8.2 Verifiable Credential Types
Nexus 作为 AP2 Credential Provider,处理以下三种 W3C Verifiable Credential:
CartMandate VC (由 Merchant Agent 签发)
interface CartMandateSubject {
merchant_did: string;
cart_id: string;
total: string;
currency: string;
line_items: CartLineItem[];
expires_at: string;
}IntentMandate VC (由 User Agent 签发)
interface IntentMandateSubject {
agent_did: string;
budget: string;
currency: string;
merchant_whitelist: string[];
ttl: number; // seconds
}PaymentMandate VC (由 Nexus Credential Provider 签发)
当支付转为 ESCROWED 状态时,Core 签发 PaymentMandate 作为支付凭证:
interface PaymentMandateSubject {
credential_provider_did: string;
nexus_payment_id: string;
group_id: string;
payer: Address;
total_amount: string;
currency: string;
chain_id: number;
escrow_contract: Address;
deposit_tx_hash: Hex;
}8.3 Proof Structure (Data Integrity)
interface JCSProof {
type: "DataIntegrityProof";
cryptosuite: "ecdsa-jcs-2022";
verificationMethod: string; // DID URL
proofPurpose: "assertionMethod";
created: string; // ISO 8601
proofValue: string; // base64url-encoded r||s
}签名算法: ECDSA P-256, JSON Canonicalization Scheme (JCS)
8.4 Mandate Storage
CREATE TABLE mandate_evidence (
evidence_id TEXT PRIMARY KEY,
nexus_payment_id TEXT NOT NULL,
group_id TEXT NOT NULL,
mandate_type TEXT NOT NULL, -- 'cart_mandate' | 'intent_mandate' | 'payment_mandate'
mandate_vc JSONB NOT NULL, -- complete W3C VC JSON
issuer_did TEXT NOT NULL,
subject_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);9. Chain Watcher Specification
9.1 Polling Strategy
- Poll PlatON RPC every 3 seconds for new blocks
- Filter NexusEscrow contract logs for:
Deposited(paymentId, payer, merchant, amount, orderRef)BatchDeposited(groupId, payer, totalAmount, count)Released(paymentId, merchant, merchantAmount, feeAmount)Refunded(paymentId, payer, amount)Disputed(paymentId, payer, reason)Resolved(paymentId, merchantBps, merchantAmount, payerAmount)
9.2 Event Processing
On Deposited event:
1. Match paymentId to payment record (payment_id_bytes32)
2. Check if payment is EXPIRED → if so, auto-refund (submit refund tx)
3. Transition payment: CREATED -> ESCROWED
4. Record deposit_tx_hash, release_deadline, dispute_deadline
5. If protocol === "ap2": issue PaymentMandate VC
6. Send webhook: payment.escrowed
On Released event:
1. Match paymentId to payment record
2. Transition payment: ESCROWED -> SETTLED
3. Record release_tx_hash, protocol_fee
4. Send webhook: payment.settled
On Refunded event:
1. Match paymentId to payment record
2. Check cancel context → CANCELLED or REFUNDED
3. Record refund_tx_hash
4. Send webhook: payment.refunded or payment.cancelled10. Relayer Service
The Relayer is a server-side service that submits transactions on behalf of the Core:
| Operation | Trigger | Gas Payer | v3 | v4 |
|---|---|---|---|---|
batchDepositGasless(...) | User signs EIP-3009 | Relayer | N/A | NEW |
release(paymentId) | Merchant confirms fulfillment | Relayer | Yes | Yes |
refund(paymentId) | Timeout auto-refund | Relayer | Yes | Yes |
cancelByOperator(paymentId) | Merchant/operator cancel | Relayer | N/A | NEW |
resolve(paymentId, bps) | Arbiter resolves dispute | Relayer | N/A | NEW |
Relayer wallet: 0xf7EA5d3f0Bf8185c4f3C2F405D9a71009CF4D920 (also coreOperator on contract)
Retry logic: exponential backoff (1s, 3s, 9s), max timeout 120s per transaction.
11. Webhook Notification
See RFC-009 v1.2 for full specification. Key points:
11.1 Event Types (11)
| Event | Trigger |
|---|---|
| payment.created | Payment created |
| payment.escrowed | Funds deposited in escrow |
| payment.settled | Escrow released to merchant |
| payment.completed | Merchant confirmed fulfillment |
| payment.expired | Payment timed out |
| payment.failed | On-chain transaction reverted |
| payment.refunded | Escrow refunded to payer (timeout/dispute) |
| payment.cancelled | Payment cancelled by merchant/operator |
| dispute.opened | Payer opened dispute |
| dispute.resolved | Arbiter resolved dispute |
11.2 Security
- HMAC-SHA256 signature in
X-Nexus-Signatureheader - Exponential backoff retry (6 attempts: 10s, 30s, 2min, 10min, 30min)
- Idempotent via event_id
- Delivery logged in
webhook_delivery_logstable
12. Database Schema
Core tables:
- payments: Payment order records (13-state machine, escrow fields, protocol field: 'nups' | 'ap2')
- payment_groups: Group aggregation records (batch deposits)
- payment_events: Event sourcing (append-only, 15 event types)
- merchant_registry: Merchant identity + marketplace metadata + ap2_did
- webhook_delivery_logs: Webhook delivery tracking with retry state
- mandate_evidence: AP2 Verifiable Credential storage (v4 新增)
13. Deployed Addresses
| Contract | Address | Type |
|---|---|---|
| NexusEscrow (Proxy) | 0xeB33a9C2b4c7D3F44Fd5514F90C355AF6bb79236 | UUPS Proxy |
| NexusEscrow (Impl v4.3.0) | 0xF6ED311f8ea594572872E78DB945277ab37ECE37 | Implementation |
| XSGD | 0x0Fd437613dE3d14F4dDaB8331DC0f2C0C543BdD0 | ERC-3009 Token |
| USDC (legacy) | 0xFF8dEe9983768D0399673014cf77826896F97e4d | ERC-3009 Token |
| Relayer / Core Operator | 0xf7EA5d3f0Bf8185c4f3C2F405D9a71009CF4D920 | EOA |
14. Contract Version History
| Version | Date | Key Changes |
|---|---|---|
| v2.0.0 | 2026-02-24 | Initial escrow (depositWithAuthorization, release, refund, dispute, resolve) |
| v3.0.0 | 2026-02-24 | Batch deposits, RESOLVED_SPLIT, non-upgradeable |
| v4.0.0 | 2026-02-26 | UUPS proxy, batchDepositWithGroupApproval, refundUnresolvedDispute, MAX_BATCH_SIZE=20, feeBps snapshot |
| v4.1.0 | 2026-03-12 | cancelByOperator() — immediate refund by operator/merchant |
| v4.3.0 | 2026-04-01 | batchDepositGasless() — relayer-submitted gasless deposits, setToken() for multi-token |
15. Upgrade Path
| Current (this RFC) | Future |
|---|---|
| Gasless Escrow (single chain) | Hub-Spoke cross-chain (PlatON + Base + ETH) |
| Local merchant_registry | On-chain NexusMerchantRegistry |
| Basic signature verification | Full RiskGatekeeper with Permit (RFC-006) |
| PlatON Devnet only | Multi-chain production |
| XSGD/USDC stablecoins | Multi-asset (any ERC-3009 token) |
| AP2 Credential Provider | Full AP2 ecosystem participation |
| Browser checkout + MCP | @nexus/buyer-skills SDK (RFC-011) |
16. Copyright
Copyright (c) 2026 Nexus Protocol. All Rights Reserved.