Skip to content

Commit 2f6d09a

Browse files
committed
chore(release): bump to 0.3.2
- Auto-setup: fetch without wallet launches setup wizard, then continues - 402 handling parses PAYMENT-REQUIRED header for actual accepted networks/costs - CI pipeline (check, build, test on push/PR) - Automated npm publishing with OIDC provenance on tag push - Tests for wallet derivation and transaction history (25 tests) - Version injected at build time via __VERSION__ (no stale strings) - Removed wallet fund command (funding hint in wallet instead)
1 parent 187fa47 commit 2f6d09a

16 files changed

Lines changed: 600 additions & 174 deletions

File tree

.claude/CLAUDE.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ CLI and library for x402 paid HTTP requests and MCP proxy. Monorepo with single
2727
- Build package only: `cd packages/x402-proxy && pnpm build`
2828

2929
## Key Files
30-
- `src/app.ts` - Stricli route map, **contains versionInfo that must match package.json**
30+
- `src/app.ts` - Stricli route map (version injected via `__VERSION__` at build time)
3131
- `src/bin/cli.ts` - CLI entry point
3232
- `src/lib/resolve-wallet.ts` - wallet resolution cascade (flags > env > mnemonic > file)
3333
- `src/lib/derive.ts` - BIP-39 mnemonic derivation (Solana + EVM)
@@ -53,23 +53,36 @@ pnpm check # type-check + biome lint (from root)
5353
pnpm build # clean + tsdown --publint (via Turbo)
5454
```
5555

56+
## Version Management
57+
- Version is defined ONLY in `packages/x402-proxy/package.json`
58+
- `tsdown.config.ts` reads it and injects as `__VERSION__` at build time via `define`
59+
- `src/app.ts` and `src/commands/mcp.ts` use `__VERSION__` - never hardcode version strings in source
60+
5661
## Release Workflow
5762

58-
**Order: check -> build -> version bump -> changelog -> commit + tag + push -> ask user to publish -> verify -> GitHub release**
63+
**CRITICAL: Before saying "ready to release", run through EVERY item in this checklist. Do NOT skip any step.**
64+
65+
### Pre-release checklist (run through ALL before committing)
66+
- [ ] `pnpm check` passes (type-check + biome)
67+
- [ ] `pnpm build` passes (publint validates package)
68+
- [ ] `pnpm --filter x402-proxy test` passes
69+
- [ ] Version bumped in `packages/x402-proxy/package.json` (the ONLY place - __VERSION__ handles the rest)
70+
- [ ] `CHANGELOG.md` updated: new `## [<version>] - YYYY-MM-DD` section with all changes since last release
71+
- [ ] `CHANGELOG.md` comparison links updated at bottom of file
72+
- [ ] README.md reflects any command/feature changes
73+
- [ ] No stale version strings anywhere in source (grep for old version number)
5974

60-
1. Run `pnpm check` and `pnpm build` (publint validates package) - stop on any failure
61-
2. Bump version in both `packages/x402-proxy/package.json` and `packages/x402-proxy/src/app.ts` (Stricli `versionInfo.currentVersion`)
62-
3. Update `packages/x402-proxy/CHANGELOG.md` (add new `## [<version>] - YYYY-MM-DD` section, add comparison link at bottom)
63-
4. Commit, tag, and push:
75+
### Release steps
76+
1. Commit and tag:
6477
```bash
65-
git add packages/x402-proxy/package.json packages/x402-proxy/src/app.ts packages/x402-proxy/CHANGELOG.md
78+
git add -A
6679
git commit -m "chore(release): bump to <version>"
6780
git tag v<version>
6881
git push && git push origin v<version>
6982
```
70-
5. Publish from `packages/x402-proxy/`: `pnpm publish` (no `--no-git-checks`, working tree must be clean)
71-
6. After publish, verify with `npm view x402-proxy@<version> bin`
72-
7. Create GitHub release:
83+
2. CI publishes automatically via `.github/workflows/publish.yml` (OIDC provenance, triggered on `v*` tags)
84+
3. Verify: `npm view x402-proxy@<version> bin`
85+
4. Create GitHub release:
7386
```bash
7487
gh release create v<version> --title "v<version>" --notes "$(cat <<'EOF'
7588
<paste relevant CHANGELOG section>

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
- uses: pnpm/action-setup@v4
15+
- uses: actions/setup-node@v6
16+
with:
17+
node-version: 22
18+
cache: pnpm
19+
- run: pnpm install --frozen-lockfile
20+
- run: pnpm check
21+
- run: pnpm build
22+
- run: pnpm --filter x402-proxy test

.github/workflows/publish.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Publish to npm
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: read
10+
id-token: write
11+
12+
jobs:
13+
publish:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v6
17+
- uses: pnpm/action-setup@v4
18+
- uses: actions/setup-node@v6
19+
with:
20+
node-version: 24
21+
registry-url: https://registry.npmjs.org
22+
cache: pnpm
23+
- run: pnpm install --frozen-lockfile
24+
- run: pnpm check
25+
- run: pnpm build
26+
- run: pnpm --filter x402-proxy test
27+
- run: pnpm --filter x402-proxy publish --provenance --access public --no-git-checks

packages/x402-proxy/CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.2] - 2026-03-13
11+
12+
### Added
13+
- Auto-setup: running `npx x402-proxy <url>` without a wallet launches the setup wizard, then continues with the request
14+
- 402 error handling parses the endpoint's `PAYMENT-REQUIRED` header to show actual accepted networks and costs
15+
- CI pipeline (GitHub Actions: check, build, test on push/PR)
16+
- Automated npm publishing with OIDC provenance on tag push
17+
- Tests for wallet derivation and transaction history (25 tests)
18+
- Funding hint in `wallet` when USDC balance is zero
19+
20+
### Changed
21+
- Version injected at build time from package.json (no more stale hardcoded strings)
22+
- `wallet fund` command removed (addresses and hint shown in `wallet` directly)
23+
- All command references use `$ npx x402-proxy` format
24+
1025
## [0.3.1] - 2026-03-12
1126

1227
### Added
@@ -80,7 +95,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8095
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
8196
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
8297

83-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.3.1...HEAD
98+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.3.2...HEAD
99+
[0.3.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.3.1...v0.3.2
84100
[0.3.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.3.0...v0.3.1
85101
[0.3.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.2.1...v0.3.0
86102
[0.2.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.2.0...v0.2.1

packages/x402-proxy/README.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,7 @@ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
1010

1111
That's it. The endpoint returns 402, x402-proxy pays and streams the response.
1212

13-
First time? Set up a wallet:
14-
15-
```bash
16-
npx x402-proxy setup # generate wallet from BIP-39 mnemonic
17-
npx x402-proxy wallet fund # see where to send USDC
18-
```
19-
20-
One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
13+
No wallet? It'll walk you through setup automatically. One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
2114

2215
## MCP Proxy
2316

@@ -45,29 +38,28 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
4538

4639
```bash
4740
# GET request
48-
x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
41+
$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
4942

5043
# POST with body and headers
51-
x402-proxy --method POST \
44+
$ npx x402-proxy --method POST \
5245
--header "Content-Type: application/json" \
5346
--body '{"url":"https://x402.org"}' \
5447
https://web.surf.cascade.fyi/v1/crawl
5548

5649
# Pipe-safe
57-
x402-proxy https://api.example.com/data | jq '.results'
50+
$ npx x402-proxy https://api.example.com/data | jq '.results'
5851
```
5952

6053
## Commands
6154

6255
```bash
63-
x402-proxy <url> # paid HTTP request (default command)
64-
x402-proxy mcp <url> # MCP stdio proxy for agents
65-
x402-proxy setup # onboarding wizard
66-
x402-proxy status # config + wallet + spend summary
67-
x402-proxy wallet # show addresses
68-
x402-proxy wallet history # payment history
69-
x402-proxy wallet fund # funding instructions
70-
x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
56+
$ npx x402-proxy <url> # paid HTTP request (default command)
57+
$ npx x402-proxy mcp <url> # MCP stdio proxy for agents
58+
$ npx x402-proxy setup # onboarding wizard
59+
$ npx x402-proxy status # config + wallet + spend summary
60+
$ npx x402-proxy wallet # show addresses and balances
61+
$ npx x402-proxy wallet history # payment history
62+
$ npx x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
7163
```
7264

7365
All commands support `--help` for details.
@@ -84,8 +76,8 @@ Config stored at `$XDG_CONFIG_HOME/x402-proxy/` (default `~/.config/x402-proxy/`
8476

8577
```bash
8678
# Pipe-safe - outputs bare key/mnemonic to stdout
87-
MY_KEY=$(npx x402-proxy wallet export-key evm)
88-
MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
79+
$ MY_KEY=$(npx x402-proxy wallet export-key evm)
80+
$ MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
8981
```
9082

9183
## Env Vars

packages/x402-proxy/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "x402-proxy",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
55
"type": "module",
66
"sideEffects": false,
@@ -22,6 +22,7 @@
2222
"build": "rm -rf dist && tsdown --publint",
2323
"dev": "tsdown --watch",
2424
"type-check": "tsc --noEmit",
25+
"test": "vitest run",
2526
"check": "pnpm type-check && biome check --write",
2627
"prepublishOnly": "pnpm build"
2728
},
@@ -48,7 +49,8 @@
4849
"@types/node": "^22.0.0",
4950
"publint": "^0.3.17",
5051
"tsdown": "^0.20.3",
51-
"typescript": "^5.9.0"
52+
"typescript": "^5.9.0",
53+
"vitest": "^4.0.18"
5254
},
5355
"files": [
5456
"dist/**",

packages/x402-proxy/src/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
declare const __VERSION__: string;
2+
13
import { buildApplication, buildRouteMap } from "@stricli/core";
24
import { fetchCommand } from "./commands/fetch.js";
35
import { mcpCommand } from "./commands/mcp.js";
46
import { setupCommand } from "./commands/setup.js";
57
import { statusCommand } from "./commands/status.js";
68
import { walletInfoCommand } from "./commands/wallet.js";
79
import { walletExportCommand } from "./commands/wallet-export.js";
8-
import { walletFundCommand } from "./commands/wallet-fund.js";
910
import { walletHistoryCommand } from "./commands/wallet-history.js";
1011

1112
const walletRoutes = buildRouteMap({
1213
routes: {
1314
info: walletInfoCommand,
1415
history: walletHistoryCommand,
15-
fund: walletFundCommand,
1616
"export-key": walletExportCommand,
1717
},
1818
defaultCommand: "info",
@@ -38,7 +38,7 @@ const routes = buildRouteMap({
3838
export const app = buildApplication(routes, {
3939
name: "x402-proxy",
4040
versionInfo: {
41-
currentVersion: "0.3.1",
41+
currentVersion: __VERSION__,
4242
},
4343
scanner: {
4444
caseStyle: "allow-kebab-for-camel",

packages/x402-proxy/src/commands/fetch.ts

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { buildCommand, type CommandContext } from "@stricli/core";
2+
import type { PaymentRequired } from "@x402/fetch";
23
import pc from "picocolors";
34
import { createX402ProxyHandler, extractTxSignature } from "../handler.js";
45
import { appendHistory, type TxRecord } from "../history.js";
@@ -91,7 +92,6 @@ Examples:
9192
console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
9293
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
9394
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Full payment history`);
94-
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
9595
console.log();
9696
console.log(
9797
pc.dim(" try: ") +
@@ -113,7 +113,6 @@ Examples:
113113
);
114114
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
115115
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
116-
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
117116
console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
118117
console.log();
119118
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy setup"));
@@ -133,15 +132,29 @@ Examples:
133132
process.exit(1);
134133
}
135134

136-
// Resolve wallet
137-
const wallet = resolveWallet({
135+
// Resolve wallet - auto-setup on first use
136+
let wallet = resolveWallet({
138137
evmKey: flags.evmKey,
139138
solanaKey: flags.solanaKey,
140139
});
141140
if (wallet.source === "none") {
142-
error("No wallet configured.");
143-
console.error(pc.dim(`Run ${pc.cyan("x402-proxy setup")} or set X402_PROXY_WALLET_MNEMONIC`));
144-
process.exit(1);
141+
if (!isTTY()) {
142+
error("No wallet configured.");
143+
console.error(
144+
pc.dim(
145+
`Run:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set X402_PROXY_WALLET_MNEMONIC`,
146+
),
147+
);
148+
process.exit(1);
149+
}
150+
dim(" No wallet found. Let's set one up first.\n");
151+
const { runSetup } = await import("./setup.js");
152+
await runSetup();
153+
console.log();
154+
wallet = resolveWallet();
155+
if (wallet.source === "none") {
156+
return;
157+
}
145158
}
146159

147160
const config = loadConfig();
@@ -183,8 +196,68 @@ Examples:
183196
const payment = shiftPayment();
184197
const txSig = extractTxSignature(response);
185198

199+
// Payment failed - show funding instructions from the endpoint's actual requirements
200+
if (response.status === 402 && isTTY()) {
201+
const prHeader =
202+
response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
203+
let accepts: PaymentRequired["accepts"] = [];
204+
if (prHeader) {
205+
try {
206+
const decoded = JSON.parse(Buffer.from(prHeader, "base64").toString()) as PaymentRequired;
207+
accepts = decoded.accepts ?? [];
208+
} catch {
209+
// Fall through with empty accepts
210+
}
211+
}
212+
213+
if (accepts.length > 0) {
214+
const cheapest = accepts.reduce((min, a) =>
215+
Number(a.amount) < Number(min.amount) ? a : min,
216+
);
217+
const cost = (Number(cheapest.amount) / 1_000_000).toFixed(4);
218+
error(`Payment required: ${cost} USDC`);
219+
} else {
220+
error("Payment required");
221+
}
222+
223+
const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
224+
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
225+
const hasOther = accepts.some(
226+
(a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"),
227+
);
228+
229+
if (hasEvm || hasSolana) {
230+
console.error();
231+
dim(" Fund your wallet with USDC:");
232+
if (hasEvm && wallet.evmAddress) {
233+
console.error(` Base: ${pc.cyan(wallet.evmAddress)}`);
234+
}
235+
if (hasSolana && wallet.solanaAddress) {
236+
console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}`);
237+
}
238+
if (hasEvm && !wallet.evmAddress) {
239+
dim(" Base: endpoint accepts EVM but no EVM wallet configured");
240+
}
241+
if (hasSolana && !wallet.solanaAddress) {
242+
dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
243+
}
244+
} else if (hasOther) {
245+
const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
246+
console.error();
247+
error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
248+
}
249+
250+
console.error();
251+
dim(" Then re-run:");
252+
console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
253+
console.error();
254+
return;
255+
}
256+
186257
if (payment && isTTY()) {
187-
info(` Payment: ${payment.amount ?? "?"} (${payment.network ?? "unknown"})`);
258+
info(
259+
` Payment: ${payment.amount ? (Number(payment.amount) / 1_000_000).toFixed(4) : "?"} USDC (${payment.network ?? "unknown"})`,
260+
);
188261
if (txSig) dim(` Tx: ${txSig}`);
189262
}
190263

0 commit comments

Comments
 (0)