Additional
Coinbase Agentic Signer Contract
Synced from github.com/CoWork-OS/CoWork-OS/docs
This document defines the HTTP contract expected by CoWork OS when wallet.provider = "coinbase_agentic".
Implementation reference:
src/electron/infra/providers/coinbase-agentic-wallet-provider.ts
Purpose
CoWork OS delegates wallet operations and x402 signing to a remote signer service instead of storing private keys locally in the desktop app.
Signer configuration is separate from task access. The task's access profile and wallet/payment policy must allow the operation before CoWork calls the signer; an account ID, signer token, or remote endpoint cannot grant command, network, filesystem, payment, or signing authority by itself. Keep signing and payment actions behind their explicit confirmation and hard-guardrail paths.
Base URL
Configure in Settings > Infrastructure > Wallet > Signer Endpoint.
Example:
https://signer.example.com
CoWork OS will call:
POST /wallet/statusPOST /wallet/ensurePOST /x402/checkPOST /x402/fetch
Common Request Fields
Most signer requests include:
{
"accountId": "agent-wallet-prod",
"network": "base-mainnet"
}
accountId(string, optional): tenant/account selector on signer side.network("base-mainnet" | "base-sepolia"): target chain context.
1) Wallet Status
POST /wallet/status
Request
{
"accountId": "agent-wallet-prod",
"network": "base-mainnet"
}
paymentPolicy(object, optional but strongly recommended): desktop-side policy and any user-approved preflight requirement. If present, the signer must enforce it before signing the real upstream payment challenge.
Response
{
"connected": true,
"address": "0xabc123...",
"network": "base-mainnet",
"balanceUsdc": "42.10"
}
connected(boolean): signer account is healthy/usable.address(string, optional): public wallet address.balanceUsdc(string, optional): decimal USDC balance.
2) Wallet Ensure
POST /wallet/ensure
Ensures the signer has a wallet/account provisioned for the request context.
Request
{
"accountId": "agent-wallet-prod",
"network": "base-mainnet"
}
Response
Any JSON object is accepted by CoWork OS (result is not parsed deeply), but recommended:
{
"ok": true,
"address": "0xabc123..."
}
3) x402 Check
POST /x402/check
Checks whether a URL requires x402 payment and returns payment metadata if required.
CoWork OS uses the canonical x402 v2 wire format. PAYMENT-REQUIRED is a
base64-encoded object with x402Version: 2, a resource object, and an
accepts array. For the exact EVM scheme, amount is an atomic USDC string;
1000 means 0.001 USDC and must remain 1000 when it is signed.
The retry PAYMENT-SIGNATURE contains the v2 PaymentPayload with the
selected accepted requirement and an EIP-3009 payload.authorization whose
value is that same atomic string.
Request
{
"url": "https://paid-api.example.com/data",
"accountId": "agent-wallet-prod",
"network": "base-mainnet"
}
Response (no payment required)
{
"requires402": false,
"url": "https://paid-api.example.com/data"
}
Response (payment required)
{
"requires402": true,
"url": "https://paid-api.example.com/data",
"paymentDetails": {
"x402Version": 2,
"resource": {
"url": "https://paid-api.example.com/data",
"description": "Premium endpoint access"
},
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
}]
}
}
4) x402 Fetch
POST /x402/fetch
Performs the request with signing/payment flow handled server-side.
The signer must treat any desktop preflight result as advisory only. The real
upstream 402 Payment Required response from this fetch is the authoritative
payment challenge and must be checked against the policy envelope before signing.
Request
{
"url": "https://paid-api.example.com/data",
"method": "GET",
"body": "",
"headers": {
"accept": "application/json"
},
"accountId": "agent-wallet-prod",
"network": "base-mainnet",
"paymentPolicy": {
"policyVersion": 1,
"effectiveHardLimitUsd": 100,
"maxAutoApproveUsd": 1,
"requireApproval": true,
"allowedHosts": ["paid-api.example.com"],
"preflight": {
"requires402": true,
"url": "https://paid-api.example.com/data",
"paymentDetails": {
"x402Version": 2,
"resource": { "url": "https://paid-api.example.com/data" },
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
}],
"selectedRequirement": {
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
},
"resourceUrl": "https://paid-api.example.com/data",
"scheme": "exact",
"payTo": "0xmerchant...",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"network": "eip155:8453"
}
},
"approvedPaymentDetails": {
"x402Version": 2,
"resource": { "url": "https://paid-api.example.com/data" },
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
}],
"selectedRequirement": {
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
},
"resourceUrl": "https://paid-api.example.com/data",
"scheme": "exact",
"payTo": "0xmerchant...",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"network": "eip155:8453"
},
"approvedAt": "2026-06-01T10:00:00.000Z"
}
}
Response
{
"status": 200,
"body": "{\"result\":\"ok\"}",
"headers": {
"content-type": "application/json"
},
"paymentMade": true,
"amountPaid": "250000",
"paymentPolicyEnforced": true,
"paymentDetails": {
"x402Version": 2,
"resource": { "url": "https://paid-api.example.com/data" },
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32d4f71b54bda02913",
"payTo": "0xmerchant...",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "eip3009" }
}],
"resourceUrl": "https://paid-api.example.com/data",
"scheme": "exact",
"payTo": "0xmerchant...",
"amount": "250000",
"asset": "0x833589fCD6eDb6E08f4c7C32d4f71b54bdA02913",
"network": "eip155:8453"
}
}
status(number): upstream HTTP status.body(string): upstream response body as text.headers(object): flattened response headers.paymentMade(boolean): whether payment/signature flow was used.amountPaid(string, optional): atomic USDC amount from the selected v2 requirement.paymentPolicyEnforced(boolean, required whenpaymentMade=true): signer confirmed it enforced the supplied policy envelope before signing.paymentDetails(object, required whenpaymentMade=true): exact upstream challenge details that were signed.
Error Handling
- Return non-2xx for operational errors; CoWork OS surfaces response text.
- Prefer JSON error body with stable codes:
{
"error": {
"code": "SIGNER_POLICY_BLOCKED",
"message": "Host not allowed"
}
}
Suggested codes:
SIGNER_UNAUTHORIZEDSIGNER_POLICY_BLOCKEDX402_PAYMENT_REQUIREMENT_CHANGEDWALLET_NOT_READYX402_PRECHECK_FAILEDX402_FETCH_FAILEDINSUFFICIENT_FUNDS
Security Requirements (Recommended)
- Require authenticated requests from CoWork OS clients:
- mTLS, signed JWT, or short-lived bearer token.
- Enforce server-side policy independent of desktop settings:
- host allowlist, per-request max, per-day budget, account scoping.
- Enforce the desktop
paymentPolicyenvelope before signing:- reject missing or unsupported
policyVersion - reject if the real upstream atomic payment amount exceeds
effectiveHardLimitUsd - reject unsupported scheme/asset/currency/network combinations. Supported USDC
assets are Base mainnet
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913and Base Sepolia0x036CbD53842c5426634e7929541eC2318f3dCF7e - reject resources that do not match the requested URL
- require
x402Version: 2, an absolute resource URL matching the request, exactly one supported exact EVM Base USDC requirement, and EIP-3009 token domain metadata inextra - when
approvedPaymentDetailsis present, compare the complete canonical challenge, includingresource, everyacceptsentry, the selected requirement,maxTimeoutSeconds, token-domain metadata, and extensions - when
requireApprovalis true and no exact approved payment details are present, reject instead of signing silently
- reject missing or unsupported
- Never expose private keys over API.
- Log and audit all signing/payment actions with correlation IDs.
- Add replay protection and strict request timeouts.
CoWork OS Policy Interaction
Desktop-side policy is enforced at two points:
- Optional host allowlist (
payments.allowedHosts) - Advisory preflight estimate (
/x402/check), when available - Final policy/approval gate on the real upstream
402challenge (payments.hardLimitUsd,payments.requireApproval,maxAutoApproveUsd)
The final signer-side check is mandatory because HEAD preflight is not
authoritative. If /x402/check returns no payment but /x402/fetch receives a
real upstream 402, the signer must still apply the policy envelope before
signing.
Quick Smoke Test
curl -sS -X POST "$SIGNER_ENDPOINT/wallet/status" \
-H "content-type: application/json" \
-d '{"accountId":"agent-wallet-prod","network":"base-mainnet"}'
curl -sS -X POST "$SIGNER_ENDPOINT/x402/check" \
-H "content-type: application/json" \
-d '{"url":"https://paid-api.example.com/data","accountId":"agent-wallet-prod","network":"base-mainnet"}'