x402-proxy 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/README.md +9 -0
- package/dist/bin/cli.js +74 -29
- package/dist/index.js +18 -14
- package/dist/openclaw/plugin.d.ts +55 -0
- package/dist/openclaw/plugin.js +1120 -0
- package/dist/openclaw.plugin.json +28 -0
- package/openclaw.plugin.json +28 -0
- package/package.json +26 -4
- package/skills/SKILL.md +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-03-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `--verbose` flag on `fetch` command - debug logging for protocol negotiation, headers, session lifecycle, and payment flow
|
|
14
|
+
- OpenClaw plugin integration (`x402-proxy/openclaw`) - registers `x_balance` tool, `x_payment` tool, `/x_wallet` command, and HTTP route proxy for upstream x402 endpoints
|
|
15
|
+
- `openclaw.plugin.json` manifest with config schema for providers, keypair path, RPC URL, and dashboard URL
|
|
16
|
+
- `./openclaw` subpath export in package.json
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- MPP SSE requests silently losing `Content-Type` and other headers when `Headers` instances are spread (workaround for mppx SDK bug, upstream fix: wevm/mppx#209)
|
|
20
|
+
- MPP session `close()` errors no longer crash the CLI - wrapped in try/catch with verbose error reporting
|
|
21
|
+
- MPP payment history now includes `amount` (converted from base units) and `channelId` in transaction records
|
|
22
|
+
- MPP streaming history records now use `channelId` as fallback for `tx` field when no receipt reference is available
|
|
23
|
+
|
|
10
24
|
## [0.6.0] - 2026-03-19
|
|
11
25
|
|
|
12
26
|
### Added
|
|
@@ -172,7 +186,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
172
186
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
173
187
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
174
188
|
|
|
175
|
-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.
|
|
189
|
+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.0...HEAD
|
|
190
|
+
[0.7.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.6.0...v0.7.0
|
|
176
191
|
[0.6.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.2...v0.6.0
|
|
177
192
|
[0.5.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.1...v0.5.2
|
|
178
193
|
[0.5.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.0...v0.5.1
|
package/README.md
CHANGED
|
@@ -49,6 +49,9 @@ $ npx x402-proxy --method POST \
|
|
|
49
49
|
# Force a specific network
|
|
50
50
|
$ npx x402-proxy --network base https://api.example.com/data
|
|
51
51
|
|
|
52
|
+
# Debug protocol negotiation and payment flow
|
|
53
|
+
$ npx x402-proxy --verbose https://api.example.com/data
|
|
54
|
+
|
|
52
55
|
# Use MPP protocol for streaming payments
|
|
53
56
|
$ npx x402-proxy --protocol mpp \
|
|
54
57
|
--method POST \
|
|
@@ -118,6 +121,12 @@ import {
|
|
|
118
121
|
|
|
119
122
|
See the [library API docs](https://github.com/cascade-protocol/x402-proxy/tree/main/packages/x402-proxy#library-api) for details.
|
|
120
123
|
|
|
124
|
+
## OpenClaw Plugin
|
|
125
|
+
|
|
126
|
+
x402-proxy ships as an [OpenClaw](https://openclaw.dev) plugin, giving your gateway automatic x402 payment capabilities. Registers `x_balance` and `x_payment` tools, `/x_wallet` command, and an HTTP route proxy for upstream x402 endpoints.
|
|
127
|
+
|
|
128
|
+
Configure providers and models in OpenClaw plugin settings. Uses the standard wallet resolution (env vars or `wallet.json`).
|
|
129
|
+
|
|
121
130
|
## License
|
|
122
131
|
|
|
123
132
|
Apache-2.0
|
package/dist/bin/cli.js
CHANGED
|
@@ -115,20 +115,24 @@ async function createMppProxyHandler(opts) {
|
|
|
115
115
|
async close() {
|
|
116
116
|
if (session?.opened) {
|
|
117
117
|
const receipt = await session.close();
|
|
118
|
-
if (receipt)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
if (receipt) {
|
|
119
|
+
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
120
|
+
paymentQueue.push({
|
|
121
|
+
protocol: "mpp",
|
|
122
|
+
network: TEMPO_NETWORK,
|
|
123
|
+
intent: "session",
|
|
124
|
+
amount: spentUsdc,
|
|
125
|
+
channelId: session.channelId ?? void 0,
|
|
126
|
+
receipt: {
|
|
127
|
+
method: receipt.method,
|
|
128
|
+
reference: receipt.reference,
|
|
129
|
+
status: receipt.status,
|
|
130
|
+
timestamp: receipt.timestamp,
|
|
131
|
+
acceptedCumulative: receipt.acceptedCumulative,
|
|
132
|
+
txHash: receipt.txHash
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
138
|
};
|
|
@@ -195,6 +199,11 @@ Examples:
|
|
|
195
199
|
kind: "boolean",
|
|
196
200
|
brief: "Force JSON output",
|
|
197
201
|
default: false
|
|
202
|
+
},
|
|
203
|
+
verbose: {
|
|
204
|
+
kind: "boolean",
|
|
205
|
+
brief: "Show debug details (protocol negotiation, headers, payment flow)",
|
|
206
|
+
default: false
|
|
198
207
|
}
|
|
199
208
|
},
|
|
200
209
|
positional: {
|
|
@@ -207,6 +216,18 @@ Examples:
|
|
|
207
216
|
}
|
|
208
217
|
},
|
|
209
218
|
async func(flags, url) {
|
|
219
|
+
const verbose = (msg) => {
|
|
220
|
+
if (flags.verbose) dim(` [verbose] ${msg}`);
|
|
221
|
+
};
|
|
222
|
+
const closeMppSession = async (handler) => {
|
|
223
|
+
verbose("closing MPP session...");
|
|
224
|
+
try {
|
|
225
|
+
await handler.close();
|
|
226
|
+
verbose("session closed successfully");
|
|
227
|
+
} catch (closeErr) {
|
|
228
|
+
verbose(`session close failed: ${closeErr instanceof Error ? closeErr.message : String(closeErr)}`);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
210
231
|
if (!url) {
|
|
211
232
|
if (isConfigured()) {
|
|
212
233
|
const { displayStatus } = await import("../status-w5y-fhhe.js");
|
|
@@ -270,6 +291,8 @@ Examples:
|
|
|
270
291
|
const config = loadConfig();
|
|
271
292
|
const resolvedProtocol = flags.protocol ?? config?.preferredProtocol;
|
|
272
293
|
const maxDeposit = config?.mppSessionBudget ?? "1";
|
|
294
|
+
verbose(`wallet source: ${wallet.source}`);
|
|
295
|
+
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
273
296
|
let preferredNetwork = config?.defaultNetwork;
|
|
274
297
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
275
298
|
const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
|
|
@@ -287,7 +310,7 @@ Examples:
|
|
|
287
310
|
const method = flags.method || "GET";
|
|
288
311
|
const init = {
|
|
289
312
|
method,
|
|
290
|
-
headers
|
|
313
|
+
headers: Object.fromEntries(headers.entries())
|
|
291
314
|
};
|
|
292
315
|
if (flags.body) init.body = flags.body;
|
|
293
316
|
if (isTTY()) dim(` ${method} ${parsedUrl.toString()}`);
|
|
@@ -306,17 +329,30 @@ Examples:
|
|
|
306
329
|
evmKey: wallet.evmKey,
|
|
307
330
|
maxDeposit
|
|
308
331
|
});
|
|
309
|
-
|
|
332
|
+
const isStreamingRequest = flags.body != null && /"stream"\s*:\s*true/.test(flags.body);
|
|
333
|
+
verbose(`mpp handler created, streaming: ${isStreamingRequest}`);
|
|
334
|
+
if (isStreamingRequest) {
|
|
310
335
|
try {
|
|
336
|
+
verbose("opening SSE session...");
|
|
311
337
|
const tokens = await mppHandler.sse(parsedUrl.toString(), init);
|
|
338
|
+
verbose("SSE stream opened, reading tokens...");
|
|
312
339
|
for await (const token of tokens) process.stdout.write(token);
|
|
340
|
+
verbose("SSE stream complete");
|
|
313
341
|
} finally {
|
|
314
|
-
await mppHandler
|
|
342
|
+
await closeMppSession(mppHandler);
|
|
315
343
|
}
|
|
316
|
-
|
|
344
|
+
mppHandler.shiftPayment();
|
|
345
|
+
const closeReceipt = mppHandler.shiftPayment();
|
|
346
|
+
verbose(closeReceipt ? `close receipt: amount=${closeReceipt.amount ?? "none"}, channelId=${closeReceipt.channelId ?? "none"}, txHash=${closeReceipt.receipt?.txHash ?? "none"}` : "no close receipt (session close may have failed)");
|
|
347
|
+
mppPayment = closeReceipt ?? {
|
|
348
|
+
protocol: "mpp",
|
|
349
|
+
network: TEMPO_NETWORK,
|
|
350
|
+
intent: "session"
|
|
351
|
+
};
|
|
317
352
|
usedProtocol = "mpp";
|
|
318
353
|
const elapsedMs = Date.now() - startMs;
|
|
319
|
-
|
|
354
|
+
const spentAmount = mppPayment.amount ? Number(mppPayment.amount) : void 0;
|
|
355
|
+
if (mppPayment && isTTY()) info(` MPP session: ${spentAmount != null ? `${spentAmount.toFixed(4)} USDC ` : ""}(${displayNetwork(mppPayment.network)})`);
|
|
320
356
|
if (isTTY()) dim(` Streamed (${elapsedMs}ms)`);
|
|
321
357
|
if (mppPayment) {
|
|
322
358
|
const record = {
|
|
@@ -325,8 +361,8 @@ Examples:
|
|
|
325
361
|
kind: "mpp_payment",
|
|
326
362
|
net: mppPayment.network,
|
|
327
363
|
from: wallet.evmAddress ?? "unknown",
|
|
328
|
-
tx: mppPayment.receipt?.reference,
|
|
329
|
-
amount:
|
|
364
|
+
tx: mppPayment.receipt?.reference ?? mppPayment.channelId,
|
|
365
|
+
amount: spentAmount,
|
|
330
366
|
token: "USDC",
|
|
331
367
|
ms: elapsedMs,
|
|
332
368
|
label: parsedUrl.hostname
|
|
@@ -336,10 +372,12 @@ Examples:
|
|
|
336
372
|
if (isTTY()) process.stdout.write("\n");
|
|
337
373
|
return;
|
|
338
374
|
}
|
|
375
|
+
verbose("sending non-streaming MPP request...");
|
|
339
376
|
try {
|
|
340
377
|
response = await mppHandler.fetch(parsedUrl.toString(), init);
|
|
378
|
+
verbose(`response: ${response.status} ${response.statusText}`);
|
|
341
379
|
} finally {
|
|
342
|
-
await mppHandler
|
|
380
|
+
await closeMppSession(mppHandler);
|
|
343
381
|
}
|
|
344
382
|
mppPayment = mppHandler.shiftPayment();
|
|
345
383
|
usedProtocol = "mpp";
|
|
@@ -364,15 +402,19 @@ Examples:
|
|
|
364
402
|
x402Payment = handler.shiftPayment();
|
|
365
403
|
usedProtocol = "x402";
|
|
366
404
|
if (response.status === 402 && wallet.evmKey) {
|
|
367
|
-
|
|
405
|
+
const detected = detectProtocols(response);
|
|
406
|
+
verbose(`auto-detect: x402=${detected.x402}, mpp=${detected.mpp}`);
|
|
407
|
+
if (detected.mpp) {
|
|
408
|
+
verbose("falling through to MPP...");
|
|
368
409
|
const mppHandler = await createMppProxyHandler({
|
|
369
410
|
evmKey: wallet.evmKey,
|
|
370
411
|
maxDeposit
|
|
371
412
|
});
|
|
372
413
|
try {
|
|
373
414
|
response = await mppHandler.fetch(parsedUrl.toString(), init);
|
|
415
|
+
verbose(`MPP response: ${response.status} ${response.statusText}`);
|
|
374
416
|
} finally {
|
|
375
|
-
await mppHandler
|
|
417
|
+
await closeMppSession(mppHandler);
|
|
376
418
|
}
|
|
377
419
|
mppPayment = mppHandler.shiftPayment();
|
|
378
420
|
x402Payment = void 0;
|
|
@@ -387,6 +429,8 @@ Examples:
|
|
|
387
429
|
const elapsedMs = Date.now() - startMs;
|
|
388
430
|
const payment = x402Payment ?? mppPayment;
|
|
389
431
|
const txSig = extractTxSignature(response);
|
|
432
|
+
verbose(`protocol used: ${usedProtocol ?? "none"}`);
|
|
433
|
+
for (const [k, v] of response.headers) if (/payment|auth|www|x-pay/i.test(k)) verbose(`header ${k}: ${v.slice(0, 200)}`);
|
|
390
434
|
if (response.status === 402 && isTTY()) {
|
|
391
435
|
const detected = detectProtocols(response);
|
|
392
436
|
const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
|
|
@@ -491,6 +535,7 @@ Examples:
|
|
|
491
535
|
net: mppPayment.network,
|
|
492
536
|
from: wallet.evmAddress ?? "unknown",
|
|
493
537
|
tx: mppPayment.receipt?.reference ?? txSig,
|
|
538
|
+
amount: mppPayment.amount ? Number(mppPayment.amount) : void 0,
|
|
494
539
|
token: "USDC",
|
|
495
540
|
ms: elapsedMs,
|
|
496
541
|
label: parsedUrl.hostname
|
|
@@ -617,7 +662,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
617
662
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
618
663
|
const remoteClient = new Client({
|
|
619
664
|
name: "x402-proxy",
|
|
620
|
-
version: "0.
|
|
665
|
+
version: "0.7.0"
|
|
621
666
|
});
|
|
622
667
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
623
668
|
autoPayment: true,
|
|
@@ -656,7 +701,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
656
701
|
}
|
|
657
702
|
const localServer = new Server({
|
|
658
703
|
name: "x402-proxy",
|
|
659
|
-
version: "0.
|
|
704
|
+
version: "0.7.0"
|
|
660
705
|
}, { capabilities: {
|
|
661
706
|
tools: tools.length > 0 ? {} : void 0,
|
|
662
707
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -722,7 +767,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
722
767
|
const maxDeposit = config?.mppSessionBudget ?? "1";
|
|
723
768
|
const remoteClient = new Client({
|
|
724
769
|
name: "x402-proxy",
|
|
725
|
-
version: "0.
|
|
770
|
+
version: "0.7.0"
|
|
726
771
|
});
|
|
727
772
|
await connectTransport(remoteClient);
|
|
728
773
|
const mppClient = McpClient.wrap(remoteClient, { methods: [tempo({
|
|
@@ -740,7 +785,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
740
785
|
}
|
|
741
786
|
const localServer = new Server({
|
|
742
787
|
name: "x402-proxy",
|
|
743
|
-
version: "0.
|
|
788
|
+
version: "0.7.0"
|
|
744
789
|
}, { capabilities: {
|
|
745
790
|
tools: tools.length > 0 ? {} : void 0,
|
|
746
791
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -940,7 +985,7 @@ const routes = buildRouteMap({
|
|
|
940
985
|
});
|
|
941
986
|
const app = buildApplication(routes, {
|
|
942
987
|
name: "x402-proxy",
|
|
943
|
-
versionInfo: { currentVersion: "0.
|
|
988
|
+
versionInfo: { currentVersion: "0.7.0" },
|
|
944
989
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
945
990
|
});
|
|
946
991
|
|
package/dist/index.js
CHANGED
|
@@ -112,20 +112,24 @@ async function createMppProxyHandler(opts) {
|
|
|
112
112
|
async close() {
|
|
113
113
|
if (session?.opened) {
|
|
114
114
|
const receipt = await session.close();
|
|
115
|
-
if (receipt)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
if (receipt) {
|
|
116
|
+
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
117
|
+
paymentQueue.push({
|
|
118
|
+
protocol: "mpp",
|
|
119
|
+
network: TEMPO_NETWORK,
|
|
120
|
+
intent: "session",
|
|
121
|
+
amount: spentUsdc,
|
|
122
|
+
channelId: session.channelId ?? void 0,
|
|
123
|
+
receipt: {
|
|
124
|
+
method: receipt.method,
|
|
125
|
+
reference: receipt.reference,
|
|
126
|
+
status: receipt.status,
|
|
127
|
+
timestamp: receipt.timestamp,
|
|
128
|
+
acceptedCumulative: receipt.acceptedCumulative,
|
|
129
|
+
txHash: receipt.txHash
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { x402Client } from "@x402/fetch";
|
|
2
|
+
//#region src/openclaw/tools.d.ts
|
|
3
|
+
type ModelEntry = {
|
|
4
|
+
provider: string;
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
maxTokens: number;
|
|
8
|
+
reasoning: boolean;
|
|
9
|
+
input: string[];
|
|
10
|
+
cost: {
|
|
11
|
+
input: number;
|
|
12
|
+
output: number;
|
|
13
|
+
cacheRead: number;
|
|
14
|
+
cacheWrite: number;
|
|
15
|
+
};
|
|
16
|
+
contextWindow: number;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/openclaw/plugin.d.ts
|
|
20
|
+
type OpenClawPluginApi = {
|
|
21
|
+
pluginConfig?: Record<string, unknown>;
|
|
22
|
+
logger: {
|
|
23
|
+
info: (msg: string) => void;
|
|
24
|
+
error: (msg: string) => void;
|
|
25
|
+
};
|
|
26
|
+
registerProvider: (provider: {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
auth: unknown[];
|
|
30
|
+
models: {
|
|
31
|
+
baseUrl: string;
|
|
32
|
+
api: string;
|
|
33
|
+
authHeader: boolean;
|
|
34
|
+
models: Array<Omit<ModelEntry, "provider"> & {
|
|
35
|
+
input: Array<"text" | "image">;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
}) => void;
|
|
39
|
+
registerTool: (tool: unknown) => void;
|
|
40
|
+
registerCommand: (command: unknown) => void;
|
|
41
|
+
registerService: (service: {
|
|
42
|
+
id: string;
|
|
43
|
+
start: () => Promise<void>;
|
|
44
|
+
stop: () => Promise<void>;
|
|
45
|
+
}) => void;
|
|
46
|
+
registerHttpRoute: (params: {
|
|
47
|
+
path: string;
|
|
48
|
+
match: string;
|
|
49
|
+
auth: string;
|
|
50
|
+
handler: (req: unknown, res: unknown) => Promise<void>;
|
|
51
|
+
}) => void;
|
|
52
|
+
};
|
|
53
|
+
declare function register(api: OpenClawPluginApi): void;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { register };
|