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,187 +1,187 @@
1
- "use strict";
2
-
3
- // Provider-agnostic BSV / UTXO (BOLT) adapter.
4
- //
5
- // Makes the BOLT lane a REAL on-chain rail: it verifies that a presented BSV
6
- // transaction (txid) pays the operator's address for at least the required
7
- // amount, by fetching the transaction from a configured BSV chain API. The txid
8
- // is the proof of payment; replay is prevented by treating each txid as
9
- // single-use. Engages only when env configures a provider; otherwise the caller
10
- // preserves the existing bespoke token scheme. Dependency-free (node http/https).
11
- //
12
- // Env:
13
- // XYTARA_BOLT_PROVIDER = "whatsonchain" | "generic" (unset => not configured)
14
- // XYTARA_BOLT_PAYTO_ADDRESS (operator BSV address that must be paid)
15
- // XYTARA_BOLT_PRICE_SATS (minimum amount, satoshis)
16
- // XYTARA_BOLT_MIN_CONFIRMATIONS (default 0 => accept mempool)
17
- // XYTARA_BOLT_NETWORK = "main" | "test" (default main)
18
- // XYTARA_BOLT_ROOT_KEY | XYTARA_BOLT_SHARED_SECRET (HMAC key for the per-quote
19
- // amount offset; recommended)
20
- // whatsonchain: API base defaults to https://api.whatsonchain.com/v1/bsv/<network>
21
- // generic: XYTARA_BOLT_API_URL (tx-by-hash base; "/tx/hash/<txid>" appended)
22
-
23
- // REQUEST BINDING (production-safe) & LIMITATIONS:
24
- // - FLAT-RATE lane: the base price is a flat XYTARA_BOLT_PRICE_SATS (BSV sats
25
- // cannot be derived from a USD quote without a rate oracle). Use for flat-priced
26
- // calls; do not expose high-value, variably-priced commands on this lane.
27
- // - Per-quote binding: the front advertises a UNIQUE required amount
28
- // (priceSats + an unpredictable HMAC-derived offset) per quote and asks the
29
- // payer to pay it EXACTLY. verifyBsvPayment with options.requiredSats then
30
- // requires paidSats === requiredSats, so a pre-existing/unrelated payment to
31
- // the operator address cannot be reused. Each txid is also single-use.
32
- // (When called without requiredSats the legacy ">= priceSats" check applies.)
33
-
34
- const https = require("https");
35
- const http = require("http");
36
- const crypto = require("crypto");
37
- const { URL } = require("url");
38
-
39
- function env(name) {
40
- const v = process.env[name];
41
- return typeof v === "string" && v.trim() ? v.trim() : "";
42
- }
43
-
44
- function loadBoltConfig() {
45
- const provider = env("XYTARA_BOLT_PROVIDER").toLowerCase();
46
- if (provider !== "whatsonchain" && provider !== "generic") return { provider: null };
47
- const network = (env("XYTARA_BOLT_NETWORK") || env("XYTARA_BSV_TERANODE_NETWORK") || "main")
48
- .replace("mainnet", "main")
49
- .replace("testnet", "test");
50
- return {
51
- provider,
52
- network,
53
- apiUrl: env("XYTARA_BOLT_API_URL") || (provider === "whatsonchain" ? `https://api.whatsonchain.com/v1/bsv/${network}` : ""),
54
- payToAddress: env("XYTARA_BOLT_PAYTO_ADDRESS"),
55
- priceSats: Number(env("XYTARA_BOLT_PRICE_SATS") || "") || null,
56
- // Default to 1 confirmation so an unconfirmed (double-spendable) mempool tx is
57
- // not accepted as proof of payment. An operator may explicitly set 0 to accept
58
- // 0-conf at their own risk.
59
- minConfirmations: env("XYTARA_BOLT_MIN_CONFIRMATIONS") === "" ? 1 : Math.max(0, Number(env("XYTARA_BOLT_MIN_CONFIRMATIONS")) || 0),
60
- // Secret + window for the per-quote amount offset (request binding). A secret
61
- // key is recommended so the offset is unpredictable; it falls back to the
62
- // (public) payToAddress, which still binds via exact-amount + single-use.
63
- bindingSecret: env("XYTARA_BOLT_ROOT_KEY") || env("XYTARA_BOLT_SHARED_SECRET") || (env("XYTARA_BOLT_PAYTO_ADDRESS") + "|" + network),
64
- bindingWindowSats: Math.max(1, Number(env("XYTARA_BOLT_BINDING_WINDOW_SATS")) || 10000)
65
- };
66
- }
67
-
68
- function isConfigured(config) {
69
- return Boolean(config && config.provider);
70
- }
71
-
72
- // Deterministic unique required amount for a quote: base price + an HMAC-derived
73
- // offset in [0, bindingWindowSats). Both challenge and verify derive the same
74
- // value from the (stored, stable) quote, so the payer is told an exact amount and
75
- // the tx must pay it precisely.
76
- function computeRequiredSats(config, quote) {
77
- const base = Number(config && config.priceSats) || 0;
78
- if (!quote || !quote.quote_id) return base;
79
- const mac = crypto.createHmac("sha256", String(config.bindingSecret || "")).update(String(quote.quote_id)).digest("hex");
80
- const offset = parseInt(mac.slice(0, 8), 16) % Math.max(1, Number(config.bindingWindowSats) || 10000);
81
- return base + offset;
82
- }
83
-
84
- function httpGetJson(urlStr, timeoutMs) {
85
- return new Promise((resolve, reject) => {
86
- let u;
87
- try {
88
- u = new URL(urlStr);
89
- } catch (e) {
90
- return reject(new Error("invalid_url"));
91
- }
92
- const lib = u.protocol === "http:" ? http : https;
93
- const req = lib.request(
94
- { method: "GET", hostname: u.hostname, port: u.port || (u.protocol === "http:" ? 80 : 443), path: u.pathname + u.search, headers: { Accept: "application/json" } },
95
- (res) => {
96
- let d = "";
97
- res.on("data", (c) => (d += c));
98
- res.on("end", () => {
99
- let j = null;
100
- try {
101
- j = d ? JSON.parse(d) : null;
102
- } catch (_) {
103
- j = null;
104
- }
105
- resolve({ status: res.statusCode, json: j, raw: d });
106
- });
107
- }
108
- );
109
- req.on("error", reject);
110
- req.setTimeout(timeoutMs || 15000, () => req.destroy(new Error("bolt_api_timeout")));
111
- req.end();
112
- });
113
- }
114
-
115
- // Collect the payout addresses declared on a vout's scriptPubKey across the
116
- // shapes different BSV APIs use.
117
- function voutAddresses(vout) {
118
- const spk = (vout && vout.scriptPubKey) || {};
119
- if (Array.isArray(spk.addresses)) return spk.addresses;
120
- if (spk.address) return [spk.address];
121
- if (Array.isArray(vout && vout.addresses)) return vout.addresses;
122
- return [];
123
- }
124
-
125
- // Verify a BSV txid pays >= priceSats to payToAddress. Returns
126
- // { ok, txid, paid_sats, confirmations, network } | { ok:false, reason, detail? }.
127
- async function verifyBsvPayment(config, txid, fetchTxOverride, options) {
128
- if (!isConfigured(config)) return { ok: false, reason: "bolt_provider_not_configured" };
129
- if (!config.payToAddress || !config.priceSats) return { ok: false, reason: "bolt_payto_or_price_missing" };
130
- if (!config.apiUrl) return { ok: false, reason: "bolt_api_url_missing" };
131
- if (!/^[0-9a-fA-F]{64}$/.test(String(txid || ""))) return { ok: false, reason: "bolt_txid_invalid" };
132
- const fetchTx = typeof fetchTxOverride === "function" ? fetchTxOverride : (u) => httpGetJson(u, 15000);
133
- let res;
134
- try {
135
- res = await fetchTx(config.apiUrl.replace(/\/$/, "") + "/tx/hash/" + txid);
136
- } catch (e) {
137
- return { ok: false, reason: "bolt_api_unreachable", detail: e && e.message ? e.message : "request_error" };
138
- }
139
- if (!res.json || res.status >= 400) return { ok: false, reason: "bolt_tx_not_found", detail: `HTTP ${res.status}` };
140
- const j = res.json;
141
- const confirmations = Number(j.confirmations || 0);
142
- if (config.minConfirmations > 0 && confirmations < config.minConfirmations) {
143
- return { ok: false, reason: "bolt_insufficient_confirmations", detail: `${confirmations}/${config.minConfirmations}` };
144
- }
145
- const vouts = Array.isArray(j.vout) ? j.vout : [];
146
- let paidSats = 0;
147
- for (const o of vouts) {
148
- if (voutAddresses(o).includes(config.payToAddress)) {
149
- // BSV APIs report value in BSV; convert to satoshis.
150
- paidSats += Math.round(Number(o.value || 0) * 1e8);
151
- }
152
- }
153
- // Per-quote binding: when a unique required amount is supplied, the tx must pay
154
- // it EXACTLY (not merely >=), so an unrelated payment to the address cannot be
155
- // reused. Without it, fall back to the flat ">= priceSats" check.
156
- const requiredSats = options && Number.isFinite(Number(options.requiredSats)) ? Math.round(Number(options.requiredSats)) : null;
157
- if (requiredSats !== null) {
158
- if (paidSats !== requiredSats) {
159
- return { ok: false, reason: "bolt_amount_mismatch", detail: `${paidSats}/${requiredSats}` };
160
- }
161
- } else if (paidSats < config.priceSats) {
162
- return { ok: false, reason: "bolt_amount_insufficient", detail: `${paidSats}/${config.priceSats}` };
163
- }
164
- return { ok: true, txid: String(txid), paid_sats: paidSats, confirmations, network: config.network, required_sats: requiredSats };
165
- }
166
-
167
- // Parse "BOLT <txid>" (64-hex). Returns { txid } | null.
168
- function parseBoltHeader(authorization) {
169
- const m = String(authorization || "").match(/^BOLT\s+([0-9a-fA-F]{64})\s*$/);
170
- return m ? { txid: m[1] } : null;
171
- }
172
-
173
- // The challenge an unpaid caller needs: where to pay and how much. When a
174
- // per-quote required amount is supplied, advertise that exact amount (the payer
175
- // must pay it precisely) instead of the flat base price.
176
- function buildBoltChallenge(config, options) {
177
- const requiredSats = options && Number.isFinite(Number(options.requiredSats)) ? Math.round(Number(options.requiredSats)) : null;
178
- return {
179
- address: config.payToAddress,
180
- amount_sats: requiredSats !== null ? requiredSats : config.priceSats,
181
- exact: requiredSats !== null,
182
- network: config.network,
183
- min_confirmations: config.minConfirmations
184
- };
185
- }
186
-
187
- module.exports = { loadBoltConfig, isConfigured, verifyBsvPayment, parseBoltHeader, buildBoltChallenge, computeRequiredSats };
1
+ "use strict";
2
+
3
+ // Provider-agnostic BSV / UTXO (BOLT) adapter.
4
+ //
5
+ // Makes the BOLT lane a REAL on-chain rail: it verifies that a presented BSV
6
+ // transaction (txid) pays the operator's address for at least the required
7
+ // amount, by fetching the transaction from a configured BSV chain API. The txid
8
+ // is the proof of payment; replay is prevented by treating each txid as
9
+ // single-use. Engages only when env configures a provider; otherwise the caller
10
+ // preserves the existing bespoke token scheme. Dependency-free (node http/https).
11
+ //
12
+ // Env:
13
+ // XYTARA_BOLT_PROVIDER = "whatsonchain" | "generic" (unset => not configured)
14
+ // XYTARA_BOLT_PAYTO_ADDRESS (operator BSV address that must be paid)
15
+ // XYTARA_BOLT_PRICE_SATS (minimum amount, satoshis)
16
+ // XYTARA_BOLT_MIN_CONFIRMATIONS (default 0 => accept mempool)
17
+ // XYTARA_BOLT_NETWORK = "main" | "test" (default main)
18
+ // XYTARA_BOLT_ROOT_KEY | XYTARA_BOLT_SHARED_SECRET (HMAC key for the per-quote
19
+ // amount offset; recommended)
20
+ // whatsonchain: API base defaults to https://api.whatsonchain.com/v1/bsv/<network>
21
+ // generic: XYTARA_BOLT_API_URL (tx-by-hash base; "/tx/hash/<txid>" appended)
22
+
23
+ // REQUEST BINDING (production-safe) & LIMITATIONS:
24
+ // - FLAT-RATE lane: the base price is a flat XYTARA_BOLT_PRICE_SATS (BSV sats
25
+ // cannot be derived from a USD quote without a rate oracle). Use for flat-priced
26
+ // calls; do not expose high-value, variably-priced commands on this lane.
27
+ // - Per-quote binding: the front advertises a UNIQUE required amount
28
+ // (priceSats + an unpredictable HMAC-derived offset) per quote and asks the
29
+ // payer to pay it EXACTLY. verifyBsvPayment with options.requiredSats then
30
+ // requires paidSats === requiredSats, so a pre-existing/unrelated payment to
31
+ // the operator address cannot be reused. Each txid is also single-use.
32
+ // (When called without requiredSats the legacy ">= priceSats" check applies.)
33
+
34
+ const https = require("https");
35
+ const http = require("http");
36
+ const crypto = require("crypto");
37
+ const { URL } = require("url");
38
+
39
+ function env(name) {
40
+ const v = process.env[name];
41
+ return typeof v === "string" && v.trim() ? v.trim() : "";
42
+ }
43
+
44
+ function loadBoltConfig() {
45
+ const provider = env("XYTARA_BOLT_PROVIDER").toLowerCase();
46
+ if (provider !== "whatsonchain" && provider !== "generic") return { provider: null };
47
+ const network = (env("XYTARA_BOLT_NETWORK") || env("XYTARA_BSV_TERANODE_NETWORK") || "main")
48
+ .replace("mainnet", "main")
49
+ .replace("testnet", "test");
50
+ return {
51
+ provider,
52
+ network,
53
+ apiUrl: env("XYTARA_BOLT_API_URL") || (provider === "whatsonchain" ? `https://api.whatsonchain.com/v1/bsv/${network}` : ""),
54
+ payToAddress: env("XYTARA_BOLT_PAYTO_ADDRESS"),
55
+ priceSats: Number(env("XYTARA_BOLT_PRICE_SATS") || "") || null,
56
+ // Default to 1 confirmation so an unconfirmed (double-spendable) mempool tx is
57
+ // not accepted as proof of payment. An operator may explicitly set 0 to accept
58
+ // 0-conf at their own risk.
59
+ minConfirmations: env("XYTARA_BOLT_MIN_CONFIRMATIONS") === "" ? 1 : Math.max(0, Number(env("XYTARA_BOLT_MIN_CONFIRMATIONS")) || 0),
60
+ // Secret + window for the per-quote amount offset (request binding). A secret
61
+ // key is recommended so the offset is unpredictable; it falls back to the
62
+ // (public) payToAddress, which still binds via exact-amount + single-use.
63
+ bindingSecret: env("XYTARA_BOLT_ROOT_KEY") || env("XYTARA_BOLT_SHARED_SECRET") || (env("XYTARA_BOLT_PAYTO_ADDRESS") + "|" + network),
64
+ bindingWindowSats: Math.max(1, Number(env("XYTARA_BOLT_BINDING_WINDOW_SATS")) || 10000)
65
+ };
66
+ }
67
+
68
+ function isConfigured(config) {
69
+ return Boolean(config && config.provider);
70
+ }
71
+
72
+ // Deterministic unique required amount for a quote: base price + an HMAC-derived
73
+ // offset in [0, bindingWindowSats). Both challenge and verify derive the same
74
+ // value from the (stored, stable) quote, so the payer is told an exact amount and
75
+ // the tx must pay it precisely.
76
+ function computeRequiredSats(config, quote) {
77
+ const base = Number(config && config.priceSats) || 0;
78
+ if (!quote || !quote.quote_id) return base;
79
+ const mac = crypto.createHmac("sha256", String(config.bindingSecret || "")).update(String(quote.quote_id)).digest("hex");
80
+ const offset = parseInt(mac.slice(0, 8), 16) % Math.max(1, Number(config.bindingWindowSats) || 10000);
81
+ return base + offset;
82
+ }
83
+
84
+ function httpGetJson(urlStr, timeoutMs) {
85
+ return new Promise((resolve, reject) => {
86
+ let u;
87
+ try {
88
+ u = new URL(urlStr);
89
+ } catch (e) {
90
+ return reject(new Error("invalid_url"));
91
+ }
92
+ const lib = u.protocol === "http:" ? http : https;
93
+ const req = lib.request(
94
+ { method: "GET", hostname: u.hostname, port: u.port || (u.protocol === "http:" ? 80 : 443), path: u.pathname + u.search, headers: { Accept: "application/json" } },
95
+ (res) => {
96
+ let d = "";
97
+ res.on("data", (c) => (d += c));
98
+ res.on("end", () => {
99
+ let j = null;
100
+ try {
101
+ j = d ? JSON.parse(d) : null;
102
+ } catch (_) {
103
+ j = null;
104
+ }
105
+ resolve({ status: res.statusCode, json: j, raw: d });
106
+ });
107
+ }
108
+ );
109
+ req.on("error", reject);
110
+ req.setTimeout(timeoutMs || 15000, () => req.destroy(new Error("bolt_api_timeout")));
111
+ req.end();
112
+ });
113
+ }
114
+
115
+ // Collect the payout addresses declared on a vout's scriptPubKey across the
116
+ // shapes different BSV APIs use.
117
+ function voutAddresses(vout) {
118
+ const spk = (vout && vout.scriptPubKey) || {};
119
+ if (Array.isArray(spk.addresses)) return spk.addresses;
120
+ if (spk.address) return [spk.address];
121
+ if (Array.isArray(vout && vout.addresses)) return vout.addresses;
122
+ return [];
123
+ }
124
+
125
+ // Verify a BSV txid pays >= priceSats to payToAddress. Returns
126
+ // { ok, txid, paid_sats, confirmations, network } | { ok:false, reason, detail? }.
127
+ async function verifyBsvPayment(config, txid, fetchTxOverride, options) {
128
+ if (!isConfigured(config)) return { ok: false, reason: "bolt_provider_not_configured" };
129
+ if (!config.payToAddress || !config.priceSats) return { ok: false, reason: "bolt_payto_or_price_missing" };
130
+ if (!config.apiUrl) return { ok: false, reason: "bolt_api_url_missing" };
131
+ if (!/^[0-9a-fA-F]{64}$/.test(String(txid || ""))) return { ok: false, reason: "bolt_txid_invalid" };
132
+ const fetchTx = typeof fetchTxOverride === "function" ? fetchTxOverride : (u) => httpGetJson(u, 15000);
133
+ let res;
134
+ try {
135
+ res = await fetchTx(config.apiUrl.replace(/\/$/, "") + "/tx/hash/" + txid);
136
+ } catch (e) {
137
+ return { ok: false, reason: "bolt_api_unreachable", detail: e && e.message ? e.message : "request_error" };
138
+ }
139
+ if (!res.json || res.status >= 400) return { ok: false, reason: "bolt_tx_not_found", detail: `HTTP ${res.status}` };
140
+ const j = res.json;
141
+ const confirmations = Number(j.confirmations || 0);
142
+ if (config.minConfirmations > 0 && confirmations < config.minConfirmations) {
143
+ return { ok: false, reason: "bolt_insufficient_confirmations", detail: `${confirmations}/${config.minConfirmations}` };
144
+ }
145
+ const vouts = Array.isArray(j.vout) ? j.vout : [];
146
+ let paidSats = 0;
147
+ for (const o of vouts) {
148
+ if (voutAddresses(o).includes(config.payToAddress)) {
149
+ // BSV APIs report value in BSV; convert to satoshis.
150
+ paidSats += Math.round(Number(o.value || 0) * 1e8);
151
+ }
152
+ }
153
+ // Per-quote binding: when a unique required amount is supplied, the tx must pay
154
+ // it EXACTLY (not merely >=), so an unrelated payment to the address cannot be
155
+ // reused. Without it, fall back to the flat ">= priceSats" check.
156
+ const requiredSats = options && Number.isFinite(Number(options.requiredSats)) ? Math.round(Number(options.requiredSats)) : null;
157
+ if (requiredSats !== null) {
158
+ if (paidSats !== requiredSats) {
159
+ return { ok: false, reason: "bolt_amount_mismatch", detail: `${paidSats}/${requiredSats}` };
160
+ }
161
+ } else if (paidSats < config.priceSats) {
162
+ return { ok: false, reason: "bolt_amount_insufficient", detail: `${paidSats}/${config.priceSats}` };
163
+ }
164
+ return { ok: true, txid: String(txid), paid_sats: paidSats, confirmations, network: config.network, required_sats: requiredSats };
165
+ }
166
+
167
+ // Parse "BOLT <txid>" (64-hex). Returns { txid } | null.
168
+ function parseBoltHeader(authorization) {
169
+ const m = String(authorization || "").match(/^BOLT\s+([0-9a-fA-F]{64})\s*$/);
170
+ return m ? { txid: m[1] } : null;
171
+ }
172
+
173
+ // The challenge an unpaid caller needs: where to pay and how much. When a
174
+ // per-quote required amount is supplied, advertise that exact amount (the payer
175
+ // must pay it precisely) instead of the flat base price.
176
+ function buildBoltChallenge(config, options) {
177
+ const requiredSats = options && Number.isFinite(Number(options.requiredSats)) ? Math.round(Number(options.requiredSats)) : null;
178
+ return {
179
+ address: config.payToAddress,
180
+ amount_sats: requiredSats !== null ? requiredSats : config.priceSats,
181
+ exact: requiredSats !== null,
182
+ network: config.network,
183
+ min_confirmations: config.minConfirmations
184
+ };
185
+ }
186
+
187
+ module.exports = { loadBoltConfig, isConfigured, verifyBsvPayment, parseBoltHeader, buildBoltChallenge, computeRequiredSats };