warpmetal 0.7.5 → 0.7.7
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 +2 -2
- package/package.json +1 -1
- package/skills/warpmetal/SKILL.md +20 -5
- package/skills/warpmetal/references/cli-reference.md +1 -1
- package/skills/warpmetal/references/payments.md +16 -8
- package/skills/warpmetal/references/renewals.md +3 -1
- package/skills/warpmetal/references/safety.md +12 -6
- package/src/cli.js +50 -5
- package/src/payment.js +1 -1
- package/src/state.js +152 -0
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.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.
|
|
61
|
+
artifact from the npm CLI and requires `warpmetal` CLI version 0.7.7 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.7` 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`.
|
|
@@ -136,12 +136,22 @@ WarpMetal verifies that the artifact matches the exact saved request and an
|
|
|
136
136
|
advertised sponsored requirement, gas reservation, resource, extensions, and
|
|
137
137
|
buyer payment identifier before sending its signature. The legacy
|
|
138
138
|
`--payment-signature-file` input remains available for another compatible
|
|
139
|
-
external signer. If a signed request returns
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
external signer. If a signed request returns `payment_rejected`, preserve its
|
|
140
|
+
safe `errorCode` and `requestId` diagnostics and inspect `replacementAllowed`.
|
|
141
|
+
When it is true, run the unsigned challenge flow again and re-evaluate payment
|
|
142
|
+
authority before creating the one permitted replacement authorization. When it
|
|
143
|
+
is false or absent, stop without signing again. On `manual_review` or an
|
|
143
144
|
ambiguous attempt, stop and never create another payment.
|
|
144
145
|
|
|
146
|
+
WarpMetal continues read-only backend reconciliation for
|
|
147
|
+
`manual_review/stale_payment_outcome`; this is not a client bypass. Re-read the
|
|
148
|
+
task on a later autonomous cycle. Only when it becomes `expired` with
|
|
149
|
+
`failure.code` equal to `payment_expired_unsettled`, `paidAt` null, and
|
|
150
|
+
`retrySafe` true has WarpMetal proved that the prior charge expired unpaid. An
|
|
151
|
+
autonomous workflow may then prepare a new order if the original intent and
|
|
152
|
+
standing authority still apply. Never reuse the old signature or infer safety
|
|
153
|
+
from wallet balance, timeout, `paidAt`, `expired`, or `manual_review` alone.
|
|
154
|
+
|
|
145
155
|
## Renew autonomously within policy
|
|
146
156
|
|
|
147
157
|
Configure a bounded server policy before unattended renewal. Require a
|
|
@@ -178,6 +188,11 @@ Poll a prepared or paid order with:
|
|
|
178
188
|
warpmetal order status --task <taskId> --wait --json
|
|
179
189
|
```
|
|
180
190
|
|
|
191
|
+
`manual_review` remains terminal for payment and mutation attempts. A later
|
|
192
|
+
read-only status check may observe the backend's authoritative reconciliation;
|
|
193
|
+
prepare a replacement only for the exact `retrySafe: true` result described
|
|
194
|
+
above.
|
|
195
|
+
|
|
181
196
|
After initial provisioning and every OS reload, WarpMetal reapplies the same
|
|
182
197
|
key-only policy: `PasswordAuthentication no`,
|
|
183
198
|
`KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`, and
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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,14 +191,17 @@ 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.
|
|
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.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
- Signed HTTP 402: the artifact was definitively rejected. A `paymentId` may be
|
|
200
|
+
absent because settlement never began; preserve the safe `errorCode`,
|
|
201
|
+
`requestId`, and `replacementAllowed` result instead. Only when
|
|
202
|
+
`replacementAllowed` is true may you run the unsigned WarpMetal challenge
|
|
203
|
+
flow again, inspect the replacement terms, and authorize the one permitted
|
|
204
|
+
replacement artifact. When false or absent, stop without signing again.
|
|
202
205
|
- `sponsorship_allowance_unavailable`, `sponsorship_payment_cap_exceeded`,
|
|
203
206
|
`sponsorship_payment_allowance_exhausted`,
|
|
204
207
|
`sponsorship_volume_allowance_exhausted`, or
|
|
@@ -206,7 +209,12 @@ current `termEndsAt` generation are different.
|
|
|
206
209
|
terminal. The merchant tenant must restore or change its allowance and issue
|
|
207
210
|
a fresh challenge; never retry the artifact or fall back to buyer-funded gas.
|
|
208
211
|
- `manual_review`: payment or fulfillment may be final. Stop all payment and
|
|
209
|
-
mutation retries.
|
|
212
|
+
mutation retries. WarpMetal may continue read-only backend reconciliation for
|
|
213
|
+
`stale_payment_outcome`; check status on a later autonomous cycle without
|
|
214
|
+
submitting a payment. Only `expired` plus
|
|
215
|
+
`failure.code: payment_expired_unsettled`, `paidAt: null`, and
|
|
216
|
+
`retrySafe: true` proves the old charge expired unpaid and permits preparing
|
|
217
|
+
a new order under current standing authority. Never reuse the old signature.
|
|
210
218
|
- Expired or corrupt signed artifact, changed request digest, unexpected recipient,
|
|
211
219
|
unsupported network, asset, profile, sponsorship error, or request-binding
|
|
212
220
|
mismatch: stop instead of falling back or asking the buyer to fund ETH/SOL.
|
|
@@ -127,7 +127,9 @@ generated QR encoding only that address.
|
|
|
127
127
|
## Stop and recovery states
|
|
128
128
|
|
|
129
129
|
- Insufficient balance: notify once, stop, and recheck later.
|
|
130
|
-
- Signed HTTP 402:
|
|
130
|
+
- Signed HTTP 402: preserve the safe rejection diagnostics. Re-run prepare and
|
|
131
|
+
re-evaluate one replacement challenge only when `replacementAllowed` is true;
|
|
132
|
+
stop when false or absent.
|
|
131
133
|
- Pending or timeout after authorization: reuse the same artifact; do not sign again.
|
|
132
134
|
- Price or payment rail outside policy: stop for deliberate policy revision.
|
|
133
135
|
- Exhausted count, horizon, or cumulative budget: stop for deliberate policy revision.
|
|
@@ -115,10 +115,13 @@ 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.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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. A `paymentId` can be absent;
|
|
121
|
+
preserve the safe `errorCode`, `requestId`, and `replacementAllowed` result.
|
|
122
|
+
Request a new live challenge and create the one permitted replacement only
|
|
123
|
+
when `replacementAllowed` is true and the new terms remain within current
|
|
124
|
+
interactive or autonomous payment authority. When false or absent, stop.
|
|
122
125
|
- Treat `sponsorship_allowance_unavailable`,
|
|
123
126
|
`sponsorship_payment_cap_exceeded`,
|
|
124
127
|
`sponsorship_payment_allowance_exhausted`,
|
|
@@ -130,8 +133,11 @@ Confirm the plan, hostname, OS, and public key before preparing it.
|
|
|
130
133
|
attempt and artifact through WarpMetal. Never authorize again merely because
|
|
131
134
|
submission or fulfillment is uncertain, and never send the private owner
|
|
132
135
|
token through `x402api pay`, `payment submit`, or `payment reconcile`.
|
|
133
|
-
- Treat `manual_review` as terminal
|
|
134
|
-
mutation.
|
|
136
|
+
- Treat `manual_review` as terminal for payments and mutations. Do not pay
|
|
137
|
+
again and do not repeat the mutation. A later read-only status check may
|
|
138
|
+
observe automatic reconciliation of `stale_payment_outcome`; only the exact
|
|
139
|
+
`expired/payment_expired_unsettled` result with `paidAt: null` and
|
|
140
|
+
`retrySafe: true` permits preparing a new order.
|
|
135
141
|
- Treat HTTP 202 as accepted or pending, never as proof of success. Poll the
|
|
136
142
|
returned task or operation until a documented terminal state.
|
|
137
143
|
- Temporary sandbox expiry is a local hard deadline. Stop, restart, and
|
package/src/cli.js
CHANGED
|
@@ -379,19 +379,26 @@ async function pollGrant(
|
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
function challengeResult(
|
|
382
|
+
function challengeResult(
|
|
383
|
+
taskId,
|
|
384
|
+
checkoutBody,
|
|
385
|
+
response,
|
|
386
|
+
{ requireChallenge = true } = {},
|
|
387
|
+
) {
|
|
388
|
+
const status = response.data?.status;
|
|
389
|
+
const rejected = response.status === 402 && status === "payment_rejected";
|
|
383
390
|
const paymentRequired = response.headers["payment-required"];
|
|
384
391
|
const challengeHandle = response.headers["x-x402api-challenge-handle"];
|
|
385
|
-
if (response.status === 402 && !paymentRequired) {
|
|
392
|
+
if (requireChallenge && response.status === 402 && !paymentRequired) {
|
|
386
393
|
throw new CliError("WarpMetal returned HTTP 402 without PAYMENT-REQUIRED.");
|
|
387
394
|
}
|
|
388
|
-
if (response.status === 402 && !challengeHandle) {
|
|
395
|
+
if (requireChallenge && response.status === 402 && !challengeHandle) {
|
|
389
396
|
throw new CliError(
|
|
390
397
|
"WarpMetal returned HTTP 402 without X-X402API-Challenge-Handle.",
|
|
391
398
|
);
|
|
392
399
|
}
|
|
393
400
|
return {
|
|
394
|
-
status
|
|
401
|
+
status,
|
|
395
402
|
taskId,
|
|
396
403
|
paymentAttemptId:
|
|
397
404
|
response.data?.paymentAttemptId ||
|
|
@@ -399,6 +406,13 @@ function challengeResult(taskId, checkoutBody, response) {
|
|
|
399
406
|
paymentRequired,
|
|
400
407
|
challengeHandle,
|
|
401
408
|
checkoutBodySha256: createHash("sha256").update(checkoutBody).digest("hex"),
|
|
409
|
+
...(rejected
|
|
410
|
+
? {
|
|
411
|
+
errorCode: response.data?.errorCode,
|
|
412
|
+
requestId: response.data?.requestId,
|
|
413
|
+
replacementAllowed: response.data?.replacementAllowed,
|
|
414
|
+
}
|
|
415
|
+
: {}),
|
|
402
416
|
};
|
|
403
417
|
}
|
|
404
418
|
|
|
@@ -736,13 +750,26 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
736
750
|
token,
|
|
737
751
|
paymentSignature,
|
|
738
752
|
});
|
|
753
|
+
if (response.data?.paymentId) {
|
|
754
|
+
await store.saveOrderPaymentId(taskId, response.data.paymentId);
|
|
755
|
+
}
|
|
739
756
|
const safe = await attachPaymentWorkflow(
|
|
740
757
|
client,
|
|
741
758
|
store,
|
|
742
759
|
taskId,
|
|
743
760
|
order,
|
|
744
|
-
challengeResult(taskId, order.checkoutBody, response
|
|
761
|
+
challengeResult(taskId, order.checkoutBody, response, {
|
|
762
|
+
requireChallenge: false,
|
|
763
|
+
}),
|
|
745
764
|
);
|
|
765
|
+
if (response.status === 402 && safe.status === "payment_rejected") {
|
|
766
|
+
await store.saveOrderPaymentRejection(taskId, {
|
|
767
|
+
paymentAttemptId: safe.paymentAttemptId,
|
|
768
|
+
errorCode: safe.errorCode,
|
|
769
|
+
requestId: safe.requestId,
|
|
770
|
+
replacementAllowed: safe.replacementAllowed,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
746
773
|
const retryable =
|
|
747
774
|
response.status === 202 &&
|
|
748
775
|
["payment_pending", "payment_finalizing"].includes(response.data?.status);
|
|
@@ -755,6 +782,7 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
755
782
|
const output = {
|
|
756
783
|
...safe,
|
|
757
784
|
task: response.data?.task,
|
|
785
|
+
paymentId: response.data?.paymentId,
|
|
758
786
|
message: response.data?.message,
|
|
759
787
|
...(walletPayment
|
|
760
788
|
? {
|
|
@@ -1327,6 +1355,19 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1327
1355
|
paymentSignature: walletPayment.paymentSignature,
|
|
1328
1356
|
},
|
|
1329
1357
|
);
|
|
1358
|
+
if (response.data?.paymentId) {
|
|
1359
|
+
await store.saveRenewalPaymentId(serverId, response.data.paymentId);
|
|
1360
|
+
}
|
|
1361
|
+
if (response.status === 402 && response.data?.status === "payment_rejected") {
|
|
1362
|
+
await store.saveRenewalPaymentRejection(serverId, {
|
|
1363
|
+
paymentAttemptId:
|
|
1364
|
+
response.data?.paymentAttemptId ||
|
|
1365
|
+
response.headers["x-warpmetal-payment-attempt"],
|
|
1366
|
+
errorCode: response.data?.errorCode,
|
|
1367
|
+
requestId: response.data?.requestId,
|
|
1368
|
+
replacementAllowed: response.data?.replacementAllowed,
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1330
1371
|
const retryable =
|
|
1331
1372
|
response.status === 202 &&
|
|
1332
1373
|
["payment_pending", "payment_finalizing"].includes(response.data?.status);
|
|
@@ -1339,9 +1380,13 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1339
1380
|
status: response.data?.status,
|
|
1340
1381
|
serverId,
|
|
1341
1382
|
task: response.data?.task,
|
|
1383
|
+
paymentId: response.data?.paymentId,
|
|
1342
1384
|
paymentAttemptId:
|
|
1343
1385
|
response.data?.paymentAttemptId ||
|
|
1344
1386
|
response.headers["x-warpmetal-payment-attempt"],
|
|
1387
|
+
errorCode: response.data?.errorCode,
|
|
1388
|
+
requestId: response.data?.requestId,
|
|
1389
|
+
replacementAllowed: response.data?.replacementAllowed,
|
|
1345
1390
|
walletPayment: {
|
|
1346
1391
|
attemptId: walletPayment.attemptId,
|
|
1347
1392
|
wallet: walletPayment.wallet,
|
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.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,61 @@ const WALLET_PAYMENT_STATE_FIELDS = [
|
|
|
13
13
|
"walletPayerAddress",
|
|
14
14
|
"paymentArtifactExpiresAt",
|
|
15
15
|
"paymentArtifactSavedAt",
|
|
16
|
+
"gatewayPaymentId",
|
|
17
|
+
"rejectedPaymentAttemptId",
|
|
18
|
+
"paymentRejectionErrorCode",
|
|
19
|
+
"paymentRejectionRequestId",
|
|
20
|
+
"paymentReplacementAllowed",
|
|
21
|
+
"paymentRejectedAt",
|
|
16
22
|
];
|
|
23
|
+
const X402API_PAYMENT_ID =
|
|
24
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
25
|
+
const SAFE_PROTOCOL_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
26
|
+
|
|
27
|
+
function validatedPaymentRejection({
|
|
28
|
+
paymentAttemptId,
|
|
29
|
+
errorCode,
|
|
30
|
+
requestId,
|
|
31
|
+
replacementAllowed,
|
|
32
|
+
}) {
|
|
33
|
+
if (
|
|
34
|
+
typeof paymentAttemptId !== "string" ||
|
|
35
|
+
!SAFE_PROTOCOL_IDENTIFIER.test(paymentAttemptId)
|
|
36
|
+
) {
|
|
37
|
+
throw new CliError("WarpMetal returned an invalid payment-attempt ID.", {
|
|
38
|
+
exitCode: 3,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
for (const [label, value] of [
|
|
42
|
+
["payment rejection error code", errorCode],
|
|
43
|
+
["payment rejection request ID", requestId],
|
|
44
|
+
]) {
|
|
45
|
+
if (
|
|
46
|
+
value !== undefined &&
|
|
47
|
+
(typeof value !== "string" || !SAFE_PROTOCOL_IDENTIFIER.test(value))
|
|
48
|
+
) {
|
|
49
|
+
throw new CliError(`WarpMetal returned an invalid ${label}.`, {
|
|
50
|
+
exitCode: 3,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
replacementAllowed !== undefined &&
|
|
56
|
+
typeof replacementAllowed !== "boolean"
|
|
57
|
+
) {
|
|
58
|
+
throw new CliError(
|
|
59
|
+
"WarpMetal returned an invalid payment replacement decision.",
|
|
60
|
+
{ exitCode: 3 },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
rejectedPaymentAttemptId: paymentAttemptId,
|
|
65
|
+
paymentRejectionErrorCode: errorCode,
|
|
66
|
+
paymentRejectionRequestId: requestId,
|
|
67
|
+
paymentReplacementAllowed: replacementAllowed,
|
|
68
|
+
paymentRejectedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
17
71
|
|
|
18
72
|
function replaceChallenge(record, challenge) {
|
|
19
73
|
const changed =
|
|
@@ -361,6 +415,94 @@ export class StateStore {
|
|
|
361
415
|
});
|
|
362
416
|
}
|
|
363
417
|
|
|
418
|
+
async saveOrderPaymentId(taskId, paymentId) {
|
|
419
|
+
if (!X402API_PAYMENT_ID.test(paymentId || "")) {
|
|
420
|
+
throw new CliError("WarpMetal returned an invalid x402api payment ID.", {
|
|
421
|
+
exitCode: 3,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
await this.update((state) => {
|
|
425
|
+
const order = state.orders[taskId];
|
|
426
|
+
if (!order) {
|
|
427
|
+
throw new CliError(`No local order state exists for ${taskId}.`, {
|
|
428
|
+
exitCode: 2,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (order.gatewayPaymentId && order.gatewayPaymentId !== paymentId) {
|
|
432
|
+
throw new CliError("WarpMetal changed the x402api payment ID for this attempt.", {
|
|
433
|
+
exitCode: 3,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
order.gatewayPaymentId = paymentId;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async saveOrderPaymentRejection(taskId, rejection) {
|
|
441
|
+
const safe = validatedPaymentRejection(rejection);
|
|
442
|
+
await this.update((state) => {
|
|
443
|
+
const order = state.orders[taskId];
|
|
444
|
+
if (!order) {
|
|
445
|
+
throw new CliError(`No local order state exists for ${taskId}.`, {
|
|
446
|
+
exitCode: 2,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (
|
|
450
|
+
order.paymentAttemptId &&
|
|
451
|
+
order.paymentAttemptId !== safe.rejectedPaymentAttemptId
|
|
452
|
+
) {
|
|
453
|
+
throw new CliError(
|
|
454
|
+
"WarpMetal changed the payment-attempt ID for this authorization.",
|
|
455
|
+
{ exitCode: 3 },
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
Object.assign(order, safe);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async saveRenewalPaymentId(serverId, paymentId) {
|
|
463
|
+
if (!X402API_PAYMENT_ID.test(paymentId || "")) {
|
|
464
|
+
throw new CliError("WarpMetal returned an invalid x402api payment ID.", {
|
|
465
|
+
exitCode: 3,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
await this.update((state) => {
|
|
469
|
+
const renewal = state.renewals[serverId];
|
|
470
|
+
if (!renewal) {
|
|
471
|
+
throw new CliError(`No local renewal state exists for ${serverId}.`, {
|
|
472
|
+
exitCode: 2,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (renewal.gatewayPaymentId && renewal.gatewayPaymentId !== paymentId) {
|
|
476
|
+
throw new CliError("WarpMetal changed the x402api payment ID for this renewal.", {
|
|
477
|
+
exitCode: 3,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
renewal.gatewayPaymentId = paymentId;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async saveRenewalPaymentRejection(serverId, rejection) {
|
|
485
|
+
const safe = validatedPaymentRejection(rejection);
|
|
486
|
+
await this.update((state) => {
|
|
487
|
+
const renewal = state.renewals[serverId];
|
|
488
|
+
if (!renewal) {
|
|
489
|
+
throw new CliError(`No local renewal state exists for ${serverId}.`, {
|
|
490
|
+
exitCode: 2,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (
|
|
494
|
+
renewal.paymentAttemptId &&
|
|
495
|
+
renewal.paymentAttemptId !== safe.rejectedPaymentAttemptId
|
|
496
|
+
) {
|
|
497
|
+
throw new CliError(
|
|
498
|
+
"WarpMetal changed the payment-attempt ID for this renewal authorization.",
|
|
499
|
+
{ exitCode: 3 },
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
Object.assign(renewal, safe);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
364
506
|
async saveAccessToken(serverId, accessToken, expiresAt) {
|
|
365
507
|
await this.update((state) => {
|
|
366
508
|
const server = state.servers[serverId] || { serverId };
|
|
@@ -488,11 +630,16 @@ export class StateStore {
|
|
|
488
630
|
serverId: order.serverId,
|
|
489
631
|
planId: order.planId,
|
|
490
632
|
paymentAttemptId: order.paymentAttemptId,
|
|
633
|
+
paymentId: order.gatewayPaymentId,
|
|
491
634
|
challengeHandle: order.challengeHandle,
|
|
492
635
|
walletPaymentAttemptId: order.walletPaymentAttemptId,
|
|
493
636
|
walletName: order.walletName,
|
|
494
637
|
payerAddress: order.walletPayerAddress,
|
|
495
638
|
paymentArtifactExpiresAt: order.paymentArtifactExpiresAt,
|
|
639
|
+
rejectedPaymentAttemptId: order.rejectedPaymentAttemptId,
|
|
640
|
+
paymentRejectionErrorCode: order.paymentRejectionErrorCode,
|
|
641
|
+
paymentRejectionRequestId: order.paymentRejectionRequestId,
|
|
642
|
+
paymentReplacementAllowed: order.paymentReplacementAllowed,
|
|
496
643
|
credentialStored: Boolean(order.ownerToken),
|
|
497
644
|
})),
|
|
498
645
|
servers: Object.values(state.servers).map((server) => ({
|
|
@@ -523,8 +670,13 @@ export class StateStore {
|
|
|
523
670
|
refillTargetAtomic: renewal.refillTargetAtomic,
|
|
524
671
|
policy: renewal.policy,
|
|
525
672
|
paymentAttemptId: renewal.paymentAttemptId,
|
|
673
|
+
paymentId: renewal.gatewayPaymentId,
|
|
526
674
|
challengeHandle: renewal.challengeHandle,
|
|
527
675
|
paymentArtifactExpiresAt: renewal.paymentArtifactExpiresAt,
|
|
676
|
+
rejectedPaymentAttemptId: renewal.rejectedPaymentAttemptId,
|
|
677
|
+
paymentRejectionErrorCode: renewal.paymentRejectionErrorCode,
|
|
678
|
+
paymentRejectionRequestId: renewal.paymentRejectionRequestId,
|
|
679
|
+
paymentReplacementAllowed: renewal.paymentReplacementAllowed,
|
|
528
680
|
})),
|
|
529
681
|
};
|
|
530
682
|
}
|