Billetera del Agent
Dale a tu Agent de IA una cuenta que pueda iniciar transacciones sin manejar claves privadas.
En una frase: Una llamada a la API devuelve la dirección de una cuenta inteligente ERC-4337. Nosotros custodiamos las claves privadas y pagamos el gas del despliegue; la cuenta paga el gas de las transferencias posteriores.
Este capítulo está dirigido a desarrolladores que integran sus sistemas con el servicio de billeteras de Agents de BOT Chain. Explica cómo crear una billetera, por qué su dirección existe antes que el contrato, cómo enviar tokens y cómo restringir los destinatarios.
Lee este capítulo antes de Identidad del Agent. Para registrar una identidad se necesita una billetera existente.
Todas las rutas de este capítulo son relativas a una de estas URL base:
| Entorno | URL |
|---|---|
| Red principal de BOT | https://wallet-api.botchain.ai |
| Red de pruebas de Bohr | https://wallet-api.bohr.life |
Las claves de API están vinculadas al entorno. Las solicitudes a la red principal con una clave de pruebas se rechazan; nunca se redirigen silenciosamente a otro entorno.
1. Descripción general
| Pregunta | Respuesta |
|---|---|
| ¿Qué obtengo? | Una dirección de cuenta inteligente ERC-4337 desde la que el Agent inicia transacciones |
| ¿Debo custodiar claves privadas? | No. Nosotros las almacenamos y utilizamos; tu sistema no firma nada |
| ¿Quién paga el gas? | Nosotros pagamos el gas del despliegue. El Agent no necesita saldo al crearse; la cuenta paga el gas de las transferencias posteriores |
| ¿Puedo usar la dirección de inmediato? | Para recibir fondos, sí. Para enviarlos, primero debe desplegarse la cuenta |
| ¿Cómo transfiero fondos? | POST /wallets/{id}/transfers envía una UserOperation |
| ¿Puedo restringir destinatarios? | Sí, y es obligatorio. La lista de permitidos se comprueba antes de ejecutar |
| ¿Qué cadenas se admiten? | Red principal de BOT (chain ID 677) y red de pruebas de Bohr (chain ID 968) |
Recuerda:
La dirección existe desde que se crea la billetera, pero el contrato se despliega después en esa dirección. Es normal: una dirección sin código puede recibir fondos.
2. Obtener credenciales
Los servicios de billetera e identidad comparten credenciales. Cada integrador es un proyecto con un ID y una o varias claves de API que emitimos nosotros:
ak_<environment>_k_<key_id>.<secret>
Incluye la cadena completa, incluido el punto, en la cabecera X-API-Key. El secreto solo se devuelve una vez, al crearlo, y no puede recuperarse después. Guárdalo inmediatamente en tu gestor de secretos.
Las claves dependen del entorno: una clave de pruebas no funciona en la red principal.
3. Crear una billetera
POST /v1/wallets
Content-Type: application/json
X-API-Key: ak_…
{
"external_agent_id": "tu-agent-id",
"chain_id": 677,
"idempotency_key": "create-agent-42",
"callback_url": "https://your-service.example/webhooks/wallet"
}
| Campo | Obligatorio | Significado |
|---|---|---|
external_agent_id | Sí | Identificador del Agent en tu sistema |
chain_id | Sí | Red principal 677; pruebas 968 |
idempotency_key | Sí | También puede enviarse en la cabecera Idempotency-Key |
identity_id | No | Vincula una identidad existente. Normalmente se omite, porque la identidad se registra después |
callback_url | No | Dirección que recibe los cambios de estado |
No debes proporcionar ni la dirección del propietario ni la de la cuenta: nosotros generamos ambas y las devolvemos. Tu sistema no genera nada y ningún material de claves cruza la frontera entre sistemas.
Devuelve 202:
{
"wallet": {
"wallet_id": "wal_de43139cef869dc9c42ae3",
"project_id": "launchpad",
"external_agent_id": "tu-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
}
La respuesta contiene dos direcciones que no son intercambiables:
| Campo | Significado |
|---|---|
account_address | Dirección del Agent. Envía los fondos aquí; también figura como remitente en la cadena |
owner_address | Dirección que controla la cuenta. Se usa al registrar la identidad; no envíes fondos aquí |
Guarda wallet_id: se usa en todos los endpoints posteriores.
Los endpoints de escritura envuelven el objeto en wallet; los de lectura devuelven el objeto directamente. Tras crear, usa response.wallet.account_address; tras un GET, usa response.account_address.
Repetir la misma clave de idempotencia devuelve 200, "replayed": true y la billetera original. Reintentar tras un tiempo de espera agotado no crea una segunda cuenta.
También puedes buscar por tu propio identificador y evitar almacenar el nuestro:
GET /v1/wallets/{wallet_id}
GET /v1/wallets/by-external-id/{chain_id}/{external_agent_id}
Las billeteras ajenas a tu proyecto siempre devuelven 404. «No es tuya» y «no existe» tienen la misma respuesta.
4. La dirección existe antes que el contrato
El valor inicial de deployment_status es PREDICTED. No es un estado transitorio que se resuelva simplemente esperando: así funcionan las cuentas ERC-4337.
CREATE2 calcula la dirección a partir del propietario y el salt; por eso se conoce de antemano y no cambia. El contrato se despliega allí más tarde. Hasta entonces:
| Operación | ¿Disponible en PREDICTED? |
|---|---|
| Recibir tokens nativos o ERC-20 | Sí. Es normal enviar fondos a una dirección sin código |
| Mostrar la dirección a los usuarios | Sí |
| Enviar tokens | No |
| Estado | Significado |
|---|---|
PREDICTED | Dirección calculada; aún no hay contrato en la cadena |
DEPLOYING | Despliegue enviado; esperando inclusión en la cadena |
DEPLOYED | Contrato listo; la cuenta puede enviar fondos |
Las transferencias se rechazan si deployment_status no es DEPLOYED. Una transferencia no inicia el despliegue automáticamente: solo devuelve 409 WALLET_NOT_READY. Incluye el despliegue en la inicialización; no esperes que el primer pago lo active.
POST /v1/wallets/{wallet_id}/deploy
{ "idempotency_key": "deploy-agent-42" }
Devuelve 202 y una UserOperation. Consulta la billetera hasta que deployment_status sea DEPLOYED; solo entonces se aceptarán transferencias.
No hace falta saldo para desplegar la cuenta: nosotros pagamos el gas.
5. Enviar tokens
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"
}
| Campo | Obligatorio | Significado |
|---|---|---|
receiver | Sí | Dirección del destinatario; debe estar en la lista de permitidos de esta billetera |
token_address | Sí | Dirección del contrato ERC-20, o el literal BOT_NATIVE para el token nativo |
amount | Sí | Unidades mínimas, solo dígitos. Para USDT con 6 decimales, "1000000" equivale a 1 USDT |
request_source | Sí | API, CONSOLE o ADMIN; registra el origen de la transferencia |
idempotency_key | Sí | Evita transferencias duplicadas al reintentar |
Se rechazan inmediatamente cuatro casos:
- No omitas
token_address. Para el token nativo usaBOT_NATIVE, no un valor vacío ni la dirección cero. amountsolo admite dígitos. Los decimales y la notación científica se rechazan, no se redondean: tanto"1.5"como"1e6"fallan. Las conversiones de coma flotante pueden perder precisión y alterar la cantidad enviada; por eso no se aceptan esos formatos.request_sourcees obligatorio y debe ser uno de los tres valores indicados.- El destinatario no puede ser la propia dirección de cuenta de la billetera. Una transferencia a sí misma se ejecuta en la cadena y consume gas sin cambiar el saldo, por lo que se bloquea aquí.
amount es una cadena, no un número. Las cantidades de tokens pueden superar el rango numérico exacto de JavaScript.
Devuelve 202 y una UserOperation. La transferencia se ha enviado, pero aún no está confirmada definitivamente:
{
"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"
}
Seguir una transferencia
GET /v1/user-operations/{user_op_hash}
| Estado | Significado | ¿Final? |
|---|---|---|
BUILT | Construida, aún sin firmar | No |
SIGNED | Firmada, aún sin enviar | No |
SUBMITTED | Enviada al bundler | No |
INCLUDED | Incluida en un bloque; recibo aún sin confirmar | No |
SUCCEEDED | Completada. tx_hash y block_number tienen valor | Sí |
REVERTED | La ejecución en cadena falló y consumió gas | Sí |
FAILED | Nunca llegó a la cadena; consulta last_error | Sí |
Trata REVERTED y FAILED de forma distinta. REVERTED significa que llegó a la cadena y falló allí, por ejemplo por saldo insuficiente o rechazo del contrato del token. FAILED significa que no llegó a ese paso. Es razonable reintentar FAILED con una nueva clave de idempotencia; reintentar REVERTED sin cambios volverá a revertir.
attempt y last_error muestran los intentos que el servicio ya ha realizado por ti.
6. Lista de destinatarios permitidos
La billetera solo puede enviar fondos a direcciones registradas en la lista. Una lista vacía impide transferir a cualquier dirección; no significa acceso ilimitado. Registra cada destinatario antes de su primera transferencia o se rechazará.
Es intencional: el Agent decide cuándo transferir, por lo que los destinos permitidos deben fijarse de antemano, no deducirse de las instrucciones que recibe. La validación se ejecuta en el servidor antes de construir la operación, de modo que incluso si tu sistema queda totalmente comprometido, un atacante no puede enviar fondos a una dirección nueva.
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": "Cuenta de liquidación",
"allowed_actions": "TRANSFER",
"created_by": "ops@your-company.example",
"idempotency_key": "allow-payout-1"
}
| Campo | Obligatorio | Significado |
|---|---|---|
chain_id | Sí | Las entradas se aíslan por cadena. Una entrada de 968 no autoriza transferencias en 677 |
address | Sí | Dirección del destinatario |
allowed_actions | Sí | TRANSFER, PAYMENT o ALL |
created_by | Sí | Registra quién aprobó la dirección para auditoría |
label | No | Nombre reconocible |
idempotency_key | Sí |
Las entradas coinciden por (wallet_id, chain_id, address), deben estar ACTIVE y sus allowed_actions deben permitir transferencias. El rechazo indica si falta la entrada, está revocada o no autoriza transferencias. Son tres problemas con soluciones distintas: lee el motivo en vez de volver a añadir la dirección sin más.
El orden de inicialización importa: añade los destinatarios antes de ingresar fondos. Una billetera con saldo pero sin entradas permitidas no puede pagar a nadie.
7. Pausar una billetera
POST /v1/wallets/{wallet_id}/status
{ "status": "PAUSED" }
PAUSED detiene las nuevas operaciones y nosotros podemos levantarlo. REVOKED es permanente.
El endpoint no ofrece una acción de reanudación deliberadamente. Reactivar una cuenta detenida es una decisión operativa, no la inversión mecánica de detenerla. Contacta con nosotros para reanudarla.
8. Notificaciones (webhooks)
Si proporcionas callback_url, los cambios de estado se envían por POST con la cabecera X-Callback-Token. El receptor debe seguir cuatro reglas:
- Valida primero
X-Callback-Tokencon una comparación de tiempo constante y después procesa el cuerpo. - Devuelve
2xxcuanto antes y realiza el trabajo real de forma asíncrona. - Admite entregas duplicadas. Usa el ID del objeto junto con
updated_atcomo clave de idempotencia. - Los reintentos pueden llegar desordenados. Ignora los payloads cuyo
updated_atsea anterior al registro guardado.
Los webhooks optimizan el sondeo, no lo sustituyen. Si el receptor falla, el sondeo sigue devolviendo inmediatamente el estado actual.
9. Gestión de errores
Las respuestas de error incluyen un código procesable por software:
{ "error_code": "WHITELIST_DENIED", "error_message": "…" }
Decide según error_code, no según el texto del mensaje.
| HTTP | error_code | Significado | ¿Reintentar? |
|---|---|---|---|
400 | INVALID_REQUEST | Solicitud mal formada o validación de campo fallida | No. Corrige la solicitud |
401 | — | Clave de API ausente o inválida | No |
403 | WHITELIST_DENIED | El destinatario no tiene una entrada permitida activa | No. Añádela primero |
403 | TOKEN_NOT_ALLOWED | La billetera no puede enviar este token | No |
404 | NOT_FOUND | La billetera no existe o pertenece a otro proyecto | No |
409 | WALLET_NOT_READY | Sin desplegar, pausada o revocada | Depende; consulta abajo |
409 | IDEMPOTENCY_KEY_CONFLICTS | Misma clave de idempotencia usada para otro contenido | No |
422 | INSUFFICIENT_BALANCE | Saldo insuficiente | No. Ingresa fondos primero |
501 | NOT_IMPLEMENTED | Función no habilitada en este entorno | No |
5xx | INTERNAL_ERROR | Error de nuestro lado | Sí, con espera progresiva |
WALLET_NOT_READY cubre tres casos, identificados por status= y deployment= en error_message:
deployment=PREDICTEDoDEPLOYING→ Reintenta cuando termine el despliegue; es temporal.status=PAUSED→ Reintenta cuando se reactive la billetera.status=REVOKED→ Fallo permanente; no reintentes.
Tratar todo 409 como conflicto permanente descarta erróneamente billeteras aún en despliegue.
10. Entornos
| Elemento | Red principal de BOT | Red de pruebas de Bohr |
|---|---|---|
| Chain ID | 677 | 968 |
| RPC | https://rpc.botchain.ai | https://rpc.bohr.life |
| Explorador de bloques | https://scan.botchain.ai | https://scan.bohr.life |
| Bundler | https://bundler.botchain.ai/rpc | https://bundler.bohr.life/rpc |
| EntryPoint | 0x0000000071727De22E5E9d8BAf0edAc6f37da032 | Igual que en la red principal |
| Tipo de cuenta | Kernel v0.3.3 | Igual que en la red principal |
EntryPoint es el contrato singleton estándar de ERC-4337 v0.7, de modo que las herramientas ERC-4337 existentes funcionan con estas cuentas sin modificaciones.
Siguiente paso: identidad
La billetera da al Agent una cuenta. La identidad permite verificarlo: cualquiera puede consultar directamente en la cadena quién es su propietario y qué billetera utiliza.
Para registrar la identidad necesitas tres valores de la billetera recién creada:
| Campo de la API de identidad | Origen |
|---|---|
wallet_id | wallet.wallet_id |
wallet_address | wallet.account_address |
owner_address | wallet.owner_address |