按量支付#
买家先存一笔到链上托管账户,每次调用从中扣费,用完时把没用的余额退回。 不像单次支付每笔都上链,按量支付只在「开账户」和「结算」时上链,中间无数次调用都在链下完成。 适合 Agent 任务多步调用、长会话聊天、订阅式 API 等需要长期累计扣费的场景。
定义、底层协议详见 核心概念 · 按量支付。
适用场景#
| 你的业务 | 是否适合 |
|---|---|
| 业务自然分段、每段单价相同(按调用次数 / 按消息数 / 按子任务数) | ✅ |
| 长周期、多次累计扣费(订阅式、Agent 任务串联多步调用) | ✅ |
| 买家希望"用多少算多少",剩余可退 | ✅ |
怎么工作#
按量支付分三步走:
| 步骤 | 一句话 |
|---|---|
| ① 预存 | 买家一次性存一笔钱到链上托管账户。这是唯一一次必须上链的动作。 |
| ② 边用边签 | 每次调用,买家签一张「截至当前累计消费多少钱」的累计账单(Voucher)(链下、瞬时、0 gas)。卖家本地存着最新一张。 |
| ③ 结账 | 卖家任意时刻把手上最新的 Voucher 提交上链,从托管账户里拿走该收的钱;剩余余额自动退回买家。 |
完整流程(含 retry、追充、强制关闭等失败分支)见下方 完整业务流程。
关键术语#
通道(Channel)#
买家预存资金的那个链上托管账户(基于 Escrow 合约托管,谁都不能违反规则挪走)。
Voucher(累计账单)#
买家每次调用时签的那张「截至当前累计消费 X」的累计账单(EIP-712 签名)。
4 个设计性质:
| 特性 | 说明 |
|---|---|
| 累计金额(不是增量) | Voucher 写"截至当前累计消费 X"而非"本次扣 Y" |
| 防重放 | cumulativeAmount 单调递增,旧 Voucher 被链上合约自然拒绝 |
| 抗丢失 | 即使中途 Voucher 丢失,只要卖家手上有最新一张,结算时就能拿到完整金额 |
| 零上链 | Voucher 不上链,卖家本地验签即可 |
为什么用累计而非增量:累计结构同时解决两个问题——一是天然防重放(旧 Voucher 数额必然小于最新一张,链上合约直接拒绝);二是抗丢失(中间几张丢了不影响,最终结算只看最新一张)。
⚠️ 卖家需要把每张 Voucher 持久化保存——具体要求见 持久化保存 Voucher。
settle vs. close#
| 操作 | 通道状态 | 资金流向 |
|---|---|---|
settle(中途结算) | 通道继续开 | 卖家从托管账户拿当前应得,剩余资金继续锁定 |
close(关闭) | 通道终态关闭 | 最终结算 + 未使用余额退回买家 |
宽限期(grace period)#
买家如果想中途单方关闭通道,会触发一个 15 分钟的倒计时窗口,让卖家有时间把手上最新账单上链结算。 这是合约层硬编码的固定值,卖家无需配置。深入细节见 通道生命周期 · 强制关闭。
完整业务流程#
读图导引:
- 阶段 1 —— HTTP 402 Challenge 引导买家完成 EIP-3009 授权 + 链上 open
- 阶段 2 —— HTTP 402 retry 模型:每次调用都走「请求 → 402 + Challenge → 签 Voucher → 重放」,每张 Voucher 都是单签的 EIP-712,cumulativeAmount 单调递增
- 阶段 3 —— 三种结算路径并存:中途
settle/ 协同close/ 强制requestClose
通道生命周期#
状态机#
OPEN(正常使用)→ CLOSING(买家发起 requestClose,进入宽限期)→ CLOSED(宽限期结束 / 卖家或买家完成最终结算)。
三种结算路径#
- 中途
settle:通道保持OPEN,卖家把当前最新 Voucher 上链 → 合约从托管账户划账 → 剩余资金继续锁定,可继续后续调用。 - 协同
close:买家配合签最终 Voucher → 卖家close→ 通道终态CLOSED→ 余额退回买家。最干净的关闭路径。 - 强制
close:见下方「强制关闭」。
强制关闭#
什么是宽限期#
宽限期是 Escrow 合约的延迟关闭保护窗口——买家单方发起强制关闭时,通道不会立即关停,而是先进入 CLOSING 状态倒计时 15 分钟,给卖家用最新 Voucher 抢先结算的机会。
没有这段窗口,买家可以在卖家把最新 Voucher 上链前瞬间关闭通道,让卖家手上那张 Voucher 成为废纸。
15 分钟是合约层硬编码值,卖家无需任何配置。
流程#
买家可单方调 requestClose 触发:
- 通道进入宽限期——状态变为
CLOSING,开始 15 分钟倒计时 - 宽限期内卖家可抢先
close用最新 Voucher 结算 - 宽限期结束后买家调
withdraw拿回剩余资金,通道终态为CLOSED
宽限期固定 15 分钟。买家发起强制关闭后,卖家只有这 15 分钟内将最新 Voucher 上链结算的窗口;窗口过期后,已签未结的 Voucher 金额无法回收。
应对方案:定期主动调用 settle 完成已签 Voucher 的链上结算——例如每 N 次调用触发一次,或每隔几分钟跑一次定时任务。频率越高,单次未结算金额越小,即使错过宽限期,损失也越可控。
持久化保存 Voucher#
⚠️ SDK 默认把最新 Voucher 存在内存里,进程重启即丢失。如果服务器在两次 settle 之间重启,最新 Voucher 丢失后只能用旧的较小那张上链——「最新累计 − 旧累计」之间的金额无法在合约层补救,构成直接资金损失。
必做:把 SDK 的 store 换成持久化存储(Redis / Postgres / SQLite / 文件 store 均可)。SDK 留了 with_store(...) 接口供注入,详见 卖家接入。
资金不足时追充#
通道开通后买家可随时调 topUp 增加预存金额;channelId 不变,无需重新 open。Voucher 累计金额可以继续增长。
- 通道长期开启(包月、长期订阅):建议定期发起中途结算,避免持有的 Voucher 价值过高带来风险
- 业务明确结束(买家取消、会话结束):主动关闭通道让买家及时拿回余额
卖家接入#
SDK 状态#
| Scheme | Node.js | Rust | Go | Java |
|---|---|---|---|---|
session(按量计费通道) | ✅ | ✅ | ✅ | 即将推出 |
完整代码#
package.json:
{
"type": "module",
"dependencies": {
"@okxweb3/mpp": "^0.1.0",
"viem": "^2.21.0"
}
}
// server.ts
// 启动: npx tsx --env-file=.env server.ts
import * as http from "node:http";
import { privateKeyToAccount } from "viem/accounts";
import { Mppx } from "@okxweb3/mpp";
import { session } from "@okxweb3/mpp/evm/server";
import { SaApiClient } from "@okxweb3/mpp/evm";
const UNIT_PRICE_BASE_UNITS = "100"; // 0.0001 of a 6-decimal token
const UNIT_TYPE = "request";
const SUGGESTED_DEPOSIT = "10000"; // 100× unit price
const saClient = new SaApiClient({
apiKey: process.env.OKX_API_KEY!,
secretKey: process.env.OKX_SECRET_KEY!,
passphrase: process.env.OKX_PASSPHRASE!,
});
// viem LocalAccount — replace with WalletClient / KMS / HSM signer in production.
// The session method fast-fails on startup if signer.address !== expected payee.
const sellerSigner = privateKeyToAccount(
process.env.MPP_MERCHANT_PRIVATE_KEY! as `0x${string}`,
);
// Default in-memory store. Pass `store: ...` for SQLite / Redis / Postgres.
const mppx = Mppx.create({
methods: [session({ saClient, signer: sellerSigner })],
realm: "test realm",
secretKey: process.env.MPP_SECRET_KEY!,
});
// Per-route session config. Charged per call; voucher accumulates;
// settle batches on /session/manage close action.
const SESSION = {
amount: UNIT_PRICE_BASE_UNITS,
currency: "0x...adb21711", // currency
recipient: "0x...378211", // receipt
description: "Pay-per-use API",
unitType: UNIT_TYPE,
suggestedDeposit: SUGGESTED_DEPOSIT,
methodDetails: {
chainId: 196, // X Layer
escrowContract: process.env.MPP_ESCROW!, // 40-hex escrow address
feePayer: true,
minVoucherDelta: "0",
},
} as const;
// Routes by `payload.action`: open / voucher / topUp / close.
// mppx.session(...)(request) handles all four uniformly:
// - 402 → challenge response
// - 200 → action-specific result; withReceipt() attaches Payment-Receipt
async function manage(request: Request): Promise<Response> {
const result = await mppx.session(SESSION)(request);
if (result.status === 402) return result.challenge;
// open / topUp / close → empty 204; voucher → resource body.
return result.withReceipt(Response.json({ status: "ok" }));
}
http.createServer(async (req, res) => {
const url = `http://${req.headers.host ?? "localhost:4023"}${req.url}`;
const webReq = new Request(url, {
method: req.method,
headers: new Headers(req.headers as Record<string, string>),
});
const path = new URL(url).pathname;
const webRes =
path === "/session/manage"
? await manage(webReq)
: new Response("not found", { status: 404 });
res.statusCode = webRes.status;
webRes.headers.forEach((v, k) => res.setHeader(k, v));
res.end(await webRes.text());
}).listen(4023);
字段说明#
EvmSessionMethod / SessionMethodDetails:
| 字段 | 含义 | 备注 |
|---|---|---|
with_escrow | Escrow 合约地址 | 必填;通道资金锁在此合约里 |
with_signer | 卖家签名器 | 接受任意 alloy::signers::Signer:PrivateKeySigner / AwsSigner / LedgerSigner / 自定义远程签名器 |
verify_payee | 启动期校验 signer.address() == 期望收款地址 | 启动失败优于运行期账目错乱 |
currency | 计价代币合约地址 | 必填;目前仅支持 USDG / USD₮0 等实现 EIP-3009 的稳定币 |
recipient | 主收款地址 | 必填,EIP-55 校验过的 40-hex 地址 |
chain_id | 链 ID | 196 = X Layer |
fee_payer | Some(true) 卖家代付 gas(transaction 模式) | 默认推荐 true,让买家无需持有 X Layer gas |
min_voucher_delta | 单张 Voucher 的最小增量(base units) | "0" 接受任意;提高数值可降低高频小额 Voucher 的签验开销 |
unit_type | 计费单位名 | 自由命名:request / message / subtask 等 |
unit_price | 单价(base units) | 6 位精度稳定币:"100" = 0.0001 |
suggested_deposit | 推荐预存金额(base units) | 通常设为单价 × 100,覆盖一段会话用量 |
realm | 命名空间隔离 | 不同业务线建议用不同 realm,避免凭证串用 |
secret_key | 卖家用于签 Challenge 的密钥 | 通过 MPP_SECRET_KEY 环境变量注入,不要硬编码 |
买家接入#
按你是否装了 Agentic Wallet 分两条路径——4 个动作(open / topUp / 提交 Voucher / close)能力一致,差别在「跟 Agent 用自然语言说」还是「自己写客户端代码」。
路径 A:装了 Agentic Wallet(推荐)#
Agentic Wallet 内置的 Skill 已经知道怎么调这 4 个动作,买家说一句话就能完成:
| 想做什么 | 跟 Agent 说 | Skill 实际干什么 |
|---|---|---|
| 开通通道 | 「帮我开通 [服务名] 的按量支付,预存 X」 | EIP-3009 授权 → Broker 提交 open |
| 调用服务 | 「用 [服务名] 做 X」 | 自动签 Voucher,附在请求里 |
| 追加预存 | 「给 [服务名] 通道再加 X 预存」 | topUp(channelId, amount) |
| 关闭通道 | 「关闭 [服务名] 通道,退回剩余」 | 协同 close(或 requestClose 进入 15 分钟宽限期) |
私钥在 TEE 内、Skill 自动签名,X Layer 上 USDG / USD₮0 由 OKX 代付——0 gas。
路径 B:用普通 EVM 钱包#
任意支持 EIP-712 签名和 EIP-3009 的钱包都能用,但开发者要自己实现 4 个动作的客户端代码——open / voucher / topUp / close 全部走 HTTP 402 challenge retry 模型,每次都把签好的凭证塞进同一个请求头:
Authorization: Payment <base64url(JCS-canonicalized JSON envelope)>
签名构造(EIP-712 Voucher / EIP-3009 transferWithAuthorization typed data 详情)见 协议规范。
以下是代码示例:
① open(开通通道)#
业务请求 curl:
curl -i 'https://api.example.com/v1/chat/completions' \
-H 'Authorization: Payment eyJjaGFsbGVuZ2UiOnsiZXhwaXJlcyI6IjIwMjYtMDUtMDdUMTI6MDA6MDBaIiwiaWQiOiJrTTl4UHFXdlQybkpySHNZNGFEZkViIiwiaW50ZW50Ijoic2Vzc2lvbiIsIm1ldGhvZCI6ImV2bSIsInJlYWxtIjoiYXBpLmV4YW1wbGUuY29tIiwicmVxdWVzdCI6ImV5SnBiblJsYm5RaU9pSnpaWE56YVc5dUlpd2lZVzF2ZFc1MElqb2lNVEF3TUNJc0ltTjFjbkpsYm1ONUlqb2lNSGczTkdJM1pqRTJNek0zWWpoa1pqVmtPVEpqT1dZNFkySmpabUpqWkdRNU1EQTBPV1ZqTW1Zd0lpd2ljbVZqYVhCcFpXNTBJam9pTUhnM1pUSm1NMk0wWkRWbE5tWTNZVGhpT1dNd1pERmxNbVl6WVRSaU5XTTJaRGRsT0dZNVlUQmlJaXdpYldWMGFHOWtSR1YwWVdsc2N5STZleUpqYUdGcGJrbGtJam94T1RZc0ltVnpZM0p2ZDBOdmJuUnlZV04wSWpvaU1IaG1aakF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNUkUlPSlxImZWVbDXBoZpwl1lqcDBjblZsZlgwIfWslmF5bG9hZCk6cyJhY3Rpb24iOiJvcGVuIiwiYXV0aG9yaXphdGlvbiI6eyJmcm9tIjoiMHgxMjM0NTY3ODkwYUJjRGVmMTIzNDU2Nzg5MGFCY0RlZjEyMzQ1Njc4Iiwibm9uY2UiOiIweGExYjJjM2Q0ZTVmNjA3MTgyOTNhNGI1YzZkN2U4ZjkwYTFiMmMzZDRlNWY2MDcxODI5M2E0YjVjNmQ3ZThmOTAiLCJ0byI6IjB4ZmYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMSIsInR5cGUiOiJlaXAtMzAwOSIsInZhbGlkQWZ0ZXIiOiIwIiwidmFsaWRCZWZvcmUiOiIxNzQ2NjE5MjAwIiwidmFsdWUiOiIxMDAwMDAifSwiY2hhbm5lbElkIjoiMHg2ZDBmNGZkZjFmMmY2YTFmNmMxYjBmYmQ2YTdkNWMyYzBhOGQzZDdiMWY2YTljMWIzZTJkNGE1YjZjN2Q4ZTlmIiwiY3VtdWxhdGl2ZUFtb3VudCI6IjAiLCJzYWx0IjoiMHg5YThiN2M2ZDVlNGYzYTJiMWMwZDllOGY3YTZiNWM0ZDNlMmYxYTBiOWM4ZDdlNmY1YTRiM2MyZDFlMGY5YThiIiwic2lnbmF0dXJlIjoiMHhhYjAxY2QyM2VmNDU2Nzg5MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjFjIiwidHlwZSI6InRyYW5zYWN0aW9uIiwidm91Y2hlclNpZ25hdHVyZSI6IjB4MTEyMjMzNDQ1NTY2Nzc4ODk5MDAxMTIyMzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDAxMTIyMzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzMWIifSwic291cmNlIjoiZGlkOnBraDplaXAxNTU6MTk2OjB4MTIzNDU2Nzg5MGFCY0RlZjEyMzQ1Njc4OTBhQmNEZWYxMjM0NTY3OCJ9'
解码后 envelope 明文:
{
"challenge": {
// 完整回显卖家 WWW-Authenticate 的 6 个字段
"id": "kM9xPqWvT2nJrHsY4aDfEb", // 服务端本次 challenge 唯一标识
"realm": "api.example.com", // 卖家域,HMAC 校验范围
"method": "evm", // EVM 链支付方法
"intent": "session", // session ↔ charge 二选一
"request": "eyJpbnRlbnQiOiJzZXNzaW9uIi...", // 卖家原始请求参数(amount/currency/methodDetails…)的 base64url
"expires": "2026-05-07T12:00:00Z" // 过期时间(RFC3339)
},
"source": "did:pkh:eip155:196:0x1234567890aBcDef1234567890aBcDef12345678",
// 付款人 DID,格式固定 did:pkh:eip155:<chainId>:<addr>
"payload": {
"action": "open", // 阶段判别 —— open/voucher/topUp/close 之一
"type": "transaction", // transaction = 卖家代付 gas;hash = 客户端自己已广播
"channelId": "0x6d0f4fdf...e9f", // 链上确定性派生,open 时计算出的
"salt": "0x9a8b...9a8b", // channelId 派生用的随机salt
"authorization": { // EIP-3009 充值授权(transaction 模式独有)
"type": "eip-3009", // 固定值
"from": "0x1234...5678", // source 里的 addr
"to": "0xff00...0001", // escrow 合约
"value": "100000", // 充值金额,atomic units(USDC 100000 = 0.1 USDC)
"validAfter": "0",
"validBefore": "1746619200", // EIP-3009 签名有效期 unix 秒
"nonce": "0xa1b2...8f90" // EIP-3009 nonce,按合约公式派生
},
"signature": "0xab01...ef1c", // 上面 authorization 的 EIP-3009 签名
"cumulativeAmount": "0", // voucher 的累计值,默认 "0"
"voucherSignature": "0x1122...331b" // Voucher(channelId, cum=0) 的 EIP-712 签名
}
}② voucher(每次业务请求扣费)#
业务请求 curl:
curl -i 'https://api.example.com/v1/chat/completions' \
-H 'Authorization: Payment eyJjaGFsbGVuZ2UiOnsiZXhwaXJlcyI6IjIwMjYtMDUtMDdUMTI6MDA6MDBaIiwiaWQiOiJrTTl4UHFXdlQybkpySHNZNGFEZkViIiwiaW50ZW50Ijoic2Vzc2lvbiIsIm1ldGhvZCI6ImV2bSIsInJlYWxtIjoiYXBpLmV4YW1wbGUuY29tIiwicmVxdWVzdCI6ImV5SnBiblJsYm5RaU9pSnpaWE56YVc5dUlpd2lZVzF2ZFc1MElqb2lNVEF3TUNJc0ltTjFjbkpsYm1ONUlqb2lNSGczTkdJM1pqRTJNek0zWWpoa1pqVmtPVEpqT1dZNFkySmpabUpqWkdRNU1EQTBPV1ZqTW1Zd0lpd2ljbVZqYVhCcFpXNTBJam9pTUhnM1pUSm1NMk0wWkRWbE5tWTNZVGhpT1dNd1pERmxNbVl6WVRSaU5XTTJaRGRsT0dZNVlUQmlJaXdpYldWMGFHOWtSR1YwWVdsc2N5STZleUpqYUdGcGJrbGtJam94T1RZc0ltVnpZM0p2ZDBOdmJuUnlZV04wSWpvaU1IaG1aakF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNUkUlPSlxImZWVbDXBoZpwl1lqcDBjblZsZlgwIfWslmF5bG9hZCk6cyJhY3Rpb24iOiJ2b3VjaGVyIiwiY2hhbm5lbElkIjoiMHg2ZDBmNGZkZjFmMmY2YTFmNmMxYjBmYmQ2YTdkNWMyYzBhOGQzZDdiMWY2YTljMWIzZTJkNGE1YjZjN2Q4ZTlmIiwiY3VtdWxhdGl2ZUFtb3VudCI6IjUwMDAiLCJzaWduYXR1cmUiOiIweGRlYWRiZWVmMDAxMTIyMzM0NDU1NjY3Nzg4OTkwMGFhYmJjY2RkZWVmZjAwMTEyMjMzNDQ1NTY2Nzc4ODk5YWFiYmNjZGRlZWZmMDAxMTIyMzM0NDU1NjY3Nzg4OTlhYWJiY2NkZGVlZmYwMDExMjIzMzQ0NTU2Njc3ODg5OTExYiJ9fQ'
解码后 envelope 明文:
{
"challenge": {
// 与 open 相同结构,回显本次 voucher challenge
"id": "kM9xPqWvT2nJrHsY4aDfEb",
"realm": "api.example.com",
"method": "evm",
"intent": "session",
"request": "eyJpbnRlbnQiOiJzZXNzaW9uIi...",
"expires": "2026-05-07T12:00:00Z"
},
"payload": {
"action": "voucher", // 阶段判别 —— voucher
"channelId": "0x6d0f4fdf...e9f", // 必须是 open 时拿到的同一个 channelId
"cumulativeAmount": "5000", // 累计值(atomic units),必须严格大于上一张
"signature": "0xdeadbeef...11b" // EIP-712 Voucher(channelId, cum) 的签名
}
}③ topUp(追加预存)#
业务请求 curl:
curl -i -X POST 'https://api.example.com/session/manage' \
-H 'Authorization: Payment eyJjaGFsbGVuZ2UiOnsiZXhwaXJlcyI6IjIwMjYtMDUtMDdUMTI6MDA6MDBaIiwiaWQiOiJrTTl4UHFXdlQybkpySHNZNGFEZkViIiwiaW50ZW50Ijoic2Vzc2lvbiIsIm1ldGhvZCI6ImV2bSIsInJlYWxtIjoiYXBpLmV4YW1wbGUuY29tIiwicmVxdWVzdCI6ImV5SnBiblJsYm5RaU9pSnpaWE56YVc5dUlpd2lZVzF2ZFc1MElqb2lNVEF3TUNJc0ltTjFjbkpsYm1ONUlqb2lNSGczTkdJM1pqRTJNek0zWWpoa1pqVmtPVEpqT1dZNFkySmpabUpqWkdRNU1EQTBPV1ZqTW1Zd0lpd2ljbVZqYVhCcFpXNTBJam9pTUhnM1pUSm1NMk0wWkRWbE5tWTNZVGhpT1dNd1pERmxNbVl6WVRSaU5XTTJaRGRsT0dZNVlUQmlJaXdpYldWMGFHOWtSR1YwWVdsc2N5STZleUpqYUdGcGJrbGtJam94T1RZc0ltVnpZM0p2ZDBOdmJuUnlZV04wSWpvaU1IaG1aakF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNU0lzSW1abFpWQmhlV1Z5SWpwMGNuVmxmWDAifSwicGF5bG9hZCI6eyJhY3Rpb24iOiJ0b3BVcCIsImFkZGl0aW9uYWxEZXBvc2l0IjoiNTAwMDAiLCJhdXRob3JpemF0aW9uIjp7ImZyb20iOiIweDEyMzQ1Njc4OTBhQmNEZWYxMjM0NTY3ODkwYUJjRGVmMTIzNDU2NzgiLCJub25jZSI6IjB4YzNkNGU1ZjYwNzE4MjkzYTRiNWM2ZDdlOGY5MDAxYTFiMmMzZDRlNWY2MDcxODI5M2E0YjVjNmQ3ZThmOTAwMSIsInRvIjoiMHhmZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxIiwidHlwZSI6ImVpcC0zMDA5IiwidmFsaWRBZnRlciI6IjAiLCJ2YWxpZEJlZm9yZSI6IjE3NDY2MTk1MDAiLCJ2YWx1ZSI6IjUwMDAwIn0sImNoYW5uZWxJZCI6IjB4NmQwZjRmZGYxZjJmNmExZjZjMWIwZmJkNmE3ZDVjMmMwYThkM2Q3YjFmNmE5YzFiM2UyZDRhNWI2YzdkOGU5ZiIsInNpZ25hdHVyZSI6IjB4ZmVlZGZhY2UxMTIyMzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDAxMTIyMzM0NDU1NjY3Nzg4OTkwMDExMjIzMzQ0NTU2Njc3ODg5OTAwMTEyMjMzNDQ1NTY2Nzc4ODk5MDAxYiIsInRvcFVwU2FsdCI6IjB4YjFjMmQzZTRmNWE2MDcxODI5MzA0MTUyNjM3NDg1OTZhN2I4YzlkMGUxZjIwMzE0MjUzNjQ3NTg2OTcwOGE5YiIsInR5cGUiOiJ0cmFuc2FjdGlvbiJ9LCJzb3VyY2UiOiJkaWQ6cGtoOmVpcDE1NToxOTY6MHgxMjM0NTY3ODkwYUJjRGVmMTIzNDU2Nzg5MGFCY0RlZjEyMzQ1Njc4In0'
解码后 envelope 明文:
{
"challenge": {
// 与 open 相同结构,回显本次 topup challenge
"id": "kM9xPqWvT2nJrHsY4aDfEb",
"realm": "api.example.com",
"method": "evm",
"intent": "session",
"request": "eyJpbnRlbnQiOiJzZXNzaW9uIi...",
"expires": "2026-05-07T12:00:00Z"
},
"source": "did:pkh:eip155:196:0x1234567890aBcDef1234567890aBcDef12345678",
"payload": {
"action": "topUp", // 阶段判别
"type": "transaction", // transaction = 卖家代付;hash = 客户端已自行广播
"channelId": "0x6d0f4fdf...e9f", // 必须是 open 那个 channelId
"topUpSalt": "0xb1c2...0a9b", // 32-byte 随机 salt,用于派生 EIP-3009 nonce
"authorization": { // EIP-3009 充值授权
"type": "eip-3009", // 固定值
"from": "0x1234...5678", // 付款人
"to": "0xff00...0001", // escrow 合约
"value": "50000", // = additionalDeposit(atomic units,必须一致)
"validAfter": "0",
"validBefore": "1746619500", // 签名有效期 unix 秒
"nonce": "0xc3d4...9001" // 上面公式派生的 nonce(不能随机)
},
"signature": "0xfeed...001b", // 上面 authorization 的 EIP-3009 签名
"additionalDeposit": "50000" // 本次追加抵押额(必须等于 authorization.value)
}
}④ close(关闭通道)#
业务请求 curl:
curl -i -X POST 'https://api.example.com/session/manage' \
-H 'Authorization: Payment eyJjaGFsbGVuZ2UiOnsiZXhwaXJlcyI6IjIwMjYtMDUtMDdUMTI6MDA6MDBaIiwiaWQiOiJrTTl4UHFXdlQybkpySHNZNGFEZkViIiwiaW50ZW50Ijoic2Vzc2lvbiIsIm1ldGhvZCI6ImV2bSIsInJlYWxtIjoiYXBpLmV4YW1wbGUuY29tIiwicmVxdWVzdCI6ImV5SnBiblJsYm5RaU9pSnpaWE56YVc5dUlpd2lZVzF2ZFc1MElqb2lNVEF3TUNJc0ltTjFjbkpsYm1ONUlqb2lNSGczTkdJM1pqRTJNek0zWWpoa1pqVmtPVEpqT1dZNFkySmpabUpqWkdRNU1EQTBPV1ZqTW1Zd0lpd2ljbVZqYVhCcFpXNTBJam9pTUhnM1pUSm1NMk0wWkRWbE5tWTNZVGhpT1dNd1pERmxNbVl6WVRSaU5XTTJaRGRsT0dZNVlUQmlJaXdpYldWMGFHOWtSR1YwWVdsc2N5STZleUpqYUdGcGJrbGtJam94T1RZc0ltVnpZM0p2ZDBOdmJuUnlZV04wSWpvaU1IaG1aakF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNREF3TURBd01EQXdNUkUlPSlxImZWVbDXBoZpwl1lqcDBjblZsZlgwIfWslmF5bG9hZCk6cyJhY3Rpb24iOiJjbG9zZSIsImNoYW5uZWxJZCI6IjB4NmQwZjRmZGYxZjJmNmExZjZjMWIwZmJkNmE3ZDVjMmMwYThkM2Q3YjFmNmE5YzFiM2UyZDRhNWI2YzdkOGU5ZiIsImN1bXVsYXRpdmVBbW91bnQiOiI0MjAwMCIsInNpZ25hdHVyZSI6IjB4Y2FmZWJhYmU5OTg4Nzc2NjU1NDQzMzIyMTEwMDk5YWFiYmNjZGRlZWZmMDAxMTIyMzM0NDU1NjY3Nzg4OTlhYWJiY2NkZGVlZmYwMDExMjIzMzQ0NTU2Njc3ODg5OWFhYmJjY2RkZWVmZjAwMTEyMjMzNDQ1NTY2Nzc4ODk5MjFjIn19'
解码后 envelope 明文:
{
"challenge": {
// 同上,回显卖家最后一次 challenge
"id": "kM9xPqWvT2nJrHsY4aDfEb",
"realm": "api.example.com",
"method": "evm",
"intent": "session",
"request": "eyJpbnRlbnQiOiJzZXNzaW9uIi...",
"expires": "2026-05-07T12:00:00Z"
},
"payload": {
"action": "close", // 阶段判别 —— close
"channelId": "0x6d0f4fdf...e9f", // 必须是 open 那个 channelId
"cumulativeAmount": "42000", // = current_cum,session 内最高累计值
"signature": "0xcafebabe...21c" // EIP-712 final Voucher 签名
}
}Gas 费#
X Layer 上 USDG / USD₮0 的 gas 由 OKX 承担——两条路径都 0 gas。
边界与权衡#
Agent 卖家(即将推出)#
Agent 卖家版本即将推出。Agent 卖家场景由 OKX 在协议基础上扩展承载(与 HTTP 卖家在底层独立),但语义层(Challenge / Credential)和字段结构与 HTTP 卖家一致。
| 维度 | HTTP 卖家 | Agent 卖家(即将推出) |
|---|---|---|
| Challenge 载体 | HTTP 402 响应 | 消息通道消息体 |
| 通道开通触发 | 客户端首次请求 | Agent 在对话里主动发起 |
| Voucher 提交 | HTTP 请求头 | 消息回复 |
| 业务驱动 | API 调用 | Agent 对话 |