Web3 实战:使用 Viem 与 OpenZeppelin V5 实现 x402 协议签名验证逻辑

0 阅读6分钟

前言

2024 年至今,AI Agent(人工智能体)逐渐成为 Web3 链上活动的核心参与者,但传统支付体系(信用卡、订阅制等)无法适配 AI Agent 产生的高频、微额、自动化支付请求。为此,Coinbase 联合 Cloudflare 等企业推出 x402 协议,并配套提出 ERC-8004(Trustless Agent)概念。本文将深度解析 x402 协议的应用场景、实现原理,同时结合 Solidity 0.8.24+openzeppelinV5,梳理该协议从开发、测试到部署落地的全流程工程实践,并总结其核心价值、未来展望与面临的挑战。,未来展望和挑战,借助openzeppelinV5+solidity0.8.24+ 实现相关代码从开发、测试、部署落地全流程。

x402协议梳理

一、x402协议:是什么

由Coinbase联合Cloudflare推出,基于HTTP 402状态码的互联网原生支付协议,核心是实现机器自主微支付、链上即时结算、无Gas用户体验,作为Web与Web3的价值连接器,兼容多链与ERC-2612等标准,主打稳定币支付。

二、核心痛点:解决了什么

  • 机器/AI Agent支付难题:实现Agent自主结算,支持小额微支付,摆脱人工干预与预充值束缚。
  • Web3支付门槛高:用户无需持有主币付Gas,仅需钱包签名,开发者易集成。
  • 数字服务变现缺陷:替代订阅制/广告模式,实现按使用付费,降低变现摩擦。
  • 跨系统信任成本高:基于区块链实现可追溯、防篡改,降低跨生态支付开发成本。

三、核心使用场景

  • AI Agent经济:Agent自主购买数据、调用服务,实现无人值守结算。
  • API与数据服务:按调用次数/用量精准计费,替代传统API付费模式。
  • 内容微支付:新闻、音视频等按次/按秒付费,破除订阅捆绑。
  • 物联网(M2M):设备自主支付算力、充电等费用,适配高频小额场景。
  • DeFi融合:无Gas DeFi交互、链上数据付费、跨链策略自动结算。

四、未来展望与挑战

1. 演进方向

  • 短期:完善去中心化促进者网络,扩展多链多代币支持,新增企业级合规功能。
  • 中期:与AI Agent身份、意图金融融合,构建AI+Web3经济闭环,推动DeFi即服务。
  • 长期:重塑互联网价值层,实现支付即身份、无缝Web3体验,打造“万物可结算”生态。

2. 核心挑战

中心化促进者依赖、监管合规风险、双边网络效应构建、合约与签名安全隐患。

五、核心总结

x402核心是填补互联网价值传输的原生标准空白,实现微支付、机器自主结算与Web3无缝体验,对DeFi而言,既是降门槛的体验层,也是连接AI与链上金融的桥梁,适配轻量化内容创作需求。

智能合约开发实战

智能合约

  • 代理付款服务商
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

contract AgentPaymentFacilitator is EIP712 {
    using ECDSA for bytes32;

    // 记录已使用的 nonce,防止重放攻击
    mapping(address => mapping(bytes32 => bool)) public authorizationState;

    bytes32 private constant _TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
        keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)");

    // 定义事件,方便前端和测试脚本追踪
    event PaymentProcessed(address indexed from, address indexed to, uint256 value, bytes32 nonce);

    constructor() EIP712("AgentPaymentFacilitator", "1") {}

    function processAgentPayment(
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        bytes calldata signature
    ) external {
        // 1. 状态检查
        require(block.timestamp > validAfter, "Payment not yet valid");
        require(block.timestamp < validBefore, "Payment expired");
        require(!authorizationState[from][nonce], "Authorization already used");

        // 2. 签名验证
        bytes32 structHash = keccak256(abi.encode(
            _TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
            from,
            to,
            value,
            validAfter,
            validBefore,
            nonce
        ));

        bytes32 hash = _hashTypedDataV4(structHash);
        address signer = ECDSA.recover(hash, signature);
        require(signer == from, "Invalid agent signature");

        // 3. 修改状态:标记 nonce 已使用(消除警告的关键)
        authorizationState[from][nonce] = true;

        // 4. 触发事件
        emit PaymentProcessed(from, to, value, nonce);

        // 实际场景中这里会执行:IERC20(token).transferFrom(from, to, value);
    }
}

测试脚本

测试说明:

  1. Agent Authorization (签名验证)
  2. 当使用伪造签名时应拒绝交易
  3. 过期的支付授权应当失效
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import { network } from "hardhat";
import { hashTypedData, getAddress } from 'viem';

describe("AgentPaymentFacilitator (x402/ERC-8004)", function () {
    let facilitator: any;
    let publicClient: any;
    let owner: any, agent: any, receiver: any;
    let chainId: number;

    beforeEach(async function () {
        const { viem } = await (network as any).connect();
        publicClient = await viem.getPublicClient();
        [owner, agent, receiver] = await viem.getWalletClients();
        
        // 部署合约
        facilitator = await viem.deployContract("AgentPaymentFacilitator", []);
        
        // 获取当前 Network ChainId 用于 EIP-712 签名
        chainId = await publicClient.getChainId();
    });

    describe("Agent Authorization (签名验证)", function () {
        it("应该正确恢复 Agent 签名并允许处理支付", async function () {
            const amount = 1000000n; // 1 USDC (6位精度)
            const nonce = "0x" + "1".padStart(64, "0");
            const validAfter = 0n;
            const validBefore = BigInt(Math.floor(Date.now() / 1000) + 3600);

            // 1. 构造符合 EIP-712 标准的数据域
            const domain = {
                name: 'AgentPaymentFacilitator',
                version: '1',
                chainId: chainId,
                verifyingContract: facilitator.address,
            };

            // 2. 定义类型结构 (需与合约中的 TYPEHASH 结构一致)
            const 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' },
                ],
            }; 

            const message = {
                from: getAddress(agent.account.address),
                to: getAddress(receiver.account.address),
                value: amount,
                validAfter: validAfter,
                validBefore: validBefore,
                nonce: nonce,
            };

            // 3. AI Agent 进行私钥签名 (模拟 x402 客户端授权)
            const signature = await agent.signTypedData({
                domain,
                types,
                primaryType: 'TransferWithAuthorization',
                message,
            });

            // 4. 调用合约验证并处理
            // 在 Hardhat/Viem 环境下,我们先用 simulate 预检
            await facilitator.write.processAgentPayment([
                message.from,
                message.to,
                message.value,
                message.validAfter,
                message.validBefore,
                message.nonce,
                signature
            ], {
                account: owner.account // Facilitator (如 Coinbase 节点) 提交交易
            });

            // 5. 断言验证
            // 如果执行到这里没有 throw error,说明 OpenZeppelin 的 ECDSA.recover 成功匹配了 agent 地址
            assert.ok(signature, "签名应当存在");
            console.log(`✅ Agent ${agent.account.address} 支付签名验证通过`);
        });

        it("当使用伪造签名时应拒绝交易", async function () {
            const invalidSignature = "0x" + "a".repeat(130); // 伪造一个格式正确的假签名
            
            await assert.rejects(
                facilitator.write.processAgentPayment([
                    agent.account.address,
                    receiver.account.address,
                    100n,
                    0n,
                    BigInt(Math.floor(Date.now() / 1000) + 3600),
                    "0x" + "0".padStart(64, "0"),
                    invalidSignature
                ]),
                // 捕获签名不匹配或执行失败的错误
                /Invalid agent signature|ECDSAInvalidSignature/,
                "不合法的签名应该被合约拦截"
            );
        });

        it("过期的支付授权应当失效", async function () {
            const expiredTime = BigInt(Math.floor(Date.now() / 1000) - 60); // 1分钟前过期

            const domain = {
                name: 'AgentPaymentFacilitator', version: '1',
                chainId: chainId, verifyingContract: facilitator.address,
            };

            const 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' },
                ],
            };

            const message = {
                from: getAddress(agent.account.address),
                to: getAddress(receiver.account.address),
                value: 100n,
                validAfter: 0n,
                validBefore: expiredTime,
                nonce: "0x" + "2".padStart(64, "0"),
            };

            const signature = await agent.signTypedData({ domain, types, primaryType: 'TransferWithAuthorization', message });

            await assert.rejects(
                facilitator.write.processAgentPayment([
                    message.from, message.to, message.value,
                    message.validAfter, message.validBefore, message.nonce,
                    signature
                ]),
                /Payment expired/,
                "过期的 validBefore 应该触发合约 revert"
            );
        });
    });
});

部署脚本

// scripts/deploy.js
import { network, artifacts } from "hardhat";
async function main() {
  // 连接网络
  const { viem } = await network.connect({ network: network.name });//指定网络进行链接
  
  // 获取客户端
  const [deployer] = await viem.getWalletClients();
  const publicClient = await viem.getPublicClient();
 
  const deployerAddress = deployer.account.address;
   console.log("部署者的地址:", deployerAddress);
  // 加载合约
  const AgentPaymentFacilitatorArtifact = await artifacts.readArtifact("AgentPaymentFacilitator");

 
  // 部署
  const AgentPaymentFacilitatorHash = await deployer.deployContract({
    abi: AgentPaymentFacilitatorArtifact.abi,//获取abi
    bytecode: AgentPaymentFacilitatorArtifact.bytecode,//硬编码
    args: [],
  });
   const AgentPaymentFacilitatorReceipt = await publicClient.waitForTransactionReceipt({ hash: AgentPaymentFacilitatorHash });
   console.log("合约地址:", AgentPaymentFacilitatorReceipt.contractAddress);
}

main().catch(console.error);

结语

至此,关于 x402 协议的理论解析与基于 Solidity 0.8.24+OpenZeppelin V5 的代码实践已全部完成。作为适配 Web3 时代 AI Agent 高频、微额、自动化支付需求的关键方案,x402 协议(及配套的 ERC-8004)打破了传统支付体系的适配瓶颈,而本次从开发、测试到部署的全流程实践,也为该协议的落地应用提供了可参考的工程范式。未来,随着 AI Agent 与链上生态的深度融合,x402 协议的优化与拓展仍有广阔空间,也期待其能成为 Web3 支付体系升级的重要基石。