xytara 3.1.13 → 3.1.15

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 (68) hide show
  1. package/.env.example +38 -4
  2. package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
  3. package/REAL_PAYMENT_SETUP.md +14 -7
  4. package/RELEASE_NOTES.md +9 -4
  5. package/fixtures/catalog.js +22 -0
  6. package/lib/a2a_rpc.js +287 -280
  7. package/lib/account_auth.js +585 -585
  8. package/lib/agent_identity.js +162 -162
  9. package/lib/asyncapi_contract.js +3 -3
  10. package/lib/bolt_adapter.js +187 -187
  11. package/lib/bolt_payment_front.js +224 -224
  12. package/lib/buyer_event_log.js +215 -0
  13. package/lib/buyer_safety_policy.js +132 -0
  14. package/lib/checkout_processor_adapter.js +217 -217
  15. package/lib/command_flow.js +911 -722
  16. package/lib/commerce_artifacts.js +591 -591
  17. package/lib/commerce_bridge.js +274 -274
  18. package/lib/commerce_cases.js +399 -399
  19. package/lib/commerce_client.js +7694 -7694
  20. package/lib/commerce_quotes.js +29 -0
  21. package/lib/commerce_runtime.js +322 -322
  22. package/lib/execution_dedupe.js +86 -86
  23. package/lib/external_credit_bridge.js +142 -142
  24. package/lib/http_transport.js +99 -81
  25. package/lib/l402_adapter.js +241 -241
  26. package/lib/l402_payment_front.js +265 -265
  27. package/lib/mpp_adapter.js +237 -237
  28. package/lib/openapi_contract.js +4 -0
  29. package/lib/payment_config.js +257 -253
  30. package/lib/payment_verification.js +357 -357
  31. package/lib/provider_payout_execution.js +155 -155
  32. package/lib/provider_registry.js +852 -842
  33. package/lib/rate_limiter.js +81 -81
  34. package/lib/runtime_state_store.js +495 -369
  35. package/lib/settlement_bsv_live.js +600 -600
  36. package/lib/settlement_bsv_teranode.js +117 -117
  37. package/lib/stripe_mpp_payment_front.js +258 -258
  38. package/lib/x402_facilitator.js +198 -191
  39. package/lib/x402_lane_contract.js +5 -3
  40. package/lib/x402_settlement_recovery.js +175 -0
  41. package/package.json +9 -3
  42. package/scripts/buyer_events_daily_report.js +79 -0
  43. package/scripts/state_backup_offsite.js +196 -0
  44. package/scripts/verify_a2a_rpc.js +131 -105
  45. package/scripts/verify_agent_identity.js +153 -153
  46. package/scripts/verify_all.js +1 -1
  47. package/scripts/verify_bolt_adapter.js +128 -128
  48. package/scripts/verify_buyer_event_log.js +123 -0
  49. package/scripts/verify_buyer_safety.js +141 -0
  50. package/scripts/verify_cdp_facilitator.js +110 -110
  51. package/scripts/verify_checkout_processor.js +109 -109
  52. package/scripts/verify_commerce_bridge.js +177 -177
  53. package/scripts/verify_execution_dedupe.js +189 -189
  54. package/scripts/verify_execution_surfacing.js +76 -76
  55. package/scripts/verify_final_activation.js +191 -191
  56. package/scripts/verify_l402_adapter.js +86 -86
  57. package/scripts/verify_lifecycle_spend_binding.js +88 -0
  58. package/scripts/verify_mpp_adapter.js +128 -128
  59. package/scripts/verify_production_readiness.js +268 -268
  60. package/scripts/verify_rate_limiter.js +52 -52
  61. package/scripts/verify_refund_credit.js +106 -106
  62. package/scripts/verify_release_candidate.js +81 -81
  63. package/scripts/verify_runtime_state_guard.js +117 -0
  64. package/scripts/verify_service.js +15421 -15421
  65. package/scripts/verify_verify_execute_settle.js +209 -208
  66. package/scripts/verify_x402_facilitator.js +307 -286
  67. package/scripts/verify_x402_settlement_recovery.js +153 -0
  68. package/server.js +12242 -11974
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+
3
+ // End-to-end check (real local server, mock facilitator + mock chain RPC) that a facilitator settle
4
+ // timeout never leaves a buyer charged with nothing:
5
+ // - settle times out but the authorization IS used on-chain -> the result is delivered;
6
+ // - settle times out and the chain does not show it yet -> held, and resending the SAME payment
7
+ // delivers once the chain confirms (no second settle, no re-execution);
8
+ // - a normally settled payment resent -> the stored result is returned (replayed), not re-run;
9
+ // - the same payment resent for a DIFFERENT request -> refused (on-chain data can't fetch results).
10
+
11
+ const assert = require("assert");
12
+ const http = require("http");
13
+ const { AUTHORIZATION_STATE_SELECTOR } = require("../lib/x402_settlement_recovery");
14
+
15
+ const PAYER = "0x1111111111111111111111111111111111111111";
16
+ const ACCEPTS = [{
17
+ scheme: "exact",
18
+ network: "eip155:84532",
19
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
20
+ amount: "1000",
21
+ payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
22
+ extra: { name: "USDC", version: "2" },
23
+ maxTimeoutSeconds: 60
24
+ }];
25
+
26
+ function listen(handler) {
27
+ const server = http.createServer((req, res) => {
28
+ const chunks = [];
29
+ req.on("data", (chunk) => chunks.push(chunk));
30
+ req.on("end", () => {
31
+ let body = {};
32
+ try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
33
+ handler(req, res, body);
34
+ });
35
+ });
36
+ return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, port: server.address().port })));
37
+ }
38
+
39
+ function reply(res, payload) {
40
+ const text = JSON.stringify(payload);
41
+ res.writeHead(200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
42
+ res.end(text);
43
+ }
44
+
45
+ function paymentHeader(signature, nonceByte) {
46
+ return Buffer.from(JSON.stringify({
47
+ x402Version: 2,
48
+ accepted: ACCEPTS[0],
49
+ payload: {
50
+ signature,
51
+ authorization: { from: PAYER, to: ACCEPTS[0].payTo, value: "1000", validAfter: "0", validBefore: String(Math.floor(Date.now() / 1000) + 600), nonce: `0x${nonceByte.repeat(32)}` }
52
+ }
53
+ })).toString("base64");
54
+ }
55
+
56
+ async function post(baseUrl, body, header) {
57
+ const res = await fetch(`${baseUrl}/x402/commands/execute`, {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json", "payment-signature": header },
60
+ body: JSON.stringify(body)
61
+ });
62
+ let json = null;
63
+ try { json = await res.json(); } catch (_) { json = null; }
64
+ return { status: res.status, json };
65
+ }
66
+
67
+ async function main() {
68
+ const calls = { settle: 0, rpc: 0 };
69
+ // Facilitator: verify always ok; settle hangs for signatures marked "hang", succeeds otherwise.
70
+ const facilitator = await listen((req, res, body) => {
71
+ const sig = String((body.paymentPayload && body.paymentPayload.payload && body.paymentPayload.payload.signature) || "");
72
+ if (req.url === "/verify") return reply(res, { isValid: true, payer: PAYER });
73
+ if (req.url === "/settle") {
74
+ calls.settle += 1;
75
+ if (sig.startsWith("hang")) return; // never answers: the client times out
76
+ return reply(res, { success: true, transaction: "0xmocktx", network: ACCEPTS[0].network, payer: PAYER });
77
+ }
78
+ res.writeHead(404); res.end();
79
+ });
80
+ // Chain RPC: authorizationState(PAYER, nonce) -> used for nonces in usedNonces.
81
+ const usedNonces = new Set();
82
+ const rpc = await listen((req, res, body) => {
83
+ calls.rpc += 1;
84
+ const data = String((body.params && body.params[0] && body.params[0].data) || "");
85
+ assert.ok(data.startsWith(AUTHORIZATION_STATE_SELECTOR), "RPC asked for authorizationState");
86
+ const nonce = `0x${data.slice(-64)}`;
87
+ reply(res, { jsonrpc: "2.0", id: body.id, result: `0x${(usedNonces.has(nonce) ? 1 : 0).toString(16).padStart(64, "0")}` });
88
+ });
89
+
90
+ const keys = ["XYTARA_PAYMENT_VERIFICATION_MODE", "XYTARA_X402_ACCEPTS_JSON", "XYTARA_X402_FACILITATOR_URL", "XYTARA_X402_FACILITATOR_TIMEOUT_MS", "XYTARA_RPC_URL_EIP155_84532"];
91
+ const previous = Object.fromEntries(keys.map((key) => [key, process.env[key]]));
92
+ process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "x402_facilitator";
93
+ process.env.XYTARA_X402_ACCEPTS_JSON = JSON.stringify(ACCEPTS);
94
+ process.env.XYTARA_X402_FACILITATOR_URL = `http://127.0.0.1:${facilitator.port}`;
95
+ process.env.XYTARA_X402_FACILITATOR_TIMEOUT_MS = "1000";
96
+ process.env.XYTARA_RPC_URL_EIP155_84532 = `http://127.0.0.1:${rpc.port}`;
97
+
98
+ delete require.cache[require.resolve("../server")];
99
+ const { createServer } = require("../server");
100
+ const server = createServer();
101
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
102
+ const baseUrl = `http://127.0.0.1:${server.address().port}`;
103
+ const job = { account_id: "acct_recovery", task_ref: "trust.verify", body: { subject_id: "s-1" } };
104
+ try {
105
+ // A. Settle times out, but the chain shows the authorization used -> delivered.
106
+ usedNonces.add(`0x${"a1".repeat(32)}`);
107
+ const recovered = await post(baseUrl, job, paymentHeader("hang-a", "a1"));
108
+ assert.strictEqual(recovered.status, 200, "a settled-on-chain payment must deliver despite the facilitator timeout");
109
+ assert.ok(recovered.json.delivery_receipt || recovered.json.command || recovered.json.ok, "the result is delivered");
110
+
111
+ // B. Settle times out and the chain does not show it (yet) -> held, never "pay again".
112
+ const held = await post(baseUrl, job, paymentHeader("hang-b", "b2"));
113
+ assert.strictEqual(held.status, 502);
114
+ assert.strictEqual(held.json.reason, "settlement_not_confirmed");
115
+ assert.ok(/SAME payment signature/.test(held.json.detail), "the buyer is told to resend the same payment, not a new one");
116
+ // The transfer lands later; resending the same payment delivers the held result.
117
+ usedNonces.add(`0x${"b2".repeat(32)}`);
118
+ const settleCallsBefore = calls.settle;
119
+ const delivered = await post(baseUrl, job, paymentHeader("hang-b", "b2"));
120
+ assert.strictEqual(delivered.status, 200, "resending the same payment delivers once it is on-chain");
121
+ assert.strictEqual(delivered.json.replayed, true);
122
+ assert.strictEqual(calls.settle, settleCallsBefore, "no second settlement attempt");
123
+
124
+ // C. The same payment resent for a different request -> refused.
125
+ const other = await post(baseUrl, { ...job, body: { subject_id: "someone-else" } }, paymentHeader("hang-b", "b2"));
126
+ assert.strictEqual(other.status, 409);
127
+ assert.strictEqual(other.json.reason, "payment_already_used");
128
+
129
+ // D. Normal settle, then a resend (e.g. the buyer lost the response) -> stored result, no re-run.
130
+ const first = await post(baseUrl, job, paymentHeader("ok-d", "d4"));
131
+ assert.strictEqual(first.status, 200);
132
+ const settles = calls.settle;
133
+ const again = await post(baseUrl, job, paymentHeader("ok-d", "d4"));
134
+ assert.strictEqual(again.status, 200);
135
+ assert.strictEqual(again.json.replayed, true, "a resent settled payment returns the stored result");
136
+ assert.strictEqual(calls.settle, settles, "and is not settled again");
137
+
138
+ console.log("xytara verify:x402-settlement-recovery passed");
139
+ } finally {
140
+ await new Promise((resolve) => server.close(resolve));
141
+ facilitator.server.closeAllConnections && facilitator.server.closeAllConnections();
142
+ await new Promise((resolve) => facilitator.server.close(resolve));
143
+ await new Promise((resolve) => rpc.server.close(resolve));
144
+ for (const key of keys) {
145
+ if (previous[key] === undefined) delete process.env[key]; else process.env[key] = previous[key];
146
+ }
147
+ }
148
+ }
149
+
150
+ main().catch((error) => {
151
+ console.error(error && error.stack ? error.stack : error);
152
+ process.exit(1);
153
+ });