cdp-wallet
A small wrapper around the Coinbase CDP server wallet v2 SDK that exposes the operations an autonomous agent actually needs: get an address, check a balance, send USDC, look at transfer history, and pay x402-protected resources. Nothing else.
The wallet is a CDP server wallet — keys are generated and held inside AWS Nitro Enclaves on Coinbase's infrastructure, never on the operator's machine, and signing happens by API call against those held keys. The wallet is identified by a human-readable name (openclaw-default by default), not a seed phrase, so a fresh container with the same env vars resolves to the same wallet on first call. This is the right shape for unattended scheduled agents on Railway, Fly, Hetzner, etc.
When to use
- The agent needs to send USDC on Base (donation, peer-to-peer payment).
- The agent needs to pay an x402-protected resource (gated APIs, paid evidence access, agentic-market services).
- The agent needs to know how much USDC or ETH it has before making a decision.
- The agent needs to inspect recent USDC activity on its wallet (audit, idempotency check).
When not to use
- The agent needs to send a token other than USDC. This skill is intentionally USDC-only; the underlying SDK supports more, but the surface here is deliberately small. Wrap the SDK directly if you need swaps, ERC-20 transfers of other tokens, smart accounts, or paymaster/gasless flows.
- The agent runs on a single trusted machine and the operator wants self-custody. CDP server wallets put trust in Coinbase's TEEs. If that trust assumption is wrong for your use case, use a self-custodial path (viem EOA, Bankr, etc.) instead.
- Solana. Base only.
Setup
1. Get CDP credentials
Sign in at portal.cdp.coinbase.com, create a CDP API key, and generate a Wallet Secret. You'll have three values:
CDP_API_KEY_IDCDP_API_KEY_SECRETCDP_WALLET_SECRET
The Wallet Secret is the credential that authorizes signing operations against the keys held in CDP's TEEs. Without it, the agent can read but cannot move funds.
2. Install
git clone https://github.com/Ales375/openclaw-cdp-wallet-skill.git
cd openclaw-cdp-wallet-skill
npm install
For OpenClaw, point the skill loader at this directory or symlink ~/.openclaw/skills/cdp-wallet → /path/to/openclaw-cdp-wallet-skill. For Hermes, place under ~/.hermes/skills/cdp-wallet. For Claude Code or any other agentskills.io-compatible runtime, drop into the configured skills path.
3. Configure
Create a .env based on .env.example:
CDP_API_KEY_ID=...
CDP_API_KEY_SECRET=...
CDP_WALLET_SECRET=...
CDP_NETWORK=base # or base-sepolia for testing
CDP_ACCOUNT_NAME=openclaw-default # any name; same name → same wallet across runs
For Railway / Fly / Hetzner: set the same variables as service env, no .env needed.
4. First run — get the wallet address
npm run address
Output:
{"ok":true,"address":"0x...","network":"base","account_name":"openclaw-default"}
Same call on a fresh container with the same env vars returns the same address. The wallet is created on first call and looked up on every call after that.
5. Fund the wallet
Send USDC on Base (and a small amount of ETH for gas, or use gasless paths externally) to the printed address. The minimum useful balance depends on the agent's purpose; for donations of $5 USDC at a time, fund $50–100 USDC plus $1 of ETH.
For testnet (CDP_NETWORK=base-sepolia), the SDK exposes faucet methods — extend this skill if needed; this minimal version doesn't include a faucet command.
How the agent invokes it
Every subcommand prints a single JSON line to stdout and exits 0 on success or 1 on error. The agent should parse the JSON and act on the ok field.
address
node src/index.js address
Prints the wallet's EVM address. Useful before donating so the agent can register itself with services that require a wallet_address.
balance
node src/index.js balance
Prints ETH and USDC balances:
{
"ok": true,
"address": "0x...",
"network": "base",
"eth": "0.001234",
"usdc": "42.500000",
"raw": { "eth_wei": "1234000000000000", "usdc_atoms": "42500000" }
}
Always check balance before sending. The agent's planning logic should treat the usdc string as the spendable amount in human units.
send-usdc <to> <amount>
node src/index.js send-usdc 0xRecipientAddress 5.00
Sends 5 USDC on Base to 0xRecipientAddress. Waits for one confirmation by default.
Success:
{
"ok": true,
"tx_hash": "0xabc...",
"status": "confirmed",
"explorer": "https://basescan.org/tx/0xabc...",
"from": "0x...",
"to": "0xRecipientAddress",
"amount_usdc": "5.00",
"network": "base"
}
Submitted but confirmation timed out (rare; means the chain is congested or RPC is slow):
{ "ok": true, "tx_hash": "0xabc...", "status": "submitted_unconfirmed", ... }
In this case the transaction is on-chain but the CLI gave up waiting for the receipt. The agent should poll the explorer or call history after a short delay to confirm.
Failures (validation, CDP API error, on-chain revert):
{ "ok": false, "error": "...", "phase": "submit" }
history --limit <N>
node src/index.js history --limit 10
Returns the last N USDC Transfer events involving this wallet (in or out), looking back ~24h on Base by default. Use --lookback <blocks> to extend. Pure on-chain read against Base RPC; doesn't depend on any CDP-side history API.
{
"ok": true,
"address": "0x...",
"count": 3,
"transfers": [
{
"direction": "out",
"from": "0x...",
"to": "0xRecipient",
"amount_usdc": "5.000000",
"block_number": "12345678",
"tx_hash": "0xabc...",
"explorer": "https://basescan.org/tx/0xabc..."
}
]
}
pay-x402 <url>
node src/index.js pay-x402 https://api.example.com/protected
node src/index.js pay-x402 https://api.example.com/protected -H "Authorization: Bearer abc123"
node src/index.js pay-x402 https://api.example.com/protected -X POST -d '{"k":"v"}' -H "Content-Type: application/json"
Calls an x402-protected URL. The x402 protocol is an HTTP-native payment scheme: the server returns a 402 with payment requirements, the client signs an EIP-712 authorization, the server's facilitator settles on-chain, the resource is returned. From the agent's perspective, this is one call — the negotiate-sign-resubmit dance happens transparently.
Options:
-X, --method <method>— HTTP method, defaultGET.-H, --header <name: value>— request header. Repeat the flag for multiple headers.-d, --body <body>— request body string. Only valid with non-GET methods.
Success:
{
"ok": true,
"status": 200,
"content_type": "application/json",
"body": { "...": "the resource" },
"body_truncated": false,
"settlement": {
"transaction": "0xabc...",
"amount": "10000",
"network": "eip155:8453",
"...": "facilitator-defined fields"
},
"settled_amount_usdc": 0.01
}
The settlement object is the decoded PAYMENT-RESPONSE header from the server. settled_amount_usdc is a convenience conversion assuming USDC (6 decimals); ignore it if the resource server settled in a different asset.
Body handling:
- If
Content-Typeisapplication/json, the body is parsed and embedded as a JSON object/array. - Otherwise the body is returned as a string.
- If the body exceeds 200,000 characters, it's returned as
{_truncated: true, _length, preview}andbody_truncated: true. Increase the limit by editing the skill source if needed.
Failure (server returned non-2xx, or request failed):
{
"ok": false,
"error": "x402 endpoint returned 401 Unauthorized",
"status": 401,
"content_type": "application/json",
"body": { "error": "..." },
"body_truncated": false,
"settlement": null,
"settled_amount_usdc": null
}
Network choice. The x402 protocol is network-aware — the resource server tells the client which network to pay on (e.g., eip155:8453 for Base mainnet, eip155:84532 for Base Sepolia). pay-x402 honours whatever the server requests. The CDP_NETWORK env var does NOT constrain pay-x402; it only affects address, balance, send-usdc, and history. If the operator wants to restrict which networks the agent will pay on, do that at the prompt or persona level, not in the skill.
The signer is the same CDP wallet used by send-usdc. The agent's address as wallet_address (in zooidfund's case) and the address that signs the x402 payment authorization are the same address — no mismatch risk.
Failure modes the agent should know about
- Missing env vars →
ok: false, error: "Missing required env: ...". The agent has no recovery path for this; the operator must fix the deployment. - Invalid recipient address →
ok: false. The agent should validate addresses before invokingsend-usdc. - Insufficient USDC balance → CDP returns an error from
submitphase. The agent should callbalancefirst. - Insufficient ETH for gas → same. The wallet needs a small ETH balance unless the operator has wired a paymaster.
- OFAC-screened recipient → CDP refuses to submit. Skip that recipient rather than retry.
status: "submitted_unconfirmed"→ tx is on-chain, the CLI just didn't see the receipt within the timeout. Don't re-send; pollhistoryor the explorer.- CDP API outage → transient, retry with backoff.
pay-x402returns ok:false with a 402 status → the x402 client could not satisfy the server's payment requirements (e.g., server requested an unsupported network/scheme, or the wallet lacked funds). Inspect the response body for the server'sacceptsarray — that lists what the server would accept. If the agent's wallet can't satisfy any option, give up rather than retry.pay-x402returns ok:false with phase: "request" → request never completed (network failure, DNS, TLS, malformed URL not caught by upfront validation). Retry once with backoff before giving up.pay-x402succeeds butsettlementis null → the resource was returned without aPAYMENT-RESPONSEheader. This usually means the resource was free (server didn't gate it after all) or the server doesn't echo settlement details on this path. Treat as success.
Why CDP server wallets and not other paths
CDP server wallets v2 was chosen because: keys never reach the operator's filesystem (eliminates a whole class of leak); wallet creation is programmatic and idempotent (no interactive OTP, no seed phrase to lose); the SDK is first-party and current; and the policy engine can layer spending caps on top of this skill at the CDP-account level if the operator wants infrastructure-enforced limits in addition to whatever the agent enforces in its own logic.
The skill deliberately does not enforce per-transaction limits, daily caps, or whitelists in code. Those belong either in the CDP policy engine (for hard infrastructure-level guarantees that survive a compromised agent) or in the agent's own reasoning layer (for soft limits that depend on context). Putting them in the skill code creates the worst of both worlds — bypassable from the agent if it controls the env, but invisible to the operator if it doesn't.
Security notes
- Use a dedicated OpenClaw agent or workspace for this skill. Do not share one funded wallet across unrelated agents or tenants.
- The Wallet Secret is the most sensitive credential; protect it with the same care as a private key. Anyone with all three env vars can move the wallet's funds.
- This skill prints the wallet address but never the keys (the SDK doesn't expose them).
- Output JSON includes addresses, amounts, and tx hashes only — no secrets are logged.
- If you suspect credential compromise, rotate the Wallet Secret in the CDP portal immediately. Existing transactions can't be reversed but new ones become impossible.