Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
{
"group": "Reference",
"pages": [
"sdk/cli",
"sdk/create-client",
"sdk/examples"
]
Expand Down
204 changes: 204 additions & 0 deletions sdk/cli.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
---
title: "CLI"
description: "One-shot command-line tool for paying x402 endpoints. Wallet-agnostic with zero provider dependencies."
icon: "terminal"
---

`@x402r/cli` makes a single x402 payment from the command line. You point it at a URL, provide a signer, and get back the response body plus a settlement transaction hash.

The CLI carries zero provider SDK dependencies. Raw private keys, JSON-RPC signers (Privy, Turnkey, Fireblocks, Safe), and custom signer modules all work through the same interface.

### Install

<CodeGroup>
```bash npm
npx @x402r/cli pay <url> [options]
```
```bash pnpm
pnpm dlx @x402r/cli pay <url> [options]
```
```bash bun
bunx @x402r/cli pay <url> [options]
```
</CodeGroup>

No project install required. Pin the version (e.g. `@x402r/cli@0.2.0`) for reproducible agent workflows.

### Usage

```bash
x402r pay <url> [signer flags] [--chain <eip155:id>] [--rpc <url>] [--max-amount N] [--json]
```

If the URL does not return HTTP 402, the CLI short-circuits and prints the response body with exit code 0. No payment is made.

### Signer configuration

Exactly one signer source must be configured. CLI flags take precedence over environment variables. If zero or multiple sources are detected, the CLI exits with code 6.

| Source | Flag | Env var |
|--------|------|---------|
| Raw private key | `--key 0x...` | `PRIVATE_KEY` |
| Remote JSON-RPC | `--signer-url <url>` and `--signer-address 0x...` | `SIGNER_URL` and `SIGNER_ADDRESS` |
| Custom module | `--signer-module <pkg-or-path>` | `SIGNER_MODULE` |

Environment variable names are unprefixed to match Foundry, Hardhat, and x402-reference conventions.

### Request options

| Flag | Description |
|------|-------------|
| `--chain <eip155:id>` | Select a specific `accepts[]` entry when the merchant offers multiple chains. Required when there are multiple options. |
| `--rpc <url>` | Override the RPC URL for on-chain reads. Required for chain IDs not in `viem/chains`. |
| `--max-amount <n>` | Refuse to pay more than `n` atomic token units. Exits with code 3 if the price exceeds this. |
| `--json` | Emit a single JSON envelope to stdout instead of plain text. |

### Exit codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | Network error |
| 2 | Malformed 402 response or unusable `accepts[]` |
| 3 | Price exceeds `--max-amount` |
| 4 | Signature rejected |
| 5 | Settlement failed (merchant error after payment, or facilitator error) |
| 6 | Signer resolution failed (none, multiple, or partially configured) |

### Examples

#### Raw private key

```bash
PRIVATE_KEY=0xabc123... npx @x402r/cli pay https://api.example.com/paid-endpoint
```

#### JSON-RPC signer

Any endpoint that speaks `eth_signTypedData_v4` works: Privy wallet RPC, Turnkey, Fireblocks, Safe, a local `cast wallet` endpoint, or a hardware wallet behind an RPC bridge.

```bash
npx @x402r/cli pay https://api.example.com/paid-endpoint \
--signer-url https://signer.example/rpc \
--signer-address 0xYourAddress...
```

#### Custom module (Privy)

```javascript privy-signer.js
import { PrivyClient } from "@privy-io/server-auth";
import { createViemAccount } from "@privy-io/server-auth/viem";

export default async function () {
const privy = new PrivyClient(
process.env.PRIVY_APP_ID,
process.env.PRIVY_APP_SECRET
);
return createViemAccount({
walletId: process.env.PRIVY_WALLET_ID,
address: process.env.PRIVY_WALLET_ADDRESS,
privy,
});
}
```

```bash
npx @x402r/cli pay https://api.example.com/paid-endpoint \
--signer-module ./privy-signer.js
```

#### Custom module (Coinbase CDP)

```javascript cdp-signer.js
import { CdpClient } from "@coinbase/cdp-sdk";
import { toAccount } from "viem/accounts";

export default async function () {
const cdp = new CdpClient();
const acct = await cdp.evm.getOrCreateAccount({
name: process.env.CDP_ACCOUNT_NAME,
});
return toAccount(acct);
}
```

```bash
npx @x402r/cli pay https://api.example.com/paid-endpoint \
--signer-module ./cdp-signer.js
```

### JSON output

With `--json`, the CLI writes a single JSON envelope to stdout:

```json
{
"body": "<merchant response body>",
"status": 200,
"tx": "0x...",
"elapsedMs": 1234,
"signer": { "kind": "key", "address": "0x..." }
}
```

The `signer` field is omitted when the URL returned a non-402 response (no payment was made).

### Signer module contract

A custom signer module must default-export a factory function with the signature `() => Promise<Account>`. The returned object must be a viem `Account` with at least `address` and `signTypedData`. The CLI only needs typed-data signatures, transaction broadcasting is handled by the facilitator.

### Programmatic usage

You can also use the `pay` function and `resolveSigner` directly from TypeScript:

```typescript
import { pay } from "@x402r/cli";
import type { PayResult } from "@x402r/cli";

const result: PayResult = await pay({
url: "https://api.example.com/paid-endpoint",
key: process.env.PRIVATE_KEY,
json: true,
});

console.log(result.body);
console.log(result.tx);
```

<Tip>
The programmatic API uses the same `PayFlags` interface as the CLI binary. All options (chain, rpc, maxAmount, signer flags) are available.
</Tip>

### Exports

The `@x402r/cli` package exports:

| Export | Type | Description |
|--------|------|-------------|
| `pay` | function | Execute a one-shot payment against a URL |
| `resolveSigner` | function | Resolve a signer from flags and environment variables |
| `CliError` | class | Base error class with typed exit codes |
| `NetworkError` | class | Exit code 1 |
| `Malformed402Error` | class | Exit code 2 |
| `MaxAmountExceededError` | class | Exit code 3 |
| `SignatureRejectedError` | class | Exit code 4 |
| `SettlementError` | class | Exit code 5 |
| `SignerResolutionError` | class | Exit code 6 |

### Supported chains

The CLI auto-detects the chain from the 402 response's `accepts[].network` field. Any EVM chain known to `viem/chains` works out of the box (Base, Base Sepolia, Ethereum, Arbitrum, Optimism, and others). For unknown chain IDs, pass `--rpc <url>` to provide an RPC endpoint.

## Next steps

<CardGroup cols={3}>
<Card title="Payer guide" icon="user" href="/sdk/payer">
Use the SDK programmatically for richer payer workflows.
</Card>
<Card title="Merchant guide" icon="store" href="/sdk/merchant">
Accept payments and manage escrow releases.
</Card>
<Card title="Examples" icon="code" href="/sdk/examples">
Runnable examples for every SDK operation.
</Card>
</CardGroup>
19 changes: 18 additions & 1 deletion sdk/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,23 @@ bun add @x402r/helpers
```
</CodeGroup>

For one-shot payments from the command line (no project install required):

<CodeGroup>
```bash npm
npx @x402r/cli pay <url> [options]
```
```bash pnpm
pnpm dlx @x402r/cli pay <url> [options]
```
```bash bun
bunx @x402r/cli pay <url> [options]
```
</CodeGroup>

### Guides

<CardGroup cols={3}>
<CardGroup cols={2}>
<Card title="Merchants" icon="store" href="/sdk/merchant">
Deploy an operator, accept a payment, release funds from escrow.
</Card>
Expand All @@ -74,6 +88,9 @@ bun add @x402r/helpers
<Card title="Arbiters" icon="gavel" href="/sdk/arbiter">
Review disputes, approve or deny refunds, distribute fees.
</Card>
<Card title="CLI" icon="terminal" href="/sdk/cli">
One-shot payments from the command line or scripts.
</Card>
</CardGroup>

### Supported Chains
Expand Down
Loading