air-agentic-wallet

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AIR Agentic Wallet

AIR 代理钱包

Purpose

用途

This skill teaches an external agent how to authenticate to AIR with a fresh
signedMessage
, request wallet signatures from
POST /v2/wallet/agent-sign
, and control the user's AIR smart account onchain for common wallet, token, and NFT actions.
This skill starts after the agent key already exists.
本技能指导外部Agent如何使用新生成的
signedMessage
向AIR进行身份验证,通过
POST /v2/wallet/agent-sign
请求钱包签名,并针对常见的钱包、代币和NFT操作链上控制用户的AIR智能账户。
本技能的使用前提是:Agent密钥已存在。

Provided scripts

提供的脚本

Use the provided scripts first. Do not scaffold a new project or rewrite AIR signing logic from scratch unless the requested action is unsupported. Treat all files in this skill bundle as read-only reference tooling.
请优先使用提供的脚本。除非请求的操作不受支持,否则不要搭建新项目或从头重写AIR签名逻辑。 请将本技能包中的所有文件视为只读参考工具。

Default Execution Policy

默认执行策略

Regardless of model capability, prioritize the provided scripts first and only write custom code when the requested action is unsupported.
Task mapping:
  • plain message signing ->
    scripts/air-personal-sign.mjs
  • typed data signing ->
    scripts/air-sign-typed-data.mjs
  • read native, ERC-20, ERC-721, or ERC-1155 balances ->
    scripts/air-balance.mjs
  • native token or ERC-20 transfer ->
    scripts/air-send.mjs
  • approve ERC-20, ERC-721, or ERC-1155 spend/operator access ->
    scripts/air-approve.mjs
  • transfer ERC-721 or ERC-1155 NFTs ->
    scripts/air-nft-transfer.mjs
  • arbitrary contract execution ->
    scripts/air-execute.mjs
If a task is supported by one of these scripts, do not create a replacement script.
  • scripts/air-personal-sign.mjs
    : sign plain text or hex with AIR
  • scripts/air-sign-typed-data.mjs
    : sign EIP-712 typed data with AIR
  • scripts/air-balance.mjs
    : read native, ERC-20, ERC-404-compatible, ERC-721, or ERC-1155 balances
  • scripts/air-send.mjs
    : send native tokens or ERC-20 with AIR
  • scripts/air-approve.mjs
    : prepare or submit ERC-20, ERC-404-compatible, ERC-721, or ERC-1155 approvals
  • scripts/air-nft-transfer.mjs
    : prepare or submit ERC-721 or ERC-1155 transfers
  • scripts/air-execute.mjs
    : submit arbitrary contract calls through the AIR smart account
  • scripts/air-common.mjs
    : shared helper module used by the scripts
Before first use, run
node <script> --help
to inspect the supported parameters.
Examples:
bash
node scripts/air-personal-sign.mjs --message "Hello from AIR"
node scripts/air-sign-typed-data.mjs --typed-data-file typed-data.json
node scripts/air-balance.mjs --asset USDC --chain-id 84532
node scripts/air-send.mjs --recipient 0xabc... --amount 0.001 --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 0.1 --asset USDC --chain-id 84532 --send --wait
node scripts/air-approve.mjs --spender 0xabc... --amount 100 --asset USDC --chain-id 84532 --send --wait
node scripts/air-nft-transfer.mjs --standard erc721 --token-address 0xCollection... --recipient 0xabc... --token-id 1 --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 10 --token-address 0xToken... --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 10 --token-address 0xToken... --pre-verification-gas 0x400000 --send --wait
node scripts/air-execute.mjs --target 0xContract... --data 0xabcdef --value 0 --send --wait
For ERC404, only use the helper scripts when the contract is ERC20-compatible for the requested action. Otherwise use
air-execute.mjs
.
无论模型能力如何,都优先使用提供的脚本,仅当请求的操作不受支持时才编写自定义代码。
任务映射:
  • 普通消息签名 ->
    scripts/air-personal-sign.mjs
  • 类型化数据签名 ->
    scripts/air-sign-typed-data.mjs
  • 查询原生代币、ERC-20、ERC-721或ERC-1155余额 ->
    scripts/air-balance.mjs
  • 原生代币或ERC-20转账 ->
    scripts/air-send.mjs
  • 授权ERC-20、ERC-721或ERC-1155的支出/操作员权限 ->
    scripts/air-approve.mjs
  • 转账ERC-721或ERC-1155 NFT ->
    scripts/air-nft-transfer.mjs
  • 任意合约执行 ->
    scripts/air-execute.mjs
如果某任务可由上述脚本支持,则不要创建替代脚本。
  • scripts/air-personal-sign.mjs
    :使用AIR签署纯文本或十六进制内容
  • scripts/air-sign-typed-data.mjs
    :使用AIR签署EIP-712类型化数据
  • scripts/air-balance.mjs
    :查询原生代币、ERC-20、ERC-404兼容代币、ERC-721或ERC-1155的余额
  • scripts/air-send.mjs
    :使用AIR转账原生代币或ERC-20
  • scripts/air-approve.mjs
    :准备或提交ERC-20、ERC-404兼容代币、ERC-721或ERC-1155的授权操作
  • scripts/air-nft-transfer.mjs
    :准备或提交ERC-721或ERC-1155的转账操作
  • scripts/air-execute.mjs
    :通过AIR智能账户提交任意合约调用
  • scripts/air-common.mjs
    :脚本共用的辅助模块
首次使用前,请运行
node <script> --help
查看支持的参数。
示例:
bash
node scripts/air-personal-sign.mjs --message "Hello from AIR"
node scripts/air-sign-typed-data.mjs --typed-data-file typed-data.json
node scripts/air-balance.mjs --asset USDC --chain-id 84532
node scripts/air-send.mjs --recipient 0xabc... --amount 0.001 --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 0.1 --asset USDC --chain-id 84532 --send --wait
node scripts/air-approve.mjs --spender 0xabc... --amount 100 --asset USDC --chain-id 84532 --send --wait
node scripts/air-nft-transfer.mjs --standard erc721 --token-address 0xCollection... --recipient 0xabc... --token-id 1 --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 10 --token-address 0xToken... --send --wait
node scripts/air-send.mjs --recipient 0xabc... --amount 10 --token-address 0xToken... --pre-verification-gas 0x400000 --send --wait
node scripts/air-execute.mjs --target 0xContract... --data 0xabcdef --value 0 --send --wait
对于ERC404代币,仅当合约针对请求的操作兼容ERC20时,才使用辅助脚本;否则请使用
air-execute.mjs

Required Inputs

必要输入

Expect a handoff bundle equivalent to:
json
{
  "userId": "...",
  "walletId": "...",
  "privyAppId": "...",
  "abstractAccountAddress": "0x...",
  "airApiAgentSignUrl": "https://.../v2/wallet/agent-sign",
  "AgenticWalletSkillUrl": "https://..."
}
The agent must also already have access to its own P-256 private key.
预期的交接包格式如下:
json
{
  "userId": "...",
  "walletId": "...",
  "privyAppId": "...",
  "abstractAccountAddress": "0x...",
  "airApiAgentSignUrl": "https://.../v2/wallet/agent-sign",
  "AgenticWalletSkillUrl": "https://..."
}
Agent还必须已获取自身的P-256私钥。

Project-level defaults

项目级默认配置

It is allowed to create or update a project-level
.air-wallet-config.json
file in the working directory. Use that file for defaults such as the AIR handoff bundle, RPC, bundler, paymaster, and key paths. Do not store those defaults by editing files inside this skill bundle.
Example:
json
{
  "userId": "...",
  "walletId": "...",
  "privyAppId": "...",
  "abstractAccountAddress": "0x...",
  "airApiAgentSignUrl": "https://.../v2/wallet/agent-sign",
  "AgenticWalletSkillUrl": "https://...",
  "rpcUrl": "https://sepolia.base.org",
  "bundlerUrl": "https://api.candide.dev/public/v3/base-sepolia",
  "paymasterUrl": null,
  "privateKeyPath": "./p256-private-key.pem",
  "publicKeyPath": "./p256-public-key.pem"
}
All provided scripts resolve configuration in this order:
  1. CLI flags
  2. environment variables
  3. .air-wallet-config.json
Minimum runtime inputs:
  • userId
  • walletId
  • privyAppId
  • abstractAccountAddress
  • airApiAgentSignUrl
    as a full endpoint URL
  • agent private key
Optional runtime inputs:
  • bundlerUrl
  • paymasterUrl
  • target chain RPC
允许在工作目录中创建或更新项目级别的
.air-wallet-config.json
文件,用于存储AIR交接包、RPC、打包器、支付主和密钥路径等默认值。请勿通过修改本技能包内的文件来存储这些默认值。
示例:
json
{
  "userId": "...",
  "walletId": "...",
  "privyAppId": "...",
  "abstractAccountAddress": "0x...",
  "airApiAgentSignUrl": "https://.../v2/wallet/agent-sign",
  "AgenticWalletSkillUrl": "https://...",
  "rpcUrl": "https://sepolia.base.org",
  "bundlerUrl": "https://api.candide.dev/public/v3/base-sepolia",
  "paymasterUrl": null,
  "privateKeyPath": "./p256-private-key.pem",
  "publicKeyPath": "./p256-public-key.pem"
}
所有提供的脚本按以下优先级解析配置:
  1. CLI参数
  2. 环境变量
  3. .air-wallet-config.json
运行时最小输入要求:
  • userId
  • walletId
  • privyAppId
  • abstractAccountAddress
  • 完整的AIR API代理签名端点URL
    airApiAgentSignUrl
  • Agent私钥
可选运行时输入:
  • bundlerUrl
  • paymasterUrl
  • 目标链RPC

Hardcoded AIR Assumptions

AIR硬编码假设

Use these AIR implementation details exactly unless AIR changes them:
json
{
  "entryPointVersion": "0.7",
  "entryPointAddress": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
  "knownK1Validators": [
    "0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa"
  ],
  "baseSepolia": {
    "chainId": 84532
  }
}
Assume the same
abstractAccountAddress
is used across chains.
除非AIR官方修改,否则请严格遵循以下AIR实现细节:
json
{
  "entryPointVersion": "0.7",
  "entryPointAddress": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
  "knownK1Validators": [
    "0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa"
  ],
  "baseSepolia": {
    "chainId": 84532
  }
}
假设同一
abstractAccountAddress
可跨链使用。

Non-Negotiable Rules

不可违反的规则

  • Always call AIR's backend endpoint in
    airApiAgentSignUrl
    .
  • Never call Privy directly for wallet signing.
  • Never modify files inside this installed skill bundle.
  • It is acceptable to create or update
    .air-wallet-config.json
    in the project root for default values.
  • If a custom script is truly required, create it outside the skill directory.
  • Generate a fresh
    signedMessage
    for every request.
  • Never reuse an old
    signedMessage
    .
  • Treat
    signedMessage
    and
    agentSignature
    as two different signatures with two different purposes.
  • Discover a bundler URL yourself when you need onchain execution.
  • If a paymaster URL is provided, use paymaster-sponsored UserOps.
  • If no paymaster URL is provided, use self-funded mode.
  • 始终调用
    airApiAgentSignUrl
    中的AIR后端端点。
  • 切勿直接调用Privy进行钱包签名。
  • 切勿修改已安装技能包内的文件。
  • 可在项目根目录创建或更新
    .air-wallet-config.json
    来存储默认值。
  • 若确实需要自定义脚本,请在技能目录外创建。
  • 为每个请求生成新的
    signedMessage
  • 切勿复用旧的
    signedMessage
  • 区分
    signedMessage
    agentSignature
    这两种不同用途的签名。
  • 当需要链上执行时,自行发现打包器URL。
  • 若提供了支付主URL,请使用支付主赞助的UserOps。
  • 若未提供支付主URL,请使用自付模式。

Signature Model

签名模型

Every
POST /v2/wallet/agent-sign
request contains two signatures:
  1. signedMessage
    : proves agent identity to AIR
  2. agentSignature
    : authorizes the exact wallet signing payload AIR will send to Privy
If either one is wrong, the request fails.
每个
POST /v2/wallet/agent-sign
请求包含两个签名:
  1. signedMessage
    :向AIR证明Agent的身份
  2. agentSignature
    :授权AIR将特定的钱包签名负载发送给Privy
任一签名错误,请求都会失败。

signedMessage

signedMessage

Format:
text
agent_pubkey:userId:unixEpochTime
agent_pubkey
must be the exact registered public key string, typically PEM. Sign the raw message bytes with the agent's P-256 private key using SHA-256, then send:
json
{
  "message": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----:086c40cb-dd8d-4416-9ce8-b0a7789542f3:1773635693",
  "signature": "base64-encoded ES256 signature",
  "publicKey": "the registered public key string"
}
格式:
text
agent_pubkey:userId:unixEpochTime
agent_pubkey
必须为注册时的公钥字符串(通常为PEM格式)。使用Agent的P-256私钥对原始消息字节进行SHA-256签名,然后发送以下内容:
json
{
  "message": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----:086c40cb-dd8d-4416-9ce8-b0a7789542f3:1773635693",
  "signature": "base64编码的ES256签名",
  "publicKey": "注册的公钥字符串"
}

agentSignature

agentSignature

agentSignature
is a base64-encoded P-256 signature over this canonical Privy request payload:
json
{
  "version": 1,
  "method": "POST",
  "url": "https://api.privy.io/v1/wallets/{walletId}/rpc",
  "body": {
    "method": "...",
    "params": {}
  },
  "headers": {
    "privy-app-id": "<privyAppId-from-handoff-bundle>"
  }
}
Rules:
  • sort object keys lexicographically at every level
  • keep array order unchanged
  • sign the canonical JSON bytes with the same agent P-256 private key
  • return the signature as base64
agentSignature
是对标准Privy请求负载进行P-256签名后得到的base64编码结果,负载格式如下:
json
{
  "version": 1,
  "method": "POST",
  "url": "https://api.privy.io/v1/wallets/{walletId}/rpc",
  "body": {
    "method": "...",
    "params": {}
  },
  "headers": {
    "privy-app-id": "<来自交接包的privyAppId>"
  }
}
规则:
  • 对每个层级的对象按键名字典序排序
  • 保持数组顺序不变
  • 使用同一Agent的P-256私钥对标准JSON字节进行签名
  • 返回base64格式的签名

Privy RPC Body Mapping

Privy RPC负载映射

For
personal_sign
, transform:
json
{
  "method": "personal_sign",
  "payload": "0x48656c6c6f"
}
into:
json
{
  "method": "personal_sign",
  "params": {
    "message": "48656c6c6f",
    "encoding": "hex"
  }
}
For
eth_signTypedData_v4
, transform:
json
{
  "method": "eth_signTypedData_v4",
  "payload": {
    "domain": {},
    "primaryType": "MyType",
    "types": {},
    "message": {}
  }
}
into:
json
{
  "method": "eth_signTypedData_v4",
  "params": {
    "typed_data": {
      "primary_type": "MyType",
      "domain": {},
      "types": {},
      "message": {}
    }
  }
}
对于
personal_sign
,将:
json
{
  "method": "personal_sign",
  "payload": "0x48656c6c6f"
}
转换为:
json
{
  "method": "personal_sign",
  "params": {
    "message": "48656c6c6f",
    "encoding": "hex"
  }
}
对于
eth_signTypedData_v4
,将:
json
{
  "method": "eth_signTypedData_v4",
  "payload": {
    "domain": {},
    "primaryType": "MyType",
    "types": {},
    "message": {}
  }
}
转换为:
json
{
  "method": "eth_signTypedData_v4",
  "params": {
    "typed_data": {
      "primary_type": "MyType",
      "domain": {},
      "types": {},
      "message": {}
    }
  }
}

HTTP Request Contract

HTTP请求规范

Call
airApiAgentSignUrl
directly with:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "base64-encoded ES256 signature over message",
    "publicKey": "registered public key"
  },
  "method": "personal_sign",
  "payload": "0x48656c6c6f",
  "agentSignature": "base64-encoded ES256 signature over canonical Privy payload"
}
Typed data example:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "base64-encoded ES256 signature over message",
    "publicKey": "registered public key"
  },
  "method": "eth_signTypedData_v4",
  "payload": {
    "domain": {
      "name": "MyApp",
      "version": "1",
      "chainId": 84532
    },
    "primaryType": "Action",
    "types": {
      "EIP712Domain": [
        { "name": "name", "type": "string" },
        { "name": "version", "type": "string" },
        { "name": "chainId", "type": "uint256" }
      ],
      "Action": [
        { "name": "action", "type": "string" }
      ]
    },
    "message": {
      "action": "swap"
    }
  },
  "agentSignature": "base64-encoded ES256 signature over canonical Privy payload"
}
cURL shape:
bash
curl -X POST "$AIR_API_AGENT_SIGN_URL" \
  -H "content-type: application/json" \
  --data '{
    "signedMessage": {
      "message": "...",
      "signature": "...",
      "publicKey": "..."
    },
    "method": "personal_sign",
    "payload": "0x48656c6c6f",
    "agentSignature": "..."
  }'
直接调用
airApiAgentSignUrl
,请求格式如下:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "对消息进行base64编码的ES256签名",
    "publicKey": "注册的公钥"
  },
  "method": "personal_sign",
  "payload": "0x48656c6c6f",
  "agentSignature": "对标准Privy负载进行base64编码的ES256签名"
}
类型化数据示例:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "对消息进行base64编码的ES256签名",
    "publicKey": "注册的公钥"
  },
  "method": "eth_signTypedData_v4",
  "payload": {
    "domain": {
      "name": "MyApp",
      "version": "1",
      "chainId": 84532
    },
    "primaryType": "Action",
    "types": {
      "EIP712Domain": [
        { "name": "name", "type": "string" },
        { "name": "version", "type": "string" },
        { "name": "chainId", "type": "uint256" }
      ],
      "Action": [
        { "name": "action", "type": "string" }
      ]
    },
    "message": {
      "action": "swap"
    }
  },
  "agentSignature": "对标准Privy负载进行base64编码的ES256签名"
}
cURL格式:
bash
curl -X POST "$AIR_API_AGENT_SIGN_URL" \
  -H "content-type: application/json" \
  --data '{
    "signedMessage": {
      "message": "...",
      "signature": "...",
      "publicKey": "..."
    },
    "method": "personal_sign",
    "payload": "0x48656c6c6f",
    "agentSignature": "..."
  }'

HTTP Success Response Contract

HTTP成功响应规范

json
{
  "signature": "0x..."
}
The returned value is the final wallet signature from AIR's wallet backend.
json
{
  "signature": "0x..."
}
返回值是AIR钱包后端生成的最终钱包签名。

Off-Chain Workflow

链下流程

For
personal_sign
,
eth_signTypedData_v4
, or a
userOpHash
: build a fresh
signedMessage
, build the canonical Privy payload, produce
agentSignature
, call
airApiAgentSignUrl
, then use the returned wallet signature.
对于
personal_sign
eth_signTypedData_v4
userOpHash
:生成新的
signedMessage
,构建标准Privy负载,生成
agentSignature
,调用
airApiAgentSignUrl
,然后使用返回的钱包签名。

On-Chain Workflow

链上流程

Use AIR only for the final wallet signature. Everything else is standard ERC-4337 flow.
仅将AIR用于生成最终钱包签名,其余步骤均遵循标准ERC-4337流程。

Step 1: Discover a bundler URL

步骤1:发现打包器URL

Always try to discover a bundler URL yourself for the current chain.
Default public example for Base Sepolia:
text
https://api.candide.dev/public/v3/base-sepolia
Candide-supported networks:
  • Mainnet: Arbitrum One
    42161
    , Avalanche C-Chain
    43114
    , Base
    8453
    , BNB Smart Chain
    56
    , Celo
    42220
    , Ethereum
    1
    , Gnosis
    100
    , Optimism
    10
    , Polygon PoS
    137
    , Plasma
    9745
    , Worldchain
    480
  • Testnet: Arbitrum Sepolia
    421614
    , Base Sepolia
    84532
    , Optimism Sepolia
    11155420
    , Polygon Amoy
    80002
    , Sepolia
    11155111
    , Celo Alfajores
    44787
    , BNB Smart Chain Testnet
    97
请自行查找当前链对应的打包器URL。
Base Sepolia的默认公开示例:
text
https://api.candide.dev/public/v3/base-sepolia
Candide支持的网络:
  • 主网:Arbitrum One
    42161
    、Avalanche C-Chain
    43114
    、Base
    8453
    、BNB智能链
    56
    、Celo
    42220
    、以太坊
    1
    、Gnosis
    100
    、Optimism
    10
    、Polygon PoS
    137
    、Plasma
    9745
    、Worldchain
    480
  • 测试网:Arbitrum Sepolia
    421614
    、Base Sepolia
    84532
    、Optimism Sepolia
    11155420
    、Polygon Amoy
    80002
    、Sepolia
    11155111
    、Celo Alfajores
    44787
    、BNB智能链测试网
    97

Step 2: Optional paymaster

步骤2:可选支付主

  • If a paymaster URL is provided, build a paymaster-sponsored UserOp.
  • If no paymaster URL is provided, build a self-funded UserOp.
  • 若提供了支付主URL,请构建支付主赞助的UserOp。
  • 若未提供支付主URL,请构建自付模式的UserOp。

Common onchain actions

常见链上操作

Prefer these scripts before writing any custom tooling:
  • fungible balance checks:
    air-balance.mjs
  • NFT ownership or token balance checks:
    air-balance.mjs
  • ERC-20 approvals:
    air-approve.mjs
  • ERC-721 approvals or operator approvals:
    air-approve.mjs
  • ERC-1155 operator approvals:
    air-approve.mjs
  • ERC-721 transfers:
    air-nft-transfer.mjs
  • ERC-1155 transfers:
    air-nft-transfer.mjs
  • unusual token or NFT methods, including custom ERC404 variants:
    air-execute.mjs
在编写自定义工具前,请优先使用以下脚本:
  • fungible代币余额查询:
    air-balance.mjs
  • NFT所有权或代币余额查询:
    air-balance.mjs
  • ERC-20授权:
    air-approve.mjs
  • ERC-721授权或操作员授权:
    air-approve.mjs
  • ERC-1155操作员授权:
    air-approve.mjs
  • ERC-721转账:
    air-nft-transfer.mjs
  • ERC-1155转账:
    air-nft-transfer.mjs
  • 特殊代币或NFT方法(包括自定义ERC404变体):
    air-execute.mjs

Step 3: Minimal UserOp structure

步骤3:最小化UserOp结构

Use EntryPoint v0.7 unpacked fields for this Candide path:
json
{
  "sender": "0x...",
  "nonce": "0x...",
  "factory": null,
  "factoryData": null,
  "callData": "0x...",
  "callGasLimit": "0x...",
  "verificationGasLimit": "0x...",
  "preVerificationGas": "0x...",
  "maxFeePerGas": "0x...",
  "maxPriorityFeePerGas": "0x...",
  "paymaster": null,
  "paymasterVerificationGasLimit": null,
  "paymasterPostOpGasLimit": null,
  "paymasterData": null,
  "signature": "0x..."
}
Do not switch to legacy packed
initCode
/
paymasterAndData
for this flow.
对于Candide路径,请使用EntryPoint v0.7的展开字段:
json
{
  "sender": "0x...",
  "nonce": "0x...",
  "factory": null,
  "factoryData": null,
  "callData": "0x...",
  "callGasLimit": "0x...",
  "verificationGasLimit": "0x...",
  "preVerificationGas": "0x...",
  "maxFeePerGas": "0x...",
  "maxPriorityFeePerGas": "0x...",
  "paymaster": null,
  "paymasterVerificationGasLimit": null,
  "paymasterPostOpGasLimit": null,
  "paymasterData": null,
  "signature": "0x..."
}
请勿在此流程中切换为旧版的压缩
initCode
/
paymasterAndData
格式。

Step 4: Build arbitrary smart account calldata

步骤4:构建任意智能账户调用数据

Treat the AIR smart account as a generic programmable account. The same pattern applies to native transfers, ERC-20, ERC-721, ERC-1155, Uniswap, Aave, and arbitrary contract calls:
  1. choose
    target
  2. choose
    value
  3. encode
    data
  4. wrap it in the smart account execute call
Native transfer example:
json
{
  "target": "0xRecipient",
  "value": "1000000000000000",
  "data": "0x"
}
ERC-20 transfer example:
json
{
  "target": "0xToken",
  "value": "0",
  "data": "encoded transfer(address,uint256)"
}
将AIR智能账户视为通用可编程账户。以下模式适用于原生代币转账、ERC-20、ERC-721、ERC-1155、Uniswap、Aave及任意合约调用:
  1. 选择
    target
  2. 选择
    value
  3. 编码
    data
  4. 将其包装为智能账户的execute调用
原生代币转账示例:
json
{
  "target": "0xRecipient",
  "value": "1000000000000000",
  "data": "0x"
}
ERC-20转账示例:
json
{
  "target": "0xToken",
  "value": "0",
  "data": "编码后的transfer(address,uint256)"
}

Step 5: Detect the installed validator

步骤5:检测已安装的验证器

AIR currently uses Nexus-style validator-aware nonces.
Before fetching the nonce, check the smart account with:
text
isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext)
Use:
  • moduleTypeId = 1
  • additionalContext = 0x
Use the legacy Biconomy K1 Validator address from
knownK1Validators
.
AIR当前使用Nexus风格的验证器感知型随机数。
在获取随机数前,调用智能账户的以下方法进行检测:
text
isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext)
参数:
  • moduleTypeId = 1
  • additionalContext = 0x
使用
knownK1Validators
中的传统Biconomy K1验证器地址。

Step 6: Build the nonce key

步骤6:构建随机数密钥

Use the validator address value itself as the nonce key.
If the installed validator is:
text
0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa
then:
text
nonceKey = BigInt("0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa")
直接使用验证器地址作为随机数密钥。
若已安装的验证器地址为:
text
0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa
则:
text
nonceKey = BigInt("0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa")

Step 7: Fetch the nonce

步骤7:获取随机数

Call EntryPoint v0.7:
text
getNonce(address sender, uint192 key)
with:
  • sender = abstractAccountAddress
  • key = nonceKey
调用EntryPoint v0.7的以下方法:
text
getNonce(address sender, uint192 key)
参数:
  • sender = abstractAccountAddress
  • key = nonceKey

Step 8: Estimate with a dummy signature

步骤8:使用伪签名进行估算

For simulation and gas estimation, use a dummy 65-byte signature:
text
0x + "ff" repeated 65 times
AIR's current smart account stack requires a decodable signature during simulation.
Use this exact recipe:
  1. build the UserOp with a dummy signature
  2. estimate through the bundler
  3. if paymaster is available, attach paymaster fields
  4. recompute
    userOpHash
    using the final gas and paymaster fields
  5. ask AIR to sign that final hash
  6. replace the dummy signature with the returned wallet signature
  7. submit
If estimation fails, a temporary one-file script is acceptable. Do not scaffold a whole project unless necessary.
在模拟和燃气估算时,使用65字节的伪签名:
text
0x + 重复65次的"ff"
AIR当前的智能账户栈要求在模拟过程中使用可解码的签名。
请严格遵循以下流程:
  1. 使用伪签名构建UserOp
  2. 通过打包器进行估算
  3. 若有可用的支付主,附加支付主字段
  4. 使用最终燃气和支付主字段重新计算
    userOpHash
  5. 请求AIR对最终哈希进行签名
  6. 用返回的钱包签名替换伪签名
  7. 提交UserOp
若估算失败,可临时创建单文件脚本,无需搭建完整项目。

Step 9: Compute
userOpHash

步骤9:计算
userOpHash

Compute the ERC-4337
userOpHash
with:
  • EntryPoint version
    0.7
  • EntryPoint address
    0x0000000071727De22E5E9d8BAf0edAc6f37da032
  • the final gas fields
  • paymaster fields included if sponsored
  • signature = 0x
    in the hash input
使用以下参数计算ERC-4337的
userOpHash
  • EntryPoint版本
    0.7
  • EntryPoint地址
    0x0000000071727De22E5E9d8BAf0edAc6f37da032
  • 最终燃气字段
  • 若为赞助模式,需包含支付主字段
  • 哈希输入中的
    signature = 0x

Step 10: Ask AIR to sign the
userOpHash

步骤10:请求AIR签署
userOpHash

Call
airApiAgentSignUrl
with:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "fresh base64 signature",
    "publicKey": "registered public key"
  },
  "method": "personal_sign",
  "payload": "0xUSER_OP_HASH",
  "agentSignature": "base64 signature over canonical Privy payload"
}
Use the returned wallet signature as
userOperation.signature
.
调用
airApiAgentSignUrl
,参数如下:
json
{
  "signedMessage": {
    "message": "agent_pubkey:userId:unixEpochTime",
    "signature": "新生成的base64签名",
    "publicKey": "注册的公钥"
  },
  "method": "personal_sign",
  "payload": "0xUSER_OP_HASH",
  "agentSignature": "对标准Privy负载的base64签名"
}
将返回的钱包签名作为
userOperation.signature

Step 11: Submit

步骤11:提交

  • Sponsored: submit through bundler after paymaster fields are attached
  • Self-funded: estimate through bundler, keep account-funded gas fields, then submit
  • 赞助模式:附加支付主字段后通过打包器提交
  • 自付模式:通过打包器估算,保留账户燃气字段后提交

Step 12: Common UserOp traps

步骤12:常见UserOp陷阱

  • Use a fresh
    signedMessage
    every time you call AIR
  • Build
    agentSignature
    only after canonicalizing the exact Privy payload
  • Recompute
    userOpHash
    after final gas values are known
  • Recompute
    userOpHash
    again if paymaster fields change
  • Keep the dummy signature only for estimation; replace it before submission
  • For Candide + EntryPoint v0.7, use unpacked v0.7 fields
  • If the bundler says
    preVerificationGas
    is too low, first retry with the built-in padding. If needed, use
    --pre-verification-gas
    on
    air-send.mjs
    ,
    air-approve.mjs
    ,
    air-nft-transfer.mjs
    , or
    air-execute.mjs
    . Do not create a replacement skill script just for that.
  • If the bundler says
    Invalid UserOp signature
    , assume the final hash was computed with stale gas or paymaster fields
  • 每次调用AIR时都要生成新的
    signedMessage
  • 仅在对标准Privy负载进行规范化后再构建
    agentSignature
  • 在确定最终燃气值后重新计算
    userOpHash
  • 若支付主字段发生变化,需再次重新计算
    userOpHash
  • 伪签名仅用于估算,提交前需替换为真实签名
  • 对于Candide + EntryPoint v0.7,请使用展开的v0.7字段
  • 若打包器提示
    preVerificationGas
    过低,请先尝试使用内置填充值重试;若仍需调整,可在
    air-send.mjs
    air-approve.mjs
    air-nft-transfer.mjs
    air-execute.mjs
    中使用
    --pre-verification-gas
    参数,无需创建替代技能脚本。
  • 若打包器提示
    Invalid UserOp signature
    ,请假设最终哈希是使用过期的燃气或支付主字段计算的

Base Sepolia Example Defaults

Base Sepolia示例默认值

Use Base Sepolia as the default worked example:
json
{
  "chainId": 84532,
  "bundlerUrl": "https://api.candide.dev/public/v3/base-sepolia",
  "entryPointVersion": "0.7",
  "entryPointAddress": "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
}
Keep the overall flow chain-agnostic.
以Base Sepolia作为默认示例:
json
{
  "chainId": 84532,
  "bundlerUrl": "https://api.candide.dev/public/v3/base-sepolia",
  "entryPointVersion": "0.7",
  "entryPointAddress": "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
}
整体流程需保持链无关性。

Common Chain Assets

常见链资产

Prefer these chains by default unless the user says otherwise:
  • Base Sepolia
    84532
  • Base mainnet
    8453
  • Ethereum mainnet
    1
Common assets:
json
{
  "84532": {
    "name": "Base Sepolia",
    "assets": {
      "USDC": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "EURC": "0x808456652fdb597867f38412077A9182bf77359F"
    }
  },
  "8453": {
    "name": "Base",
    "assets": {
      "USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "MOCA": "0x2b11834ed1feaed4b4b3a86a6f571315e25a884d"
    }
  },
  "1": {
    "name": "Ethereum",
    "assets": {
      "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "MOCA": "0xf944e35f95e819e752f3ccb5faf40957d311e8c5"
    }
  }
}
除非用户另有说明,否则优先使用以下链:
  • Base Sepolia
    84532
  • Base主网
    8453
  • 以太坊主网
    1
常见资产:
json
{
  "84532": {
    "name": "Base Sepolia",
    "assets": {
      "USDC": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "EURC": "0x808456652fdb597867f38412077A9182bf77359F"
    }
  },
  "8453": {
    "name": "Base",
    "assets": {
      "USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "MOCA": "0x2b11834ed1feaed4b4b3a86a6f571315e25a884d"
    }
  },
  "1": {
    "name": "Ethereum",
    "assets": {
      "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "MOCA": "0xf944e35f95e819e752f3ccb5faf40957d311e8c5"
    }
  }
}

Failure Handling

故障处理

  • Unknown public key: the key was removed, wrong, or never registered. Stop and ask for a new handoff bundle.
  • Expired signed message: rebuild a fresh
    signedMessage
    and retry once.
  • Message timestamp too far in the future: fix clock skew, rebuild the message, retry once.
  • Invalid agent signature: rebuild the canonical payload exactly, verify
    walletId
    , verify
    privy-app-id
    , regenerate
    agentSignature
    , retry once.
  • Too many requests: back off, retry later, avoid concurrent duplicate requests for the same key.
  • 未知公钥:密钥已被移除、错误或从未注册。请停止操作并请求新的交接包。
  • 签名消息过期:重新生成新的
    signedMessage
    并重试一次。
  • 消息时间戳超前:修复时钟偏差,重新生成消息并重试一次。
  • Agent签名无效:严格按规范重新构建标准负载,验证
    walletId
    privy-app-id
    ,重新生成
    agentSignature
    并重试一次。
  • 请求过于频繁:请退避重试,避免对同一密钥发起并发重复请求。

Minimal Checklist

检查清单

  • Use the provided
    walletId
  • Use the provided
    userId
  • Use the registered public key exactly as stored
  • Generate a fresh
    signedMessage
  • Build the canonical Privy payload exactly
  • Include
    privy-app-id
    from the provided AIR handoff bundle
  • Send the request to
    airApiAgentSignUrl
  • Never call Privy directly
  • 使用提供的
    walletId
  • 使用提供的
    userId
  • 严格使用注册时的公钥
  • 生成新的
    signedMessage
  • 严格按规范构建Privy负载
  • 包含交接包中的
    privy-app-id
  • airApiAgentSignUrl
    发送请求
  • 切勿直接调用Privy