x402 Algorand exact 支付方案详解:原子交易组、feePayer 免 Gas 与即时最终性
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
x402 的exact方案在 Algorand 网络上基于标准资产(ASA)实现"精确金额"支付:资源服务器预先声明精确金额,客户端构造一个原子交易组(transaction group)并签名后回传,Facilitator 校验、为免 Gas(gasless)交易补签 feePayer 交易、先模拟后上链,借助 Algorand 无共识分叉特性获得即时最终性。读完全文,你将掌握paymentRequirements/PAYMENT-SIGNATURE/PAYMENT-RESPONSE三个关键报文的完整字段规范、8 步校验流程的源码级实现,以及交易组构造、费用池化(fee pooling)、资产 opt-in 等实操要点。
方案总览
根据 scheme_exact_algo.md 的定义,exact方案在 Algorand 上使用Algorand Standard Asset(ASA)——Algorand 协议的原生资产,无需任何智能合约——授权一笔从付款方到资源服务器的特定金额转账。其核心安全性质是:Facilitator 没有能力把资金导向除资源服务器在paymentRequirements中指定地址之外的任何地方。
整个支付流程的时序如下:
从源码结构看,这个时序在 x402 仓库中由@x402/avm包的三类实现类分别承担:客户端 ExactAvmClient、资源服务器端 ExactAvmServer、以及 Facilitator 端 ExactAvmFacilitator。
paymentRequirements:402 响应中的支付要求
在exact方案(Algorand)中,paymentRequirements记录可以(MAY)在extra元素中包含一个feePayer字段。它告知客户端:可以构造一笔包含 0 Algo 支付交易(发送方为feePayer)的交易,其fee值足以覆盖整组交易的手续费。这笔交易与被期望的资产转账交易一起放入同一个原子组,并在 Facilitator 验证交易组之后、结算上链之前由Facilitator 补签名。
此外,paymentRequirements.asset字段必须(MUST)是一个表示 ASA ID(64 位无符号整数)的字符串,而不是像 EVM 那样的ERC20合约地址。资源服务器在使用 Algorand 方案时必须校验该asset字段合法。
paymentRequirements.extra规范如下:
{ // Optional Algorand address that will pay the transaction fees. feePayer?: string; }完整的paymentRequirements示例(主网 USDC,5 美元):
{ "scheme": "exact", "network": "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", "amount": "5000000", "payTo": "RESOURCESERVERADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTSRPAE", "maxTimeoutSeconds": 60, "asset": "31566704", "extra": { "feePayer": "FACILITATORADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQCXBZE" } }几个值得注意的细节,均可在仓库中印证:
- 网络标识采用 CAIP-2 格式
algorand:<genesis-hash-base64>。主网 genesis hash 为wGHE2Pwdvdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=,测试网为SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=,见 constants.ts。 - 示例中的
asset: "31566704"正是 Algorand 主网 USDC 的 ASA ID,与 constants.ts 中定义的USDC_MAINNET_ASA_ID = "31566704"、测试网USDC_TESTNET_ASA_ID = "10458941"一致;USDC 精度固定为 6 位小数(USDC_DECIMALS = 6)。 feePayer的注入路径:Facilitator 在 supported 接口中通过getExtra()返回 feePayer 地址,资源服务器再把它合入paymentRequirements.extra。仓库实现见 facilitator/scheme.ts 的 getExtra 与 server/scheme.ts 的 enhancePaymentRequirements——前者还会把decimals一并写入extra。从源码结构看,getExtra()在多个 Facilitator 地址中随机选取一个作为 feePayer,用于分散 ALGO 手续费成本,避免单一费用账户被更快耗尽。
PAYMENT-SIGNATURE头报文:paymentGroup 与 paymentIndex
PAYMENT-SIGNATURE头的payload字段必须(MUST)包含paymentGroup字段,它是一个数组,代表一个原子交易组。交易组是 Algorand 协议原生能力(无需合约):组内可以包含多笔不同授权者的交易,整组要么全部成功、要么整体被拒绝,不存在部分执行。
交易组可包含多种交易类型,例如pay(ALGO 原生资产转账)和axfer(通用 ASA 转账)等。
payload 中还必须有paymentIndex字段,标识组内"真正向资源服务器付款"的那笔交易。组内可以执行交换、资产转账等多种辅助操作,但只有一笔交易会把资金转给资源服务器。
若组内只有一笔独立交易,
paymentIndex必须(MUST)为 0。
交易组的容量上限(Algorand 协议限制):
- 最多16 笔顶层交易,每笔可由单签名(
Ed25519)、k-of-n阈值多签名或逻辑签名(logic signature)授权; - 最多256 笔内部交易(inner transactions),由应用(智能合约)授权。
仓库中该 payload 的类型定义是 ExactAvmPayloadV2,并配套一个运行时类型守卫isExactAvmPayload做结构校验:
export interface ExactAvmPayloadV2 { /** * Array of base64-encoded msgpack transactions forming an atomic group. * May include unsigned transactions (for fee payer) that the facilitator will sign. */ paymentGroup: string[]; /** * Zero-based index of the payment transaction within paymentGroup. * This transaction must be an ASA transfer to the payTo address. */ paymentIndex: number; }一笔带 feePayer 抽象费用(即由 Facilitator 付 Gas)的 USDC 资产转账示例:
{ "paymentIndex": 1, // 0th index of the transaction in the group that will pay the resource server "paymentGroup": [ "gaN0eG6Jo2ZlZc0H0KJmds4DLgNro2dlbqxtYWlubmV0LXYxLjCiZ2jEIMBhxNj8Hb3e0tdgS+RWjj9tBBmHrDe95LYgtas5JIrfo2dycMQgfy1Szr+lgvgTJsviMY2KnHSsXqyfCJ1UOCE+2Tf3vS+ibHbOAy4HU6NyY3bEICgEhaJgm6IBjiSUgAAAAAAAAAAAAAAAAAAAAAAAAAAAo3NuZMQgKASFomCbogGOJJSAAAAAAAAAAAAAAAAAAAAAAAAAAACkdHlwZaNwYXk=", "gqNzaWfEQP3J1DI6GLSfK0nLZftvSyVMJuFOE48xPlnZpNdEJWbGbcxsD5aASwza4TjbwhgEF0dXOv8E3W/f22vkEzfFywWjdHhuiaRhYW10zgBMS0CkYXJjdsQgiSTqRESRI1JEAxxJKQAAAAAAAAAAAAAAAAAAAAAAAACiZnbOAy4Da6JnaMQgwGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit+jZ3JwxCB/LVLOv6WC+BMmy+IxjYqcdKxerJ8InVQ4IT7ZN/e9L6Jsds4DLgdTo3NuZMQgEtBGzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkdHlwZaVheGZlcqR4YWlkzgHhq3A=" ] }完整的PAYMENT-SIGNATURE头示例:
{ "x402Version": 2, "scheme": "exact", "network": "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", "resource": { "url": "https://example.net/signup", "description": "$5 registration payment", "mimeType": "text/html" }, "accepted": { "scheme": "exact", "network": "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", "amount": "5000000", "payTo": "RESOURCESERVERADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTSRPAE", "maxTimeoutSeconds": 60, "asset": "31566704", "extra": { "feePayer": "FACILITATORADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQCXBZE" } }, "extensions": {}, "outputSchema": null, "payload": { "paymentIndex": 1, "paymentGroup": [ "gaN0eG6Jo2ZlZc0H0KJmds4DLgNro2dlbqxtYWlubmV0LXYxLjCiZ2jEIMBhxNj8Hb3e0tdgS+RWjj9tBBmHrDe95LYgtas5JIrfo2dycMQgfy1Szr+lgvgTJsviMY2KnHSsXqyfCJ1UOCE+2Tf3vS+ibHbOAy4HU6NyY3bEICgEhaJgm6IBjiSUgAAAAAAAAAAAAAAAAAAAAAAAAAAAo3NuZMQgKASFomCbogGOJJSAAAAAAAAAAAAAAAAAAAAAAAAAAACkdHlwZaNwYXk=", "gqNzaWfEQP3J1DI6GLSfK0nLZftvSyVMJuFOE48xPlnZpNdEJWbGbcxsD5aASwza4TjbwhgEF0dXOv8E3W/f22vkEzfFywWjdHhuiaRhYW10zgBMS0CkYXJjdsQgiSTqRESRI1JEAxxJKQAAAAAAAAAAAAAAAAAAAAAAAACiZnbOAy4Da6JnaMQgwGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit+jZ3JwxCB/LVLOv6WC+BMmy+IxjYqcdKxerJ8InVQ4IT7ZN/e9L6Jsds4DLgdTo3NuZMQgEtBGzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkdHlwZaVheGZlcqR4YWlkzgHhq3A=" ] } }从客户端源码看,上述报文是这样构造出来的(client/scheme.ts 的 createPaymentPayload):
- 若
requirements.extra.feePayer存在,先用占位手续费追加一笔 feePayer 自付的 0 ALGOpay交易,此时paymentIndex = 1;否则paymentIndex = 0。 - 追加一笔
axferASA 转账交易(sender = 客户端地址、receiver = payTo、assetId、amount)。 - 通过 TransactionComposer 的
build()分配 group ID、建议参数(suggested params)与费用。 - 对 gasless 场景重算费用:按
fee = max(fee_per_byte × txn_size, min_fee)对组内每笔交易按其真实编码字节数计算费用,把总费用写入 feePayer 交易,并因为 group ID 由交易编码字节派生,须先剥离旧 group ID、修正费用后再重新groupTransactions()计算新的 group 哈希。 - 客户端只签名"自己的"交易索引;feePayer 交易保持未签名状态放入
paymentGroup,等待 Facilitator 补签。
PAYMENT-RESPONSE头报文
结算成功后,PAYMENT-RESPONSE必须(MUST)返回paymentGroup[paymentIndex]交易的交易 ID(transaction ID),它标识了那笔向payTo地址转账amount的资产转账交易,可用于在网络上定位该交易。若结算失败,应当(SHOULD)也返回交易 ID,但由于失败交易不会上链,它可能无法在链上查到。
完整的PAYMENT-RESPONSE头示例:
{ "success": true, "errorReason": null, "payer": "<payer>", "transaction": "NTRZR6HGMMZGYMJKUNVNLKLA427ACAVIPFNC6JHA5XNBQQHW7MWA", "network": "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=" }校验(Verification):8 步流程与源码实现
规范规定的校验步骤为:
- 校验
x402Version是受支持版本(当前为2)。 - 校验
PAYMENT-SIGNATUREpayload 的accepted字段与paymentRequirements中的scheme均为"exact"。 - 校验两者的
network一致。 - 检查
paymentGroup元素不超过 16 个。 - 解码
paymentGroup中的所有交易。 - 定位
paymentGroup[paymentIndex]交易:- 检查
aamt(资产金额)等于paymentRequirements的amount; - 检查
arcv(资产接收方)等于payTo; - 检查
xaid(资产 ID)等于asset。
- 检查
- 定位所有
snd(发送方)为 Facilitator Algorand 地址的交易:- 检查
type是pay; - 检查以下字段被省略:
close、rekey、amt; - 检查
fee是合理金额; - 由 Facilitator 对该交易签名。
- 检查
- 将支付组提交到 Algorand 节点的
simulate端点评估,确认这些交易能够成功执行。
仓库中 ExactAvmFacilitator.verify 按同样的顺序实现了这 8 步,且每一步失败都会返回机器可读的错误码(invalid_exact_avm_*前缀,见 AVM 包 README 的 Error Codes 一节)。几个源码级的实现要点值得展开:
费用上限是"每笔 5000 µAlgo × 组大小"。规范只说"检查 fee 是合理金额",仓库实现给出了具体取值:constants.ts 定义MAX_REASONABLE_FEE_PER_TXN = 5000(最低手续费 1000 µAlgo 的 5 倍),并对整组费用池化场景用maxReasonableGroupFee(groupSize) = 5000 × groupSize计算上限。选择 5 倍是因为 Algorand 费用公式fee = max(current_fee_per_byte × txn_size, min_fee)在网络拥堵时fee_per_byte会上升,正常时则等于最低费。verifyFeePayerTransaction 还额外校验 feePayer 交易必须是自付(receiver 等于 sender)、amt必须为 0、且closeRemainderTo与rekeyTo必须缺省——这正是规范第 7 步"close、rekey、amt省略"的落地。
只有 Facilitator 地址允许以未签名交易出现。decodeTransactionGroup 对每笔交易先尝试按签名交易解码;若实为未签名交易,则只有当发送方属于 Facilitator 地址集合时才被接受(否则返回invalid_exact_avm_unsigned_non_facilitator),并会校验全组 group ID 一致(invalid_exact_avm_invalid_group_id)。
支付交易必须真实签名且签名必须由发送方所出。verifyPaymentTransaction 除了核对axfer类型、金额(用 BigInt 比较避免字符串格式差异)、接收方、资产 ID 之外,还会检查原始字节含签名(invalid_exact_avm_payment_not_signed),并调用ed25519Verifier对bytesForSigning.transaction(txn)的 Ed25519 签名做密码学校验(invalid_exact_avm_invalid_signature)。
一条额外的安全约束:规范未逐字写出、但 exact 方案总规范 附录中要求"Facilitator 必须执行防止赞助滥用的安全约束"。Algorand 实现对应地检查了paymentGroup[paymentIndex]的发送方不得是 Facilitator 自身地址(invalid_exact_avm_facilitator_transferring),防止 Facilitator 用自己的资金冒充付款方。
模拟端点直接返回失败原因。simulateTransactionGroup 读取节点txnGroups[0].failureMessage,作为invalidMessage原样返回给资源服务器。
结算(Settlement)与即时最终性
交易组被资源服务器校验通过后,Facilitator 通过向任意合法 Algorand 节点的v2/transactions端点提交已验证的交易组来完成结算。
Algorand 不存在共识分叉,交易一经包含进区块即获得即时最终性(instant finality)。因此只要交易进入区块,支付即视为结算完成,Facilitator 通知资源服务器成功并继续资源交付。
settle 实现 的调用链是:先完整复跑一遍verify→ 解码并重签 feePayer 交易 →在提交前先计算支付交易的 TxID(paymentStxn.txn.txId(),保证即使后续失败也能把它写进PAYMENT-RESPONSE的transaction字段)→sendTransactions提交整组 →waitForConfirmation(paymentTxnId, network, 10)最多等待 10 轮出块确认。超时或提交异常分别返回invalid_exact_avm_settlement_failed/invalid_exact_avm_confirmation_failed。
其他注意事项(Additional Considerations)
资产 opt-in 是前置条件
资源服务器要收到某个特定资产(ASA)的支付,必须(MUST)已对该资产完成 opt-in。Algorand 中账户须显式开启接收某资产的资格,否则转给该账户的资产转账会被拒绝。因此资源服务器在部署时应确保(SHOULD)已对paymentRequirements.asset指定的资产 ID 完成 opt-in(opt-in 就是一笔金额 0 的自转账)。从 AVM 包 README 看,这一约束同样适用于客户端与 Facilitator 账户:每个账户需满足最低余额要求(MBR,每账户 0.1 ALGO,每 opt-in 一个 ASA 再加 0.1 ALGO),且 USDC 付款方(客户端)与收款方(服务器/payTo)都必须先 opt-in。
一个交易组内可携带多笔支付
Facilitator 可能需要在一组内同时处理多笔支付与 feePayer 交易。若客户端明确知道自己在支付什么,并在一组中构造多笔支付,理论上最多可有16 笔对资源服务器的支付,或8 笔免 Gas(gasless)支付(16 个槽位中一半被 feePayer 交易占用)。
签名方案
paymentGroup中的每笔顶层交易必须(MUST)由组内对应发送地址的所有者单独签名。Algorand 中顶层交易签名基于三种机制之一:
Ed25519单签名(sig);k-of-n阈值多签名(msig);- 由 Algorand 虚拟机验证的逻辑签名(Logic Signature,
lsig)。
Algorand 地址与公钥的关系
Algorand 地址是 58 字符的 base32 编码,对应以下两种之一:
- 逻辑签名程序字节码 +
Program前缀的 SHA512_256 哈希; - 公钥,并在尾部附加 sha512_256 校验和(最后 4 字节)。
这种编码保证无需预先存在的签名,即可直接从公钥推导出地址(对比 EVM 需要用ecRecover从签名反推地址)。仓库示例的编码函数:
encodeAddress(publicKey: Buffer): string { const keyHash: string = sha512_256.create().update(publicKey).hex() // last 4 bytes of the hash const checksum: string = keyHash.slice(-8) return base32.encode(Encoder.ConcatArrays(publicKey, Buffer.from(checksum, "hex"))).slice(0, 58) }编码格式:msgpack + base64
Algorand 交易使用msgpack编码。paymentGroup数组中每个元素都可以先 base64 解码、再 msgpack 解码来查看交易内容;也可以借助goal命令行工具直接检视:
% cat payload.json | jq -r '.paymentGroup[]' | base64 -d | goal clerk inspect - -[0] { "txn": { "fee": 2000, "fv": 53347179, "gen": "mainnet-v1.0", "gh": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", "grp": "fy1Szr+lgvgTJsviMY2KnHSsXqyfCJ1UOCE+2Tf3vS8=", "lv": 53348179, "rcv": "FACILITATORADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQCXBZE", "snd": "FACILITATORADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQCXBZE", "type": "pay" } } -[1] { "sig": "/cnUMjoYtJ8rSctl+29LJUwm4U4TjzE+Wdmk10QlZsZtzGwPloBLDNrhONvCGAQXR1c6/wTdb9/ba+QTN8XLBQ==", "txn": { "aamt": 5000000, "arcv": "RESOURCESERVERADDRESSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTSRPAE", "fv": 53347179, "gh": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", "grp": "fy1Szr+lgvgTJsviMY2KnHSsXqyfCJ1UOCE+2Tf3vS8=", "lv": 53348179, "snd": "CLIENTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFUPIRI", "type": "axfer", "xaid": 31566704 } }从解码结果可以直观看到校验步骤与字段的对应关系:-[0]是未签名的 feePayerpay交易(snd=rcv= Facilitator,fee覆盖整组),-[1]是带sig的axfer支付交易(aamt/arcv/xaid正是校验步骤 6 核对的三个字段),两笔交易共享同一grp(group ID)。
附录:Gasless 交易 / 赞助费用
提供 "gasless" 交易的 Facilitator,若认为某笔feePayer交易被恶意构造,可以拒绝为其签名。这类恶意检查可以下沉到一段逻辑签名(logic signature)中:由 Facilitator 改为对 TxID 提供签名,并把它作为参数传入,从而把"这笔交易是否值得我付费用"的判定逻辑上链固化为合约。
在仓库中落地该方案:@x402/avm 包速览
规范之上,typescript/packages/mechanisms/avm 提供了 TypeScript 参考实现,安装@x402/avm后即可按网络通配符注册客户端:
import { x402Client } from "@x402/core/client"; import { ExactAvmClient } from "@x402/avm"; const client = new x402Client() .register("algorand:*", new ExactAvmClient(signer));各角色需要配置的环境变量(来自 AVM 包 README):
| 角色 | 变量 | 说明 |
|---|---|---|
| 客户端 | AVM_PRIVATE_KEY | Base64 编码的 64 字节私钥(32 字节 Ed25519 seed + 32 字节公钥),用于签名支付交易 |
| 资源服务器 | AVM_ADDRESS | 收款 Algorand 地址(58 字符 base32) |
| Facilitator | AVM_PRIVATE_KEY | Base64 编码的 64 字节私钥,用于提交结算交易并支付费用 |
测试网快速起步要点:账户需通过水龙头充值 ALGO 与测试网 USDC;客户端与服务端地址须先对 USDC(测试网 ASA ID10458941)完成 opt-in;feePayer账户需留有 ALGO 余额以覆盖 gasless 费用。签名器可用包内辅助函数创建:toClientAvmSigner(privateKey)与toFacilitatorAvmSigner(privateKey, { testnetUrl?, mainnetUrl? }),后者支持自定义 Algod 端点,默认使用 algokit-utils 的AlgorandClient.testNet() / mainNet()公共端点。
端到端行为由 集成测试 exact-avm.test.ts 覆盖:它读取CLIENT_PRIVATE_KEY、FACILITATOR_PRIVATE_KEY、SERVER_ADDRESS三个环境变量,构建scheme: "exact"、network为测试网 CAIP-2 标识、asset为测试网 USDC ASA ID 的支付要求,走完整条 402 →PAYMENT-SIGNATURE→ verify/settle 链路。
小结
Algorand 上的 x402exact方案把支付约束从合约层前移到了协议层:原子交易组保证"要么全成要么全败",paymentIndex+ 三字段核对(aamt/arcv/xaid)保证资金只能流向资源服务器指定的账户且金额分毫不差,feePayer 0 ALGO 自付交易配合费用池化实现了免 Gas 支付,而 Algorand 的即时最终性让结算在交易入块时即刻完成。规范文本位于 scheme_exact_algo.md,可结合 scheme_exact.md 的跨网络关键校验要求与@x402/avm包源码交叉阅读,逐条印证每个 MUST 条款的具体实现位置。
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考