xytara 3.1.13 → 3.1.14

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.
Files changed (55) hide show
  1. package/.env.example +8 -1
  2. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
  3. package/RELEASE_NOTES.md +6 -4
  4. package/lib/a2a_rpc.js +280 -280
  5. package/lib/account_auth.js +585 -585
  6. package/lib/agent_identity.js +162 -162
  7. package/lib/bolt_adapter.js +187 -187
  8. package/lib/bolt_payment_front.js +224 -224
  9. package/lib/checkout_processor_adapter.js +217 -217
  10. package/lib/command_flow.js +722 -722
  11. package/lib/commerce_artifacts.js +591 -591
  12. package/lib/commerce_bridge.js +274 -274
  13. package/lib/commerce_cases.js +399 -399
  14. package/lib/commerce_client.js +7694 -7694
  15. package/lib/commerce_quotes.js +29 -0
  16. package/lib/commerce_runtime.js +322 -322
  17. package/lib/execution_dedupe.js +86 -86
  18. package/lib/external_credit_bridge.js +142 -142
  19. package/lib/http_transport.js +81 -81
  20. package/lib/l402_adapter.js +241 -241
  21. package/lib/l402_payment_front.js +265 -265
  22. package/lib/mpp_adapter.js +237 -237
  23. package/lib/payment_config.js +253 -253
  24. package/lib/payment_verification.js +357 -357
  25. package/lib/provider_payout_execution.js +155 -155
  26. package/lib/provider_registry.js +842 -842
  27. package/lib/rate_limiter.js +81 -81
  28. package/lib/runtime_state_store.js +491 -369
  29. package/lib/settlement_bsv_live.js +600 -600
  30. package/lib/settlement_bsv_teranode.js +117 -117
  31. package/lib/stripe_mpp_payment_front.js +258 -258
  32. package/lib/x402_facilitator.js +191 -191
  33. package/package.json +5 -3
  34. package/scripts/state_backup_offsite.js +196 -0
  35. package/scripts/verify_a2a_rpc.js +105 -105
  36. package/scripts/verify_agent_identity.js +153 -153
  37. package/scripts/verify_all.js +1 -1
  38. package/scripts/verify_bolt_adapter.js +128 -128
  39. package/scripts/verify_cdp_facilitator.js +110 -110
  40. package/scripts/verify_checkout_processor.js +109 -109
  41. package/scripts/verify_commerce_bridge.js +177 -177
  42. package/scripts/verify_execution_dedupe.js +189 -189
  43. package/scripts/verify_execution_surfacing.js +76 -76
  44. package/scripts/verify_final_activation.js +191 -191
  45. package/scripts/verify_l402_adapter.js +86 -86
  46. package/scripts/verify_mpp_adapter.js +128 -128
  47. package/scripts/verify_production_readiness.js +268 -268
  48. package/scripts/verify_rate_limiter.js +52 -52
  49. package/scripts/verify_refund_credit.js +106 -106
  50. package/scripts/verify_release_candidate.js +81 -81
  51. package/scripts/verify_runtime_state_guard.js +117 -0
  52. package/scripts/verify_service.js +15421 -15421
  53. package/scripts/verify_verify_execute_settle.js +208 -208
  54. package/scripts/verify_x402_facilitator.js +286 -286
  55. package/server.js +11976 -11974
@@ -1,286 +1,286 @@
1
- "use strict";
2
-
3
- // Verifies the x402_facilitator payment mode end to end against a mock facilitator:
4
- // - an unpaid x402 execute returns 402 and advertises the configured accepts
5
- // - a well-formed payment is verified and settled, the command runs, and the
6
- // response carries a PAYMENT-RESPONSE settlement receipt
7
- // - malformed, mismatched, facilitator-rejected, and unsettled payments all 402
8
- //
9
- // No real network or chain is used; the mock facilitator stands in for x402.org.
10
-
11
- const assert = require("assert");
12
- const http = require("http");
13
-
14
- const ACCEPTS = [
15
- {
16
- scheme: "exact",
17
- network: "eip155:84532",
18
- asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
19
- amount: "1000",
20
- payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
21
- extra: { name: "USDC", version: "2" },
22
- maxTimeoutSeconds: 60
23
- }
24
- ];
25
-
26
- function startMockFacilitator() {
27
- const calls = { verify: 0, settle: 0 };
28
- const server = http.createServer((req, res) => {
29
- const chunks = [];
30
- req.on("data", (c) => chunks.push(c));
31
- req.on("end", () => {
32
- let body = {};
33
- try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
34
- const sig = body && body.paymentPayload && body.paymentPayload.payload ? String(body.paymentPayload.payload.signature || "") : "";
35
- const reply = (payload, status) => {
36
- const text = JSON.stringify(payload);
37
- res.writeHead(status || 200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
38
- res.end(text);
39
- };
40
- if (req.method === "POST" && req.url === "/verify") {
41
- calls.verify += 1;
42
- calls.lastVerifyBody = body;
43
- if (sig === "reject-me") return reply({ isValid: false, invalidReason: "invalid_signature" });
44
- if (sig === "cdp-invalid") return reply({ isValid: false, invalidReason: "invalid_payload", invalidMessage: "contract call failed: execution reverted", payer: "0x1111111111111111111111111111111111111111" }, 400);
45
- return reply({ isValid: true, payer: "0x1111111111111111111111111111111111111111" });
46
- }
47
- if (req.method === "POST" && req.url === "/settle") {
48
- calls.settle += 1;
49
- if (sig === "settle-fails") return reply({ success: false, errorReason: "insufficient_funds" });
50
- if (sig === "cdp-settle-fail") return reply({ success: false, errorReason: "insufficient_funds" }, 400);
51
- return reply({ success: true, transaction: "0xmocktx", network: "eip155:84532", payer: "0x1111111111111111111111111111111111111111" });
52
- }
53
- res.writeHead(404); res.end();
54
- });
55
- });
56
- return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, calls, port: server.address().port })));
57
- }
58
-
59
- function paymentHeader(signature, overrides = {}) {
60
- const accepted = ACCEPTS[0];
61
- return Buffer.from(JSON.stringify({
62
- x402Version: 2,
63
- accepted: { ...accepted, ...overrides.accepted },
64
- payload: {
65
- signature,
66
- authorization: {
67
- from: "0x1111111111111111111111111111111111111111",
68
- to: accepted.payTo,
69
- value: accepted.amount,
70
- validAfter: "0",
71
- validBefore: String(Math.floor(Date.now() / 1000) + 600),
72
- nonce: `0x${"ab".repeat(32)}`
73
- }
74
- }
75
- }), "utf8").toString("base64");
76
- }
77
-
78
- async function post(baseUrl, path, body, headers = {}) {
79
- const res = await fetch(`${baseUrl}${path}`, {
80
- method: "POST",
81
- headers: { "content-type": "application/json", ...headers },
82
- body: JSON.stringify(body)
83
- });
84
- let json = null;
85
- try { json = await res.json(); } catch (_) { json = null; }
86
- return { status: res.status, json, headers: res.headers };
87
- }
88
-
89
- async function main() {
90
- const facilitator = await startMockFacilitator();
91
- const previous = {
92
- mode: process.env.XYTARA_PAYMENT_VERIFICATION_MODE,
93
- accepts: process.env.XYTARA_X402_ACCEPTS_JSON,
94
- facUrl: process.env.XYTARA_X402_FACILITATOR_URL
95
- };
96
- process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "x402_facilitator";
97
- process.env.XYTARA_X402_ACCEPTS_JSON = JSON.stringify(ACCEPTS);
98
- process.env.XYTARA_X402_FACILITATOR_URL = `http://127.0.0.1:${facilitator.port}`;
99
-
100
- delete require.cache[require.resolve("../server")];
101
- const { createServer } = require("../server");
102
- const server = createServer();
103
- await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
104
- const baseUrl = `http://127.0.0.1:${server.address().port}`;
105
- const execBody = { account_id: "acct_x402", command: "x402 facilitator demo", task_ref: "trust.verify", body: { subject_id: "s-1" } };
106
-
107
- try {
108
- // 1. Unpaid -> 402 advertising accepts.
109
- const unpaid = await post(baseUrl, "/x402/commands/execute", execBody);
110
- assert.strictEqual(unpaid.status, 402, "unpaid x402 execute must return 402");
111
- const required = unpaid.json && unpaid.json.payment_required;
112
- assert.ok(required, "402 must carry payment_required");
113
- assert.strictEqual(required.x402Version, 2, "challenge must be x402 v2");
114
- assert.ok(Array.isArray(required.accepts) && required.accepts.length === 1, "challenge must advertise accepts");
115
- assert.strictEqual(required.accepts[0].network, "eip155:84532", "advertised network mismatch");
116
- assert.strictEqual(typeof unpaid.headers.get("payment-required"), "string", "PAYMENT-REQUIRED header missing");
117
-
118
- // 2. Bogus header -> 402 payment_signature_invalid.
119
- const bogus = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": "not-a-payment" });
120
- assert.strictEqual(bogus.status, 402, "bogus payment header must 402");
121
- assert.strictEqual(bogus.json.reason, "payment_signature_invalid", "bogus reason mismatch");
122
-
123
- // 3. Mismatched requirements -> 402 before the facilitator.
124
- const verifyBefore = facilitator.calls.verify;
125
- const mismatch = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok", { accepted: { amount: "9" } }) });
126
- assert.strictEqual(mismatch.status, 402, "mismatched requirements must 402");
127
- assert.strictEqual(mismatch.json.reason, "payment_requirements_mismatch", "mismatch reason");
128
- assert.strictEqual(facilitator.calls.verify, verifyBefore, "facilitator must not be called on a requirements mismatch");
129
-
130
- // 4. Facilitator rejects -> 402 payment_verification_failed.
131
- const rejected = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("reject-me") });
132
- assert.strictEqual(rejected.status, 402, "facilitator-rejected payment must 402");
133
- assert.strictEqual(rejected.json.reason, "payment_verification_failed", "rejection reason");
134
- assert.strictEqual(rejected.json.detail, "invalid_signature", "rejection detail");
135
-
136
- // 5. Settlement fails -> 402, no tool output.
137
- const settleFails = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("settle-fails") });
138
- assert.strictEqual(settleFails.status, 402, "failed settlement must 402");
139
- assert.strictEqual(settleFails.json.reason, "payment_settlement_failed", "settlement failure reason");
140
-
141
- // 6. Verified + settled -> 200 with a result and a PAYMENT-RESPONSE receipt.
142
- // (Delivery-receipt signing keys for this run only: Ed25519 + ML-DSA-65 hybrid.)
143
- const crypto = require("crypto");
144
- const receiptEd = crypto.generateKeyPairSync("ed25519");
145
- const receiptMl = crypto.generateKeyPairSync("ml-dsa-65");
146
- process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY = receiptEd.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
147
- process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY = receiptMl.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
148
- const settleBefore = facilitator.calls.settle;
149
- const paid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
150
- assert.strictEqual(paid.status, 200, "verified+settled payment must 200");
151
- assert.strictEqual(typeof paid.json.transaction.transaction_id, "string", "paid result missing transaction");
152
- assert.strictEqual(paid.json.transaction.status, "completed", "paid transaction must be completed");
153
- assert.strictEqual(paid.json.transaction.payment.verification_mode, "x402_facilitator", "verification mode not recorded");
154
- assert.strictEqual(facilitator.calls.settle, settleBefore + 1, "facilitator settle must be called once");
155
- const receiptHeader = paid.headers.get("payment-response");
156
- assert.strictEqual(typeof receiptHeader, "string", "PAYMENT-RESPONSE header missing on a settled 200");
157
- const receipt = JSON.parse(Buffer.from(receiptHeader, "base64").toString("utf8"));
158
- assert.strictEqual(receipt.success, true, "settlement receipt must report success");
159
- assert.strictEqual(receipt.transaction, "0xmocktx", "settlement receipt transaction mismatch");
160
-
161
- // 6a. The runtime records the REAL x402 settlement (not the quote's default placeholder rail).
162
- const recorded = paid.json.receipt.settlement;
163
- assert.strictEqual(recorded.rail, "x402", "x402-paid call records the x402 rail");
164
- assert.strictEqual(recorded.mode, "x402_facilitator", "x402 settlement mode recorded");
165
- assert.strictEqual(recorded.txid, "0xmocktx", "settlement record carries the settled tx");
166
- assert.strictEqual(recorded.network, "eip155:84532", "settlement record carries the network");
167
- assert.strictEqual(recorded.pay_to, ACCEPTS[0].payTo, "settlement record carries pay_to");
168
- assert.strictEqual(recorded.status, "settled", "settlement record status");
169
- assert.strictEqual(recorded.live, undefined, "no placeholder BSV settlement record for an x402 call");
170
- assert.strictEqual(paid.json.receipt.treasury.rail, "x402", "treasury record on the x402 rail");
171
- assert.strictEqual(paid.json.receipt.default_path.settlement_mode, "x402_facilitator", "default path reflects x402");
172
-
173
- // 6b. Signed delivery receipt: binds the settlement, verifies against the published keys,
174
- // is retrievable by id, and rejects tampering and signature stripping.
175
- const delivery = paid.json.delivery_receipt;
176
- assert.ok(delivery, "paid result carries a delivery receipt");
177
- assert.strictEqual(delivery.v, "naxytra-delivery-receipt/1", "delivery receipt version");
178
- assert.deepStrictEqual(delivery.signing.suite, ["EdDSA", "ML-DSA-65"], "hybrid suite");
179
- assert.strictEqual(delivery.payment.protocol, "x402", "receipt payment protocol");
180
- assert.strictEqual(delivery.payment.settlement_tx, "0xmocktx", "receipt binds the settlement tx");
181
- assert.strictEqual(delivery.payment.pay_to, ACCEPTS[0].payTo, "receipt carries pay_to");
182
- assert.strictEqual(delivery.payment.amount, ACCEPTS[0].amount, "receipt carries the settled amount");
183
- assert.strictEqual(delivery.call.quote_id, paid.json.transaction.quote_id, "receipt binds the quote");
184
- assert.strictEqual(delivery.account_id, undefined, "receipt carries no account id");
185
- const receiptLib = require("../lib/delivery_receipt");
186
- assert.strictEqual(delivery.delivery.request_sha256, receiptLib.hashCanonical(execBody.body), "request hash recomputes from the request body");
187
- const keysRes = await fetch(`${baseUrl}/.well-known/delivery-receipt-keys`);
188
- const keys = await keysRes.json();
189
- assert.strictEqual(keys.signing_state, "signing", "keys endpoint reports signing");
190
- assert.strictEqual(receiptLib.verifyDeliveryReceipt(delivery, keys.keys).valid, true, "receipt verifies against the published keys");
191
- const verifyRes = await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: delivery });
192
- assert.strictEqual(verifyRes.json.valid, true, "verify endpoint accepts the genuine receipt");
193
- assert.strictEqual(verifyRes.json.issuer_matches, true, "verify endpoint matches the issuer");
194
- const tamperedDelivery = JSON.parse(JSON.stringify(delivery));
195
- tamperedDelivery.payment.amount = "1";
196
- assert.strictEqual((await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: tamperedDelivery })).json.valid, false, "tampered receipt rejected");
197
- const strippedDelivery = JSON.parse(JSON.stringify(delivery));
198
- strippedDelivery.sig.signatures = strippedDelivery.sig.signatures.filter((entry) => entry.alg !== "ML-DSA-65");
199
- assert.strictEqual(receiptLib.verifyDeliveryReceipt(strippedDelivery, keys.keys).valid, false, "stripping the ML-DSA leg (downgrade) rejected");
200
- const fetched = await fetch(`${baseUrl}/v1/delivery-receipts/${encodeURIComponent(delivery.receipt_id)}`);
201
- assert.strictEqual(fetched.status, 200, "receipt retrievable by id");
202
- assert.deepStrictEqual((await fetched.json()).delivery_receipt, delivery, "retrieved receipt is identical");
203
- assert.strictEqual((await fetch(`${baseUrl}/v1/delivery-receipts/drcpt_nope`)).status, 404, "unknown receipt 404");
204
- // The xoonya verifier accepts the same signatures (kid == xoonya key ref). Checked when the
205
- // sibling xoonya checkout is present (workspace); skipped in a standalone xytara checkout.
206
- let verifyExternalProofSignature = null;
207
- try { ({ verifyExternalProofSignature } = require("../../xoonya/proof_sdk")); } catch (_) { verifyExternalProofSignature = null; }
208
- const { sig: _omit, ...unsignedDelivery } = delivery;
209
- for (const entry of verifyExternalProofSignature ? delivery.sig.signatures : []) {
210
- const key = keys.keys.find((item) => item.kid === entry.kid);
211
- const viaXoonya = verifyExternalProofSignature({ material: receiptLib.canonicalize(unsignedDelivery), signature: entry.value, algorithm: entry.alg, public_key: key.public_key_spki_der_base64, expected_key_ref: entry.kid });
212
- assert.strictEqual(viaXoonya.signature_valid && viaXoonya.trust_anchored, true, `xoonya verifies + anchors the ${entry.alg} leg`);
213
- }
214
- delete process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY;
215
- delete process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY;
216
- // Without keys the receipt is still issued but honestly unsigned (never a fake signature).
217
- const unsignedPaid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
218
- assert.strictEqual(unsignedPaid.json.delivery_receipt.sig.state, "unsigned_signing_key_not_configured", "no keys -> honestly unsigned");
219
- assert.strictEqual(receiptLib.verifyDeliveryReceipt(unsignedPaid.json.delivery_receipt, keys.keys).valid, false, "unsigned receipt never verifies");
220
- // The shared module is byte-identical in the runtime and the MCP executor.
221
- const fs = require("fs");
222
- const path = require("path");
223
- assert.ok(fs.readFileSync(path.join(__dirname, "..", "lib", "delivery_receipt.js")).equals(fs.readFileSync(path.join(__dirname, "..", "services", "mcp-execution-target", "delivery_receipt.js"))), "delivery_receipt.js copies must stay byte-identical");
224
-
225
- // The facilitator must receive a clean v2 PaymentRequirements: no `resource` field (which
226
- // strict facilitators like CDP reject), and it must equal the payment payload's `accepted`.
227
- const verifyBody = facilitator.calls.lastVerifyBody;
228
- assert.ok(verifyBody && verifyBody.paymentRequirements, "facilitator must receive paymentRequirements");
229
- assert.strictEqual(verifyBody.paymentRequirements.resource, undefined, "paymentRequirements must not carry a resource field (v2)");
230
- assert.deepStrictEqual(verifyBody.paymentRequirements, verifyBody.paymentPayload.accepted, "paymentRequirements must equal paymentPayload.accepted");
231
-
232
- // 7. Native routes must not be disabled by x402_facilitator: a native paid attempt
233
- // falls back to local_signed semantics, never payment_verification_mode_unsupported.
234
- const nativeAttempt = await post(baseUrl, "/v1/commands/execute", execBody, {
235
- "x-wallet-id": "wallet-not-registered",
236
- "x-payment": "encoded",
237
- "x-payment-signature": "sig"
238
- });
239
- assert.notStrictEqual(nativeAttempt.json && nativeAttempt.json.reason, "payment_verification_mode_unsupported", "native route must fall back to local_signed under x402_facilitator");
240
-
241
- // 7b. CDP-style facilitator: a non-2xx verify carrying isValid:false is a verification
242
- // failure, not an unreachable transport error.
243
- const cdpInvalid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("cdp-invalid") });
244
- assert.strictEqual(cdpInvalid.status, 402, "cdp-style 400 verify must still 402");
245
- assert.strictEqual(cdpInvalid.json.reason, "payment_verification_failed", "cdp-style 400 must map to payment_verification_failed, not facilitator_unreachable");
246
- assert.strictEqual(cdpInvalid.json.detail, "invalid_payload", "cdp-style verify detail must surface invalidReason");
247
-
248
- // 7c. CDP-style settle failure: a non-2xx settle carrying success:false is a settlement failure.
249
- const cdpSettleFail = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("cdp-settle-fail") });
250
- assert.strictEqual(cdpSettleFail.status, 402, "cdp-style 400 settle must still 402");
251
- assert.strictEqual(cdpSettleFail.json.reason, "payment_settlement_failed", "cdp-style 400 settle must map to payment_settlement_failed");
252
-
253
- // 8. Self-serve credits top-up: unpaid -> 402 advertising accepts + credits_per_topup.
254
- const topupUnpaid = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" });
255
- assert.strictEqual(topupUnpaid.status, 402, "unpaid topup must 402");
256
- assert.ok(Array.isArray(topupUnpaid.json.payment_required.accepts), "topup 402 must advertise accepts");
257
- assert.strictEqual(typeof topupUnpaid.json.payment_required.credits_per_topup, "number", "topup 402 must state credits_per_topup");
258
-
259
- // 9. Paid top-up -> 200, credits minted to the account, with a settlement receipt.
260
- const topupPaid = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" }, { "payment-signature": paymentHeader("ok") });
261
- assert.strictEqual(topupPaid.status, 200, "paid topup must 200");
262
- assert.strictEqual(topupPaid.json.ok, true, "paid topup must be ok");
263
- assert.strictEqual(typeof topupPaid.json.credit_grant, "object", "paid topup must return a credit grant");
264
- const grantedUnits = Number(topupPaid.json.balance.available_units);
265
- assert.ok(grantedUnits > 0, "paid topup must leave a positive credit balance");
266
- assert.strictEqual(typeof topupPaid.headers.get("payment-response"), "string", "paid topup must carry a settlement receipt");
267
-
268
- // 10. Replayed top-up (same payment nonce) is idempotent: no double-mint.
269
- const topupReplay = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" }, { "payment-signature": paymentHeader("ok") });
270
- assert.strictEqual(topupReplay.status, 200, "replayed topup must still 200");
271
- assert.strictEqual(Number(topupReplay.json.balance.available_units), grantedUnits, "replayed topup must not mint credits twice");
272
-
273
- console.log("xytara verify:x402-facilitator passed");
274
- } finally {
275
- await new Promise((resolve) => server.close(resolve));
276
- await new Promise((resolve) => facilitator.server.close(resolve));
277
- if (previous.mode === undefined) delete process.env.XYTARA_PAYMENT_VERIFICATION_MODE; else process.env.XYTARA_PAYMENT_VERIFICATION_MODE = previous.mode;
278
- if (previous.accepts === undefined) delete process.env.XYTARA_X402_ACCEPTS_JSON; else process.env.XYTARA_X402_ACCEPTS_JSON = previous.accepts;
279
- if (previous.facUrl === undefined) delete process.env.XYTARA_X402_FACILITATOR_URL; else process.env.XYTARA_X402_FACILITATOR_URL = previous.facUrl;
280
- }
281
- }
282
-
283
- main().catch((error) => {
284
- console.error(error && error.message ? error.message : error);
285
- process.exitCode = 1;
286
- });
1
+ "use strict";
2
+
3
+ // Verifies the x402_facilitator payment mode end to end against a mock facilitator:
4
+ // - an unpaid x402 execute returns 402 and advertises the configured accepts
5
+ // - a well-formed payment is verified and settled, the command runs, and the
6
+ // response carries a PAYMENT-RESPONSE settlement receipt
7
+ // - malformed, mismatched, facilitator-rejected, and unsettled payments all 402
8
+ //
9
+ // No real network or chain is used; the mock facilitator stands in for x402.org.
10
+
11
+ const assert = require("assert");
12
+ const http = require("http");
13
+
14
+ const ACCEPTS = [
15
+ {
16
+ scheme: "exact",
17
+ network: "eip155:84532",
18
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
19
+ amount: "1000",
20
+ payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
21
+ extra: { name: "USDC", version: "2" },
22
+ maxTimeoutSeconds: 60
23
+ }
24
+ ];
25
+
26
+ function startMockFacilitator() {
27
+ const calls = { verify: 0, settle: 0 };
28
+ const server = http.createServer((req, res) => {
29
+ const chunks = [];
30
+ req.on("data", (c) => chunks.push(c));
31
+ req.on("end", () => {
32
+ let body = {};
33
+ try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
34
+ const sig = body && body.paymentPayload && body.paymentPayload.payload ? String(body.paymentPayload.payload.signature || "") : "";
35
+ const reply = (payload, status) => {
36
+ const text = JSON.stringify(payload);
37
+ res.writeHead(status || 200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
38
+ res.end(text);
39
+ };
40
+ if (req.method === "POST" && req.url === "/verify") {
41
+ calls.verify += 1;
42
+ calls.lastVerifyBody = body;
43
+ if (sig === "reject-me") return reply({ isValid: false, invalidReason: "invalid_signature" });
44
+ if (sig === "cdp-invalid") return reply({ isValid: false, invalidReason: "invalid_payload", invalidMessage: "contract call failed: execution reverted", payer: "0x1111111111111111111111111111111111111111" }, 400);
45
+ return reply({ isValid: true, payer: "0x1111111111111111111111111111111111111111" });
46
+ }
47
+ if (req.method === "POST" && req.url === "/settle") {
48
+ calls.settle += 1;
49
+ if (sig === "settle-fails") return reply({ success: false, errorReason: "insufficient_funds" });
50
+ if (sig === "cdp-settle-fail") return reply({ success: false, errorReason: "insufficient_funds" }, 400);
51
+ return reply({ success: true, transaction: "0xmocktx", network: "eip155:84532", payer: "0x1111111111111111111111111111111111111111" });
52
+ }
53
+ res.writeHead(404); res.end();
54
+ });
55
+ });
56
+ return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, calls, port: server.address().port })));
57
+ }
58
+
59
+ function paymentHeader(signature, overrides = {}) {
60
+ const accepted = ACCEPTS[0];
61
+ return Buffer.from(JSON.stringify({
62
+ x402Version: 2,
63
+ accepted: { ...accepted, ...overrides.accepted },
64
+ payload: {
65
+ signature,
66
+ authorization: {
67
+ from: "0x1111111111111111111111111111111111111111",
68
+ to: accepted.payTo,
69
+ value: accepted.amount,
70
+ validAfter: "0",
71
+ validBefore: String(Math.floor(Date.now() / 1000) + 600),
72
+ nonce: `0x${"ab".repeat(32)}`
73
+ }
74
+ }
75
+ }), "utf8").toString("base64");
76
+ }
77
+
78
+ async function post(baseUrl, path, body, headers = {}) {
79
+ const res = await fetch(`${baseUrl}${path}`, {
80
+ method: "POST",
81
+ headers: { "content-type": "application/json", ...headers },
82
+ body: JSON.stringify(body)
83
+ });
84
+ let json = null;
85
+ try { json = await res.json(); } catch (_) { json = null; }
86
+ return { status: res.status, json, headers: res.headers };
87
+ }
88
+
89
+ async function main() {
90
+ const facilitator = await startMockFacilitator();
91
+ const previous = {
92
+ mode: process.env.XYTARA_PAYMENT_VERIFICATION_MODE,
93
+ accepts: process.env.XYTARA_X402_ACCEPTS_JSON,
94
+ facUrl: process.env.XYTARA_X402_FACILITATOR_URL
95
+ };
96
+ process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "x402_facilitator";
97
+ process.env.XYTARA_X402_ACCEPTS_JSON = JSON.stringify(ACCEPTS);
98
+ process.env.XYTARA_X402_FACILITATOR_URL = `http://127.0.0.1:${facilitator.port}`;
99
+
100
+ delete require.cache[require.resolve("../server")];
101
+ const { createServer } = require("../server");
102
+ const server = createServer();
103
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
104
+ const baseUrl = `http://127.0.0.1:${server.address().port}`;
105
+ const execBody = { account_id: "acct_x402", command: "x402 facilitator demo", task_ref: "trust.verify", body: { subject_id: "s-1" } };
106
+
107
+ try {
108
+ // 1. Unpaid -> 402 advertising accepts.
109
+ const unpaid = await post(baseUrl, "/x402/commands/execute", execBody);
110
+ assert.strictEqual(unpaid.status, 402, "unpaid x402 execute must return 402");
111
+ const required = unpaid.json && unpaid.json.payment_required;
112
+ assert.ok(required, "402 must carry payment_required");
113
+ assert.strictEqual(required.x402Version, 2, "challenge must be x402 v2");
114
+ assert.ok(Array.isArray(required.accepts) && required.accepts.length === 1, "challenge must advertise accepts");
115
+ assert.strictEqual(required.accepts[0].network, "eip155:84532", "advertised network mismatch");
116
+ assert.strictEqual(typeof unpaid.headers.get("payment-required"), "string", "PAYMENT-REQUIRED header missing");
117
+
118
+ // 2. Bogus header -> 402 payment_signature_invalid.
119
+ const bogus = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": "not-a-payment" });
120
+ assert.strictEqual(bogus.status, 402, "bogus payment header must 402");
121
+ assert.strictEqual(bogus.json.reason, "payment_signature_invalid", "bogus reason mismatch");
122
+
123
+ // 3. Mismatched requirements -> 402 before the facilitator.
124
+ const verifyBefore = facilitator.calls.verify;
125
+ const mismatch = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok", { accepted: { amount: "9" } }) });
126
+ assert.strictEqual(mismatch.status, 402, "mismatched requirements must 402");
127
+ assert.strictEqual(mismatch.json.reason, "payment_requirements_mismatch", "mismatch reason");
128
+ assert.strictEqual(facilitator.calls.verify, verifyBefore, "facilitator must not be called on a requirements mismatch");
129
+
130
+ // 4. Facilitator rejects -> 402 payment_verification_failed.
131
+ const rejected = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("reject-me") });
132
+ assert.strictEqual(rejected.status, 402, "facilitator-rejected payment must 402");
133
+ assert.strictEqual(rejected.json.reason, "payment_verification_failed", "rejection reason");
134
+ assert.strictEqual(rejected.json.detail, "invalid_signature", "rejection detail");
135
+
136
+ // 5. Settlement fails -> 402, no tool output.
137
+ const settleFails = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("settle-fails") });
138
+ assert.strictEqual(settleFails.status, 402, "failed settlement must 402");
139
+ assert.strictEqual(settleFails.json.reason, "payment_settlement_failed", "settlement failure reason");
140
+
141
+ // 6. Verified + settled -> 200 with a result and a PAYMENT-RESPONSE receipt.
142
+ // (Delivery-receipt signing keys for this run only: Ed25519 + ML-DSA-65 hybrid.)
143
+ const crypto = require("crypto");
144
+ const receiptEd = crypto.generateKeyPairSync("ed25519");
145
+ const receiptMl = crypto.generateKeyPairSync("ml-dsa-65");
146
+ process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY = receiptEd.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
147
+ process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY = receiptMl.privateKey.export({ type: "pkcs8", format: "der" }).toString("base64");
148
+ const settleBefore = facilitator.calls.settle;
149
+ const paid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
150
+ assert.strictEqual(paid.status, 200, "verified+settled payment must 200");
151
+ assert.strictEqual(typeof paid.json.transaction.transaction_id, "string", "paid result missing transaction");
152
+ assert.strictEqual(paid.json.transaction.status, "completed", "paid transaction must be completed");
153
+ assert.strictEqual(paid.json.transaction.payment.verification_mode, "x402_facilitator", "verification mode not recorded");
154
+ assert.strictEqual(facilitator.calls.settle, settleBefore + 1, "facilitator settle must be called once");
155
+ const receiptHeader = paid.headers.get("payment-response");
156
+ assert.strictEqual(typeof receiptHeader, "string", "PAYMENT-RESPONSE header missing on a settled 200");
157
+ const receipt = JSON.parse(Buffer.from(receiptHeader, "base64").toString("utf8"));
158
+ assert.strictEqual(receipt.success, true, "settlement receipt must report success");
159
+ assert.strictEqual(receipt.transaction, "0xmocktx", "settlement receipt transaction mismatch");
160
+
161
+ // 6a. The runtime records the REAL x402 settlement (not the quote's default placeholder rail).
162
+ const recorded = paid.json.receipt.settlement;
163
+ assert.strictEqual(recorded.rail, "x402", "x402-paid call records the x402 rail");
164
+ assert.strictEqual(recorded.mode, "x402_facilitator", "x402 settlement mode recorded");
165
+ assert.strictEqual(recorded.txid, "0xmocktx", "settlement record carries the settled tx");
166
+ assert.strictEqual(recorded.network, "eip155:84532", "settlement record carries the network");
167
+ assert.strictEqual(recorded.pay_to, ACCEPTS[0].payTo, "settlement record carries pay_to");
168
+ assert.strictEqual(recorded.status, "settled", "settlement record status");
169
+ assert.strictEqual(recorded.live, undefined, "no placeholder BSV settlement record for an x402 call");
170
+ assert.strictEqual(paid.json.receipt.treasury.rail, "x402", "treasury record on the x402 rail");
171
+ assert.strictEqual(paid.json.receipt.default_path.settlement_mode, "x402_facilitator", "default path reflects x402");
172
+
173
+ // 6b. Signed delivery receipt: binds the settlement, verifies against the published keys,
174
+ // is retrievable by id, and rejects tampering and signature stripping.
175
+ const delivery = paid.json.delivery_receipt;
176
+ assert.ok(delivery, "paid result carries a delivery receipt");
177
+ assert.strictEqual(delivery.v, "naxytra-delivery-receipt/1", "delivery receipt version");
178
+ assert.deepStrictEqual(delivery.signing.suite, ["EdDSA", "ML-DSA-65"], "hybrid suite");
179
+ assert.strictEqual(delivery.payment.protocol, "x402", "receipt payment protocol");
180
+ assert.strictEqual(delivery.payment.settlement_tx, "0xmocktx", "receipt binds the settlement tx");
181
+ assert.strictEqual(delivery.payment.pay_to, ACCEPTS[0].payTo, "receipt carries pay_to");
182
+ assert.strictEqual(delivery.payment.amount, ACCEPTS[0].amount, "receipt carries the settled amount");
183
+ assert.strictEqual(delivery.call.quote_id, paid.json.transaction.quote_id, "receipt binds the quote");
184
+ assert.strictEqual(delivery.account_id, undefined, "receipt carries no account id");
185
+ const receiptLib = require("../lib/delivery_receipt");
186
+ assert.strictEqual(delivery.delivery.request_sha256, receiptLib.hashCanonical(execBody.body), "request hash recomputes from the request body");
187
+ const keysRes = await fetch(`${baseUrl}/.well-known/delivery-receipt-keys`);
188
+ const keys = await keysRes.json();
189
+ assert.strictEqual(keys.signing_state, "signing", "keys endpoint reports signing");
190
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(delivery, keys.keys).valid, true, "receipt verifies against the published keys");
191
+ const verifyRes = await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: delivery });
192
+ assert.strictEqual(verifyRes.json.valid, true, "verify endpoint accepts the genuine receipt");
193
+ assert.strictEqual(verifyRes.json.issuer_matches, true, "verify endpoint matches the issuer");
194
+ const tamperedDelivery = JSON.parse(JSON.stringify(delivery));
195
+ tamperedDelivery.payment.amount = "1";
196
+ assert.strictEqual((await post(baseUrl, "/v1/delivery-receipts/verify", { receipt: tamperedDelivery })).json.valid, false, "tampered receipt rejected");
197
+ const strippedDelivery = JSON.parse(JSON.stringify(delivery));
198
+ strippedDelivery.sig.signatures = strippedDelivery.sig.signatures.filter((entry) => entry.alg !== "ML-DSA-65");
199
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(strippedDelivery, keys.keys).valid, false, "stripping the ML-DSA leg (downgrade) rejected");
200
+ const fetched = await fetch(`${baseUrl}/v1/delivery-receipts/${encodeURIComponent(delivery.receipt_id)}`);
201
+ assert.strictEqual(fetched.status, 200, "receipt retrievable by id");
202
+ assert.deepStrictEqual((await fetched.json()).delivery_receipt, delivery, "retrieved receipt is identical");
203
+ assert.strictEqual((await fetch(`${baseUrl}/v1/delivery-receipts/drcpt_nope`)).status, 404, "unknown receipt 404");
204
+ // The xoonya verifier accepts the same signatures (kid == xoonya key ref). Checked when the
205
+ // sibling xoonya checkout is present (workspace); skipped in a standalone xytara checkout.
206
+ let verifyExternalProofSignature = null;
207
+ try { ({ verifyExternalProofSignature } = require("../../xoonya/proof_sdk")); } catch (_) { verifyExternalProofSignature = null; }
208
+ const { sig: _omit, ...unsignedDelivery } = delivery;
209
+ for (const entry of verifyExternalProofSignature ? delivery.sig.signatures : []) {
210
+ const key = keys.keys.find((item) => item.kid === entry.kid);
211
+ const viaXoonya = verifyExternalProofSignature({ material: receiptLib.canonicalize(unsignedDelivery), signature: entry.value, algorithm: entry.alg, public_key: key.public_key_spki_der_base64, expected_key_ref: entry.kid });
212
+ assert.strictEqual(viaXoonya.signature_valid && viaXoonya.trust_anchored, true, `xoonya verifies + anchors the ${entry.alg} leg`);
213
+ }
214
+ delete process.env.XYTARA_DELIVERY_RECEIPT_ED25519_KEY;
215
+ delete process.env.XYTARA_DELIVERY_RECEIPT_MLDSA65_KEY;
216
+ // Without keys the receipt is still issued but honestly unsigned (never a fake signature).
217
+ const unsignedPaid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("ok") });
218
+ assert.strictEqual(unsignedPaid.json.delivery_receipt.sig.state, "unsigned_signing_key_not_configured", "no keys -> honestly unsigned");
219
+ assert.strictEqual(receiptLib.verifyDeliveryReceipt(unsignedPaid.json.delivery_receipt, keys.keys).valid, false, "unsigned receipt never verifies");
220
+ // The shared module is byte-identical in the runtime and the MCP executor.
221
+ const fs = require("fs");
222
+ const path = require("path");
223
+ assert.ok(fs.readFileSync(path.join(__dirname, "..", "lib", "delivery_receipt.js")).equals(fs.readFileSync(path.join(__dirname, "..", "services", "mcp-execution-target", "delivery_receipt.js"))), "delivery_receipt.js copies must stay byte-identical");
224
+
225
+ // The facilitator must receive a clean v2 PaymentRequirements: no `resource` field (which
226
+ // strict facilitators like CDP reject), and it must equal the payment payload's `accepted`.
227
+ const verifyBody = facilitator.calls.lastVerifyBody;
228
+ assert.ok(verifyBody && verifyBody.paymentRequirements, "facilitator must receive paymentRequirements");
229
+ assert.strictEqual(verifyBody.paymentRequirements.resource, undefined, "paymentRequirements must not carry a resource field (v2)");
230
+ assert.deepStrictEqual(verifyBody.paymentRequirements, verifyBody.paymentPayload.accepted, "paymentRequirements must equal paymentPayload.accepted");
231
+
232
+ // 7. Native routes must not be disabled by x402_facilitator: a native paid attempt
233
+ // falls back to local_signed semantics, never payment_verification_mode_unsupported.
234
+ const nativeAttempt = await post(baseUrl, "/v1/commands/execute", execBody, {
235
+ "x-wallet-id": "wallet-not-registered",
236
+ "x-payment": "encoded",
237
+ "x-payment-signature": "sig"
238
+ });
239
+ assert.notStrictEqual(nativeAttempt.json && nativeAttempt.json.reason, "payment_verification_mode_unsupported", "native route must fall back to local_signed under x402_facilitator");
240
+
241
+ // 7b. CDP-style facilitator: a non-2xx verify carrying isValid:false is a verification
242
+ // failure, not an unreachable transport error.
243
+ const cdpInvalid = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("cdp-invalid") });
244
+ assert.strictEqual(cdpInvalid.status, 402, "cdp-style 400 verify must still 402");
245
+ assert.strictEqual(cdpInvalid.json.reason, "payment_verification_failed", "cdp-style 400 must map to payment_verification_failed, not facilitator_unreachable");
246
+ assert.strictEqual(cdpInvalid.json.detail, "invalid_payload", "cdp-style verify detail must surface invalidReason");
247
+
248
+ // 7c. CDP-style settle failure: a non-2xx settle carrying success:false is a settlement failure.
249
+ const cdpSettleFail = await post(baseUrl, "/x402/commands/execute", execBody, { "payment-signature": paymentHeader("cdp-settle-fail") });
250
+ assert.strictEqual(cdpSettleFail.status, 402, "cdp-style 400 settle must still 402");
251
+ assert.strictEqual(cdpSettleFail.json.reason, "payment_settlement_failed", "cdp-style 400 settle must map to payment_settlement_failed");
252
+
253
+ // 8. Self-serve credits top-up: unpaid -> 402 advertising accepts + credits_per_topup.
254
+ const topupUnpaid = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" });
255
+ assert.strictEqual(topupUnpaid.status, 402, "unpaid topup must 402");
256
+ assert.ok(Array.isArray(topupUnpaid.json.payment_required.accepts), "topup 402 must advertise accepts");
257
+ assert.strictEqual(typeof topupUnpaid.json.payment_required.credits_per_topup, "number", "topup 402 must state credits_per_topup");
258
+
259
+ // 9. Paid top-up -> 200, credits minted to the account, with a settlement receipt.
260
+ const topupPaid = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" }, { "payment-signature": paymentHeader("ok") });
261
+ assert.strictEqual(topupPaid.status, 200, "paid topup must 200");
262
+ assert.strictEqual(topupPaid.json.ok, true, "paid topup must be ok");
263
+ assert.strictEqual(typeof topupPaid.json.credit_grant, "object", "paid topup must return a credit grant");
264
+ const grantedUnits = Number(topupPaid.json.balance.available_units);
265
+ assert.ok(grantedUnits > 0, "paid topup must leave a positive credit balance");
266
+ assert.strictEqual(typeof topupPaid.headers.get("payment-response"), "string", "paid topup must carry a settlement receipt");
267
+
268
+ // 10. Replayed top-up (same payment nonce) is idempotent: no double-mint.
269
+ const topupReplay = await post(baseUrl, "/x402/credits/topup", { account_id: "acct_topup" }, { "payment-signature": paymentHeader("ok") });
270
+ assert.strictEqual(topupReplay.status, 200, "replayed topup must still 200");
271
+ assert.strictEqual(Number(topupReplay.json.balance.available_units), grantedUnits, "replayed topup must not mint credits twice");
272
+
273
+ console.log("xytara verify:x402-facilitator passed");
274
+ } finally {
275
+ await new Promise((resolve) => server.close(resolve));
276
+ await new Promise((resolve) => facilitator.server.close(resolve));
277
+ if (previous.mode === undefined) delete process.env.XYTARA_PAYMENT_VERIFICATION_MODE; else process.env.XYTARA_PAYMENT_VERIFICATION_MODE = previous.mode;
278
+ if (previous.accepts === undefined) delete process.env.XYTARA_X402_ACCEPTS_JSON; else process.env.XYTARA_X402_ACCEPTS_JSON = previous.accepts;
279
+ if (previous.facUrl === undefined) delete process.env.XYTARA_X402_FACILITATOR_URL; else process.env.XYTARA_X402_FACILITATOR_URL = previous.facUrl;
280
+ }
281
+ }
282
+
283
+ main().catch((error) => {
284
+ console.error(error && error.message ? error.message : error);
285
+ process.exitCode = 1;
286
+ });