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,357 +1,357 @@
1
- "use strict";
2
-
3
- const crypto = require("crypto");
4
- const { loadPaymentConfig } = require("./payment_config");
5
- const {
6
- createFacilitatorClient,
7
- verifyX402Payment,
8
- settleX402Payment,
9
- encodePaymentResponse
10
- } = require("./x402_facilitator");
11
- const { parseL402Authorization, verifyL402AccessToken } = require("./l402_payment_front");
12
- const { parseBoltAuthorization, verifyBoltAccessToken } = require("./bolt_payment_front");
13
- const { parseStripeMppAuthorization, verifyStripeMppAccessToken } = require("./stripe_mpp_payment_front");
14
- const {
15
- buildBsvTeranodeSettlementHints,
16
- extractBsvTeranodePaymentReference,
17
- isBsvTeranodeSettlementMode,
18
- normalizeSettlementMode
19
- } = require("./settlement_bsv_teranode");
20
-
21
- function decodeBase64UrlJson(value) {
22
- try {
23
- return JSON.parse(Buffer.from(String(value || ""), "base64url").toString("utf8"));
24
- } catch (_) {
25
- return null;
26
- }
27
- }
28
-
29
- function safeEqualHex(left, right) {
30
- const a = Buffer.from(String(left || ""), "hex");
31
- const b = Buffer.from(String(right || ""), "hex");
32
- if (a.length === 0 || b.length === 0 || a.length !== b.length) return false;
33
- return crypto.timingSafeEqual(a, b);
34
- }
35
-
36
- function parseNativePaymentHeaders(headers) {
37
- return {
38
- wallet_id: headers["x-wallet-id"] || null,
39
- encoded: headers["x-payment"] || null,
40
- signature: headers["x-payment-signature"] || null,
41
- scheme: "native"
42
- };
43
- }
44
-
45
- function parseInstantPaymentHeaders(headers) {
46
- const decoded = decodeBase64UrlJson(headers["payment-signature"]);
47
- if (!decoded || typeof decoded !== "object") return null;
48
- return {
49
- wallet_id: decoded.wallet_id || null,
50
- encoded: decoded.payload || null,
51
- signature: decoded.signature || null,
52
- scheme: decoded.scheme || "exact"
53
- };
54
- }
55
-
56
- function parsePayment(headers, requiredHeaders, options) {
57
- const required = Array.isArray(requiredHeaders) ? requiredHeaders : [];
58
- const opts = options || {};
59
- if (required.includes("authorization")) {
60
- if (opts.authorization_scheme === "mpp" || opts.payment_protocol === "mpp") return parseStripeMppAuthorization(headers);
61
- if (opts.authorization_scheme === "bolt" || opts.payment_protocol === "bolt") return parseBoltAuthorization(headers);
62
- return parseL402Authorization(headers);
63
- }
64
- if (required.includes("payment-signature")) return parseInstantPaymentHeaders(headers);
65
- return parseNativePaymentHeaders(headers);
66
- }
67
-
68
- function decodePaymentPayload(encoded) {
69
- try {
70
- return JSON.parse(Buffer.from(String(encoded || ""), "base64url").toString("utf8"));
71
- } catch (_) {
72
- return null;
73
- }
74
- }
75
-
76
- function verifyPaymentPayloadAgainstQuote(paymentPayload, quote, walletId, maxAgeSeconds) {
77
- if (!paymentPayload || typeof paymentPayload !== "object") {
78
- return { ok: false, reason: "payment_payload_invalid" };
79
- }
80
- if (!paymentPayload.quote_id || paymentPayload.quote_id !== quote.quote_id) {
81
- return { ok: false, reason: "payment_quote_mismatch" };
82
- }
83
- if (paymentPayload.amount_minor !== quote.amount_minor) {
84
- return { ok: false, reason: "payment_amount_mismatch" };
85
- }
86
- if (paymentPayload.currency !== quote.currency) {
87
- return { ok: false, reason: "payment_currency_mismatch" };
88
- }
89
- if (paymentPayload.account_id && paymentPayload.account_id !== quote.account_id) {
90
- return { ok: false, reason: "payment_account_mismatch" };
91
- }
92
- if (walletId && paymentPayload.wallet_id && paymentPayload.wallet_id !== walletId) {
93
- return { ok: false, reason: "payment_wallet_mismatch" };
94
- }
95
- if (
96
- paymentPayload.settlement_mode &&
97
- normalizeSettlementMode(paymentPayload.settlement_mode) !== normalizeSettlementMode(quote.settlement_mode)
98
- ) {
99
- return { ok: false, reason: "payment_settlement_mode_mismatch" };
100
- }
101
- if (
102
- quote.settlement_binding_hash
103
- && paymentPayload.settlement_binding_hash
104
- && paymentPayload.settlement_binding_hash !== quote.settlement_binding_hash
105
- ) {
106
- return { ok: false, reason: "payment_settlement_binding_mismatch" };
107
- }
108
- if (isBsvTeranodeSettlementMode(quote.settlement_mode)) {
109
- const settlementHints = buildBsvTeranodeSettlementHints({
110
- settlementMode: quote.settlement_mode,
111
- amountMinor: quote.amount_minor,
112
- currency: quote.currency,
113
- settlementBindingHash: quote.settlement_binding_hash,
114
- treasuryDestination: quote.treasury
115
- });
116
- const bsvReference = extractBsvTeranodePaymentReference(paymentPayload);
117
- if (bsvReference) {
118
- if (bsvReference.network && settlementHints && bsvReference.network !== settlementHints.network) {
119
- return { ok: false, reason: "payment_bsv_network_mismatch" };
120
- }
121
- if (
122
- settlementHints &&
123
- settlementHints.merchant_address &&
124
- bsvReference.merchant_address &&
125
- bsvReference.merchant_address !== settlementHints.merchant_address
126
- ) {
127
- return { ok: false, reason: "payment_bsv_merchant_address_mismatch" };
128
- }
129
- if (
130
- settlementHints &&
131
- settlementHints.merchant_paymail &&
132
- bsvReference.merchant_paymail &&
133
- bsvReference.merchant_paymail !== settlementHints.merchant_paymail
134
- ) {
135
- return { ok: false, reason: "payment_bsv_merchant_paymail_mismatch" };
136
- }
137
- if (
138
- settlementHints &&
139
- Number.isFinite(Number(bsvReference.amount_satoshis)) &&
140
- Number(bsvReference.amount_satoshis) !== settlementHints.amount_satoshis
141
- ) {
142
- return { ok: false, reason: "payment_bsv_amount_mismatch" };
143
- }
144
- if (
145
- settlementHints &&
146
- bsvReference.settlement_reference &&
147
- bsvReference.settlement_reference !== settlementHints.settlement_reference
148
- ) {
149
- return { ok: false, reason: "payment_bsv_reference_mismatch" };
150
- }
151
- }
152
- }
153
- if (!paymentPayload.nonce || typeof paymentPayload.nonce !== "string") {
154
- return { ok: false, reason: "payment_nonce_missing" };
155
- }
156
- if (!paymentPayload.issued_at_iso || Number.isNaN(Date.parse(paymentPayload.issued_at_iso))) {
157
- return { ok: false, reason: "payment_issued_at_invalid" };
158
- }
159
- const ageSeconds = Math.max(0, Math.floor((Date.now() - Date.parse(paymentPayload.issued_at_iso)) / 1000));
160
- if (ageSeconds > maxAgeSeconds) {
161
- return { ok: false, reason: "payment_expired" };
162
- }
163
- return { ok: true, age_seconds: ageSeconds };
164
- }
165
-
166
- async function verifyPaymentAcceptance(state, headers, requiredHeaders, quote, options) {
167
- const config = loadPaymentConfig();
168
- const required = Array.isArray(requiredHeaders) ? requiredHeaders : [];
169
- const opts = options || {};
170
- // Trustless on-chain pay-per-call: the PAYMENT-SIGNATURE carries an x402 v2 payment,
171
- // which a facilitator verifies and settles on-chain before any work runs. This path is
172
- // opt-in (XYTARA_PAYMENT_VERIFICATION_MODE=x402_facilitator) and fails closed if the
173
- // facilitator is unreachable or the payment does not settle.
174
- if (config.verification_mode === "x402_facilitator" && required.includes("payment-signature")) {
175
- const h = headers || {};
176
- const facilitator = createFacilitatorClient({
177
- url: config.x402.facilitator_url,
178
- headers: config.x402.facilitator_headers,
179
- timeoutMs: config.x402.facilitator_timeout_ms,
180
- cdp: config.x402.cdp
181
- });
182
- const auth = await verifyX402Payment({
183
- paymentHeader: h["payment-signature"],
184
- accepts: config.x402.accepts,
185
- resourceUrl: opts.resource_url || null,
186
- facilitator
187
- });
188
- if (!auth.ok) {
189
- return { ok: false, status: auth.status || 402, reason: auth.reason, detail: auth.detail || null };
190
- }
191
- const authorizationView = {
192
- ok: true,
193
- verification_mode: "x402_facilitator",
194
- wallet_id: auth.authorization.payer,
195
- payment_payload: {
196
- x402_payer: auth.authorization.payer,
197
- x402_network: auth.authorization.paymentRequirements.network,
198
- x402_asset: auth.authorization.paymentRequirements.asset,
199
- x402_amount: auth.authorization.paymentRequirements.amount,
200
- x402_pay_to: auth.authorization.paymentRequirements.payTo || null
201
- }
202
- };
203
- // Deferred settlement: verify now, settle only AFTER execution succeeds, so a
204
- // failed call is never settled/charged. The caller must call settleDeferredPayment
205
- // on success. (The EIP-3009 authorization stays valid for maxTimeoutSeconds, so
206
- // verify->execute->settle must complete within that window.)
207
- if (opts.deferSettlement) {
208
- return {
209
- ...authorizationView,
210
- settlement: null,
211
- payment_response_base64: null,
212
- deferred_settlement: {
213
- protocol: "x402_facilitator",
214
- authorization: auth.authorization,
215
- facilitator,
216
- settle: config.x402.settle
217
- }
218
- };
219
- }
220
- const settled = await settleX402Payment({ authorization: auth.authorization, facilitator, settle: config.x402.settle });
221
- if (!settled.ok) {
222
- return { ok: false, status: settled.status || 402, reason: settled.reason, detail: settled.detail || null };
223
- }
224
- return {
225
- ...authorizationView,
226
- settlement: settled.settlement,
227
- payment_response_base64: encodePaymentResponse(settled.settlement)
228
- };
229
- }
230
- if (required.includes("authorization")) {
231
- if (opts.authorization_scheme === "mpp" || opts.payment_protocol === "mpp") {
232
- return verifyStripeMppAccessToken(headers || {}, quote, state);
233
- }
234
- if (opts.authorization_scheme === "bolt" || opts.payment_protocol === "bolt") {
235
- return verifyBoltAccessToken(headers || {}, quote, state);
236
- }
237
- return verifyL402AccessToken(headers || {}, quote, state);
238
- }
239
- const payment = parsePayment(headers || {}, requiredHeaders, opts);
240
- // x402_facilitator only governs the x402 (payment-signature) routes, handled above.
241
- // On the native and instant routes it falls back to local_signed, so enabling the mode
242
- // adds trustless on-chain settlement without disabling the existing paid lanes.
243
- const nativeMode = config.verification_mode === "x402_facilitator" ? "local_signed" : config.verification_mode;
244
- if (nativeMode === "presence") {
245
- return {
246
- ok: true,
247
- verification_mode: "presence",
248
- wallet_id: payment && payment.wallet_id ? payment.wallet_id : null,
249
- payment_payload: payment && payment.encoded ? decodePaymentPayload(payment.encoded) : null
250
- };
251
- }
252
-
253
- if (nativeMode !== "local_signed") {
254
- return {
255
- ok: false,
256
- status: 400,
257
- reason: "payment_verification_mode_unsupported"
258
- };
259
- }
260
-
261
- if (!payment || !payment.wallet_id || !payment.encoded || !payment.signature) {
262
- return {
263
- ok: false,
264
- status: 402,
265
- reason: "payment_headers_incomplete"
266
- };
267
- }
268
-
269
- const walletEntry = config.wallet_registry.get(payment.wallet_id);
270
- if (!walletEntry || walletEntry.enabled === false) {
271
- return {
272
- ok: false,
273
- status: 403,
274
- reason: "wallet_not_registered"
275
- };
276
- }
277
-
278
- const expectedSignature = crypto.createHmac("sha256", walletEntry.secret).update(payment.encoded).digest("hex");
279
- if (!safeEqualHex(expectedSignature, payment.signature)) {
280
- return {
281
- ok: false,
282
- status: 403,
283
- reason: "payment_signature_invalid"
284
- };
285
- }
286
-
287
- const paymentPayload = decodePaymentPayload(payment.encoded);
288
- const payloadValidation = verifyPaymentPayloadAgainstQuote(
289
- paymentPayload,
290
- quote,
291
- payment.wallet_id,
292
- config.payment_max_age_seconds
293
- );
294
- if (!payloadValidation.ok) {
295
- return {
296
- ok: false,
297
- status: 403,
298
- reason: payloadValidation.reason
299
- };
300
- }
301
-
302
- if (state && state.acceptedPaymentNonces && state.acceptedPaymentNonces.has(paymentPayload.nonce)) {
303
- return {
304
- ok: false,
305
- status: 409,
306
- reason: "payment_nonce_replayed"
307
- };
308
- }
309
-
310
- return {
311
- ok: true,
312
- verification_mode: "local_signed",
313
- wallet_id: payment.wallet_id,
314
- payment_payload: paymentPayload,
315
- age_seconds: payloadValidation.age_seconds
316
- };
317
- }
318
-
319
- function markPaymentAccepted(state, verification) {
320
- if (
321
- state
322
- && state.acceptedPaymentNonces
323
- && verification
324
- && verification.payment_payload
325
- && verification.payment_payload.nonce
326
- ) {
327
- state.acceptedPaymentNonces.add(verification.payment_payload.nonce);
328
- }
329
- }
330
-
331
- // Settle a payment whose settlement was deferred until after successful execution
332
- // (see opts.deferSettlement). For verification results WITHOUT a deferred settlement
333
- // — native/credit schemes, and the external rails (L402/BOLT/MPP) whose funds already
334
- // moved before the proof was presented — this is a no-op success (nothing for us to
335
- // settle). Returns { ok, settlement?, payment_response_base64? } | { ok:false, status, reason, detail }.
336
- async function settleDeferredPayment(verification) {
337
- const deferred = verification && verification.deferred_settlement;
338
- if (!deferred) return { ok: true };
339
- if (deferred.protocol === "x402_facilitator") {
340
- const settled = await settleX402Payment({
341
- authorization: deferred.authorization,
342
- facilitator: deferred.facilitator,
343
- settle: deferred.settle
344
- });
345
- if (!settled.ok) {
346
- return { ok: false, status: settled.status || 402, reason: settled.reason, detail: settled.detail || null };
347
- }
348
- return { ok: true, settlement: settled.settlement, payment_response_base64: encodePaymentResponse(settled.settlement) };
349
- }
350
- return { ok: true };
351
- }
352
-
353
- module.exports = {
354
- markPaymentAccepted,
355
- settleDeferredPayment,
356
- verifyPaymentAcceptance
357
- };
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const { loadPaymentConfig } = require("./payment_config");
5
+ const {
6
+ createFacilitatorClient,
7
+ verifyX402Payment,
8
+ settleX402Payment,
9
+ encodePaymentResponse
10
+ } = require("./x402_facilitator");
11
+ const { parseL402Authorization, verifyL402AccessToken } = require("./l402_payment_front");
12
+ const { parseBoltAuthorization, verifyBoltAccessToken } = require("./bolt_payment_front");
13
+ const { parseStripeMppAuthorization, verifyStripeMppAccessToken } = require("./stripe_mpp_payment_front");
14
+ const {
15
+ buildBsvTeranodeSettlementHints,
16
+ extractBsvTeranodePaymentReference,
17
+ isBsvTeranodeSettlementMode,
18
+ normalizeSettlementMode
19
+ } = require("./settlement_bsv_teranode");
20
+
21
+ function decodeBase64UrlJson(value) {
22
+ try {
23
+ return JSON.parse(Buffer.from(String(value || ""), "base64url").toString("utf8"));
24
+ } catch (_) {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ function safeEqualHex(left, right) {
30
+ const a = Buffer.from(String(left || ""), "hex");
31
+ const b = Buffer.from(String(right || ""), "hex");
32
+ if (a.length === 0 || b.length === 0 || a.length !== b.length) return false;
33
+ return crypto.timingSafeEqual(a, b);
34
+ }
35
+
36
+ function parseNativePaymentHeaders(headers) {
37
+ return {
38
+ wallet_id: headers["x-wallet-id"] || null,
39
+ encoded: headers["x-payment"] || null,
40
+ signature: headers["x-payment-signature"] || null,
41
+ scheme: "native"
42
+ };
43
+ }
44
+
45
+ function parseInstantPaymentHeaders(headers) {
46
+ const decoded = decodeBase64UrlJson(headers["payment-signature"]);
47
+ if (!decoded || typeof decoded !== "object") return null;
48
+ return {
49
+ wallet_id: decoded.wallet_id || null,
50
+ encoded: decoded.payload || null,
51
+ signature: decoded.signature || null,
52
+ scheme: decoded.scheme || "exact"
53
+ };
54
+ }
55
+
56
+ function parsePayment(headers, requiredHeaders, options) {
57
+ const required = Array.isArray(requiredHeaders) ? requiredHeaders : [];
58
+ const opts = options || {};
59
+ if (required.includes("authorization")) {
60
+ if (opts.authorization_scheme === "mpp" || opts.payment_protocol === "mpp") return parseStripeMppAuthorization(headers);
61
+ if (opts.authorization_scheme === "bolt" || opts.payment_protocol === "bolt") return parseBoltAuthorization(headers);
62
+ return parseL402Authorization(headers);
63
+ }
64
+ if (required.includes("payment-signature")) return parseInstantPaymentHeaders(headers);
65
+ return parseNativePaymentHeaders(headers);
66
+ }
67
+
68
+ function decodePaymentPayload(encoded) {
69
+ try {
70
+ return JSON.parse(Buffer.from(String(encoded || ""), "base64url").toString("utf8"));
71
+ } catch (_) {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function verifyPaymentPayloadAgainstQuote(paymentPayload, quote, walletId, maxAgeSeconds) {
77
+ if (!paymentPayload || typeof paymentPayload !== "object") {
78
+ return { ok: false, reason: "payment_payload_invalid" };
79
+ }
80
+ if (!paymentPayload.quote_id || paymentPayload.quote_id !== quote.quote_id) {
81
+ return { ok: false, reason: "payment_quote_mismatch" };
82
+ }
83
+ if (paymentPayload.amount_minor !== quote.amount_minor) {
84
+ return { ok: false, reason: "payment_amount_mismatch" };
85
+ }
86
+ if (paymentPayload.currency !== quote.currency) {
87
+ return { ok: false, reason: "payment_currency_mismatch" };
88
+ }
89
+ if (paymentPayload.account_id && paymentPayload.account_id !== quote.account_id) {
90
+ return { ok: false, reason: "payment_account_mismatch" };
91
+ }
92
+ if (walletId && paymentPayload.wallet_id && paymentPayload.wallet_id !== walletId) {
93
+ return { ok: false, reason: "payment_wallet_mismatch" };
94
+ }
95
+ if (
96
+ paymentPayload.settlement_mode &&
97
+ normalizeSettlementMode(paymentPayload.settlement_mode) !== normalizeSettlementMode(quote.settlement_mode)
98
+ ) {
99
+ return { ok: false, reason: "payment_settlement_mode_mismatch" };
100
+ }
101
+ if (
102
+ quote.settlement_binding_hash
103
+ && paymentPayload.settlement_binding_hash
104
+ && paymentPayload.settlement_binding_hash !== quote.settlement_binding_hash
105
+ ) {
106
+ return { ok: false, reason: "payment_settlement_binding_mismatch" };
107
+ }
108
+ if (isBsvTeranodeSettlementMode(quote.settlement_mode)) {
109
+ const settlementHints = buildBsvTeranodeSettlementHints({
110
+ settlementMode: quote.settlement_mode,
111
+ amountMinor: quote.amount_minor,
112
+ currency: quote.currency,
113
+ settlementBindingHash: quote.settlement_binding_hash,
114
+ treasuryDestination: quote.treasury
115
+ });
116
+ const bsvReference = extractBsvTeranodePaymentReference(paymentPayload);
117
+ if (bsvReference) {
118
+ if (bsvReference.network && settlementHints && bsvReference.network !== settlementHints.network) {
119
+ return { ok: false, reason: "payment_bsv_network_mismatch" };
120
+ }
121
+ if (
122
+ settlementHints &&
123
+ settlementHints.merchant_address &&
124
+ bsvReference.merchant_address &&
125
+ bsvReference.merchant_address !== settlementHints.merchant_address
126
+ ) {
127
+ return { ok: false, reason: "payment_bsv_merchant_address_mismatch" };
128
+ }
129
+ if (
130
+ settlementHints &&
131
+ settlementHints.merchant_paymail &&
132
+ bsvReference.merchant_paymail &&
133
+ bsvReference.merchant_paymail !== settlementHints.merchant_paymail
134
+ ) {
135
+ return { ok: false, reason: "payment_bsv_merchant_paymail_mismatch" };
136
+ }
137
+ if (
138
+ settlementHints &&
139
+ Number.isFinite(Number(bsvReference.amount_satoshis)) &&
140
+ Number(bsvReference.amount_satoshis) !== settlementHints.amount_satoshis
141
+ ) {
142
+ return { ok: false, reason: "payment_bsv_amount_mismatch" };
143
+ }
144
+ if (
145
+ settlementHints &&
146
+ bsvReference.settlement_reference &&
147
+ bsvReference.settlement_reference !== settlementHints.settlement_reference
148
+ ) {
149
+ return { ok: false, reason: "payment_bsv_reference_mismatch" };
150
+ }
151
+ }
152
+ }
153
+ if (!paymentPayload.nonce || typeof paymentPayload.nonce !== "string") {
154
+ return { ok: false, reason: "payment_nonce_missing" };
155
+ }
156
+ if (!paymentPayload.issued_at_iso || Number.isNaN(Date.parse(paymentPayload.issued_at_iso))) {
157
+ return { ok: false, reason: "payment_issued_at_invalid" };
158
+ }
159
+ const ageSeconds = Math.max(0, Math.floor((Date.now() - Date.parse(paymentPayload.issued_at_iso)) / 1000));
160
+ if (ageSeconds > maxAgeSeconds) {
161
+ return { ok: false, reason: "payment_expired" };
162
+ }
163
+ return { ok: true, age_seconds: ageSeconds };
164
+ }
165
+
166
+ async function verifyPaymentAcceptance(state, headers, requiredHeaders, quote, options) {
167
+ const config = loadPaymentConfig();
168
+ const required = Array.isArray(requiredHeaders) ? requiredHeaders : [];
169
+ const opts = options || {};
170
+ // Trustless on-chain pay-per-call: the PAYMENT-SIGNATURE carries an x402 v2 payment,
171
+ // which a facilitator verifies and settles on-chain before any work runs. This path is
172
+ // opt-in (XYTARA_PAYMENT_VERIFICATION_MODE=x402_facilitator) and fails closed if the
173
+ // facilitator is unreachable or the payment does not settle.
174
+ if (config.verification_mode === "x402_facilitator" && required.includes("payment-signature")) {
175
+ const h = headers || {};
176
+ const facilitator = createFacilitatorClient({
177
+ url: config.x402.facilitator_url,
178
+ headers: config.x402.facilitator_headers,
179
+ timeoutMs: config.x402.facilitator_timeout_ms,
180
+ cdp: config.x402.cdp
181
+ });
182
+ const auth = await verifyX402Payment({
183
+ paymentHeader: h["payment-signature"],
184
+ accepts: config.x402.accepts,
185
+ resourceUrl: opts.resource_url || null,
186
+ facilitator
187
+ });
188
+ if (!auth.ok) {
189
+ return { ok: false, status: auth.status || 402, reason: auth.reason, detail: auth.detail || null };
190
+ }
191
+ const authorizationView = {
192
+ ok: true,
193
+ verification_mode: "x402_facilitator",
194
+ wallet_id: auth.authorization.payer,
195
+ payment_payload: {
196
+ x402_payer: auth.authorization.payer,
197
+ x402_network: auth.authorization.paymentRequirements.network,
198
+ x402_asset: auth.authorization.paymentRequirements.asset,
199
+ x402_amount: auth.authorization.paymentRequirements.amount,
200
+ x402_pay_to: auth.authorization.paymentRequirements.payTo || null
201
+ }
202
+ };
203
+ // Deferred settlement: verify now, settle only AFTER execution succeeds, so a
204
+ // failed call is never settled/charged. The caller must call settleDeferredPayment
205
+ // on success. (The EIP-3009 authorization stays valid for maxTimeoutSeconds, so
206
+ // verify->execute->settle must complete within that window.)
207
+ if (opts.deferSettlement) {
208
+ return {
209
+ ...authorizationView,
210
+ settlement: null,
211
+ payment_response_base64: null,
212
+ deferred_settlement: {
213
+ protocol: "x402_facilitator",
214
+ authorization: auth.authorization,
215
+ facilitator,
216
+ settle: config.x402.settle
217
+ }
218
+ };
219
+ }
220
+ const settled = await settleX402Payment({ authorization: auth.authorization, facilitator, settle: config.x402.settle });
221
+ if (!settled.ok) {
222
+ return { ok: false, status: settled.status || 402, reason: settled.reason, detail: settled.detail || null };
223
+ }
224
+ return {
225
+ ...authorizationView,
226
+ settlement: settled.settlement,
227
+ payment_response_base64: encodePaymentResponse(settled.settlement)
228
+ };
229
+ }
230
+ if (required.includes("authorization")) {
231
+ if (opts.authorization_scheme === "mpp" || opts.payment_protocol === "mpp") {
232
+ return verifyStripeMppAccessToken(headers || {}, quote, state);
233
+ }
234
+ if (opts.authorization_scheme === "bolt" || opts.payment_protocol === "bolt") {
235
+ return verifyBoltAccessToken(headers || {}, quote, state);
236
+ }
237
+ return verifyL402AccessToken(headers || {}, quote, state);
238
+ }
239
+ const payment = parsePayment(headers || {}, requiredHeaders, opts);
240
+ // x402_facilitator only governs the x402 (payment-signature) routes, handled above.
241
+ // On the native and instant routes it falls back to local_signed, so enabling the mode
242
+ // adds trustless on-chain settlement without disabling the existing paid lanes.
243
+ const nativeMode = config.verification_mode === "x402_facilitator" ? "local_signed" : config.verification_mode;
244
+ if (nativeMode === "presence") {
245
+ return {
246
+ ok: true,
247
+ verification_mode: "presence",
248
+ wallet_id: payment && payment.wallet_id ? payment.wallet_id : null,
249
+ payment_payload: payment && payment.encoded ? decodePaymentPayload(payment.encoded) : null
250
+ };
251
+ }
252
+
253
+ if (nativeMode !== "local_signed") {
254
+ return {
255
+ ok: false,
256
+ status: 400,
257
+ reason: "payment_verification_mode_unsupported"
258
+ };
259
+ }
260
+
261
+ if (!payment || !payment.wallet_id || !payment.encoded || !payment.signature) {
262
+ return {
263
+ ok: false,
264
+ status: 402,
265
+ reason: "payment_headers_incomplete"
266
+ };
267
+ }
268
+
269
+ const walletEntry = config.wallet_registry.get(payment.wallet_id);
270
+ if (!walletEntry || walletEntry.enabled === false) {
271
+ return {
272
+ ok: false,
273
+ status: 403,
274
+ reason: "wallet_not_registered"
275
+ };
276
+ }
277
+
278
+ const expectedSignature = crypto.createHmac("sha256", walletEntry.secret).update(payment.encoded).digest("hex");
279
+ if (!safeEqualHex(expectedSignature, payment.signature)) {
280
+ return {
281
+ ok: false,
282
+ status: 403,
283
+ reason: "payment_signature_invalid"
284
+ };
285
+ }
286
+
287
+ const paymentPayload = decodePaymentPayload(payment.encoded);
288
+ const payloadValidation = verifyPaymentPayloadAgainstQuote(
289
+ paymentPayload,
290
+ quote,
291
+ payment.wallet_id,
292
+ config.payment_max_age_seconds
293
+ );
294
+ if (!payloadValidation.ok) {
295
+ return {
296
+ ok: false,
297
+ status: 403,
298
+ reason: payloadValidation.reason
299
+ };
300
+ }
301
+
302
+ if (state && state.acceptedPaymentNonces && state.acceptedPaymentNonces.has(paymentPayload.nonce)) {
303
+ return {
304
+ ok: false,
305
+ status: 409,
306
+ reason: "payment_nonce_replayed"
307
+ };
308
+ }
309
+
310
+ return {
311
+ ok: true,
312
+ verification_mode: "local_signed",
313
+ wallet_id: payment.wallet_id,
314
+ payment_payload: paymentPayload,
315
+ age_seconds: payloadValidation.age_seconds
316
+ };
317
+ }
318
+
319
+ function markPaymentAccepted(state, verification) {
320
+ if (
321
+ state
322
+ && state.acceptedPaymentNonces
323
+ && verification
324
+ && verification.payment_payload
325
+ && verification.payment_payload.nonce
326
+ ) {
327
+ state.acceptedPaymentNonces.add(verification.payment_payload.nonce);
328
+ }
329
+ }
330
+
331
+ // Settle a payment whose settlement was deferred until after successful execution
332
+ // (see opts.deferSettlement). For verification results WITHOUT a deferred settlement
333
+ // — native/credit schemes, and the external rails (L402/BOLT/MPP) whose funds already
334
+ // moved before the proof was presented — this is a no-op success (nothing for us to
335
+ // settle). Returns { ok, settlement?, payment_response_base64? } | { ok:false, status, reason, detail }.
336
+ async function settleDeferredPayment(verification) {
337
+ const deferred = verification && verification.deferred_settlement;
338
+ if (!deferred) return { ok: true };
339
+ if (deferred.protocol === "x402_facilitator") {
340
+ const settled = await settleX402Payment({
341
+ authorization: deferred.authorization,
342
+ facilitator: deferred.facilitator,
343
+ settle: deferred.settle
344
+ });
345
+ if (!settled.ok) {
346
+ return { ok: false, status: settled.status || 402, reason: settled.reason, detail: settled.detail || null };
347
+ }
348
+ return { ok: true, settlement: settled.settlement, payment_response_base64: encodePaymentResponse(settled.settlement) };
349
+ }
350
+ return { ok: true };
351
+ }
352
+
353
+ module.exports = {
354
+ markPaymentAccepted,
355
+ settleDeferredPayment,
356
+ verifyPaymentAcceptance
357
+ };