warpmetal 0.7.5 → 0.7.6

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.6
37
+ npm install --global @x402api/agent-wallet-cli@0.2.7
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.5 or newer.
61
+ artifact from the npm CLI and requires `warpmetal` CLI version 0.7.6 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.5",
3
+ "version": "0.7.6",
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.5` or newer for this
14
+ 1. Run `warpmetal --version` and require version `0.7.6` 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`.
@@ -69,7 +69,7 @@ retire that attempt and return a new `paymentAttemptId`. The CLI replaces the
69
69
  saved challenge and stale wallet-attempt metadata; use only the newly returned
70
70
  workflow.
71
71
 
72
- The current integration targets `@x402api/agent-wallet-cli@0.2.6`. A compatible
72
+ The current integration targets `@x402api/agent-wallet-cli@0.2.7`. A compatible
73
73
  live term is marked `agentWalletSupported: true` and must use the sponsored
74
74
  Base USDC or Solana USDC/USDT launch profile with buyer native fees disabled.
75
75
  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.6`. Do not add it as a WarpMetal dependency,
28
+ `@x402api/agent-wallet-cli@0.2.7`. 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
 
@@ -149,7 +149,7 @@ reservation or any buyer-funded, unsupported, or unbound alternative.
149
149
 
150
150
  x402api pays actual gas from its platform treasury. The merchant tenant's
151
151
  active sponsorship allowance controls admission, but actual gas is not a
152
- tenant debit. During the coordinated rollout, accept only the exact 0.2.6
152
+ tenant debit. During the coordinated rollout, accept only the exact 0.2.7
153
153
  platform-treasury declaration or the matched legacy tenant-credit declaration;
154
154
  never accept a mixed billing and final-charge policy.
155
155
 
@@ -191,12 +191,13 @@ current `termEndsAt` generation are different.
191
191
  terms and authorize only the new workflow; never reuse an expired envelope or
192
192
  artifact.
193
193
  - `payment_pending` or `payment_finalizing`: keep the same checkout bytes and
194
- artifact. `--wait` performs bounded retries with that exact authorization.
194
+ artifact. Preserve the returned durable `paymentId`; `--wait` records it in
195
+ private WarpMetal state and performs bounded retries with that exact authorization.
195
196
  - Timeout or process restart after authorization: use the saved x402api attempt
196
197
  ID and artifact. Reconcile with WarpMetal and reuse its submit argv. Never
197
198
  authorize again merely because submission is unknown.
198
- - Signed HTTP 402: the artifact was definitively rejected. Run the WarpMetal
199
- challenge flow again, inspect the replacement terms, and authorize one new
199
+ - Signed HTTP 402: the artifact was definitively rejected. Preserve its
200
+ `paymentId`, then run the WarpMetal challenge flow again, inspect the replacement terms, and authorize one new
200
201
  artifact only when the protocol and current interactive or autonomous
201
202
  payment authority allow it.
202
203
  - `sponsorship_allowance_unavailable`, `sponsorship_payment_cap_exceeded`,
@@ -115,9 +115,10 @@ Confirm the plan, hostname, OS, and public key before preparing it.
115
115
  replaces its saved challenge metadata. Re-evaluate the new terms before
116
116
  authorization; never sign or submit the expired challenge.
117
117
  - For `payment_pending`, retry the exact checkout body and the exact same
118
- payment artifact. Do not create a replacement payment.
119
- - A signed HTTP 402 rejects that signature. Use the new live challenge for one
120
- replacement authorization after checking the new terms against the current
118
+ payment artifact. Preserve the returned `paymentId` as the durable x402api
119
+ reconciliation key. Do not create a replacement payment.
120
+ - A signed HTTP 402 rejects that signature. Preserve its `paymentId`, request a
121
+ new live challenge, and create one replacement authorization after checking the new terms against the current
121
122
  interactive or autonomous payment authority.
122
123
  - Treat `sponsorship_allowance_unavailable`,
123
124
  `sponsorship_payment_cap_exceeded`,
package/src/cli.js CHANGED
@@ -379,13 +379,18 @@ async function pollGrant(
379
379
  }
380
380
  }
381
381
 
382
- function challengeResult(taskId, checkoutBody, response) {
382
+ function challengeResult(
383
+ taskId,
384
+ checkoutBody,
385
+ response,
386
+ { requireChallenge = true } = {},
387
+ ) {
383
388
  const paymentRequired = response.headers["payment-required"];
384
389
  const challengeHandle = response.headers["x-x402api-challenge-handle"];
385
- if (response.status === 402 && !paymentRequired) {
390
+ if (requireChallenge && response.status === 402 && !paymentRequired) {
386
391
  throw new CliError("WarpMetal returned HTTP 402 without PAYMENT-REQUIRED.");
387
392
  }
388
- if (response.status === 402 && !challengeHandle) {
393
+ if (requireChallenge && response.status === 402 && !challengeHandle) {
389
394
  throw new CliError(
390
395
  "WarpMetal returned HTTP 402 without X-X402API-Challenge-Handle.",
391
396
  );
@@ -736,12 +741,17 @@ async function handleCheckoutSubmit(client, store, options, context) {
736
741
  token,
737
742
  paymentSignature,
738
743
  });
744
+ if (response.data?.paymentId) {
745
+ await store.saveOrderPaymentId(taskId, response.data.paymentId);
746
+ }
739
747
  const safe = await attachPaymentWorkflow(
740
748
  client,
741
749
  store,
742
750
  taskId,
743
751
  order,
744
- challengeResult(taskId, order.checkoutBody, response),
752
+ challengeResult(taskId, order.checkoutBody, response, {
753
+ requireChallenge: false,
754
+ }),
745
755
  );
746
756
  const retryable =
747
757
  response.status === 202 &&
@@ -755,6 +765,7 @@ async function handleCheckoutSubmit(client, store, options, context) {
755
765
  const output = {
756
766
  ...safe,
757
767
  task: response.data?.task,
768
+ paymentId: response.data?.paymentId,
758
769
  message: response.data?.message,
759
770
  ...(walletPayment
760
771
  ? {
@@ -1327,6 +1338,9 @@ async function handleRenewalSubmit(client, store, options, context) {
1327
1338
  paymentSignature: walletPayment.paymentSignature,
1328
1339
  },
1329
1340
  );
1341
+ if (response.data?.paymentId) {
1342
+ await store.saveRenewalPaymentId(serverId, response.data.paymentId);
1343
+ }
1330
1344
  const retryable =
1331
1345
  response.status === 202 &&
1332
1346
  ["payment_pending", "payment_finalizing"].includes(response.data?.status);
@@ -1339,6 +1353,7 @@ async function handleRenewalSubmit(client, store, options, context) {
1339
1353
  status: response.data?.status,
1340
1354
  serverId,
1341
1355
  task: response.data?.task,
1356
+ paymentId: response.data?.paymentId,
1342
1357
  paymentAttemptId:
1343
1358
  response.data?.paymentAttemptId ||
1344
1359
  response.headers["x-warpmetal-payment-attempt"],
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.6";
18
+ export const AGENT_WALLET_VERSION = "0.2.7";
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";
package/src/state.js CHANGED
@@ -13,7 +13,10 @@ const WALLET_PAYMENT_STATE_FIELDS = [
13
13
  "walletPayerAddress",
14
14
  "paymentArtifactExpiresAt",
15
15
  "paymentArtifactSavedAt",
16
+ "gatewayPaymentId",
16
17
  ];
18
+ const X402API_PAYMENT_ID =
19
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
17
20
 
18
21
  function replaceChallenge(record, challenge) {
19
22
  const changed =
@@ -361,6 +364,50 @@ export class StateStore {
361
364
  });
362
365
  }
363
366
 
367
+ async saveOrderPaymentId(taskId, paymentId) {
368
+ if (!X402API_PAYMENT_ID.test(paymentId || "")) {
369
+ throw new CliError("WarpMetal returned an invalid x402api payment ID.", {
370
+ exitCode: 3,
371
+ });
372
+ }
373
+ await this.update((state) => {
374
+ const order = state.orders[taskId];
375
+ if (!order) {
376
+ throw new CliError(`No local order state exists for ${taskId}.`, {
377
+ exitCode: 2,
378
+ });
379
+ }
380
+ if (order.gatewayPaymentId && order.gatewayPaymentId !== paymentId) {
381
+ throw new CliError("WarpMetal changed the x402api payment ID for this attempt.", {
382
+ exitCode: 3,
383
+ });
384
+ }
385
+ order.gatewayPaymentId = paymentId;
386
+ });
387
+ }
388
+
389
+ async saveRenewalPaymentId(serverId, paymentId) {
390
+ if (!X402API_PAYMENT_ID.test(paymentId || "")) {
391
+ throw new CliError("WarpMetal returned an invalid x402api payment ID.", {
392
+ exitCode: 3,
393
+ });
394
+ }
395
+ await this.update((state) => {
396
+ const renewal = state.renewals[serverId];
397
+ if (!renewal) {
398
+ throw new CliError(`No local renewal state exists for ${serverId}.`, {
399
+ exitCode: 2,
400
+ });
401
+ }
402
+ if (renewal.gatewayPaymentId && renewal.gatewayPaymentId !== paymentId) {
403
+ throw new CliError("WarpMetal changed the x402api payment ID for this renewal.", {
404
+ exitCode: 3,
405
+ });
406
+ }
407
+ renewal.gatewayPaymentId = paymentId;
408
+ });
409
+ }
410
+
364
411
  async saveAccessToken(serverId, accessToken, expiresAt) {
365
412
  await this.update((state) => {
366
413
  const server = state.servers[serverId] || { serverId };
@@ -488,6 +535,7 @@ export class StateStore {
488
535
  serverId: order.serverId,
489
536
  planId: order.planId,
490
537
  paymentAttemptId: order.paymentAttemptId,
538
+ paymentId: order.gatewayPaymentId,
491
539
  challengeHandle: order.challengeHandle,
492
540
  walletPaymentAttemptId: order.walletPaymentAttemptId,
493
541
  walletName: order.walletName,
@@ -523,6 +571,7 @@ export class StateStore {
523
571
  refillTargetAtomic: renewal.refillTargetAtomic,
524
572
  policy: renewal.policy,
525
573
  paymentAttemptId: renewal.paymentAttemptId,
574
+ paymentId: renewal.gatewayPaymentId,
526
575
  challengeHandle: renewal.challengeHandle,
527
576
  paymentArtifactExpiresAt: renewal.paymentArtifactExpiresAt,
528
577
  })),