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,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 };