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
@@ -1,191 +1,198 @@
1
- "use strict";
2
-
3
- const { buildCdpBearerToken } = require("./cdp_jwt");
4
-
5
- // Self-contained x402 v2 facilitator client for the main runtime.
6
- //
7
- // This mirrors the proven logic in services/mcp-execution-target/server.js but takes
8
- // all configuration as explicit arguments so it has no dependency on any global state
9
- // and can be reused by any paid route. It is intentionally duplicated rather than shared
10
- // with the standalone MCP executor, which deploys from its own subdirectory.
11
- //
12
- // The trust model is the same one proven live: this process never verifies balances or
13
- // broadcasts transfers itself. It asks an x402 facilitator to verify the buyer's signed
14
- // EIP-3009 authorization and to settle it on-chain, and refuses to serve work unless the
15
- // facilitator confirms both. If the facilitator is unreachable, access fails closed.
16
-
17
- function normalizeString(value) {
18
- return typeof value === "string" ? value.trim() : "";
19
- }
20
-
21
- function normalizeAddress(value) {
22
- return normalizeString(value).toLowerCase();
23
- }
24
-
25
- // Decode a PAYMENT-SIGNATURE header into an x402 v2 payment payload.
26
- function decodePaymentSignatureHeader(value) {
27
- const raw = normalizeString(value);
28
- if (!raw) return null;
29
- for (const encoding of ["base64url", "base64"]) {
30
- try {
31
- const decoded = Buffer.from(raw, encoding).toString("utf8");
32
- const parsed = JSON.parse(decoded);
33
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
34
- if (Number(parsed.x402Version) !== 2) continue;
35
- if (!parsed.accepted || typeof parsed.accepted !== "object") continue;
36
- if (!parsed.payload || typeof parsed.payload !== "object") continue;
37
- return parsed;
38
- } catch (_) {
39
- // try the next encoding
40
- }
41
- }
42
- return null;
43
- }
44
-
45
- // Find the advertised requirements entry the buyer claims to be paying.
46
- function matchAcceptedRequirements(paymentPayload, accepts) {
47
- const accepted = paymentPayload && paymentPayload.accepted ? paymentPayload.accepted : null;
48
- if (!accepted || !Array.isArray(accepts)) return null;
49
- return (
50
- accepts.find(
51
- (entry) =>
52
- entry.scheme === normalizeString(accepted.scheme) &&
53
- entry.network === normalizeString(accepted.network) &&
54
- normalizeAddress(entry.asset) === normalizeAddress(accepted.asset) &&
55
- normalizeAddress(entry.payTo) === normalizeAddress(accepted.payTo) &&
56
- String(entry.amount) === String(accepted.amount)
57
- ) || null
58
- );
59
- }
60
-
61
- function createFacilitatorClient(config) {
62
- const url = normalizeString(config && config.url).replace(/\/$/, "");
63
- const headers = config && config.headers && typeof config.headers === "object" ? config.headers : {};
64
- const timeoutMs = Math.max(1000, Number(config && config.timeoutMs) || 15000);
65
- // Optional Coinbase CDP auth: when a CDP Secret API Key is configured, a fresh short-lived
66
- // EdDSA Bearer JWT is minted per request (CDP rejects static tokens). This is what unlocks
67
- // the CDP-hosted facilitator and Base mainnet settlement.
68
- const cdp = config && config.cdp && typeof config.cdp === "object" ? config.cdp : null;
69
- const cdpKeyId = cdp ? normalizeString(cdp.key_id || cdp.keyId) : "";
70
- const cdpKeySecret = cdp ? String((cdp.key_secret || cdp.keySecret) || "").trim() : "";
71
- const cdpEnabled = Boolean(cdpKeyId && cdpKeySecret);
72
-
73
- async function post(path, body) {
74
- const controller = new AbortController();
75
- const timer = setTimeout(() => controller.abort(), timeoutMs);
76
- let authHeaders = {};
77
- if (cdpEnabled) {
78
- try {
79
- const target = new URL(`${url}${path}`);
80
- const token = buildCdpBearerToken({ keyId: cdpKeyId, keySecret: cdpKeySecret, method: "POST", host: target.host, path: target.pathname });
81
- authHeaders = { Authorization: `Bearer ${token}` };
82
- } catch (error) {
83
- clearTimeout(timer);
84
- return { ok: false, status: 0, json: null, error: error && error.message ? error.message : "cdp_jwt_failed" };
85
- }
86
- }
87
- try {
88
- const response = await fetch(`${url}${path}`, {
89
- method: "POST",
90
- headers: { "content-type": "application/json", accept: "application/json", ...headers, ...authHeaders },
91
- body: JSON.stringify(body),
92
- signal: controller.signal
93
- });
94
- const text = await response.text();
95
- let json = null;
96
- try {
97
- json = text ? JSON.parse(text) : null;
98
- } catch (_) {
99
- json = null;
100
- }
101
- return { ok: response.ok, status: response.status, json, bodyExcerpt: text ? text.slice(0, 400) : "" };
102
- } catch (error) {
103
- return { ok: false, status: 0, json: null, error: error && error.message ? error.message : "facilitator_request_failed" };
104
- } finally {
105
- clearTimeout(timer);
106
- }
107
- }
108
-
109
- return {
110
- verify: (payload, requirements) => post("/verify", { x402Version: 2, paymentPayload: payload, paymentRequirements: requirements }),
111
- settle: (payload, requirements) => post("/settle", { x402Version: 2, paymentPayload: payload, paymentRequirements: requirements })
112
- };
113
- }
114
-
115
- // Verify a buyer's x402 payment against the advertised requirements via the facilitator.
116
- // Returns { ok:true, authorization } or { ok:false, status:402, reason, detail }.
117
- async function verifyX402Payment({ paymentHeader, accepts, resourceUrl, facilitator }) {
118
- const header = normalizeString(paymentHeader);
119
- if (!header) {
120
- return { ok: false, status: 402, reason: "payment_required" };
121
- }
122
- const paymentPayload = decodePaymentSignatureHeader(header);
123
- if (!paymentPayload) {
124
- return { ok: false, status: 402, reason: "payment_signature_invalid", detail: "PAYMENT-SIGNATURE must be a base64 JSON x402 v2 payment payload" };
125
- }
126
- const paymentRequirements = matchAcceptedRequirements(paymentPayload, accepts);
127
- if (!paymentRequirements) {
128
- return { ok: false, status: 402, reason: "payment_requirements_mismatch", detail: "accepted requirements do not match any advertised payment option" };
129
- }
130
- // The x402 v2 PaymentRequirements shape is exactly {scheme, network, asset, amount, payTo,
131
- // maxTimeoutSeconds, extra} with no `resource` field (resource lives on the payload in v2).
132
- // Adding one makes strict facilitators (e.g. Coinbase CDP) reject the request with HTTP 400
133
- // and makes paymentRequirements diverge from paymentPayload.accepted, so send it clean.
134
- // resourceUrl is accepted for API compatibility but intentionally not sent to the facilitator.
135
- void resourceUrl;
136
- const requirementsForFacilitator = { ...paymentRequirements };
137
- const verification = await facilitator.verify(paymentPayload, requirementsForFacilitator);
138
- // A facilitator signals an invalid payment either with a 200 and isValid:false (x402.org) or
139
- // with a non-2xx status and the same body (Coinbase CDP returns HTTP 400). Treat any response
140
- // that carries an isValid flag as a verification result; only a missing/garbled body is a
141
- // genuine transport failure.
142
- if (!verification.json || typeof verification.json.isValid === "undefined") {
143
- const body = verification.bodyExcerpt ? ` body=${verification.bodyExcerpt}` : "";
144
- return { ok: false, status: 402, reason: "facilitator_unreachable", detail: verification.error || `facilitator verify returned HTTP ${verification.status}${body}` };
145
- }
146
- if (verification.json.isValid !== true) {
147
- const detail = normalizeString(verification.json.invalidReason) || normalizeString(verification.json.invalidMessage) || "facilitator rejected the payment";
148
- return { ok: false, status: 402, reason: "payment_verification_failed", detail };
149
- }
150
- return {
151
- ok: true,
152
- authorization: {
153
- paymentPayload,
154
- paymentRequirements: requirementsForFacilitator,
155
- payer: normalizeString(verification.json.payer) || null
156
- }
157
- };
158
- }
159
-
160
- // Settle a verified payment on-chain via the facilitator (unless settlement is disabled).
161
- // Returns { ok:true, settlement } or { ok:false, status:402, reason, detail }.
162
- async function settleX402Payment({ authorization, facilitator, settle }) {
163
- if (settle === false) {
164
- return { ok: true, settlement: { success: true, skipped: true, reason: "settlement_disabled_by_operator", payer: authorization.payer } };
165
- }
166
- const settlement = await facilitator.settle(authorization.paymentPayload, authorization.paymentRequirements);
167
- // As with verify, a facilitator may report a failed settlement with a non-2xx status and a
168
- // body carrying success:false; only a missing/garbled body is a genuine transport failure.
169
- if (!settlement.json || typeof settlement.json.success === "undefined") {
170
- const body = settlement.bodyExcerpt ? ` body=${settlement.bodyExcerpt}` : "";
171
- return { ok: false, status: 402, reason: "facilitator_unreachable", detail: settlement.error || `facilitator settle returned HTTP ${settlement.status}${body}` };
172
- }
173
- if (settlement.json.success !== true) {
174
- const detail = normalizeString(settlement.json.errorReason) || normalizeString(settlement.json.errorMessage) || "facilitator could not settle the payment";
175
- return { ok: false, status: 402, reason: "payment_settlement_failed", detail };
176
- }
177
- return { ok: true, settlement: settlement.json };
178
- }
179
-
180
- function encodePaymentResponse(settlement) {
181
- return Buffer.from(JSON.stringify(settlement), "utf8").toString("base64");
182
- }
183
-
184
- module.exports = {
185
- decodePaymentSignatureHeader,
186
- matchAcceptedRequirements,
187
- createFacilitatorClient,
188
- verifyX402Payment,
189
- settleX402Payment,
190
- encodePaymentResponse
191
- };
1
+ "use strict";
2
+
3
+ const { buildCdpBearerToken } = require("./cdp_jwt");
4
+
5
+ // Self-contained x402 v2 facilitator client for the main runtime.
6
+ //
7
+ // This mirrors the proven logic in services/mcp-execution-target/server.js but takes
8
+ // all configuration as explicit arguments so it has no dependency on any global state
9
+ // and can be reused by any paid route. It is intentionally duplicated rather than shared
10
+ // with the standalone MCP executor, which deploys from its own subdirectory.
11
+ //
12
+ // The trust model is the same one proven live: this process never verifies balances or
13
+ // broadcasts transfers itself. It asks an x402 facilitator to verify the buyer's signed
14
+ // EIP-3009 authorization and to settle it on-chain, and refuses to serve work unless the
15
+ // facilitator confirms both. If the facilitator is unreachable, access fails closed.
16
+
17
+ function normalizeString(value) {
18
+ return typeof value === "string" ? value.trim() : "";
19
+ }
20
+
21
+ function normalizeAddress(value) {
22
+ return normalizeString(value).toLowerCase();
23
+ }
24
+
25
+ // Decode a PAYMENT-SIGNATURE header into an x402 v2 payment payload.
26
+ function decodePaymentSignatureHeader(value) {
27
+ const raw = normalizeString(value);
28
+ if (!raw) return null;
29
+ for (const encoding of ["base64url", "base64"]) {
30
+ try {
31
+ const decoded = Buffer.from(raw, encoding).toString("utf8");
32
+ const parsed = JSON.parse(decoded);
33
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
34
+ if (Number(parsed.x402Version) !== 2) continue;
35
+ if (!parsed.accepted || typeof parsed.accepted !== "object") continue;
36
+ if (!parsed.payload || typeof parsed.payload !== "object") continue;
37
+ return parsed;
38
+ } catch (_) {
39
+ // try the next encoding
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ // Find the advertised requirements entry the buyer claims to be paying.
46
+ function matchAcceptedRequirements(paymentPayload, accepts) {
47
+ const accepted = paymentPayload && paymentPayload.accepted ? paymentPayload.accepted : null;
48
+ if (!accepted || !Array.isArray(accepts)) return null;
49
+ return (
50
+ accepts.find(
51
+ (entry) =>
52
+ entry.scheme === normalizeString(accepted.scheme) &&
53
+ entry.network === normalizeString(accepted.network) &&
54
+ normalizeAddress(entry.asset) === normalizeAddress(accepted.asset) &&
55
+ normalizeAddress(entry.payTo) === normalizeAddress(accepted.payTo) &&
56
+ String(entry.amount) === String(accepted.amount)
57
+ ) || null
58
+ );
59
+ }
60
+
61
+ function createFacilitatorClient(config) {
62
+ const url = normalizeString(config && config.url).replace(/\/$/, "");
63
+ const headers = config && config.headers && typeof config.headers === "object" ? config.headers : {};
64
+ const timeoutMs = Math.max(1000, Number(config && config.timeoutMs) || 15000);
65
+ // Optional Coinbase CDP auth: when a CDP Secret API Key is configured, a fresh short-lived
66
+ // EdDSA Bearer JWT is minted per request (CDP rejects static tokens). This is what unlocks
67
+ // the CDP-hosted facilitator and Base mainnet settlement.
68
+ const cdp = config && config.cdp && typeof config.cdp === "object" ? config.cdp : null;
69
+ const cdpKeyId = cdp ? normalizeString(cdp.key_id || cdp.keyId) : "";
70
+ const cdpKeySecret = cdp ? String((cdp.key_secret || cdp.keySecret) || "").trim() : "";
71
+ const cdpEnabled = Boolean(cdpKeyId && cdpKeySecret);
72
+
73
+ async function post(path, body) {
74
+ const controller = new AbortController();
75
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
76
+ let authHeaders = {};
77
+ if (cdpEnabled) {
78
+ try {
79
+ const target = new URL(`${url}${path}`);
80
+ const token = buildCdpBearerToken({ keyId: cdpKeyId, keySecret: cdpKeySecret, method: "POST", host: target.host, path: target.pathname });
81
+ authHeaders = { Authorization: `Bearer ${token}` };
82
+ } catch (error) {
83
+ clearTimeout(timer);
84
+ return { ok: false, status: 0, json: null, error: error && error.message ? error.message : "cdp_jwt_failed" };
85
+ }
86
+ }
87
+ try {
88
+ const response = await fetch(`${url}${path}`, {
89
+ method: "POST",
90
+ headers: { "content-type": "application/json", accept: "application/json", ...headers, ...authHeaders },
91
+ body: JSON.stringify(body),
92
+ signal: controller.signal
93
+ });
94
+ const text = await response.text();
95
+ let json = null;
96
+ try {
97
+ json = text ? JSON.parse(text) : null;
98
+ } catch (_) {
99
+ json = null;
100
+ }
101
+ return { ok: response.ok, status: response.status, json, bodyExcerpt: text ? text.slice(0, 400) : "" };
102
+ } catch (error) {
103
+ return { ok: false, status: 0, json: null, error: error && error.message ? error.message : "facilitator_request_failed" };
104
+ } finally {
105
+ clearTimeout(timer);
106
+ }
107
+ }
108
+
109
+ return {
110
+ verify: (payload, requirements) => post("/verify", { x402Version: 2, paymentPayload: payload, paymentRequirements: requirements }),
111
+ settle: (payload, requirements) => post("/settle", { x402Version: 2, paymentPayload: payload, paymentRequirements: requirements })
112
+ };
113
+ }
114
+
115
+ // Verify a buyer's x402 payment against the advertised requirements via the facilitator.
116
+ // Returns { ok:true, authorization } or { ok:false, status:402, reason, detail }.
117
+ async function verifyX402Payment({ paymentHeader, accepts, resourceUrl, facilitator }) {
118
+ const header = normalizeString(paymentHeader);
119
+ if (!header) {
120
+ return { ok: false, status: 402, reason: "payment_required" };
121
+ }
122
+ const paymentPayload = decodePaymentSignatureHeader(header);
123
+ if (!paymentPayload) {
124
+ return { ok: false, status: 402, reason: "payment_signature_invalid", detail: "PAYMENT-SIGNATURE must be a base64 JSON x402 v2 payment payload" };
125
+ }
126
+ const paymentRequirements = matchAcceptedRequirements(paymentPayload, accepts);
127
+ if (!paymentRequirements) {
128
+ return { ok: false, status: 402, reason: "payment_requirements_mismatch", detail: "accepted requirements do not match any advertised payment option" };
129
+ }
130
+ // The x402 v2 PaymentRequirements shape is exactly {scheme, network, asset, amount, payTo,
131
+ // maxTimeoutSeconds, extra} with no `resource` field (resource lives on the payload in v2).
132
+ // Adding one makes strict facilitators (e.g. Coinbase CDP) reject the request with HTTP 400
133
+ // and makes paymentRequirements diverge from paymentPayload.accepted, so send it clean.
134
+ // resourceUrl is accepted for API compatibility but intentionally not sent to the facilitator.
135
+ void resourceUrl;
136
+ // maxTimeoutSeconds is not part of the match (validBefore inside the signed authorization is what
137
+ // binds time), so echo the buyer's value: requirements must equal paymentPayload.accepted even if
138
+ // the buyer signed against an older challenge with a different window.
139
+ const acceptedTimeout = Number(paymentPayload.accepted && paymentPayload.accepted.maxTimeoutSeconds);
140
+ const requirementsForFacilitator = {
141
+ ...paymentRequirements,
142
+ ...(Number.isFinite(acceptedTimeout) && acceptedTimeout > 0 ? { maxTimeoutSeconds: acceptedTimeout } : {})
143
+ };
144
+ const verification = await facilitator.verify(paymentPayload, requirementsForFacilitator);
145
+ // A facilitator signals an invalid payment either with a 200 and isValid:false (x402.org) or
146
+ // with a non-2xx status and the same body (Coinbase CDP returns HTTP 400). Treat any response
147
+ // that carries an isValid flag as a verification result; only a missing/garbled body is a
148
+ // genuine transport failure.
149
+ if (!verification.json || typeof verification.json.isValid === "undefined") {
150
+ const body = verification.bodyExcerpt ? ` body=${verification.bodyExcerpt}` : "";
151
+ return { ok: false, status: 402, reason: "facilitator_unreachable", detail: verification.error || `facilitator verify returned HTTP ${verification.status}${body}` };
152
+ }
153
+ if (verification.json.isValid !== true) {
154
+ const detail = normalizeString(verification.json.invalidReason) || normalizeString(verification.json.invalidMessage) || "facilitator rejected the payment";
155
+ return { ok: false, status: 402, reason: "payment_verification_failed", detail };
156
+ }
157
+ return {
158
+ ok: true,
159
+ authorization: {
160
+ paymentPayload,
161
+ paymentRequirements: requirementsForFacilitator,
162
+ payer: normalizeString(verification.json.payer) || null
163
+ }
164
+ };
165
+ }
166
+
167
+ // Settle a verified payment on-chain via the facilitator (unless settlement is disabled).
168
+ // Returns { ok:true, settlement } or { ok:false, status:402, reason, detail }.
169
+ async function settleX402Payment({ authorization, facilitator, settle }) {
170
+ if (settle === false) {
171
+ return { ok: true, settlement: { success: true, skipped: true, reason: "settlement_disabled_by_operator", payer: authorization.payer } };
172
+ }
173
+ const settlement = await facilitator.settle(authorization.paymentPayload, authorization.paymentRequirements);
174
+ // As with verify, a facilitator may report a failed settlement with a non-2xx status and a
175
+ // body carrying success:false; only a missing/garbled body is a genuine transport failure.
176
+ if (!settlement.json || typeof settlement.json.success === "undefined") {
177
+ const body = settlement.bodyExcerpt ? ` body=${settlement.bodyExcerpt}` : "";
178
+ return { ok: false, status: 402, reason: "facilitator_unreachable", detail: settlement.error || `facilitator settle returned HTTP ${settlement.status}${body}` };
179
+ }
180
+ if (settlement.json.success !== true) {
181
+ const detail = normalizeString(settlement.json.errorReason) || normalizeString(settlement.json.errorMessage) || "facilitator could not settle the payment";
182
+ return { ok: false, status: 402, reason: "payment_settlement_failed", detail };
183
+ }
184
+ return { ok: true, settlement: settlement.json };
185
+ }
186
+
187
+ function encodePaymentResponse(settlement) {
188
+ return Buffer.from(JSON.stringify(settlement), "utf8").toString("base64");
189
+ }
190
+
191
+ module.exports = {
192
+ decodePaymentSignatureHeader,
193
+ matchAcceptedRequirements,
194
+ createFacilitatorClient,
195
+ verifyX402Payment,
196
+ settleX402Payment,
197
+ encodePaymentResponse
198
+ };
@@ -26,7 +26,8 @@ function buildX402LanePack() {
26
26
  lane_state: "first_party_x402_present",
27
27
  default_protocol: centerSummary.default_protocol,
28
28
  default_payment_protocol: "x402",
29
- default_settlement_mode: centerSummary.default_settlement_mode,
29
+ // The x402 lane settles USDC on Base through an x402 facilitator (EIP-3009 exact scheme).
30
+ default_settlement_mode: "x402_evm_exact",
30
31
  default_transaction_center: center,
31
32
  default_path: defaultX402Path,
32
33
  release_default_path: releasePack.default_path,
@@ -93,8 +94,9 @@ function buildX402LanePack() {
93
94
  }
94
95
  ],
95
96
  settlement_posture: {
96
- default_mode: centerSummary.default_settlement_mode,
97
- current_native_lane: "bsv_teranode",
97
+ default_mode: "x402_evm_exact",
98
+ current_native_lane: "x402_evm_exact",
99
+ runtime_default_settlement_mode: centerSummary.default_settlement_mode,
98
100
  native_readiness_surface: "/v1/settlement/bsv-teranode/readiness",
99
101
  challenge_quote_hints: [
100
102
  "payment quotes carry settlement-aware runtime linkage",
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+
3
+ const { decodePaymentSignatureHeader } = require("./x402_facilitator");
4
+
5
+ // Settlement-outcome recovery for the deferred x402 lane.
6
+ //
7
+ // A facilitator /settle that times out or drops the connection does NOT mean the transfer failed:
8
+ // the facilitator may already have broadcast it. Telling the buyer "you were not charged" and
9
+ // withholding the result could leave them charged with nothing. Instead we ask the chain: an
10
+ // EIP-3009 authorization that was used is a settled payment (USDC authorizationState(authorizer,
11
+ // nonce) == true). And every settled (or not-yet-confirmed) result is kept briefly under its
12
+ // payment nonce, so a buyer who lost the response — or was told to wait — can resend the SAME
13
+ // payment signature and receive the result instead of paying again.
14
+
15
+ const AUTHORIZATION_STATE_SELECTOR = "0xe94a0102"; // authorizationState(address,bytes32)
16
+ const DEFAULT_RPC_URLS = {
17
+ "eip155:8453": "https://mainnet.base.org",
18
+ "eip155:84532": "https://sepolia.base.org"
19
+ };
20
+ const REPLAY_TTL_MS = 15 * 60 * 1000;
21
+ const REPLAY_MAX = 1000;
22
+
23
+ function rpcUrlFor(network) {
24
+ const key = `XYTARA_RPC_URL_${String(network || "").replace(/[^0-9A-Za-z]/g, "_").toUpperCase()}`;
25
+ const fromEnv = String(process.env[key] || "").trim();
26
+ return fromEnv || DEFAULT_RPC_URLS[String(network || "").toLowerCase()] || null;
27
+ }
28
+
29
+ function hex32(value, bytes) {
30
+ const clean = String(value || "").toLowerCase().replace(/^0x/, "");
31
+ if (!/^[0-9a-f]*$/.test(clean) || clean.length > bytes * 2) return null;
32
+ return clean.padStart(64, "0");
33
+ }
34
+
35
+ // true = authorization used (payment settled), false = not used, null = could not tell.
36
+ async function authorizationUsed({ network, asset, authorizer, nonce }, options = {}) {
37
+ const url = rpcUrlFor(network);
38
+ const from = hex32(authorizer, 20);
39
+ const n = hex32(nonce, 32);
40
+ if (!url || !from || !n || !/^0x[0-9a-fA-F]{40}$/.test(String(asset || ""))) return null;
41
+ const fetchImpl = options.fetch || fetch;
42
+ try {
43
+ const response = await fetchImpl(url, {
44
+ method: "POST",
45
+ headers: { "content-type": "application/json" },
46
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_call", params: [{ to: asset, data: `${AUTHORIZATION_STATE_SELECTOR}${from}${n}` }, "latest"] }),
47
+ signal: AbortSignal.timeout(options.timeoutMs || 5000)
48
+ });
49
+ const json = await response.json();
50
+ if (!json || typeof json.result !== "string" || !/^0x[0-9a-fA-F]+$/.test(json.result)) return null;
51
+ return BigInt(json.result) !== 0n;
52
+ } catch (_) {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ // A just-broadcast transfer may not be mined yet: poll a few times before concluding.
58
+ async function confirmAuthorizationUsed(ref, options = {}) {
59
+ const attempts = options.attempts || 3;
60
+ const delayMs = options.delayMs === undefined ? 2000 : options.delayMs;
61
+ let sawAnswer = false;
62
+ for (let i = 0; i < attempts; i += 1) {
63
+ const used = await authorizationUsed(ref, options);
64
+ if (used === true) return true;
65
+ if (used === false) sawAnswer = true;
66
+ if (i < attempts - 1 && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
67
+ }
68
+ return sawAnswer ? false : null;
69
+ }
70
+
71
+ // The on-chain reference of a deferred x402 verification (or of a raw payment header).
72
+ function authorizationRefFromVerification(verification) {
73
+ const deferred = verification && verification.deferred_settlement;
74
+ const auth = deferred && deferred.authorization;
75
+ const paymentPayload = auth && auth.paymentPayload;
76
+ const signed = paymentPayload && paymentPayload.payload && paymentPayload.payload.authorization;
77
+ const requirements = (auth && auth.paymentRequirements) || (paymentPayload && paymentPayload.accepted) || {};
78
+ if (!signed || !signed.nonce || !signed.from) return null;
79
+ return { network: requirements.network, asset: requirements.asset, authorizer: signed.from, nonce: signed.nonce };
80
+ }
81
+
82
+ function authorizationRefFromHeaders(headers) {
83
+ const decoded = decodePaymentSignatureHeader((headers || {})["payment-signature"]);
84
+ const signed = decoded && decoded.payload && decoded.payload.authorization;
85
+ if (!signed || !signed.nonce || !signed.from) return null;
86
+ return { network: decoded.accepted.network, asset: decoded.accepted.asset, authorizer: signed.from, nonce: signed.nonce };
87
+ }
88
+
89
+ function replayKey(ref) {
90
+ return ref ? `${String(ref.network || "").toLowerCase()}:${String(ref.nonce || "").toLowerCase()}` : null;
91
+ }
92
+
93
+ function ensureReplayStore(state) {
94
+ if (!(state.x402SettledReplays instanceof Map)) {
95
+ // In-memory only: non-enumerable, so it is never written to the state snapshot.
96
+ Object.defineProperty(state, "x402SettledReplays", { value: new Map(), enumerable: false, writable: true });
97
+ }
98
+ return state.x402SettledReplays;
99
+ }
100
+
101
+ function pruneReplays(store) {
102
+ const now = Date.now();
103
+ for (const [key, entry] of store) {
104
+ if (entry.expires_at_ms <= now) store.delete(key);
105
+ }
106
+ while (store.size > REPLAY_MAX) store.delete(store.keys().next().value);
107
+ }
108
+
109
+ // entry: { status: "settled" | "unconfirmed", ref, response?, finalize?: async (settlement) => response }
110
+ function rememberReplay(state, ref, entry) {
111
+ const key = replayKey(ref);
112
+ if (!key) return;
113
+ const store = ensureReplayStore(state);
114
+ pruneReplays(store);
115
+ store.set(key, { ...entry, ref, expires_at_ms: Date.now() + REPLAY_TTL_MS });
116
+ }
117
+
118
+ // A resent payment that we already settled (or were confirming) gets its result, not a re-run.
119
+ // The payment authorization becomes public on-chain once settled, so it is not enough on its own:
120
+ // the resent REQUEST must match the original too (request_digest), or nothing is returned.
121
+ async function resolveReplay(state, headers, options = {}) {
122
+ const ref = authorizationRefFromHeaders(headers);
123
+ const key = replayKey(ref);
124
+ if (!key) return null;
125
+ const store = ensureReplayStore(state);
126
+ pruneReplays(store);
127
+ const entry = store.get(key);
128
+ if (!entry) return null;
129
+ if (String(entry.ref.authorizer || "").toLowerCase() !== String(ref.authorizer || "").toLowerCase()) return null;
130
+ if (!entry.request_digest || entry.request_digest !== options.request_digest) {
131
+ return {
132
+ status: 409,
133
+ payload: {
134
+ ok: false,
135
+ reason: "payment_already_used",
136
+ detail: "This payment authorization was already used for a different request. Sign a new payment for this request."
137
+ }
138
+ };
139
+ }
140
+ if (entry.status === "settled" && entry.response) {
141
+ return { ...entry.response, payload: { ...entry.response.payload, replayed: true } };
142
+ }
143
+ const used = await confirmAuthorizationUsed(entry.ref, { attempts: 1, ...(options.rpc || {}) });
144
+ if (used === true && typeof entry.finalize === "function") {
145
+ const response = await entry.finalize({ success: true, transaction: null, network: entry.ref.network, payer: entry.ref.authorizer, recovered_from: "authorization_state" });
146
+ store.set(key, { ...entry, status: "settled", response, finalize: undefined });
147
+ return { ...response, payload: { ...response.payload, replayed: true } };
148
+ }
149
+ if (used === false) {
150
+ // Never settled: forget it so the payment is verified and executed normally.
151
+ store.delete(key);
152
+ return null;
153
+ }
154
+ return {
155
+ status: 409,
156
+ payload: {
157
+ ok: false,
158
+ reason: "settlement_outcome_unknown",
159
+ detail: "We are still confirming whether this payment settled on-chain. Resend the same payment signature in a minute to receive the result; do not sign a new payment.",
160
+ payment_settled: null
161
+ },
162
+ headers: { "Retry-After": "30" }
163
+ };
164
+ }
165
+
166
+ module.exports = {
167
+ AUTHORIZATION_STATE_SELECTOR,
168
+ authorizationRefFromHeaders,
169
+ authorizationRefFromVerification,
170
+ authorizationUsed,
171
+ confirmAuthorizationUsed,
172
+ rememberReplay,
173
+ resolveReplay,
174
+ rpcUrlFor
175
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xytara",
3
- "version": "3.1.13",
3
+ "version": "3.1.15",
4
4
  "description": "x402 pay-per-call agent-commerce runtime: gasless USDC on Base, an open settling provider network with a ledger-reconciled revenue split and automated on-chain payouts (key-less runtime, delegated signer), and bounded cryptographic delegated spend. Proven live on Base mainnet.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -106,7 +106,7 @@
106
106
  "verify:production-readiness": "node scripts/verify_production_readiness.js",
107
107
  "verify:final-activation": "node scripts/verify_final_activation.js",
108
108
  "verify:x402-facilitator": "node scripts/verify_x402_facilitator.js",
109
- "verify:all": "node scripts/verify_all.js && node scripts/verify_integrations.js && node scripts/verify_adapters.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_framework_provider_promotion.js && node scripts/verify_operator_observability_boundary.js && node scripts/verify_pricing_experiment_plan.js && node scripts/verify_treasury_public_boundary.js && node scripts/verify_market_lanes.js && node scripts/verify_market_lane_depth.js && node scripts/verify_market_lane_traces.js && node scripts/verify_market_lane_promotion.js && node scripts/verify_provider_runtime_breadth.js && node scripts/verify_provider_runtime_journeys.js && node scripts/verify_lifecycle_continuity.js && node scripts/verify_first_run_conversions.js && node scripts/verify_delivery_summary.js && node scripts/verify_runtime_durability.js && node scripts/verify_revenue_launch_paths.js && node scripts/verify_full_catalog.js && node scripts/verify_tooling.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_x402_facilitator.js && node scripts/verify_cdp_facilitator.js && node scripts/verify_a2a_rpc.js && node scripts/verify_checkout_processor.js && node scripts/verify_l402_adapter.js && node scripts/verify_bolt_adapter.js && node scripts/verify_mpp_adapter.js && node scripts/verify_rate_limiter.js && node scripts/verify_execution_surfacing.js && node scripts/verify_agent_identity.js && node scripts/verify_verify_execute_settle.js && node scripts/verify_execution_dedupe.js && node scripts/verify_refund_credit.js && node scripts/verify_commerce_bridge.js && node scripts/verify_settlement.js && node scripts/verify_provider_network.js && node scripts/verify_external_execution_retry.js",
109
+ "verify:all": "node scripts/verify_all.js && node scripts/verify_integrations.js && node scripts/verify_adapters.js && node scripts/verify_control_plane_contracts.js && node scripts/verify_control_plane_routes.js && node scripts/verify_framework_provider_promotion.js && node scripts/verify_operator_observability_boundary.js && node scripts/verify_pricing_experiment_plan.js && node scripts/verify_treasury_public_boundary.js && node scripts/verify_market_lanes.js && node scripts/verify_market_lane_depth.js && node scripts/verify_market_lane_traces.js && node scripts/verify_market_lane_promotion.js && node scripts/verify_provider_runtime_breadth.js && node scripts/verify_provider_runtime_journeys.js && node scripts/verify_lifecycle_continuity.js && node scripts/verify_first_run_conversions.js && node scripts/verify_delivery_summary.js && node scripts/verify_runtime_durability.js && node scripts/verify_revenue_launch_paths.js && node scripts/verify_full_catalog.js && node scripts/verify_tooling.js && node scripts/verify_examples.js && node scripts/verify_service.js && node scripts/verify_x402_facilitator.js && node scripts/verify_cdp_facilitator.js && node scripts/verify_a2a_rpc.js && node scripts/verify_checkout_processor.js && node scripts/verify_l402_adapter.js && node scripts/verify_bolt_adapter.js && node scripts/verify_mpp_adapter.js && node scripts/verify_rate_limiter.js && node scripts/verify_execution_surfacing.js && node scripts/verify_agent_identity.js && node scripts/verify_verify_execute_settle.js && node scripts/verify_execution_dedupe.js && node scripts/verify_refund_credit.js && node scripts/verify_commerce_bridge.js && node scripts/verify_settlement.js && node scripts/verify_provider_network.js && node scripts/verify_external_execution_retry.js && node scripts/verify_runtime_state_guard.js && node scripts/state_backup_offsite.js selftest && node scripts/verify_lifecycle_spend_binding.js && node scripts/verify_buyer_safety.js && node scripts/verify_buyer_event_log.js && node scripts/verify_x402_settlement_recovery.js",
110
110
  "prepublishOnly": "npm run verify:release-candidate",
111
111
  "verify:cdp-facilitator": "node scripts/verify_cdp_facilitator.js",
112
112
  "verify:a2a-rpc": "node scripts/verify_a2a_rpc.js",
@@ -123,6 +123,12 @@
123
123
  "verify:commerce-bridge": "node scripts/verify_commerce_bridge.js",
124
124
  "verify:settlement": "node scripts/verify_settlement.js",
125
125
  "verify:provider-network": "node scripts/verify_provider_network.js",
126
- "verify:external-execution-retry": "node scripts/verify_external_execution_retry.js"
126
+ "verify:external-execution-retry": "node scripts/verify_external_execution_retry.js",
127
+ "verify:runtime-state-guard": "node scripts/verify_runtime_state_guard.js",
128
+ "verify:state-backup": "node scripts/state_backup_offsite.js selftest",
129
+ "verify:lifecycle-spend-binding": "node scripts/verify_lifecycle_spend_binding.js",
130
+ "verify:buyer-safety": "node scripts/verify_buyer_safety.js",
131
+ "verify:buyer-event-log": "node scripts/verify_buyer_event_log.js",
132
+ "verify:x402-settlement-recovery": "node scripts/verify_x402_settlement_recovery.js"
127
133
  }
128
134
  }