跳到主要内容

Agent 钱包

给你的 AI Agent 一个能发起交易的账户,全程不接触私钥。

一句话概括: 调一次接口拿到一个地址,这是一个 ERC-4337 智能账户,私钥由我们保管, 部署 gas 由我们代付,之后转账的 gas 由账户自付。

本章面向要把自己的系统接入 BOT Chain Agent 钱包服务的开发者。读完你应该能创建钱包、 理解为什么地址先于合约存在、从钱包转出代币,以及限制它能转给谁。

请先读本章,再读 Agent 身份。注册身份需要一个已经存在的钱包。

本章所有路径都相对于下面两个地址之一:

环境地址
BOT 主网https://wallet-api.botchain.ai
Bohr 测试网https://wallet-api.bohr.life

API Key 与环境绑定。使用测试网的 Key 请求主网地址将被拒绝,不会被静默路由至其他环境。

1. 概览​

问题回答
我能得到什么?一个 ERC-4337 智能账户地址,Agent 从这里发起交易
我需要保管私钥吗?不需要。私钥在我们这边保管和使用,你的系统不做任何签名
gas 谁出?部署 gas 由我们代付。创建时 Agent 不需要有余额,之后转账的 gas 由账户自付
地址能立刻用吗?收款可以。转出需要账户先完成部署
怎么转账?POST /wallets/{id}/transfers,会提交一笔 UserOperation
能限制转账目标吗?能,而且是强制的。白名单在执行前校验
支持哪些链?BOT 主网(chain ID 677)和 Bohr 测试网(chain ID 968)

需要记住的一点:

创建钱包的那一刻地址就存在了,但那个地址上的合约是后面才部署的。 两者都正常,一个没有代码的地址照样可以收款。

2. 获取凭据​

钱包和身份共用一套凭据。每个接入方是一个项目,有一个项目 ID 和一个或多个 API Key, 由我们签发:

ak_<environment>_k_<key_id>.<secret>

把整串(含中间那个点)放在 X-API-Key 请求头里。secret 只在创建时返回一次, 之后无法再取回,拿到后立即存进密钥管理系统。

Key 区分环境,测试网的 Key 在主网上不可用。

3. 创建钱包​

POST /v1/wallets
Content-Type: application/json
X-API-Key: ak_…
{
"external_agent_id": "你自己的-agent-id",
"chain_id": 677,
"idempotency_key": "create-agent-42",
"callback_url": "https://your-service.example/webhooks/wallet"
}
字段必填含义
external_agent_id是你自己系统里的 Agent 标识
chain_id是主网 677,测试网 968
idempotency_key是也可以放在 Idempotency-Key 请求头里
identity_id否关联已有身份。一般不填,身份是后一步
callback_url否状态变化推送到这个地址

注意你不需要提供的东西:没有 owner 地址,也没有账户地址。两者都在我们这边生成后 返回给你。你的系统不需要生成任何东西,也没有任何密钥材料跨越边界。

返回 202:

{
"wallet": {
"wallet_id": "wal_de43139cef869dc9c42ae3",
"project_id": "launchpad",
"external_agent_id": "你自己的-agent-id",
"chain_id": 677,
"owner_address": "0x5f5802fA78d5eE2A3B4fD67F49CD502430C3E88C",
"account_address": "0xd9DC4F46d9da603A2A077A7190BAa85f4e2f05fe",
"deployment_status": "PREDICTED",
"status": "ACTIVE",
"created_at": "2026-09-07T09:37:40Z"
},
"replayed": false
}

返回里有两个地址,它们不能混用:

字段是什么
account_addressAgent 的地址。 往这里打款,链上显示的发起方也是它
owner_address控制这个账户的地址。注册身份时要用,不要往这里打款

记下 wallet_id,后续每个接口都用它。

读写接口的返回结构不一样

写接口把对象包在 wallet 字段里,读接口直接返回对象本身。所以创建之后是 response.wallet.account_address,而 GET 之后是 response.account_address。

用同一个幂等键重放会返回 200 和 "replayed": true,以及原来那个钱包,所以超时重试 不会创建出第二个账户。

也可以用你自己的标识反查,这样不必存我们的 ID:

GET /v1/wallets/{wallet_id}
GET /v1/wallets/by-external-id/{chain_id}/{external_agent_id}

不属于你项目的钱包一律返回 404。「不是你的」和「不存在」在返回上没有区别。

4. 地址先于合约存在​

deployment_status 初始是 PREDICTED。这不是一个需要等待的中间态,而是 ERC-4337 账户本来的工作方式。

地址由 CREATE2 从 owner 和 salt 算出,所以能提前知道,而且不会变。合约稍后才部署到 这个地址上。在那之前:

操作PREDICTED 状态下可用?
接收原生代币或 ERC-20可以。 向一个没有代码的地址转账是正常的
把地址展示给用户可以
转出代币不可以
状态含义
PREDICTED地址已算出,链上还没有合约
DEPLOYING部署已提交,等待上链
DEPLOYED合约已就绪,账户可以转出
部署是一个需要主动调用的步骤

deployment_status 不是 DEPLOYED 时转账会被拒绝,而且转账不会自动触发部署, 只会返回 409 WALLET_NOT_READY。请把部署放进你的初始化流程,不要指望首次付款时惰性触发。

POST /v1/wallets/{wallet_id}/deploy
{ "idempotency_key": "deploy-agent-42" }

返回 202 和一笔 UserOperation。轮询钱包直到 deployment_status 变成 DEPLOYED, 之后转账才会被接受。

部署不需要账户里有余额,gas 由我们代付。

5. 转出代币​

POST /v1/wallets/{wallet_id}/transfers
Content-Type: application/json
X-API-Key: ak_…
{
"receiver": "0x1111111111111111111111111111111111111111",
"token_address": "0x2222222222222222222222222222222222222222",
"amount": "1000000",
"request_source": "API",
"idempotency_key": "payout-2026-09-08-001"
}
字段必填含义
receiver是目标地址,必须在该钱包的白名单里
token_address是ERC-20 合约地址,原生代币填字面量 BOT_NATIVE
amount是最小单位,纯数字。 6 位精度的 USDT,"1000000" 表示 1 USDT
request_source是API、CONSOLE 或 ADMIN,记录转账来源
idempotency_key是防止重试导致重复转账

有四种情况会被直接拒绝:

  • token_address 不能省略。 转原生代币要填字符串 BOT_NATIVE,不是留空, 也不是零地址。
  • amount 只能是数字。 小数和科学计数法会被拒绝,而不是四舍五入,"1.5" 和 "1e6" 均会失败。浮点数转换存在精度损失,可能导致实际转出数量与预期不符, 因此接口不接受此类格式。
  • request_source 必填,且必须是上面三个值之一。
  • 收款方不能是钱包自己的账户地址。 自己转给自己在链上是一次真实执行, 余额不变但 gas 照扣,所以在这里就拦掉。

amount 是字符串不是数字。代币数量会超出 JavaScript 数字能精确表示的范围。

返回 202 和一笔 UserOperation,转账已提交、尚未最终确认:

{
"user_op_hash": "0x…",
"wallet_id": "wal_de43139cef869dc9c42ae3",
"chain_id": 677,
"op_type": "TRANSFER",
"status": "BUILT",
"sender": "0xd9DC4F46d9da603A2A077A7190BAa85f4e2f05fe",
"attempt": 0,
"created_at": "2026-09-08T07:20:11Z"
}

跟踪一笔转账​

GET /v1/user-operations/{user_op_hash}
状态含义终态?
BUILT已构造,尚未签名否
SIGNED已签名,尚未提交否
SUBMITTED已提交给 bundler否
INCLUDED已进块,回执未确认否
SUCCEEDED完成。 tx_hash 和 block_number 已填充是
REVERTED上链执行失败,gas 已消耗是
FAILED从未上链,原因见 last_error是

REVERTED 和 FAILED 含义不同,处理方式也应该不同。REVERTED 是交易到了链上并在 那里失败了:余额不足、代币合约拒绝等等。FAILED 是根本没走到那一步。用新的幂等键 重试 FAILED 是合理的;什么都不改就重试 REVERTED,结果还是 revert。

attempt 和 last_error 显示服务已经替你尝试过什么。

6. 目标白名单​

白名单是强制的,不是可选项

钱包只能转账至已登记在白名单中的地址。白名单为空表示该钱包不能向任何地址转账, 而非「不受限制」。每一个目标地址都必须在首次转账前完成登记,否则转账将被拒绝。

这是刻意设计的:Agent 会自己决定何时转账,所以「能转到哪里」这件事必须提前定死, 而不是从 Agent 收到的指令里推断出来。校验在服务端、在构造操作之前执行,所以即使你的 系统被完全攻陷,攻击者也没法把钱转到一个新地址去。

POST   /v1/wallets/{wallet_id}/allowlist
GET /v1/wallets/{wallet_id}/allowlist
DELETE /v1/wallets/{wallet_id}/allowlist/{entry_id}
{
"chain_id": 677,
"address": "0x1111111111111111111111111111111111111111",
"label": "结算账户",
"allowed_actions": "TRANSFER",
"created_by": "ops@your-company.example",
"idempotency_key": "allow-payout-1"
}
字段必填含义
chain_id是条目按链隔离。968 上的条目不能用于 677 上的转账
address是目标地址
allowed_actions是TRANSFER、PAYMENT 或 ALL
created_by是审计用,记录是谁批准了这个地址
label否便于识别的名称
idempotency_key是

条目按 (wallet_id, chain_id, address) 匹配,且必须是 ACTIVE 状态、 allowed_actions 覆盖转账。被拒时返回信息会说明是哪一条不满足:压根没有条目、 条目已吊销、还是 allowed_actions 不含转账。这是三种不同的问题,对应三种不同的 处理,所以要读拒绝原因,而不是直接把地址再加一遍。

初始化钱包时顺序很重要:先加目标地址,再往里打款。一个有钱但没有白名单条目的钱包 付不了任何人。

7. 暂停钱包​

POST /v1/wallets/{wallet_id}/status
{ "status": "PAUSED" }

PAUSED 会停止新的操作,可以由我们解除。REVOKED 是永久的。

这个接口刻意没有提供「恢复」。让一个已停止的账户重新运行是一个运营决定,而不是停止 操作的机械反向操作。需要恢复请联系我们。

8. 回调(Webhook)​

如果你填了 callback_url,状态变化会以 POST 推送过去,带 X-Callback-Token 请求头。 接收端有四条规则:

  1. 先校验 X-Callback-Token,用常数时间比较,通过后再处理请求体。
  2. 尽快返回 2xx,真正的处理放到异步去做。
  3. 要能处理重复投递。 用对象 ID 加 updated_at 做幂等键。
  4. 重试会导致乱序,updated_at 比已存记录旧的payload 直接忽略。

回调是对轮询的优化,不是替代。你的接收端挂掉时,轮询依然能立刻拿到当前状态。

9. 错误处理​

错误返回里带一个可供程序判断的错误码:

{ "error_code": "WHITELIST_DENIED", "error_message": "…" }

请根据 error_code 分支,不要匹配错误文案。

HTTPerror_code含义可重试?
400INVALID_REQUEST请求格式错误,或某个字段没通过校验否。改请求
401—API Key 缺失或无效否
403WHITELIST_DENIED目标地址没有生效中的白名单条目否。先加白名单
403TOKEN_NOT_ALLOWED该钱包不允许转出这个代币否
404NOT_FOUND钱包不存在,或不属于你的项目否
409WALLET_NOT_READY尚未部署、已暂停或已吊销看情况,见下
409IDEMPOTENCY_KEY_CONFLICTS同一个幂等键用于了不同内容否
422INSUFFICIENT_BALANCE余额不足否。先充值
501NOT_IMPLEMENTED当前环境未启用该能力否
5xxINTERNAL_ERROR我们这边出错是,退避后重试

WALLET_NOT_READY 一个码覆盖了三种情况,error_message 里的 status= 和 deployment= 会说明是哪一种:

  • deployment=PREDICTED 或 DEPLOYING → 部署完成后重试,是临时状态。
  • status=PAUSED → 等钱包恢复后再重试。
  • status=REVOKED → 永久失败,不要重试。

只看到 409 就当成永久冲突,会把正在部署中的钱包误判为失败丢掉。

10. 环境​

项目BOT 主网Bohr 测试网
Chain ID677968
RPChttps://rpc.botchain.aihttps://rpc.bohr.life
区块浏览器https://scan.botchain.aihttps://scan.bohr.life
Bundlerhttps://bundler.botchain.ai/rpchttps://bundler.bohr.life/rpc
EntryPoint0x0000000071727De22E5E9d8BAf0edAc6f37da032同左
账户类型Kernel v0.3.3同左

EntryPoint 是标准的 ERC-4337 v0.7 单例合约,所以现有的 ERC-4337 工具链无需改造 就能配合这些账户使用。

下一步:身份​

钱包给了 Agent 一个账户。身份让这个 Agent 可被验证:任何人都能 直接从链上查到它归谁所有、通过哪个钱包行动。

注册身份需要用到刚创建的钱包里的三个值:

身份接口字段取自
wallet_idwallet.wallet_id
wallet_addresswallet.account_address
owner_addresswallet.owner_address