warpmetal 0.7.7 → 0.7.8
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/README.md
CHANGED
|
@@ -34,7 +34,7 @@ published wallet CLI with the exact version WarpMetal reports:
|
|
|
34
34
|
npm install --global warpmetal
|
|
35
35
|
warpmetal --help
|
|
36
36
|
|
|
37
|
-
npm install --global @x402api/agent-wallet-cli@0.2.
|
|
37
|
+
npm install --global @x402api/agent-wallet-cli@0.2.8
|
|
38
38
|
x402api help --json
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -58,7 +58,7 @@ integration.
|
|
|
58
58
|
|
|
59
59
|
The repository also contains a skills-only WarpMetal plugin for the public
|
|
60
60
|
Plugins Directory shared by Codex and ChatGPT. The plugin remains a separate
|
|
61
|
-
artifact from the npm CLI and requires `warpmetal` CLI version 0.7.
|
|
61
|
+
artifact from the npm CLI and requires `warpmetal` CLI version 0.7.8 or newer.
|
|
62
62
|
|
|
63
63
|
To test the repository marketplace after the plugin lands on `main`:
|
|
64
64
|
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ with ad hoc HTTP commands.
|
|
|
11
11
|
|
|
12
12
|
## Start safely
|
|
13
13
|
|
|
14
|
-
1. Run `warpmetal --version` and require version `0.7.
|
|
14
|
+
1. Run `warpmetal --version` and require version `0.7.8` or newer for this
|
|
15
15
|
plugin. If it is missing or older, explain the compatibility requirement,
|
|
16
16
|
ask before installing or upgrading software, and use only the official npm
|
|
17
17
|
package from `https://www.npmjs.com/package/warpmetal`.
|
|
@@ -64,12 +64,15 @@ suggested artifact paths live under the
|
|
|
64
64
|
private WarpMetal state directory. An explicit output path must not already
|
|
65
65
|
contain different content. `challengeHandle` is intentionally absent from the
|
|
66
66
|
wallet envelope: it is neither a buyer payment identifier nor signing input.
|
|
67
|
+
`paymentChallengeDigest` is the distinct authoritative signing input forwarded
|
|
68
|
+
from x402api; the CLI fails closed instead of deriving it from
|
|
69
|
+
`PAYMENT-REQUIRED`.
|
|
67
70
|
If a previously issued unsigned sponsorship reservation expired, WarpMetal may
|
|
68
71
|
retire that attempt and return a new `paymentAttemptId`. The CLI replaces the
|
|
69
72
|
saved challenge and stale wallet-attempt metadata; use only the newly returned
|
|
70
73
|
workflow.
|
|
71
74
|
|
|
72
|
-
The current integration targets `@x402api/agent-wallet-cli@0.2.
|
|
75
|
+
The current integration targets `@x402api/agent-wallet-cli@0.2.8`. A compatible
|
|
73
76
|
live term is marked `agentWalletSupported: true` and must use the sponsored
|
|
74
77
|
Base USDC or Solana USDC/USDT launch profile with buyer native fees disabled.
|
|
75
78
|
For the current declaration, x402api pays actual gas from its platform treasury
|
|
@@ -25,7 +25,7 @@ contract.
|
|
|
25
25
|
WarpMetal runs on Node.js 20 or 22. The x402api Agent Wallet currently requires
|
|
26
26
|
Node.js 22. Use the exact published package reported by
|
|
27
27
|
`paymentWorkflow.signerPackage.spec`; the current contract is
|
|
28
|
-
`@x402api/agent-wallet-cli@0.2.
|
|
28
|
+
`@x402api/agent-wallet-cli@0.2.8`. Do not add it as a WarpMetal dependency,
|
|
29
29
|
install executable wallet code from an unpinned repository URL, or substitute
|
|
30
30
|
a similarly named package.
|
|
31
31
|
|
|
@@ -140,6 +140,9 @@ also contains `challengeHandle`, an opaque value retained by WarpMetal for
|
|
|
140
140
|
merchant reconciliation. It is deliberately excluded from the x402api V1
|
|
141
141
|
request envelope and must not be confused with the wallet-created buyer payment
|
|
142
142
|
identifier.
|
|
143
|
+
The wallet nonce is bound to the authoritative `paymentChallengeDigest` that
|
|
144
|
+
WarpMetal forwards from x402api's charge response. Never derive or replace that
|
|
145
|
+
digest with a hash of `PAYMENT-REQUIRED`.
|
|
143
146
|
|
|
144
147
|
Choose only a term marked `agentWalletSupported: true`,
|
|
145
148
|
`sponsoredNetworkFee: true`, and `buyerNativeFeeRequired: false`. The supported
|
|
@@ -149,7 +152,7 @@ reservation or any buyer-funded, unsupported, or unbound alternative.
|
|
|
149
152
|
|
|
150
153
|
x402api pays actual gas from its platform treasury. The merchant tenant's
|
|
151
154
|
active sponsorship allowance controls admission, but actual gas is not a
|
|
152
|
-
tenant debit. During the coordinated rollout, accept only the exact 0.2.
|
|
155
|
+
tenant debit. During the coordinated rollout, accept only the exact 0.2.8
|
|
153
156
|
platform-treasury declaration or the matched legacy tenant-credit declaration;
|
|
154
157
|
never accept a mixed billing and final-charge policy.
|
|
155
158
|
|
package/src/cli.js
CHANGED
|
@@ -389,6 +389,19 @@ function challengeResult(
|
|
|
389
389
|
const rejected = response.status === 402 && status === "payment_rejected";
|
|
390
390
|
const paymentRequired = response.headers["payment-required"];
|
|
391
391
|
const challengeHandle = response.headers["x-x402api-challenge-handle"];
|
|
392
|
+
const headerChallengeDigest =
|
|
393
|
+
response.headers["x-x402api-challenge-digest"];
|
|
394
|
+
const bodyChallengeDigest = response.data?.challengeDigest;
|
|
395
|
+
if (
|
|
396
|
+
headerChallengeDigest &&
|
|
397
|
+
bodyChallengeDigest &&
|
|
398
|
+
headerChallengeDigest !== bodyChallengeDigest
|
|
399
|
+
) {
|
|
400
|
+
throw new CliError(
|
|
401
|
+
"WarpMetal returned conflicting x402 challenge digests.",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const challengeDigest = bodyChallengeDigest || headerChallengeDigest;
|
|
392
405
|
if (requireChallenge && response.status === 402 && !paymentRequired) {
|
|
393
406
|
throw new CliError("WarpMetal returned HTTP 402 without PAYMENT-REQUIRED.");
|
|
394
407
|
}
|
|
@@ -397,6 +410,11 @@ function challengeResult(
|
|
|
397
410
|
"WarpMetal returned HTTP 402 without X-X402API-Challenge-Handle.",
|
|
398
411
|
);
|
|
399
412
|
}
|
|
413
|
+
if (paymentRequired && !challengeDigest) {
|
|
414
|
+
throw new CliError(
|
|
415
|
+
"WarpMetal returned PAYMENT-REQUIRED without X-X402API-Challenge-Digest.",
|
|
416
|
+
);
|
|
417
|
+
}
|
|
400
418
|
return {
|
|
401
419
|
status,
|
|
402
420
|
taskId,
|
|
@@ -405,6 +423,7 @@ function challengeResult(
|
|
|
405
423
|
response.headers["x-warpmetal-payment-attempt"],
|
|
406
424
|
paymentRequired,
|
|
407
425
|
challengeHandle,
|
|
426
|
+
challengeDigest,
|
|
408
427
|
checkoutBodySha256: createHash("sha256").update(checkoutBody).digest("hex"),
|
|
409
428
|
...(rejected
|
|
410
429
|
? {
|
|
@@ -430,6 +449,7 @@ async function attachPaymentWorkflow(
|
|
|
430
449
|
checkoutPath: order.checkoutPath,
|
|
431
450
|
checkoutBody: order.checkoutBody,
|
|
432
451
|
paymentRequired: safe.paymentRequired,
|
|
452
|
+
challengeDigest: safe.challengeDigest,
|
|
433
453
|
merchantReference: taskId,
|
|
434
454
|
});
|
|
435
455
|
const defaults = defaultPaymentPaths(
|
|
@@ -727,6 +747,7 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
727
747
|
checkoutPath: order.checkoutPath,
|
|
728
748
|
checkoutBody: order.checkoutBody,
|
|
729
749
|
paymentRequired: order.paymentRequired,
|
|
750
|
+
challengeDigest: order.paymentChallengeDigest,
|
|
730
751
|
merchantReference: taskId,
|
|
731
752
|
});
|
|
732
753
|
walletPayment = await readPaymentArtifact(artifactFile, expected);
|
|
@@ -1043,6 +1064,7 @@ async function attachRenewalPaymentWorkflow(
|
|
|
1043
1064
|
checkoutPath,
|
|
1044
1065
|
checkoutBody: bodyText,
|
|
1045
1066
|
paymentRequired: safe.paymentRequired,
|
|
1067
|
+
challengeDigest: safe.challengeDigest,
|
|
1046
1068
|
merchantReference: `renew:${serverId}:${server.termEndsAt || "unset"}`,
|
|
1047
1069
|
});
|
|
1048
1070
|
const matchingTerms = request.terms.filter(
|
|
@@ -1317,6 +1339,7 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1317
1339
|
checkoutPath: renewal.checkoutPath,
|
|
1318
1340
|
checkoutBody: renewal.checkoutBody,
|
|
1319
1341
|
paymentRequired: renewal.paymentRequired,
|
|
1342
|
+
challengeDigest: renewal.paymentChallengeDigest,
|
|
1320
1343
|
merchantReference: `renew:${serverId}:${renewal.termEndsAt || "unset"}`,
|
|
1321
1344
|
});
|
|
1322
1345
|
const walletPayment = await readPaymentArtifact(artifactFile, expected);
|
package/src/payment.js
CHANGED
|
@@ -15,7 +15,7 @@ const MAX_ARTIFACT_BYTES = 1024 * 1024;
|
|
|
15
15
|
const MAX_SIGNATURE_BYTES = 512 * 1024;
|
|
16
16
|
|
|
17
17
|
export const AGENT_WALLET_PACKAGE = "@x402api/agent-wallet-cli";
|
|
18
|
-
export const AGENT_WALLET_VERSION = "0.2.
|
|
18
|
+
export const AGENT_WALLET_VERSION = "0.2.8";
|
|
19
19
|
const AGENT_WALLET_SPEC = `${AGENT_WALLET_PACKAGE}@${AGENT_WALLET_VERSION}`;
|
|
20
20
|
const GAS_SPONSORSHIP_EXTENSION = "com.x402api.gas-sponsorship";
|
|
21
21
|
const BASE_NETWORK = "eip155:8453";
|
|
@@ -399,7 +399,7 @@ export function parsePaymentRequired(value, expectedUrl) {
|
|
|
399
399
|
const sponsorship = extensions[GAS_SPONSORSHIP_EXTENSION]?.info;
|
|
400
400
|
return {
|
|
401
401
|
challenge: parsed,
|
|
402
|
-
|
|
402
|
+
paymentRequiredDigest: digestJson(parsed),
|
|
403
403
|
accepts,
|
|
404
404
|
sponsorshipExpiresAt: sponsorship?.expiresAt,
|
|
405
405
|
terms: accepts.map((requirement) => {
|
|
@@ -434,6 +434,7 @@ export function createPaymentRequestEnvelope({
|
|
|
434
434
|
checkoutPath,
|
|
435
435
|
checkoutBody,
|
|
436
436
|
paymentRequired,
|
|
437
|
+
challengeDigest,
|
|
437
438
|
merchantReference,
|
|
438
439
|
}) {
|
|
439
440
|
const url = new URL(
|
|
@@ -441,6 +442,14 @@ export function createPaymentRequestEnvelope({
|
|
|
441
442
|
`${baseUrl.replace(/\/$/, "")}/`,
|
|
442
443
|
).toString();
|
|
443
444
|
const parsed = parsePaymentRequired(paymentRequired, url);
|
|
445
|
+
if (typeof challengeDigest !== "string" || !SHA256.test(challengeDigest)) {
|
|
446
|
+
throw invalid("The authoritative x402 challenge digest is malformed.");
|
|
447
|
+
}
|
|
448
|
+
if (challengeDigest === parsed.paymentRequiredDigest) {
|
|
449
|
+
throw invalid(
|
|
450
|
+
"The x402 challenge digest was derived from PAYMENT-REQUIRED instead of the authoritative charge response.",
|
|
451
|
+
);
|
|
452
|
+
}
|
|
444
453
|
const supportedTerms = parsed.terms.filter(
|
|
445
454
|
(term) => term.agentWalletSupported,
|
|
446
455
|
);
|
|
@@ -466,13 +475,13 @@ export function createPaymentRequestEnvelope({
|
|
|
466
475
|
contentType: "application/json",
|
|
467
476
|
bodyBase64: Buffer.from(checkoutBody, "utf8").toString("base64"),
|
|
468
477
|
paymentRequired,
|
|
469
|
-
challengeDigest
|
|
478
|
+
challengeDigest,
|
|
470
479
|
merchantReference,
|
|
471
480
|
};
|
|
472
481
|
return {
|
|
473
482
|
envelope,
|
|
474
483
|
requestDigest: digestJson(envelope),
|
|
475
|
-
challengeDigest
|
|
484
|
+
challengeDigest,
|
|
476
485
|
challenge: parsed.challenge,
|
|
477
486
|
sponsorshipExpiresAt: parsed.sponsorshipExpiresAt,
|
|
478
487
|
requirementDigests: new Set(
|