air-agentic-wallet
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAIR Agentic Wallet
AIR 代理钱包
Purpose
用途
This skill teaches an external agent how to authenticate to AIR with a fresh , request wallet signatures from , and control the user's AIR smart account onchain for common wallet, token, and NFT actions.
signedMessagePOST /v2/wallet/agent-signThis skill starts after the agent key already exists.
本技能指导外部Agent如何使用新生成的向AIR进行身份验证,通过请求钱包签名,并针对常见的钱包、代币和NFT操作链上控制用户的AIR智能账户。
signedMessagePOST /v2/wallet/agent-sign本技能的使用前提是: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.
- : sign plain text or hex with AIR
scripts/air-personal-sign.mjs - : sign EIP-712 typed data with AIR
scripts/air-sign-typed-data.mjs - : read native, ERC-20, ERC-404-compatible, ERC-721, or ERC-1155 balances
scripts/air-balance.mjs - : send native tokens or ERC-20 with AIR
scripts/air-send.mjs - : prepare or submit ERC-20, ERC-404-compatible, ERC-721, or ERC-1155 approvals
scripts/air-approve.mjs - : prepare or submit ERC-721 or ERC-1155 transfers
scripts/air-nft-transfer.mjs - : submit arbitrary contract calls through the AIR smart account
scripts/air-execute.mjs - : shared helper module used by the scripts
scripts/air-common.mjs
Before first use, run to inspect the supported parameters.
node <script> --helpExamples:
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 --waitFor 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
如果某任务可由上述脚本支持,则不要创建替代脚本。
- :使用AIR签署纯文本或十六进制内容
scripts/air-personal-sign.mjs - :使用AIR签署EIP-712类型化数据
scripts/air-sign-typed-data.mjs - :查询原生代币、ERC-20、ERC-404兼容代币、ERC-721或ERC-1155的余额
scripts/air-balance.mjs - :使用AIR转账原生代币或ERC-20
scripts/air-send.mjs - :准备或提交ERC-20、ERC-404兼容代币、ERC-721或ERC-1155的授权操作
scripts/air-approve.mjs - :准备或提交ERC-721或ERC-1155的转账操作
scripts/air-nft-transfer.mjs - :通过AIR智能账户提交任意合约调用
scripts/air-execute.mjs - :脚本共用的辅助模块
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.mjsRequired 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 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.
.air-wallet-config.jsonExample:
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:
- CLI flags
- environment variables
.air-wallet-config.json
Minimum runtime inputs:
userIdwalletIdprivyAppIdabstractAccountAddress- as a full endpoint URL
airApiAgentSignUrl - agent private key
Optional runtime inputs:
bundlerUrlpaymasterUrl- target chain RPC
允许在工作目录中创建或更新项目级别的文件,用于存储AIR交接包、RPC、打包器、支付主和密钥路径等默认值。请勿通过修改本技能包内的文件来存储这些默认值。
.air-wallet-config.json示例:
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"
}所有提供的脚本按以下优先级解析配置:
- CLI参数
- 环境变量
.air-wallet-config.json
运行时最小输入要求:
userIdwalletIdprivyAppIdabstractAccountAddress- 完整的AIR API代理签名端点URL
airApiAgentSignUrl - Agent私钥
可选运行时输入:
bundlerUrlpaymasterUrl- 目标链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 is used across chains.
abstractAccountAddress除非AIR官方修改,否则请严格遵循以下AIR实现细节:
json
{
"entryPointVersion": "0.7",
"entryPointAddress": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"knownK1Validators": [
"0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa"
],
"baseSepolia": {
"chainId": 84532
}
}假设同一可跨链使用。
abstractAccountAddressNon-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 in the project root for default values.
.air-wallet-config.json - If a custom script is truly required, create it outside the skill directory.
- Generate a fresh for every request.
signedMessage - Never reuse an old .
signedMessage - Treat and
signedMessageas two different signatures with two different purposes.agentSignature - 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.
- 始终调用中的AIR后端端点。
airApiAgentSignUrl - 切勿直接调用Privy进行钱包签名。
- 切勿修改已安装技能包内的文件。
- 可在项目根目录创建或更新来存储默认值。
.air-wallet-config.json - 若确实需要自定义脚本,请在技能目录外创建。
- 为每个请求生成新的。
signedMessage - 切勿复用旧的。
signedMessage - 区分和
signedMessage这两种不同用途的签名。agentSignature - 当需要链上执行时,自行发现打包器URL。
- 若提供了支付主URL,请使用支付主赞助的UserOps。
- 若未提供支付主URL,请使用自付模式。
Signature Model
签名模型
Every request contains two signatures:
POST /v2/wallet/agent-sign- : proves agent identity to AIR
signedMessage - : authorizes the exact wallet signing payload AIR will send to Privy
agentSignature
If either one is wrong, the request fails.
每个请求包含两个签名:
POST /v2/wallet/agent-sign- :向AIR证明Agent的身份
signedMessage - :授权AIR将特定的钱包签名负载发送给Privy
agentSignature
任一签名错误,请求都会失败。
signedMessage
signedMessagesignedMessage
signedMessageFormat:
text
agent_pubkey:userId:unixEpochTimeagent_pubkeyjson
{
"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:unixEpochTimeagent_pubkeyjson
{
"message": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----:086c40cb-dd8d-4416-9ce8-b0a7789542f3:1773635693",
"signature": "base64编码的ES256签名",
"publicKey": "注册的公钥字符串"
}agentSignature
agentSignatureagentSignature
agentSignatureagentSignaturejson
{
"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
agentSignaturejson
{
"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 , transform:
personal_signjson
{
"method": "personal_sign",
"payload": "0x48656c6c6f"
}into:
json
{
"method": "personal_sign",
"params": {
"message": "48656c6c6f",
"encoding": "hex"
}
}For , transform:
eth_signTypedData_v4json
{
"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_signjson
{
"method": "personal_sign",
"payload": "0x48656c6c6f"
}转换为:
json
{
"method": "personal_sign",
"params": {
"message": "48656c6c6f",
"encoding": "hex"
}
}对于,将:
eth_signTypedData_v4json
{
"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 directly with:
airApiAgentSignUrljson
{
"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": "..."
}'直接调用,请求格式如下:
airApiAgentSignUrljson
{
"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 , , or a : build a fresh , build the canonical Privy payload, produce , call , then use the returned wallet signature.
personal_signeth_signTypedData_v4userOpHashsignedMessageagentSignatureairApiAgentSignUrl对于、或:生成新的,构建标准Privy负载,生成,调用,然后使用返回的钱包签名。
personal_signeth_signTypedData_v4userOpHashsignedMessageagentSignatureairApiAgentSignUrlOn-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-sepoliaCandide-supported networks:
- Mainnet: Arbitrum One , Avalanche C-Chain
42161, Base43114, BNB Smart Chain8453, Celo56, Ethereum42220, Gnosis1, Optimism100, Polygon PoS10, Plasma137, Worldchain9745480 - Testnet: Arbitrum Sepolia , Base Sepolia
421614, Optimism Sepolia84532, Polygon Amoy11155420, Sepolia80002, Celo Alfajores11155111, BNB Smart Chain Testnet4478797
请自行查找当前链对应的打包器URL。
Base Sepolia的默认公开示例:
text
https://api.candide.dev/public/v3/base-sepoliaCandide支持的网络:
- 主网:Arbitrum One 、Avalanche C-Chain
42161、Base43114、BNB智能链8453、Celo56、以太坊42220、Gnosis1、Optimism100、Polygon PoS10、Plasma137、Worldchain9745480 - 测试网:Arbitrum Sepolia 、Base Sepolia
421614、Optimism Sepolia84532、Polygon Amoy11155420、Sepolia80002、Celo Alfajores11155111、BNB智能链测试网4478797
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 / for this flow.
initCodepaymasterAndData对于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..."
}请勿在此流程中切换为旧版的压缩/格式。
initCodepaymasterAndDataStep 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:
- choose
target - choose
value - encode
data - 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及任意合约调用:
- 选择
target - 选择
value - 编码
data - 将其包装为智能账户的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 = 1additionalContext = 0x
Use the legacy Biconomy K1 Validator address from .
knownK1ValidatorsAIR当前使用Nexus风格的验证器感知型随机数。
在获取随机数前,调用智能账户的以下方法进行检测:
text
isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext)参数:
moduleTypeId = 1additionalContext = 0x
使用中的传统Biconomy K1验证器地址。
knownK1ValidatorsStep 6: Build the nonce key
步骤6:构建随机数密钥
Use the validator address value itself as the nonce key.
If the installed validator is:
text
0x0000002D6DB27c52E3C11c1Cf24072004AC75cBathen:
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 = abstractAccountAddresskey = nonceKey
调用EntryPoint v0.7的以下方法:
text
getNonce(address sender, uint192 key)参数:
sender = abstractAccountAddresskey = 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 timesAIR's current smart account stack requires a decodable signature during simulation.
Use this exact recipe:
- build the UserOp with a dummy signature
- estimate through the bundler
- if paymaster is available, attach paymaster fields
- recompute using the final gas and paymaster fields
userOpHash - ask AIR to sign that final hash
- replace the dummy signature with the returned wallet signature
- 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当前的智能账户栈要求在模拟过程中使用可解码的签名。
请严格遵循以下流程:
- 使用伪签名构建UserOp
- 通过打包器进行估算
- 若有可用的支付主,附加支付主字段
- 使用最终燃气和支付主字段重新计算
userOpHash - 请求AIR对最终哈希进行签名
- 用返回的钱包签名替换伪签名
- 提交UserOp
若估算失败,可临时创建单文件脚本,无需搭建完整项目。
Step 9: Compute userOpHash
userOpHash步骤9:计算userOpHash
userOpHashCompute the ERC-4337 with:
userOpHash- EntryPoint version
0.7 - EntryPoint address
0x0000000071727De22E5E9d8BAf0edAc6f37da032 - the final gas fields
- paymaster fields included if sponsored
- in the hash input
signature = 0x
使用以下参数计算ERC-4337的:
userOpHash- EntryPoint版本
0.7 - EntryPoint地址
0x0000000071727De22E5E9d8BAf0edAc6f37da032 - 最终燃气字段
- 若为赞助模式,需包含支付主字段
- 哈希输入中的
signature = 0x
Step 10: Ask AIR to sign the userOpHash
userOpHash步骤10:请求AIR签署userOpHash
userOpHashCall with:
airApiAgentSignUrljson
{
"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调用,参数如下:
airApiAgentSignUrljson
{
"signedMessage": {
"message": "agent_pubkey:userId:unixEpochTime",
"signature": "新生成的base64签名",
"publicKey": "注册的公钥"
},
"method": "personal_sign",
"payload": "0xUSER_OP_HASH",
"agentSignature": "对标准Privy负载的base64签名"
}将返回的钱包签名作为。
userOperation.signatureStep 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 every time you call AIR
signedMessage - Build only after canonicalizing the exact Privy payload
agentSignature - Recompute after final gas values are known
userOpHash - Recompute again if paymaster fields change
userOpHash - 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 is too low, first retry with the built-in padding. If needed, use
preVerificationGason--pre-verification-gas,air-send.mjs,air-approve.mjs, orair-nft-transfer.mjs. Do not create a replacement skill script just for that.air-execute.mjs - If the bundler says , assume the final hash was computed with stale gas or paymaster fields
Invalid UserOp signature
- 每次调用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 and retry once.
signedMessage - Message timestamp too far in the future: fix clock skew, rebuild the message, retry once.
- Invalid agent signature: rebuild the canonical payload exactly, verify , verify
walletId, regenerateprivy-app-id, retry once.agentSignature - 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 from the provided AIR handoff bundle
privy-app-id - Send the request to
airApiAgentSignUrl - Never call Privy directly
- 使用提供的
walletId - 使用提供的
userId - 严格使用注册时的公钥
- 生成新的
signedMessage - 严格按规范构建Privy负载
- 包含交接包中的
privy-app-id - 向发送请求
airApiAgentSignUrl - 切勿直接调用Privy