warpmetal 0.8.0 → 0.8.2
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 +14 -3
- package/package.json +1 -1
- package/skills/warpmetal/SKILL.md +10 -2
- package/skills/warpmetal/references/cli-reference.md +8 -2
- package/skills/warpmetal/references/payments.md +14 -6
- package/skills/warpmetal/references/renewals.md +2 -1
- package/skills/warpmetal/references/runtime.md +4 -1
- package/skills/warpmetal/references/safety.md +3 -0
- package/src/cli.js +54 -6
- package/src/payment.js +4 -1
- package/src/state.js +80 -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.9
|
|
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.
|
|
61
|
+
artifact from the npm CLI and requires `warpmetal` CLI version 0.8.1 or newer.
|
|
62
62
|
|
|
63
63
|
To test the repository marketplace after the plugin lands on `main`:
|
|
64
64
|
|
|
@@ -98,6 +98,11 @@ checkout returns `serverId`, the CLI binds that ID to the identity so
|
|
|
98
98
|
an `--identity` flag. Use `--ssh-public-key-file` instead when supplying a
|
|
99
99
|
user-managed public key.
|
|
100
100
|
|
|
101
|
+
WarpMetal installs the owner key for `root` on every supported image. The SSH
|
|
102
|
+
key does not encode a login username: use `root@<server-ip>` for an owner shell
|
|
103
|
+
and `--ssh-user root` for runtime installation, not a distribution convention
|
|
104
|
+
such as `ubuntu`.
|
|
105
|
+
|
|
101
106
|
Pass `--json` for structured, secret-redacted output. Use
|
|
102
107
|
`WARPMETAL_API_URL` for an alternate API origin and `WARPMETAL_HOME` for an
|
|
103
108
|
alternate state directory.
|
|
@@ -130,6 +135,12 @@ external-recipient extension bindings to match the advertised alternatives
|
|
|
130
135
|
exactly, then preserves the chosen authorization artifact byte-for-byte for
|
|
131
136
|
every pending or ambiguous retry.
|
|
132
137
|
|
|
138
|
+
Submission output includes the server-validated `paymentId`, `confirmed`, and
|
|
139
|
+
`finalized` fields when WarpMetal supplies lifecycle evidence. Retry only while
|
|
140
|
+
confirmation is pending. As soon as `confirmed` is true, stop all payment
|
|
141
|
+
submission—even when `finalized` is false—and let WarpMetal continue signed-
|
|
142
|
+
receipt finality while provisioning or renewal proceeds.
|
|
143
|
+
|
|
133
144
|
- When a human is actively chatting with the agent, show the exact live terms
|
|
134
145
|
and ask for confirmation immediately before authorizing and submitting.
|
|
135
146
|
- In an unattended run, a pre-funded dedicated wallet is standing spend
|
|
@@ -298,7 +309,7 @@ compatible external signer.
|
|
|
298
309
|
warpmetal runtime enable --server <serverId> --json
|
|
299
310
|
warpmetal runtime install \
|
|
300
311
|
--server <serverId> \
|
|
301
|
-
--ssh-user
|
|
312
|
+
--ssh-user root \
|
|
302
313
|
--confirm INSTALL \
|
|
303
314
|
--wait \
|
|
304
315
|
--json
|
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.
|
|
14
|
+
1. Run `warpmetal --version` and require version `0.8.1` 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`.
|
|
@@ -178,7 +178,8 @@ SSH-authorized human recipients;
|
|
|
178
178
|
never add an email address to it. Never make a partial payment.
|
|
179
179
|
|
|
180
180
|
After funding, prepare again, authorize exactly once, submit with WarpMetal,
|
|
181
|
-
and
|
|
181
|
+
and require `confirmed: true` before accepting the returned `termEndsAt`.
|
|
182
|
+
Stop payment submission at confirmation even when `finalized` is false. On `reconcile_pending` or
|
|
182
183
|
`manual_review`, do not create another authorization.
|
|
183
184
|
|
|
184
185
|
## Provision and manage
|
|
@@ -208,6 +209,13 @@ key-only policy: `PasswordAuthentication no`,
|
|
|
208
209
|
`KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`, and
|
|
209
210
|
`AuthenticationMethods publickey`.
|
|
210
211
|
|
|
212
|
+
The WarpMetal owner SSH account is `root` on every supported image. SSH public
|
|
213
|
+
keys do not bind a login username. Use `root@<server-ip>` for an owner shell and
|
|
214
|
+
pass `--ssh-user root` whenever a CLI command asks for the owner SSH account;
|
|
215
|
+
never infer `ubuntu` or another distribution-default username. This does not
|
|
216
|
+
apply to `sandbox connect`, which uses the gateway username from its connection
|
|
217
|
+
profile.
|
|
218
|
+
|
|
211
219
|
For routine management, prove possession of the installed SSH key without
|
|
212
220
|
reading it:
|
|
213
221
|
|
|
@@ -72,7 +72,7 @@ retire that attempt and return a new `paymentAttemptId`. The CLI replaces the
|
|
|
72
72
|
saved challenge and stale wallet-attempt metadata; use only the newly returned
|
|
73
73
|
workflow.
|
|
74
74
|
|
|
75
|
-
The current integration targets `@x402api/agent-wallet-cli@0.2.
|
|
75
|
+
The current integration targets `@x402api/agent-wallet-cli@0.2.9`. A compatible
|
|
76
76
|
live term is marked `agentWalletSupported: true` and must use the sponsored
|
|
77
77
|
Base USDC or Solana USDC/USDT launch profile with buyer native fees disabled.
|
|
78
78
|
For the current declaration, x402api pays actual gas from its platform treasury
|
|
@@ -83,6 +83,12 @@ term. Because the checkout is authenticated, do not substitute `x402api pay`,
|
|
|
83
83
|
`payment submit`, or `payment reconcile` for the returned WarpMetal submission
|
|
84
84
|
command.
|
|
85
85
|
|
|
86
|
+
`warpmetal checkout submit` and `warpmetal renewal submit` surface
|
|
87
|
+
`paymentId`, `confirmed`, and `finalized` from WarpMetal and persist those safe
|
|
88
|
+
lifecycle fields in private CLI state. `confirmed: true` is terminal for
|
|
89
|
+
payment submission and starts the business operation; `finalized: false` only
|
|
90
|
+
means signed-receipt reconciliation continues asynchronously.
|
|
91
|
+
|
|
86
92
|
`--payment-artifact` accepts the owner-only JSON artifact produced by a
|
|
87
93
|
compatible pinned x402api Agent Wallet release. WarpMetal validates its request
|
|
88
94
|
and payment-requirement digests, resource, extensions, buyer payment identifier,
|
|
@@ -196,7 +202,7 @@ process listings.
|
|
|
196
202
|
warpmetal runtime enable --server <serverId> [--idempotency-key <key>] --json
|
|
197
203
|
warpmetal runtime get --server <serverId> [--wait] [--timeout-seconds <n>] --json
|
|
198
204
|
warpmetal runtime install \
|
|
199
|
-
--server <serverId> [--identity <owner-key>] --ssh-user
|
|
205
|
+
--server <serverId> [--identity <owner-key>] --ssh-user root \
|
|
200
206
|
--confirm INSTALL [--wait] [--timeout-seconds <n>] --json
|
|
201
207
|
|
|
202
208
|
warpmetal sandbox create \
|
|
@@ -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.9`. 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
|
|
|
@@ -39,7 +39,9 @@ skill automatically.
|
|
|
39
39
|
|
|
40
40
|
1. Install the exact `paymentWorkflow.signerPackage.install.argv` under Node
|
|
41
41
|
22. Run `command -v x402api` and the returned
|
|
42
|
-
`paymentWorkflow.signerContract.probe.argv`. Require contract version 1
|
|
42
|
+
`paymentWorkflow.signerContract.probe.argv`. Require contract version 1 and
|
|
43
|
+
every entry in `paymentWorkflow.signerContract.requiredCommands` to appear
|
|
44
|
+
in the probe's `commands` array.
|
|
43
45
|
2. Run the exact `paymentWorkflow.walletWorkflow.setup.argv`. The idempotent
|
|
44
46
|
`x402api wallet setup --json` command creates an owner-only managed unlock
|
|
45
47
|
file inside the x402api home directory and never prints its generated
|
|
@@ -152,7 +154,7 @@ reservation or any buyer-funded, unsupported, or unbound alternative.
|
|
|
152
154
|
|
|
153
155
|
x402api pays actual gas from its platform treasury. The merchant tenant's
|
|
154
156
|
active sponsorship allowance controls admission, but actual gas is not a
|
|
155
|
-
tenant debit. During the coordinated rollout, accept only the exact 0.2.
|
|
157
|
+
tenant debit. During the coordinated rollout, accept only the exact 0.2.9
|
|
156
158
|
platform-treasury declaration or the matched legacy tenant-credit declaration;
|
|
157
159
|
never accept a mixed billing and final-charge policy.
|
|
158
160
|
|
|
@@ -193,9 +195,15 @@ current `termEndsAt` generation are different.
|
|
|
193
195
|
wallet-attempt metadata when it saves the new challenge. Inspect the new
|
|
194
196
|
terms and authorize only the new workflow; never reuse an expired envelope or
|
|
195
197
|
artifact.
|
|
196
|
-
- `payment_pending
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
- `payment_pending`, or legacy `payment_finalizing`, without
|
|
199
|
+
`confirmed: true`: keep the same checkout bytes and artifact. Preserve the
|
|
200
|
+
returned durable `paymentId`; `--wait` records it in private WarpMetal state
|
|
201
|
+
and performs bounded retries with that exact authorization.
|
|
202
|
+
- `confirmed: true`: stop payment submission immediately, including when
|
|
203
|
+
`finalized` is false or a legacy pending-like status is present. WarpMetal
|
|
204
|
+
has admitted provisioning or renewal exactly once and will reconcile payment
|
|
205
|
+
detail plus the signed final receipt asynchronously. Never authorize a
|
|
206
|
+
replacement because receipt finality is pending.
|
|
199
207
|
- Timeout or process restart after authorization: use the saved x402api attempt
|
|
200
208
|
ID and artifact. Reconcile with WarpMetal and reuse its submit argv. Never
|
|
201
209
|
authorize again merely because submission is unknown.
|
|
@@ -59,7 +59,8 @@ The CLI does not install a daemon. Treat actions as follows:
|
|
|
59
59
|
- `refill_required`: follow the signed refill workflow and stop.
|
|
60
60
|
- `reconcile_pending`: preserve the artifact and reconcile; never authorize again.
|
|
61
61
|
- `manual_review`: stop all payment and mutation retries.
|
|
62
|
-
- `renewed`: verify the new `termEndsAt` and stop
|
|
62
|
+
- `renewed` with `confirmed: true`: verify the new `termEndsAt` and stop
|
|
63
|
+
payment submission successfully, even when `finalized` is false.
|
|
63
64
|
|
|
64
65
|
## Authorize and submit renewal
|
|
65
66
|
|
|
@@ -48,12 +48,15 @@ the existing separate interactive-or-autonomous payment authority check.
|
|
|
48
48
|
|
|
49
49
|
For an existing ready server:
|
|
50
50
|
|
|
51
|
+
Use the WarpMetal owner account `root` for every supported host image; do not
|
|
52
|
+
substitute a distribution-default account such as `ubuntu`.
|
|
53
|
+
|
|
51
54
|
```sh
|
|
52
55
|
warpmetal runtime enable --server <serverId> --json
|
|
53
56
|
warpmetal runtime install \
|
|
54
57
|
--server <serverId> \
|
|
55
58
|
--identity <owner-private-key-path> \
|
|
56
|
-
--ssh-user
|
|
59
|
+
--ssh-user root \
|
|
57
60
|
--confirm INSTALL \
|
|
58
61
|
--wait \
|
|
59
62
|
--json
|
|
@@ -117,6 +117,9 @@ Confirm the plan, hostname, OS, and public key before preparing it.
|
|
|
117
117
|
- For `payment_pending`, retry the exact checkout body and the exact same
|
|
118
118
|
payment artifact. Preserve the returned `paymentId` as the durable x402api
|
|
119
119
|
reconciliation key. Do not create a replacement payment.
|
|
120
|
+
- For `confirmed: true`, stop payment submission even when `finalized` is
|
|
121
|
+
false. Finality and reorg checks are read-only WarpMetal backend work; they
|
|
122
|
+
never authorize another payment.
|
|
120
123
|
- A signed HTTP 402 rejects that signature. A `paymentId` can be absent;
|
|
121
124
|
preserve the safe `errorCode`, `requestId`, and `replacementAllowed` result.
|
|
122
125
|
Request a new live challenge and create the one permitted replacement only
|
package/src/cli.js
CHANGED
|
@@ -215,6 +215,42 @@ function suggestedDelay(result, fallback = 2) {
|
|
|
215
215
|
);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
function paymentLifecycle(result) {
|
|
219
|
+
const confirmed = result.data?.confirmed;
|
|
220
|
+
const finalized = result.data?.finalized;
|
|
221
|
+
if (confirmed === undefined && finalized === undefined) {
|
|
222
|
+
return { confirmed: undefined, finalized: undefined };
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
typeof confirmed !== "boolean" ||
|
|
226
|
+
typeof finalized !== "boolean" ||
|
|
227
|
+
(finalized && !confirmed)
|
|
228
|
+
) {
|
|
229
|
+
throw new CliError("WarpMetal returned contradictory payment lifecycle evidence.", {
|
|
230
|
+
exitCode: 3,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (confirmed && !result.data?.paymentId) {
|
|
234
|
+
throw new CliError("WarpMetal confirmed payment without a payment ID.", {
|
|
235
|
+
exitCode: 3,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (["provisioning", "renewed"].includes(result.data?.status) && !confirmed) {
|
|
239
|
+
throw new CliError("WarpMetal returned a terminal payment status without confirmation.", {
|
|
240
|
+
exitCode: 3,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return { confirmed, finalized };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function paymentSubmissionPending(result, lifecycle) {
|
|
247
|
+
return (
|
|
248
|
+
result.status === 202 &&
|
|
249
|
+
lifecycle.confirmed !== true &&
|
|
250
|
+
["payment_pending", "payment_finalizing"].includes(result.data?.status)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
218
254
|
async function readHeaderValueFile(path, label) {
|
|
219
255
|
const value = (await readFile(resolve(path), "utf8")).trim();
|
|
220
256
|
if (!value || value.includes("\n") || value.includes("\r")) {
|
|
@@ -806,8 +842,12 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
806
842
|
token,
|
|
807
843
|
paymentSignature,
|
|
808
844
|
});
|
|
845
|
+
const lifecycle = paymentLifecycle(response);
|
|
809
846
|
if (response.data?.paymentId) {
|
|
810
847
|
await store.saveOrderPaymentId(taskId, response.data.paymentId);
|
|
848
|
+
if (lifecycle.confirmed !== undefined) {
|
|
849
|
+
await store.saveOrderPaymentOutcome(taskId, response.data.paymentId, lifecycle);
|
|
850
|
+
}
|
|
811
851
|
}
|
|
812
852
|
const safe = await attachPaymentWorkflow(
|
|
813
853
|
client,
|
|
@@ -826,9 +866,7 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
826
866
|
replacementAllowed: safe.replacementAllowed,
|
|
827
867
|
});
|
|
828
868
|
}
|
|
829
|
-
const retryable =
|
|
830
|
-
response.status === 202 &&
|
|
831
|
-
["payment_pending", "payment_finalizing"].includes(response.data?.status);
|
|
869
|
+
const retryable = paymentSubmissionPending(response, lifecycle);
|
|
832
870
|
if (wait && retryable) {
|
|
833
871
|
ensureBeforeDeadline(deadline, `Checkout ${taskId}`);
|
|
834
872
|
await delay(suggestedDelay(response));
|
|
@@ -839,6 +877,8 @@ async function handleCheckoutSubmit(client, store, options, context) {
|
|
|
839
877
|
...safe,
|
|
840
878
|
task: response.data?.task,
|
|
841
879
|
paymentId: response.data?.paymentId,
|
|
880
|
+
confirmed: lifecycle.confirmed,
|
|
881
|
+
finalized: lifecycle.finalized,
|
|
842
882
|
message: response.data?.message,
|
|
843
883
|
...(walletPayment
|
|
844
884
|
? {
|
|
@@ -1404,8 +1444,16 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1404
1444
|
paymentSignature: walletPayment.paymentSignature,
|
|
1405
1445
|
},
|
|
1406
1446
|
);
|
|
1447
|
+
const lifecycle = paymentLifecycle(response);
|
|
1407
1448
|
if (response.data?.paymentId) {
|
|
1408
1449
|
await store.saveRenewalPaymentId(serverId, response.data.paymentId);
|
|
1450
|
+
if (lifecycle.confirmed !== undefined) {
|
|
1451
|
+
await store.saveRenewalPaymentOutcome(
|
|
1452
|
+
serverId,
|
|
1453
|
+
response.data.paymentId,
|
|
1454
|
+
lifecycle,
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1409
1457
|
}
|
|
1410
1458
|
if (response.status === 402 && response.data?.status === "payment_rejected") {
|
|
1411
1459
|
await store.saveRenewalPaymentRejection(serverId, {
|
|
@@ -1417,9 +1465,7 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1417
1465
|
replacementAllowed: response.data?.replacementAllowed,
|
|
1418
1466
|
});
|
|
1419
1467
|
}
|
|
1420
|
-
const retryable =
|
|
1421
|
-
response.status === 202 &&
|
|
1422
|
-
["payment_pending", "payment_finalizing"].includes(response.data?.status);
|
|
1468
|
+
const retryable = paymentSubmissionPending(response, lifecycle);
|
|
1423
1469
|
if (booleanOption(options, "wait") && retryable) {
|
|
1424
1470
|
ensureBeforeDeadline(deadline, `Renewal ${serverId}`);
|
|
1425
1471
|
await delay(suggestedDelay(response));
|
|
@@ -1430,6 +1476,8 @@ async function handleRenewalSubmit(client, store, options, context) {
|
|
|
1430
1476
|
serverId,
|
|
1431
1477
|
task: response.data?.task,
|
|
1432
1478
|
paymentId: response.data?.paymentId,
|
|
1479
|
+
confirmed: lifecycle.confirmed,
|
|
1480
|
+
finalized: lifecycle.finalized,
|
|
1433
1481
|
paymentAttemptId:
|
|
1434
1482
|
response.data?.paymentAttemptId ||
|
|
1435
1483
|
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.
|
|
18
|
+
export const AGENT_WALLET_VERSION = "0.2.9";
|
|
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";
|
|
@@ -842,6 +842,9 @@ export function paymentWorkflow({
|
|
|
842
842
|
signerContract: {
|
|
843
843
|
version: 1,
|
|
844
844
|
probe: { argv: ["x402api", "help", "--json"] },
|
|
845
|
+
requiredCommands: [
|
|
846
|
+
"payment authorize --wallet <name> --request-envelope <file> --artifact-out <file>",
|
|
847
|
+
],
|
|
845
848
|
},
|
|
846
849
|
signerReleaseStatus:
|
|
847
850
|
"published launch payer; sponsored Base USDC and Solana USDC/USDT only",
|
package/src/state.js
CHANGED
|
@@ -14,6 +14,8 @@ const WALLET_PAYMENT_STATE_FIELDS = [
|
|
|
14
14
|
"paymentArtifactExpiresAt",
|
|
15
15
|
"paymentArtifactSavedAt",
|
|
16
16
|
"gatewayPaymentId",
|
|
17
|
+
"gatewayPaymentConfirmed",
|
|
18
|
+
"gatewayPaymentFinalized",
|
|
17
19
|
"rejectedPaymentAttemptId",
|
|
18
20
|
"paymentRejectionErrorCode",
|
|
19
21
|
"paymentRejectionRequestId",
|
|
@@ -437,6 +439,43 @@ export class StateStore {
|
|
|
437
439
|
});
|
|
438
440
|
}
|
|
439
441
|
|
|
442
|
+
async saveOrderPaymentOutcome(taskId, paymentId, { confirmed, finalized }) {
|
|
443
|
+
if (
|
|
444
|
+
!X402API_PAYMENT_ID.test(paymentId || "") ||
|
|
445
|
+
typeof confirmed !== "boolean" ||
|
|
446
|
+
typeof finalized !== "boolean" ||
|
|
447
|
+
(finalized && !confirmed)
|
|
448
|
+
) {
|
|
449
|
+
throw new CliError("WarpMetal returned invalid payment lifecycle evidence.", {
|
|
450
|
+
exitCode: 3,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
await this.update((state) => {
|
|
454
|
+
const order = state.orders[taskId];
|
|
455
|
+
if (!order) {
|
|
456
|
+
throw new CliError(`No local order state exists for ${taskId}.`, {
|
|
457
|
+
exitCode: 2,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (order.gatewayPaymentId && order.gatewayPaymentId !== paymentId) {
|
|
461
|
+
throw new CliError("WarpMetal changed the x402api payment ID for this attempt.", {
|
|
462
|
+
exitCode: 3,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (
|
|
466
|
+
(order.gatewayPaymentConfirmed === true && !confirmed) ||
|
|
467
|
+
(order.gatewayPaymentFinalized === true && !finalized)
|
|
468
|
+
) {
|
|
469
|
+
throw new CliError("WarpMetal payment lifecycle evidence moved backward.", {
|
|
470
|
+
exitCode: 3,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
order.gatewayPaymentId = paymentId;
|
|
474
|
+
order.gatewayPaymentConfirmed = confirmed;
|
|
475
|
+
order.gatewayPaymentFinalized = finalized;
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
440
479
|
async saveOrderPaymentRejection(taskId, rejection) {
|
|
441
480
|
const safe = validatedPaymentRejection(rejection);
|
|
442
481
|
await this.update((state) => {
|
|
@@ -481,6 +520,43 @@ export class StateStore {
|
|
|
481
520
|
});
|
|
482
521
|
}
|
|
483
522
|
|
|
523
|
+
async saveRenewalPaymentOutcome(serverId, paymentId, { confirmed, finalized }) {
|
|
524
|
+
if (
|
|
525
|
+
!X402API_PAYMENT_ID.test(paymentId || "") ||
|
|
526
|
+
typeof confirmed !== "boolean" ||
|
|
527
|
+
typeof finalized !== "boolean" ||
|
|
528
|
+
(finalized && !confirmed)
|
|
529
|
+
) {
|
|
530
|
+
throw new CliError("WarpMetal returned invalid payment lifecycle evidence.", {
|
|
531
|
+
exitCode: 3,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
await this.update((state) => {
|
|
535
|
+
const renewal = state.renewals[serverId];
|
|
536
|
+
if (!renewal) {
|
|
537
|
+
throw new CliError(`No local renewal state exists for ${serverId}.`, {
|
|
538
|
+
exitCode: 2,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (renewal.gatewayPaymentId && renewal.gatewayPaymentId !== paymentId) {
|
|
542
|
+
throw new CliError("WarpMetal changed the x402api payment ID for this renewal.", {
|
|
543
|
+
exitCode: 3,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
if (
|
|
547
|
+
(renewal.gatewayPaymentConfirmed === true && !confirmed) ||
|
|
548
|
+
(renewal.gatewayPaymentFinalized === true && !finalized)
|
|
549
|
+
) {
|
|
550
|
+
throw new CliError("WarpMetal renewal payment lifecycle evidence moved backward.", {
|
|
551
|
+
exitCode: 3,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
renewal.gatewayPaymentId = paymentId;
|
|
555
|
+
renewal.gatewayPaymentConfirmed = confirmed;
|
|
556
|
+
renewal.gatewayPaymentFinalized = finalized;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
484
560
|
async saveRenewalPaymentRejection(serverId, rejection) {
|
|
485
561
|
const safe = validatedPaymentRejection(rejection);
|
|
486
562
|
await this.update((state) => {
|
|
@@ -631,6 +707,8 @@ export class StateStore {
|
|
|
631
707
|
planId: order.planId,
|
|
632
708
|
paymentAttemptId: order.paymentAttemptId,
|
|
633
709
|
paymentId: order.gatewayPaymentId,
|
|
710
|
+
paymentConfirmed: order.gatewayPaymentConfirmed,
|
|
711
|
+
paymentFinalized: order.gatewayPaymentFinalized,
|
|
634
712
|
challengeHandle: order.challengeHandle,
|
|
635
713
|
walletPaymentAttemptId: order.walletPaymentAttemptId,
|
|
636
714
|
walletName: order.walletName,
|
|
@@ -671,6 +749,8 @@ export class StateStore {
|
|
|
671
749
|
policy: renewal.policy,
|
|
672
750
|
paymentAttemptId: renewal.paymentAttemptId,
|
|
673
751
|
paymentId: renewal.gatewayPaymentId,
|
|
752
|
+
paymentConfirmed: renewal.gatewayPaymentConfirmed,
|
|
753
|
+
paymentFinalized: renewal.gatewayPaymentFinalized,
|
|
674
754
|
challengeHandle: renewal.challengeHandle,
|
|
675
755
|
paymentArtifactExpiresAt: renewal.paymentArtifactExpiresAt,
|
|
676
756
|
rejectedPaymentAttemptId: renewal.rejectedPaymentAttemptId,
|