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.7
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.7 or newer.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "warpmetal",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "Agent-safe CLI and skill for purchasing, renewing, and managing WarpMetal VPS servers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.7` or newer for this
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.7`. A compatible
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.7`. Do not add it as a WarpMetal dependency,
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.7
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.7";
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
- challengeDigest: digestJson(parsed),
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: parsed.challengeDigest,
478
+ challengeDigest,
470
479
  merchantReference,
471
480
  };
472
481
  return {
473
482
  envelope,
474
483
  requestDigest: digestJson(envelope),
475
- challengeDigest: parsed.challengeDigest,
484
+ challengeDigest,
476
485
  challenge: parsed.challenge,
477
486
  sponsorshipExpiresAt: parsed.sponsorshipExpiresAt,
478
487
  requirementDigests: new Set(