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,155 +1,155 @@
1
- "use strict";
2
-
3
- // Operator-triggered payout AUTO-EXECUTION via an EXTERNAL signer. The runtime never holds a
4
- // treasury key: it creates a payout, dispatches it to a configured signer service (which the
5
- // operator deploys and which holds the key), and confirms it with the on-chain tx the signer
6
- // returns. Off by default (no XYTARA_PAYOUT_SIGNER_URL => the manual create->send->confirm flow
7
- // is unchanged). Failure handling is deliberately conservative to avoid double-sends:
8
- // - signer responds a definite failure -> it did NOT send -> cancel (free the reservation).
9
- // - signer unreachable / timeout -> AMBIGUOUS (may have sent) -> leave the payout
10
- // PENDING for manual reconciliation; NEVER auto-cancel (that could free funds already sent).
11
-
12
- const {
13
- createProviderPayout,
14
- confirmProviderPayout,
15
- cancelProviderPayout,
16
- markProviderPayoutDispatch,
17
- reinstateCancelledPayoutForReconciliation
18
- } = require("./provider_registry");
19
- const { buildFetchOptionsForPurpose } = require("./network_transport");
20
-
21
- function normStr(value) {
22
- const s = typeof value === "string" ? value.trim() : "";
23
- return s || null;
24
- }
25
-
26
- function loadPayoutSignerConfig() {
27
- const url = normStr(process.env.XYTARA_PAYOUT_SIGNER_URL);
28
- if (!url || !/^https?:\/\//i.test(url)) return null;
29
- return {
30
- url,
31
- bearer: normStr(process.env.XYTARA_PAYOUT_SIGNER_BEARER),
32
- timeout_ms: Math.max(1000, Number(process.env.XYTARA_PAYOUT_SIGNER_TIMEOUT_MS || 20000) || 20000)
33
- };
34
- }
35
-
36
- // AMOUNT/UNIT CONTRACT with the signer: amount_minor is in `currency` MINOR units (USD_CENTS,
37
- // i.e. US cents). The signer is responsible for converting that into `asset` (e.g. USDC) base
38
- // units on `network` before sending. This must be agreed with the signer implementation.
39
- // onPayoutPersist (optional): awaited AFTER the pending payout is created and BEFORE the send,
40
- // so the intent is durable and a crash during the signer call can't leave funds sent with no
41
- // reconciliation record.
42
- async function dispatchProviderPayout(state, providerId, input, signer, onPayoutPersist) {
43
- if (!signer) return { ok: false, status: 400, reason: "payout_signer_not_configured" };
44
-
45
- const created = createProviderPayout(state, providerId, input || {});
46
- if (!created.ok) {
47
- return { ok: false, status: created.reason === "provider_not_found" ? 404 : 400, reason: created.reason, detail: created };
48
- }
49
- const payout = created.payout;
50
-
51
- // Mark IN FLIGHT before the send: while the signer call is outstanding the payout cannot be
52
- // cancelled (a cancel would free a reservation for funds the signer may already have moved).
53
- markProviderPayoutDispatch(state, payout.payout_id, "in_flight");
54
-
55
- // Persist the PENDING (in-flight) record before any send (best-effort: proceed on flush failure).
56
- if (typeof onPayoutPersist === "function") {
57
- try { await onPayoutPersist(); } catch (_) { /* non-fatal */ }
58
- }
59
-
60
- const controller = new AbortController();
61
- const timer = setTimeout(() => controller.abort(), signer.timeout_ms);
62
- let response;
63
- let text = "";
64
- try {
65
- response = await fetch(signer.url, {
66
- method: "POST",
67
- headers: {
68
- "content-type": "application/json",
69
- ...(signer.bearer ? { authorization: `Bearer ${signer.bearer}` } : {})
70
- },
71
- body: JSON.stringify({
72
- payout_id: payout.payout_id,
73
- idempotency_key: payout.payout_id,
74
- provider_id: payout.provider_id,
75
- to_address: payout.payout_address,
76
- network: payout.payout_network,
77
- asset: payout.payout_asset,
78
- amount_minor: payout.amount_minor,
79
- currency: payout.currency
80
- }),
81
- signal: controller.signal,
82
- ...buildFetchOptionsForPurpose("settlement")
83
- });
84
- text = await response.text();
85
- } catch (error) {
86
- // Transport failure (network/timeout): the signer MAY have sent. Leave the payout PENDING.
87
- return {
88
- ok: false,
89
- status: 502,
90
- reason: error && error.name === "AbortError" ? "payout_dispatch_timeout_unconfirmed" : "payout_dispatch_unconfirmed",
91
- payout_id: payout.payout_id,
92
- remediation: "the payout may or may not have been sent on-chain; reconcile manually — confirm with the tx hash if it was sent, or cancel if it was not"
93
- };
94
- } finally {
95
- clearTimeout(timer);
96
- // The signer has answered (or definitively failed to): the in-flight guard lifts. Everything
97
- // below runs synchronously, so no cancel can interleave before the outcome is recorded.
98
- markProviderPayoutDispatch(state, payout.payout_id, null);
99
- }
100
-
101
- let parsed = null;
102
- try { parsed = text ? JSON.parse(text) : null; } catch (_) { parsed = null; }
103
- const txRef = parsed && parsed.ok === true ? normStr(parsed.tx_ref) : null;
104
-
105
- // DEFINITE SUCCESS: HTTP 2xx + explicit ok:true + a tx_ref -> confirm (credit paid_out once).
106
- if (response.ok && txRef) {
107
- const confirmed = confirmProviderPayout(state, payout.payout_id, { tx_ref: txRef });
108
- if (!confirmed.ok) {
109
- // Confirm failed AFTER a likely send (e.g. a buggy signer reusing a tx_ref -> duplicate_tx_ref).
110
- // Do NOT cancel — funds may have moved. Leave PENDING and surface the failure.
111
- // If the record was somehow cancelled meanwhile (guard expired / legacy state), put it back
112
- // to pending with the tx so the reservation is held again and the operator reconciles.
113
- if (confirmed.reason === "payout_not_pending" && payout.status === "cancelled") {
114
- reinstateCancelledPayoutForReconciliation(state, payout.payout_id, txRef);
115
- }
116
- return {
117
- ok: false,
118
- status: 502,
119
- reason: "payout_confirm_failed",
120
- confirm_reason: confirmed.reason,
121
- payout_id: payout.payout_id,
122
- tx_ref: txRef,
123
- remediation: "the signer reported a tx but the payout could not be confirmed; reconcile manually"
124
- };
125
- }
126
- return { ok: true, dispatched: true, payout: confirmed.payout, payout_state: confirmed.payout_state };
127
- }
128
-
129
- // DEFINITE NOT-SENT: HTTP 2xx + a parsed body with explicit ok:false. The ONLY case where it
130
- // is safe to cancel and free the reservation.
131
- if (response.ok && parsed && parsed.ok === false) {
132
- cancelProviderPayout(state, payout.payout_id);
133
- return {
134
- ok: false,
135
- status: 502,
136
- reason: "payout_signer_rejected",
137
- detail: normStr(parsed.reason) || normStr(parsed.error) || "signer reported failure",
138
- payout_id: payout.payout_id
139
- };
140
- }
141
-
142
- // EVERYTHING ELSE is AMBIGUOUS (non-2xx incl. 5xx-after-send, non-JSON/empty body, ok:true
143
- // without a tx_ref, missing ok) -> the signer may have sent -> LEAVE PENDING, NEVER free the
144
- // reservation (freeing it would let an operator retry send a second real transfer).
145
- return {
146
- ok: false,
147
- status: 502,
148
- reason: "payout_dispatch_unconfirmed",
149
- payout_id: payout.payout_id,
150
- detail: !response.ok ? `signer HTTP ${response.status}` : "signer response did not confirm a tx",
151
- remediation: "the payout may or may not have been sent on-chain; reconcile manually — confirm with the tx hash if it was sent, or cancel if it was not"
152
- };
153
- }
154
-
155
- module.exports = { loadPayoutSignerConfig, dispatchProviderPayout };
1
+ "use strict";
2
+
3
+ // Operator-triggered payout AUTO-EXECUTION via an EXTERNAL signer. The runtime never holds a
4
+ // treasury key: it creates a payout, dispatches it to a configured signer service (which the
5
+ // operator deploys and which holds the key), and confirms it with the on-chain tx the signer
6
+ // returns. Off by default (no XYTARA_PAYOUT_SIGNER_URL => the manual create->send->confirm flow
7
+ // is unchanged). Failure handling is deliberately conservative to avoid double-sends:
8
+ // - signer responds a definite failure -> it did NOT send -> cancel (free the reservation).
9
+ // - signer unreachable / timeout -> AMBIGUOUS (may have sent) -> leave the payout
10
+ // PENDING for manual reconciliation; NEVER auto-cancel (that could free funds already sent).
11
+
12
+ const {
13
+ createProviderPayout,
14
+ confirmProviderPayout,
15
+ cancelProviderPayout,
16
+ markProviderPayoutDispatch,
17
+ reinstateCancelledPayoutForReconciliation
18
+ } = require("./provider_registry");
19
+ const { buildFetchOptionsForPurpose } = require("./network_transport");
20
+
21
+ function normStr(value) {
22
+ const s = typeof value === "string" ? value.trim() : "";
23
+ return s || null;
24
+ }
25
+
26
+ function loadPayoutSignerConfig() {
27
+ const url = normStr(process.env.XYTARA_PAYOUT_SIGNER_URL);
28
+ if (!url || !/^https?:\/\//i.test(url)) return null;
29
+ return {
30
+ url,
31
+ bearer: normStr(process.env.XYTARA_PAYOUT_SIGNER_BEARER),
32
+ timeout_ms: Math.max(1000, Number(process.env.XYTARA_PAYOUT_SIGNER_TIMEOUT_MS || 20000) || 20000)
33
+ };
34
+ }
35
+
36
+ // AMOUNT/UNIT CONTRACT with the signer: amount_minor is in `currency` MINOR units (USD_CENTS,
37
+ // i.e. US cents). The signer is responsible for converting that into `asset` (e.g. USDC) base
38
+ // units on `network` before sending. This must be agreed with the signer implementation.
39
+ // onPayoutPersist (optional): awaited AFTER the pending payout is created and BEFORE the send,
40
+ // so the intent is durable and a crash during the signer call can't leave funds sent with no
41
+ // reconciliation record.
42
+ async function dispatchProviderPayout(state, providerId, input, signer, onPayoutPersist) {
43
+ if (!signer) return { ok: false, status: 400, reason: "payout_signer_not_configured" };
44
+
45
+ const created = createProviderPayout(state, providerId, input || {});
46
+ if (!created.ok) {
47
+ return { ok: false, status: created.reason === "provider_not_found" ? 404 : 400, reason: created.reason, detail: created };
48
+ }
49
+ const payout = created.payout;
50
+
51
+ // Mark IN FLIGHT before the send: while the signer call is outstanding the payout cannot be
52
+ // cancelled (a cancel would free a reservation for funds the signer may already have moved).
53
+ markProviderPayoutDispatch(state, payout.payout_id, "in_flight");
54
+
55
+ // Persist the PENDING (in-flight) record before any send (best-effort: proceed on flush failure).
56
+ if (typeof onPayoutPersist === "function") {
57
+ try { await onPayoutPersist(); } catch (_) { /* non-fatal */ }
58
+ }
59
+
60
+ const controller = new AbortController();
61
+ const timer = setTimeout(() => controller.abort(), signer.timeout_ms);
62
+ let response;
63
+ let text = "";
64
+ try {
65
+ response = await fetch(signer.url, {
66
+ method: "POST",
67
+ headers: {
68
+ "content-type": "application/json",
69
+ ...(signer.bearer ? { authorization: `Bearer ${signer.bearer}` } : {})
70
+ },
71
+ body: JSON.stringify({
72
+ payout_id: payout.payout_id,
73
+ idempotency_key: payout.payout_id,
74
+ provider_id: payout.provider_id,
75
+ to_address: payout.payout_address,
76
+ network: payout.payout_network,
77
+ asset: payout.payout_asset,
78
+ amount_minor: payout.amount_minor,
79
+ currency: payout.currency
80
+ }),
81
+ signal: controller.signal,
82
+ ...buildFetchOptionsForPurpose("settlement")
83
+ });
84
+ text = await response.text();
85
+ } catch (error) {
86
+ // Transport failure (network/timeout): the signer MAY have sent. Leave the payout PENDING.
87
+ return {
88
+ ok: false,
89
+ status: 502,
90
+ reason: error && error.name === "AbortError" ? "payout_dispatch_timeout_unconfirmed" : "payout_dispatch_unconfirmed",
91
+ payout_id: payout.payout_id,
92
+ remediation: "the payout may or may not have been sent on-chain; reconcile manually — confirm with the tx hash if it was sent, or cancel if it was not"
93
+ };
94
+ } finally {
95
+ clearTimeout(timer);
96
+ // The signer has answered (or definitively failed to): the in-flight guard lifts. Everything
97
+ // below runs synchronously, so no cancel can interleave before the outcome is recorded.
98
+ markProviderPayoutDispatch(state, payout.payout_id, null);
99
+ }
100
+
101
+ let parsed = null;
102
+ try { parsed = text ? JSON.parse(text) : null; } catch (_) { parsed = null; }
103
+ const txRef = parsed && parsed.ok === true ? normStr(parsed.tx_ref) : null;
104
+
105
+ // DEFINITE SUCCESS: HTTP 2xx + explicit ok:true + a tx_ref -> confirm (credit paid_out once).
106
+ if (response.ok && txRef) {
107
+ const confirmed = confirmProviderPayout(state, payout.payout_id, { tx_ref: txRef });
108
+ if (!confirmed.ok) {
109
+ // Confirm failed AFTER a likely send (e.g. a buggy signer reusing a tx_ref -> duplicate_tx_ref).
110
+ // Do NOT cancel — funds may have moved. Leave PENDING and surface the failure.
111
+ // If the record was somehow cancelled meanwhile (guard expired / legacy state), put it back
112
+ // to pending with the tx so the reservation is held again and the operator reconciles.
113
+ if (confirmed.reason === "payout_not_pending" && payout.status === "cancelled") {
114
+ reinstateCancelledPayoutForReconciliation(state, payout.payout_id, txRef);
115
+ }
116
+ return {
117
+ ok: false,
118
+ status: 502,
119
+ reason: "payout_confirm_failed",
120
+ confirm_reason: confirmed.reason,
121
+ payout_id: payout.payout_id,
122
+ tx_ref: txRef,
123
+ remediation: "the signer reported a tx but the payout could not be confirmed; reconcile manually"
124
+ };
125
+ }
126
+ return { ok: true, dispatched: true, payout: confirmed.payout, payout_state: confirmed.payout_state };
127
+ }
128
+
129
+ // DEFINITE NOT-SENT: HTTP 2xx + a parsed body with explicit ok:false. The ONLY case where it
130
+ // is safe to cancel and free the reservation.
131
+ if (response.ok && parsed && parsed.ok === false) {
132
+ cancelProviderPayout(state, payout.payout_id);
133
+ return {
134
+ ok: false,
135
+ status: 502,
136
+ reason: "payout_signer_rejected",
137
+ detail: normStr(parsed.reason) || normStr(parsed.error) || "signer reported failure",
138
+ payout_id: payout.payout_id
139
+ };
140
+ }
141
+
142
+ // EVERYTHING ELSE is AMBIGUOUS (non-2xx incl. 5xx-after-send, non-JSON/empty body, ok:true
143
+ // without a tx_ref, missing ok) -> the signer may have sent -> LEAVE PENDING, NEVER free the
144
+ // reservation (freeing it would let an operator retry send a second real transfer).
145
+ return {
146
+ ok: false,
147
+ status: 502,
148
+ reason: "payout_dispatch_unconfirmed",
149
+ payout_id: payout.payout_id,
150
+ detail: !response.ok ? `signer HTTP ${response.status}` : "signer response did not confirm a tx",
151
+ remediation: "the payout may or may not have been sent on-chain; reconcile manually — confirm with the tx hash if it was sent, or cancel if it was not"
152
+ };
153
+ }
154
+
155
+ module.exports = { loadPayoutSignerConfig, dispatchProviderPayout };