x402 quickstart — pay each invoke from a Base USDC wallet

Target reading time: 60 seconds. Target time-to-first-paid-invoke: 3 minutes. Spec: v1.1 §6.

Pre-release. x402 is specified in v1.1.0-rc3 and implemented in the Hub, but it is not yet enabled on the production Hub, and the Splitter contract is not yet deployed to Base mainnet. Until then, paid invokes use the Stripe wallet path. Gates to GA are listed in the roadmap.

Four commands take you from npm install to your agent settling USDC on Base for every paid capability invoke. No Stripe top-ups, no human in the loop. The SDK never sees your private key.

1. Install

npm install @jecpdev/sdk ethers

ethers is a peer dependency — the SDK does not ship it (browser bundles stay lean). v6 or newer is required.

2. Set the env var

export AGENT_BASE_KEY=0x...   # 32-byte private key for an EOA on Base

This is the wallet that will pay for invokes. Keep it isolated to your agent — don't reuse a personal wallet.

3. Top up the wallet

Move USDC to that address on Base mainnet (chain id 8453) or Base Sepolia (84532). Two paths:

A typical x402 invoke is $0.20-$1.00, so $5 of USDC is plenty for testing.

4. Code (10 lines)

import { JecpClient, walletFromEnv } from '@jecpdev/sdk';

const jecp = new JecpClient({
  agentId: process.env.JECP_AGENT_ID!,
  apiKey:  process.env.JECP_API_KEY!,
  payment: {
    mode: 'x402',
    signer: walletFromEnv(),       // reads AGENT_BASE_KEY
    maxPerCallUsdc: 1_000_000n,    // safety cap: $1 max per invoke
  },
});

const r = await jecp.invoke('jobdonebot/bg-remover-pro', 'remove', {
  image_url: 'https://example.com/cat.png',
});

Verify

console.log(r.output);          // capability result
console.log(r.payment?.txHash); // 0x... — settlement tx on Base
console.log(r.payment?.amount_usd); // 0.20

Look up the tx on https://basescan.org/tx/{txHash} to confirm the on-chain 85/10/5 revenue split landed in a single block.

Safety caps (recommended in production)

An autonomous agent that can be tricked into draining its wallet is a CVE. The SDK enforces three caps before signing, so a compromised facilitator or prompt-injected agent cannot extract a signature for a too-large amount.

payment: {
  mode: 'x402',
  signer: walletFromEnv(),
  maxPerCallUsdc: 1_000_000n,    // refuse to sign if 402 asks for > $1
  maxPerHourUsdc: 10_000_000n,   // refuse if rolling 1h sum + ask > $10
  maxGasRatio: 0.05,             // refuse if gas > 5% of invoke amount
}

Cap breaches throw typed errors with nextAction hints:

ErrornextAction.typeWhat to do
X402AmountCapExceededErrorraise_capThe Hub asked for more than your per-call cap. Audit the capability's pricing or bump the cap.
X402HourlyCapExceededErrorreview_intentYour agent ran hot — pause it and inspect call patterns.
X402GasRatioExceededErrorcheck_gasBase congested. Wait, or raise maxGasRatio if you accept higher overhead.

These caps are not a substitute for Hub-side mandate budgets — they are the inner-most ring of defense, paired with mandate.budget_usdc.

Default mode is auto — backward compatible

mode: 'x402' strictly refuses to fall back to Stripe wallet path. If you want both:

payment: { mode: 'auto', signer: walletFromEnv() }

auto (default) tries x402 first when the capability supports it, otherwise surfaces the typed 402 error so you can drive a Stripe Checkout via err.nextAction.

Error handling

import {
  X402PaymentInvalidError,
  X402SettlementTimeoutError,
  X402FacilitatorUnreachableError,
  X402SettlementReusedError,
  X402AmountCapExceededError,
} from '@jecpdev/sdk';

try {
  const r = await jecp.invoke(/* ... */);
} catch (e) {
  if (e instanceof X402PaymentInvalidError) {
    // subcause: signature_invalid | amount_mismatch | nonce_reused | expired
    console.log('facilitator rejected:', e.subcause, e.nextAction?.hint);
  } else if (e instanceof X402SettlementTimeoutError) {
    // retryable — wait, regenerate nonce, retry
  }
  // ... other branches
}

The full error catalog is in spec/03-errors.md.

Performance characteristics

The 402-then-X-Payment retry adds 200-300ms to the first invoke wall clock (one extra round-trip + EIP-712 signing). Subsequent invokes in the same JecpClient re-use the signer adapter — there's no warm-up cost.

If you'd rather avoid the round-trip, the Hub publishes per-action pricing in the catalog manifest; pre-sign by reading JecpClient.estimateCost() and constructing your own EIP-3009 authorization.

Running the live example

git clone https://github.com/jecpdev/jecp-sdk-typescript
cd jecp-sdk-typescript
npm install
export AGENT_BASE_KEY=0x...
export JECP_AGENT_ID=jdb_ag_...
export JECP_API_KEY=jdb_ak_...
npx tsx examples/05-x402-invoke.ts

What's NOT in this quickstart

Two phases, not one transaction. The x402.org facilitator pulls USDC into the Splitter (T+0). After 3 Base block confirmations (~6s), the Hub keeper records the 85/10/5 split (p99 ≤30s). The split is atomic by contract — Provider, Hub, and reserve are all paid, or nothing settles — but do not assume single-tx or single-block semantics.