Skip to content

CP Wallet ​

Binary: cp-wallet  ·  Package: @computeportal/cp-wallet

cp-wallet is Compute Portal's client-side wallet orchestrator. It pairs a browser wallet (WalletConnect v2), mints short-lived EIP-7702 session keys for unattended signing, and produces the X-Payment header that unlocks any x402-gated endpoint — all without the API or CP infrastructure ever touching your private keys.

Developer Preview

Live WalletConnect pairing and on-chain revocation are not yet wired in the shipped CLI. The entire surface is exercisable today via CP_WALLET_MOCK=1 against a mock adapter — this is the supported path for development, agent integration, and the MCP tool surface. Live-mode wiring (@walletconnect/sign-client adapter + revoke broadcaster) lands separately.


Why cp-wallet ​

The CP platform spends GPU time. Agents on top of CP need to spend small amounts of USDC against x402-gated endpoints. You don't want:

  • An agent that stops every 30 seconds for you to click "sign" in a popup.
  • A private key sitting in an agent's environment variable.
  • A long-lived API key that can drain the wallet if leaked.

cp-wallet resolves this with a two-tier model:

  1. Main wallet pairs once via WalletConnect v2 — your keys stay in MetaMask / Rabbit / whichever wallet you use.
  2. Session key is minted via EIP-7702 with a cap, an expiry, and (optionally) a recipient allowlist. The agent calls cp-wallet sign with that session key, no popups, until the cap or clock runs out.

If your wallet doesn't support 7702 yet, sign falls back to a per-request wallet prompt — no feature loss, just more clicks.


Install ​

bash
npm i -g @computeportal/cp-wallet
cp-wallet --help

Node 20+ required.


Quick tour ​

bash
# 1. Pair a wallet (QR → scan in MetaMask / Rabby / Rainbow)
export CP_WALLET_MOCK=1       # until live WC is wired, see note above
cp-wallet connect

# 2. Mint a session key: up to $20/req, pay only to the CP settlement wallet,
#    good for 7 days
cp-wallet delegate --cap 20.00 --expires 7d --to 0xCP_PAYTO_ADDRESS

# 3. Point a 402-gated call at the signer
curl -s https://api.computeportal.io/v1/x402/sample-route -o /dev/null \
  -w "%{http_code} %{header_json}\n" \
  | tee /tmp/402.json
# → 402, body includes `accepts`
cp-wallet sign --accepts /tmp/402.json          # prints X-Payment: ...

# 4. (optional) See what cp-wallet actually decided and why
cp-wallet history --decision approve --limit 5

For MCP-driven agents, replace step 3-4 with cp-wallet mcp serve and let the model call wallet_sign_x402 directly — see MCP tools.


Where cp-wallet stores things ​

WhatWherePermissions
Sessions, delegations, policy, audit log~/.config/computeportal/wallet-sessions.json and sibling files0600
Session-key private material (one file per delegation)macOS Keychain by default; ~/.config/computeportal/wallet-keystore/* if CP_WALLET_KEYSTORE=file0600

Override the config dir with CP_WALLET_CONFIG_DIR.

Keystore backends ​

CP_WALLET_KEYSTOREBehavior
(unset, macOS)macOS Keychain (probed at startup)
file0600 plaintext file — opt-in; delegate refuses otherwise with keychain_unavailable
memoryTest-only (ephemeral; never used in production code paths)

The default refuses to silently write plaintext. If Keychain is unavailable (Linux, headless CI, locked session), you must explicitly set CP_WALLET_KEYSTORE=file to acknowledge the fallback.


CLI commands ​

Every command accepts --json to emit a stable, parseable envelope:

json
{ "ok": true, "version": "v1", "result": { /* command-specific */ } }

On error:

json
{ "ok": false, "version": "v1", "error": { "code": "error_code", "message": "...", "details": {} } }

Exit codes: 0 success, 1 runtime error, 2 usage error.

cp-wallet connect ​

Pair a wallet via WalletConnect v2. Prints a QR code and a wc:// URI to the terminal.

bash
cp-wallet connect [--json]

JSON result

json
{
  "session_id": "wc-sess-...",
  "address": "0xabc...",
  "chain_id": 8453,
  "wallet_name": "MetaMask",
  "expires_at": "2026-11-24T12:34:56Z"
}

Errors: pair_timeout, connect_failed.


cp-wallet list ​

List paired wallet sessions, with the active one marked.

bash
cp-wallet list [--json]

cp-wallet use <session-id> ​

Switch the active session. Later commands (sign, delegate) default to this session when --session isn't passed.

bash
cp-wallet use wc-sess-abc123

cp-wallet disconnect <session-id> ​

Tear down a WalletConnect session and remove it from local storage.

bash
cp-wallet disconnect wc-sess-abc123 [--json]

Errors: session_not_found, disconnect_failed.


cp-wallet delegate ​

Mint an EIP-7702 session-key delegation. The session key is generated locally, stored in the keystore, and authorized by the main wallet via a 7702 signature.

bash
cp-wallet delegate \
  --cap <usd> \
  --expires <duration> \
  --to <0x-address> \
  [--json]
FlagRequiredExampleNotes
--cap✓20.00Max USD per request; must be ≥ $0.01
--expires✓7d, 72h, 30dMax 30 days
--to✓0xCP_PAYTO…Pay-to address this delegation is bound to

JSON result

json
{
  "delegation_id": "del_...",
  "session_key_pub": "0x...",
  "cap_per_request_usd": "20.00",
  "pay_to": "0xCP_PAYTO…",
  "expires_at": "2026-10-31T12:34:56Z",
  "chain_id": 8453
}

Errors: invalid_pay_to, keychain_unavailable, wallet_lacks_7702, cap_too_small, expiry_too_long, wallet_session_expired, delegate_failed.

If wallet_lacks_7702 is returned, delegation is impossible but sign still works — each request falls back to a per-request wallet prompt.


cp-wallet delegations ​

List all delegations (active + expired). Never returns private key material.

bash
cp-wallet delegations [--json]

Columns: ID, CAP, PAY_TO, EXPIRES, STATUS.


cp-wallet sign ​

Sign an x402 accepts body and emit the X-Payment header. If an active delegation covers the request (chain, pay-to, amount, not expired), it signs with the session key — otherwise it prompts the paired wallet.

bash
cp-wallet sign --accepts <file|-> [--session <id>] [--json]

Pass - for stdin:

bash
curl -sS https://api.computeportal.io/v1/sample-x402 -I \
  | grep -i x-payment-required \
  | base64 -d \
  | cp-wallet sign --accepts -

JSON result

json
{
  "x_payment": "base64url-encoded-eip-3009-authorization...",
  "session_id": "wc-sess-...",
  "signer_address": "0x...",
  "network": "base-mainnet",
  "value": "20000000"
}

Policy-driven rejection codes (returned as error.code):

CodeMeaning
cap_per_request_exceededRequest amount > cap_per_request_usd
cap_per_period_exceededRolling-window spend would exceed cap_per_period_usd
pay_to_not_allowedPay-to not in pay_to_allowlist
chain_not_allowedSession chain not in chain_allowlist
policy_expiredPolicy expires_at has passed

Other errors: session_not_found, wallet_session_expired, user_rejected, sign_timeout, accepts_validation_error, sign_failed.

Scheme support. Both exact and upto schemes on EIP-3009 TransferWithAuthorization flow through unchanged — the on-chain primitive is the same; the scheme tag only tells the facilitator whether to settle equal to or at most the authorized value. When the accepts body offers both for the same chain, exact is preferred (strictly stronger cap).


cp-wallet policy ​

Local signing policy — a second layer of safety on top of the per-delegation cap. Policies are evaluated for every sign request, including delegated ones.

bash
cp-wallet policy show [--json]
cp-wallet policy set   [flags]   [--json]
cp-wallet policy clear --yes     [--json]

Settable fields

FlagFieldExample
--cap-per-day <usd>cap_per_period_usd + period_window_hours=24--cap-per-day 100.00
--cap-per-request <usd>cap_per_request_usd--cap-per-request 10.00
--period-hours <h>period_window_hours--period-hours 12
--allow-pay-to <0x…>appends to pay_to_allowlistrepeatable
--allow-chain <net>appends to chain_allowlistbase-sepolia or base-mainnet
--expires-at <iso>expires_at2026-12-31T00:00:00Z

policy set is merge semantics — unset flags leave existing fields alone. Use policy clear --yes to wipe.


cp-wallet history ​

Append-only audit log of every sign decision (approve + reject), including policy-reject reasons and settlement tx hashes once observed.

bash
cp-wallet history \
  [--since <iso>] \
  [--decision approve|reject] \
  [--pay-to <0x…>] \
  [--limit <n>] \
  [--json]

The audit log is written synchronously before the X-Payment header leaves the process, so a crash mid-sign cannot leave an authorized but unlogged payment.


cp-wallet revoke <delegation-id> ​

Revoke an EIP-7702 delegation on-chain. The keystore entry is purged before broadcast — if the broadcast fails, no key material remains locally, and you must delegate again to retry.

bash
cp-wallet revoke <delegation-id> [--dry-run] [--json]

--dry-run prints the plan (chain, delegate=zero-address, estimated gas) without broadcasting.

Live broadcast is TODO

In the current preview, the CLI builds the revoke transaction and purges the keystore, but the broadcast step requires an injected opts.broadcast function. MCP callers can wire this; the standalone CLI will gain a --rpc-url/CP_WALLET_RPC_URL broadcaster path alongside live WalletConnect.


MCP tools ​

cp-wallet mcp serve exposes nine tools over stdio (JSON-RPC 2.0). Two are destructive and gated by the same two-step dry_run → confirm_token flow as the CP MCP server: mint a token with dry_run: true, replay with confirm_token to execute. Tokens are single-use, bound to the exact (tool, args) pair, and expire after 2 minutes.

ToolKindPurpose
wallet_connectmutatingPair a wallet; returns session_id, address, chain_id, and a wc:// URI
wallet_sessions_listreadAll sessions, active flag
wallet_delegatemutatingMint 7702 session key — inputs: cap_usd, expires, pay_to, optional session_id
wallet_sign_x402mutatingSign an accepts body; returns header, signed_by: wallet | delegation
wallet_delegations_listreadAll delegations, active flag
wallet_policy_showreadCurrent policy
wallet_policy_setdestructiveMerge policy fields; requires dry_run → confirm_token
wallet_historyreadAudit log — filter by since, decision, limit
wallet_revokedestructiveRevoke a delegation; requires dry_run → confirm_token

Claude Desktop integration ​

~/Library/Application Support/Claude/claude_desktop_config.json:

json
{
  "mcpServers": {
    "cp-wallet": {
      "command": "cp-wallet",
      "args": ["mcp", "serve"]
    }
  }
}

With this plus the hosted computeportal MCP server, an agent can:

  1. Call cp_deploy to deploy a GPU service.
  2. Hit an x402-gated endpoint, get a 402.
  3. Call wallet_sign_x402 with the accepts body — no popup, no key leaves the host.
  4. Retry the original call with the X-Payment header.

The agent never sees your signing key; cp-wallet enforces policy locally.


Programmatic API ​

For non-CLI callers (test harnesses, custom clients):

ts
import { signAccepts } from "@computeportal/cp-wallet/x402/sign";

const { xPayment, signerAddress } = await signAccepts({
  accepts,           // the 402 response body
  sessionId,         // optional, defaults to active session
  adapter,           // WcAdapter (mock or live)
  simulationFetch,   // optional; otherwise falls back to platform RPC
});

Under NODE_ENV=test, simulation is skipped unless simulationFetch is injected (otherwise unit tests would hit sepolia.base.org on every call).


Environment variables ​

VariablePurposeDefault
CP_WALLET_CONFIG_DIROverride config / keystore directory~/.config/computeportal
CP_WALLET_KEYSTOREKeystore backend: file or memoryOS default (Keychain on macOS)
CP_WALLET_RPC_URLRPC endpoint for sign-time simulationPlatform default per network
CP_WALLET_MOCK1 = use mock WalletConnect adapter (required in preview)unset

Security model ​

  • Keys never leave the host. The main wallet's key lives in your wallet app. The session key lives in your OS Keychain (or opt-in 0600 file). The CP API only ever sees signed EIP-3009 authorizations and settlement tx hashes.
  • Caps are enforced locally before any signature is produced. The wallet_sign_x402 tool returns policy_rejected before contacting the wallet.
  • Delegations expire. Max 30 days. Revoke writes a 7702 delegation to the zero address — after that transaction mines, the session key is dead on-chain even if the agent still has the local file (and the keystore entry is purged pre-broadcast anyway).
  • Audit log is append-only and written before the signed header is returned. Crash recovery cannot forget a payment that happened.
  • The CP MCP server never calls wallet_* tools — the two MCP surfaces are separate processes with separate token models. An agent that compromises your CP API token still cannot authorize a payment.

GPU Compute Platform · Status