@openreceive/node 0.4.3 → 0.4.6

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.
package/README.md CHANGED
@@ -1,7 +1,50 @@
1
1
  # @openreceive/node
2
2
 
3
- The OpenReceive Node service: receive-only NWC wallet client, checkout/swap service, pricing, and the `openreceive` CLI.
3
+ Accept Bitcoin Lightning payments directly into a wallet you control from
4
+ your Node.js application. This package connects to your receive-only Nostr
5
+ Wallet Connect (NWC) wallet, creates invoices, checks settlement, and provides
6
+ the swap service and command-line tools.
7
+
8
+ OpenReceive supports optional swaps from **USDT, USDC, SOL, and ETH** through
9
+ a configured swap provider. The provider converts the payment to **BTC over
10
+ Lightning**, which settles into the merchant's connected wallet. Available
11
+ assets and networks depend on the provider; swaps are optional.
12
+
13
+ ## Install
4
14
 
5
15
  This package is ESM-only and requires Node >= 22.
6
16
 
17
+ ```sh
18
+ npm install @openreceive/node
19
+ ```
20
+
21
+ Start with the [integration quickstart](https://github.com/openreceive/openreceive/blob/master/docs/guides/quickstart-node.md)
22
+ and the [payment storage guide](https://github.com/openreceive/openreceive/blob/master/docs/guides/storage.md).
23
+
7
24
  Part of [OpenReceive](https://openreceive.org). Start with the [Node quickstart](https://github.com/openreceive/openreceive/blob/master/docs/guides/quickstart-node.md); the full API is in the [API reference](https://github.com/openreceive/openreceive/blob/master/docs/guides/api-reference.md).
25
+
26
+ ## Choose your integration
27
+
28
+ For a web checkout, install `@openreceive/express`, `@openreceive/fastify`, or
29
+ `@openreceive/next`. They use this service and add payment persistence,
30
+ authorization, and mounted routes. For a custom Node.js host, compose the
31
+ service with `@openreceive/http`.
32
+
33
+ Use this package directly when you need the service or its types as part of
34
+ that integration. The service does not persist payment attempts on its own.
35
+ The normal HTTP integration records them in your existing database before
36
+ exposing payment instructions.
37
+
38
+ - **Connect your wallet:** set `NWC_URI` on the server to a receive-only NWC
39
+ connection. Startup checks reject spend-capable connections by default.
40
+ - **Enable optional swaps:** configure your provider through `LSC_URI_PRIMARY`
41
+ and optionally `LSC_URI_BACKUP`.
42
+ - **Keep your business logic:** the host controls orders, prices,
43
+ authorization, and fulfillment.
44
+
45
+ ## Guides
46
+
47
+ - [Environment and wallet configuration](https://github.com/openreceive/openreceive/blob/master/docs/guides/environment-variables.md)
48
+ - [Optional swaps](https://github.com/openreceive/openreceive/blob/master/docs/guides/automated-swaps.md)
49
+ - [Test with a fake wallet](https://github.com/openreceive/openreceive/blob/master/docs/guides/host-testing.md)
50
+ - [Deploy and reconcile payments](https://github.com/openreceive/openreceive/blob/master/docs/guides/deploying.md)
@@ -177,6 +177,7 @@ import {
177
177
  compact,
178
178
  formatSpendCapabilityRefusedMessage,
179
179
  formatSpendCapabilityWarningMessage,
180
+ OPENRECEIVE_NWC_NOTIFICATION_TYPES,
180
181
  OpenReceiveError as OpenReceiveError2,
181
182
  parseNwcUri
182
183
  } from "@openreceive/core";
@@ -186,6 +187,8 @@ import {
186
187
  OPENRECEIVE_MAX_AMOUNT_MSATS,
187
188
  OPENRECEIVE_MIN_AMOUNT_MSATS,
188
189
  OPENRECEIVE_NWC_METADATA_MAX_BYTES,
190
+ OPENRECEIVE_NWC_REQUIRED_RECEIVE_METHODS,
191
+ OPENRECEIVE_NWC_SPEND_METHODS,
189
192
  nonEmptyString,
190
193
  recordOrEmpty
191
194
  } from "@openreceive/core";
@@ -194,13 +197,8 @@ import {
194
197
  var HEX_64 = /^[0-9a-fA-F]{64}$/;
195
198
 
196
199
  // src/nwc/normalize.ts
197
- var REQUIRED_RECEIVE_METHODS = ["make_invoice", "list_transactions"];
198
- var SPEND_METHODS = [
199
- "pay_invoice",
200
- "multi_pay_invoice",
201
- "pay_keysend",
202
- "multi_pay_keysend"
203
- ];
200
+ var REQUIRED_RECEIVE_METHODS = OPENRECEIVE_NWC_REQUIRED_RECEIVE_METHODS;
201
+ var SPEND_METHODS = OPENRECEIVE_NWC_SPEND_METHODS;
204
202
  function summarizeWalletCapabilities(connection, rawInfo, rawServiceInfo) {
205
203
  const unwrappedInfo = unwrapNwcResult(rawInfo);
206
204
  const info = recordOrEmpty(unwrappedInfo);
@@ -541,6 +539,7 @@ function delay(milliseconds) {
541
539
  }
542
540
 
543
541
  // src/alby-nwc.ts
542
+ var NOTIFICATION_TYPES = new Set(OPENRECEIVE_NWC_NOTIFICATION_TYPES);
544
543
  var AlbyNwcReceiveClient = class {
545
544
  /**
546
545
  * Safe, loggable view of the connection (wallet pubkey, relays, lud16, and
@@ -829,7 +828,7 @@ var AlbyNwcReceiveClient = class {
829
828
  "debug",
830
829
  "nwc.notifications.subscribe.requested",
831
830
  "Subscribing to NWC-02 payment_received notifications.",
832
- { notification_types: ["payment_received"] }
831
+ { notification_types: OPENRECEIVE_NWC_NOTIFICATION_TYPES }
833
832
  );
834
833
  let subscription;
835
834
  try {
@@ -837,7 +836,7 @@ var AlbyNwcReceiveClient = class {
837
836
  client,
838
837
  (rawNotification) => {
839
838
  const notification = normalizeNwcNotification(rawNotification);
840
- if (notification.type !== "payment_received") return;
839
+ if (!NOTIFICATION_TYPES.has(notification.type)) return;
841
840
  this.#log(
842
841
  "debug",
843
842
  "nwc.notifications.received",
@@ -852,7 +851,7 @@ var AlbyNwcReceiveClient = class {
852
851
  } catch {
853
852
  }
854
853
  },
855
- ["payment_received"]
854
+ [...OPENRECEIVE_NWC_NOTIFICATION_TYPES]
856
855
  );
857
856
  } catch (error) {
858
857
  const normalized = normalizeNwcWalletError(error);
@@ -941,7 +940,7 @@ function createNwcReceiveClient(options) {
941
940
  return new AlbyNwcReceiveClient(options);
942
941
  }
943
942
 
944
- // src/swap/assets.ts
943
+ // src/generated/swap-tables.ts
945
944
  var OPENRECEIVE_SWAP_PAY_IN_ASSETS = [
946
945
  "SOL_SOL",
947
946
  "USDT_TRON",
@@ -951,7 +950,7 @@ var OPENRECEIVE_SWAP_PAY_IN_ASSETS = [
951
950
  "USDT_ETH",
952
951
  "USDC_ETH"
953
952
  ];
954
- var ASSET_INFO = {
953
+ var OPENRECEIVE_SWAP_ASSET_INFO = {
955
954
  SOL_SOL: {
956
955
  pay_in_asset: "SOL_SOL",
957
956
  label: "SOL",
@@ -1002,6 +1001,37 @@ var ASSET_INFO = {
1002
1001
  network: "ETH"
1003
1002
  }
1004
1003
  };
1004
+ var OPENRECEIVE_SWAP_PROVIDER_STATES = [
1005
+ "creating_provider_order",
1006
+ "awaiting_deposit",
1007
+ "confirming",
1008
+ "exchanging",
1009
+ "paying_invoice",
1010
+ "completed",
1011
+ "expired",
1012
+ "refund_required",
1013
+ "refund_pending",
1014
+ "refunded",
1015
+ "attention",
1016
+ "failed"
1017
+ ];
1018
+ var OPENRECEIVE_SWAP_STATE_TABLE = {
1019
+ creating_provider_order: { phase: "preparing", terminal: false },
1020
+ awaiting_deposit: { phase: "awaiting_deposit", terminal: false },
1021
+ confirming: { phase: "processing", terminal: false },
1022
+ exchanging: { phase: "processing", terminal: false },
1023
+ paying_invoice: { phase: "processing", terminal: false },
1024
+ completed: { phase: "settling", terminal: false },
1025
+ expired: { phase: "terminal", terminal: true },
1026
+ refund_required: { phase: "refund", terminal: false },
1027
+ refund_pending: { phase: "refund", terminal: false },
1028
+ refunded: { phase: "terminal", terminal: true },
1029
+ attention: { phase: "attention", terminal: true },
1030
+ failed: { phase: "terminal", terminal: true }
1031
+ };
1032
+
1033
+ // src/swap/assets.ts
1034
+ var ASSET_INFO = OPENRECEIVE_SWAP_ASSET_INFO;
1005
1035
  function isSwapPayInAsset(value) {
1006
1036
  return typeof value === "string" && OPENRECEIVE_SWAP_PAY_IN_ASSETS.includes(value);
1007
1037
  }
@@ -1408,6 +1438,124 @@ function readFixedFloatCurrencies(data) {
1408
1438
 
1409
1439
  // src/swap/fixedfloat-orders.ts
1410
1440
  import { recordOrEmpty as recordOrEmpty5 } from "@openreceive/core";
1441
+
1442
+ // src/generated/swap-state-table.ts
1443
+ var OPENRECEIVE_SWAP_STATUS_ROWS = [
1444
+ {
1445
+ status: "DONE",
1446
+ refund_tx_present: true,
1447
+ choice: "any",
1448
+ state: "refunded",
1449
+ refund_reason_from_emergency: false
1450
+ },
1451
+ {
1452
+ status: "FINISHED",
1453
+ refund_tx_present: true,
1454
+ choice: "any",
1455
+ state: "refunded",
1456
+ refund_reason_from_emergency: false
1457
+ },
1458
+ {
1459
+ status: "NEW",
1460
+ refund_tx_present: "any",
1461
+ choice: "any",
1462
+ state: "awaiting_deposit",
1463
+ refund_reason_from_emergency: false
1464
+ },
1465
+ {
1466
+ status: "PENDING",
1467
+ refund_tx_present: "any",
1468
+ choice: "any",
1469
+ state: "confirming",
1470
+ refund_reason_from_emergency: false
1471
+ },
1472
+ {
1473
+ status: "EXCHANGE",
1474
+ refund_tx_present: "any",
1475
+ choice: "any",
1476
+ state: "exchanging",
1477
+ refund_reason_from_emergency: false
1478
+ },
1479
+ {
1480
+ status: "WITHDRAW",
1481
+ refund_tx_present: "any",
1482
+ choice: "any",
1483
+ state: "paying_invoice",
1484
+ refund_reason_from_emergency: false
1485
+ },
1486
+ {
1487
+ status: "DONE",
1488
+ refund_tx_present: "any",
1489
+ choice: "any",
1490
+ state: "completed",
1491
+ refund_reason_from_emergency: false
1492
+ },
1493
+ {
1494
+ status: "EXPIRED",
1495
+ refund_tx_present: "any",
1496
+ choice: "any",
1497
+ state: "expired",
1498
+ refund_reason_from_emergency: false
1499
+ },
1500
+ {
1501
+ status: "EMERGENCY",
1502
+ refund_tx_present: true,
1503
+ choice: "REFUND",
1504
+ state: "refunded",
1505
+ refund_reason_from_emergency: true
1506
+ },
1507
+ {
1508
+ status: "EMERGENCY",
1509
+ refund_tx_present: "any",
1510
+ choice: "REFUND",
1511
+ state: "refund_pending",
1512
+ refund_reason_from_emergency: true
1513
+ },
1514
+ {
1515
+ status: "EMERGENCY",
1516
+ refund_tx_present: "any",
1517
+ choice: "EXCHANGE",
1518
+ state: "attention",
1519
+ attention_reason: "provider_reported_emergency",
1520
+ refund_reason_from_emergency: false
1521
+ },
1522
+ {
1523
+ status: "EMERGENCY",
1524
+ refund_tx_present: "any",
1525
+ choice: "any",
1526
+ state: "refund_required",
1527
+ refund_reason_from_emergency: true
1528
+ },
1529
+ {
1530
+ status: "*",
1531
+ status_contains: "FAIL",
1532
+ refund_tx_present: "any",
1533
+ choice: "any",
1534
+ state: "failed",
1535
+ refund_reason_from_emergency: false
1536
+ },
1537
+ {
1538
+ status: "*",
1539
+ refund_tx_present: "any",
1540
+ choice: "any",
1541
+ state: "attention",
1542
+ attention_reason: "provider_status_unrecognized",
1543
+ refund_reason_from_emergency: false
1544
+ }
1545
+ ];
1546
+ var OPENRECEIVE_SWAP_EMERGENCY_STATUS_ALIASES = {
1547
+ OVER: "MORE",
1548
+ OVERPAID: "MORE"
1549
+ };
1550
+ var OPENRECEIVE_SWAP_REFUND_REASON_ROWS = [
1551
+ { all_of: ["LESS", "EXPIRED"], refund_reason: "underpaid_and_late" },
1552
+ { all_of: ["MORE", "EXPIRED"], refund_reason: "overpaid_and_late" },
1553
+ { all_of: ["LESS"], refund_reason: "underpaid" },
1554
+ { all_of: ["MORE"], refund_reason: "overpaid" },
1555
+ { all_of: ["EXPIRED"], refund_reason: "late_deposit" }
1556
+ ];
1557
+
1558
+ // src/swap/fixedfloat-orders.ts
1411
1559
  function normalizeFixedFloatOrder(data, input) {
1412
1560
  const fields = extractFixedFloatOrderFields(recordOrEmpty5(data), input);
1413
1561
  const { attention, attention_reason } = fields.status;
@@ -1492,58 +1640,28 @@ function extractFixedFloatOrderFields(record, input) {
1492
1640
  }
1493
1641
  function normalizeFixedFloatStatus(status, emergency, refundTxId) {
1494
1642
  const normalized = status.toUpperCase();
1495
- if (refundTxId !== void 0 && (normalized === "DONE" || normalized === "FINISHED")) {
1496
- return { state: "refunded" };
1497
- }
1498
- if (normalized === "NEW") return { state: "awaiting_deposit" };
1499
- if (normalized === "PENDING") return { state: "confirming" };
1500
- if (normalized === "EXCHANGE") return { state: "exchanging" };
1501
- if (normalized === "WITHDRAW") return { state: "paying_invoice" };
1502
- if (normalized === "DONE") return { state: "completed" };
1503
- if (normalized === "EXPIRED") return { state: "expired" };
1504
- if (normalized === "EMERGENCY") {
1505
- const choice = optionalStringField(emergency, "choice")?.toUpperCase();
1506
- const emergencyStatuses = optionalStringArrayField(emergency, "status").map(
1507
- (item) => item.toUpperCase()
1508
- );
1509
- const refundReason = refundReasonFromEmergencyStatuses(emergencyStatuses);
1510
- if (choice === "REFUND" && refundTxId !== void 0) {
1511
- return {
1512
- state: "refunded",
1513
- ...refundReason === void 0 ? {} : { refund_reason: refundReason }
1514
- };
1515
- }
1516
- if (choice === "REFUND") {
1517
- return {
1518
- state: "refund_pending",
1519
- ...refundReason === void 0 ? {} : { refund_reason: refundReason }
1520
- };
1521
- }
1522
- if (choice === "EXCHANGE") {
1523
- return {
1524
- state: "attention",
1525
- attention: true,
1526
- attention_reason: "provider_reported_emergency"
1527
- };
1528
- }
1529
- return {
1530
- state: "refund_required",
1531
- ...refundReason === void 0 ? {} : { refund_reason: refundReason }
1532
- };
1533
- }
1534
- if (normalized.includes("FAIL")) return { state: "failed" };
1535
- return { state: "attention", attention: true, attention_reason: "provider_status_unrecognized" };
1643
+ const refundTxPresent = refundTxId !== void 0;
1644
+ const choice = optionalStringField(emergency, "choice")?.toUpperCase();
1645
+ const row = OPENRECEIVE_SWAP_STATUS_ROWS.find(
1646
+ (candidate) => (candidate.status === "*" ? candidate.status_contains === void 0 || normalized.includes(candidate.status_contains) : candidate.status === normalized) && (candidate.refund_tx_present === "any" || candidate.refund_tx_present === refundTxPresent) && (candidate.choice === "any" || (choice === void 0 ? candidate.choice === "absent" : candidate.choice === choice))
1647
+ );
1648
+ const refundReason = row.refund_reason_from_emergency ? refundReasonFromEmergencyStatuses(optionalStringArrayField(emergency, "status")) : void 0;
1649
+ return {
1650
+ state: row.state,
1651
+ ...row.attention_reason === void 0 ? {} : { attention: true, attention_reason: row.attention_reason },
1652
+ ...refundReason === void 0 ? {} : { refund_reason: refundReason }
1653
+ };
1536
1654
  }
1537
1655
  function refundReasonFromEmergencyStatuses(statuses) {
1538
- const less = statuses.includes("LESS");
1539
- const more = statuses.includes("MORE") || statuses.includes("OVER") || statuses.includes("OVERPAID");
1540
- const expired = statuses.includes("EXPIRED");
1541
- if (less && expired) return "underpaid_and_late";
1542
- if (more && expired) return "overpaid_and_late";
1543
- if (less) return "underpaid";
1544
- if (more) return "overpaid";
1545
- if (expired) return "late_deposit";
1546
- return void 0;
1656
+ const present = new Set(
1657
+ statuses.map((item) => {
1658
+ const upper = item.toUpperCase();
1659
+ return OPENRECEIVE_SWAP_EMERGENCY_STATUS_ALIASES[upper] ?? upper;
1660
+ })
1661
+ );
1662
+ return OPENRECEIVE_SWAP_REFUND_REASON_ROWS.find(
1663
+ (row) => row.all_of.every((item) => present.has(item))
1664
+ )?.refund_reason;
1547
1665
  }
1548
1666
  function isRefundPathState(state) {
1549
1667
  return state === "refund_required" || state === "refund_pending" || state === "refunded";
@@ -2418,6 +2536,8 @@ export {
2418
2536
  HEX_64,
2419
2537
  createNwcReceiveClient,
2420
2538
  OPENRECEIVE_SWAP_PAY_IN_ASSETS,
2539
+ OPENRECEIVE_SWAP_PROVIDER_STATES,
2540
+ OPENRECEIVE_SWAP_STATE_TABLE,
2421
2541
  isSwapPayInAsset,
2422
2542
  listSwapAssetInfo,
2423
2543
  TransientSwapCache,
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  createNwcReceiveClient,
3
3
  readLscConnectionsFromEnvironment,
4
4
  redactSecrets
5
- } from "./chunk-LWN4JJ24.js";
5
+ } from "./chunk-65UPROSE.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { existsSync } from "fs";
package/dist/index.d.ts CHANGED
@@ -148,7 +148,18 @@ declare class AlbyNwcReceiveClient implements ReceiveNwcClient {
148
148
  declare function createNwcReceiveClient(options: AlbyNwcReceiveClientOptions): AlbyNwcReceiveClient;
149
149
 
150
150
  declare const OPENRECEIVE_SWAP_PAY_IN_ASSETS: readonly ["SOL_SOL", "USDT_TRON", "USDT_SOL", "USDC_SOL", "ETH_ETH", "USDT_ETH", "USDC_ETH"];
151
- type SwapPayInAsset = (typeof OPENRECEIVE_SWAP_PAY_IN_ASSETS)[number];
151
+ type GeneratedSwapPayInAsset = (typeof OPENRECEIVE_SWAP_PAY_IN_ASSETS)[number];
152
+ declare const OPENRECEIVE_SWAP_PROVIDER_STATES: readonly ["creating_provider_order", "awaiting_deposit", "confirming", "exchanging", "paying_invoice", "completed", "expired", "refund_required", "refund_pending", "refunded", "attention", "failed"];
153
+ type GeneratedSwapProviderState = (typeof OPENRECEIVE_SWAP_PROVIDER_STATES)[number];
154
+ type GeneratedSwapPhase = "preparing" | "awaiting_deposit" | "processing" | "settling" | "terminal" | "refund" | "attention";
155
+ declare const OPENRECEIVE_SWAP_ATTENTION_REASONS: readonly ["provider_reported_emergency", "provider_status_unrecognized", "provider_completed_without_wallet_settlement"];
156
+ type GeneratedSwapAttentionReason = (typeof OPENRECEIVE_SWAP_ATTENTION_REASONS)[number];
157
+ declare const OPENRECEIVE_SWAP_REFUND_REASONS: readonly ["underpaid", "overpaid", "late_deposit", "underpaid_and_late", "overpaid_and_late"];
158
+ type GeneratedSwapRefundReason = (typeof OPENRECEIVE_SWAP_REFUND_REASONS)[number];
159
+ declare const OPENRECEIVE_SWAP_AVAILABILITY_REASONS: readonly ["provider_unconfigured", "amount_too_small", "amount_too_large", "pair_temporarily_unavailable", "region_unsupported", "provider_rate_limited", "provider_unreachable"];
160
+ type GeneratedSwapAvailabilityReason = (typeof OPENRECEIVE_SWAP_AVAILABILITY_REASONS)[number];
161
+
162
+ type SwapPayInAsset = GeneratedSwapPayInAsset;
152
163
 
153
164
  interface SwapCacheResolveOptions<T> {
154
165
  readonly refreshSeconds: number;
@@ -169,15 +180,16 @@ declare class TransientSwapCache {
169
180
  resolve<T>(key: string, options: SwapCacheResolveOptions<T>): Promise<T>;
170
181
  }
171
182
 
172
- type SwapProviderState = "creating_provider_order" | "awaiting_deposit" | "confirming" | "exchanging" | "paying_invoice" | "completed" | "expired" | "refund_required" | "refund_pending" | "refunded" | "attention" | "failed";
173
- type SwapAvailabilityReason = "provider_unconfigured" | "amount_too_small" | "amount_too_large" | "pair_temporarily_unavailable" | "region_unsupported" | "provider_rate_limited" | "provider_unreachable";
183
+ type SwapProviderState = GeneratedSwapProviderState;
184
+ type SwapAvailabilityReason = GeneratedSwapAvailabilityReason;
174
185
  /**
175
186
  * Why a swap attempt entered the `attention` state and needs human/support review.
176
187
  * Every code path that sets `attention: true` records one of these so a dashboard or
177
- * runbook can branch on the cause instead of a bare boolean. See the "Attention"
178
- * section of docs/internal/swap-operations.md for the per-reason operator runbook.
188
+ * runbook can branch on the cause instead of a bare boolean. The FixedFloat status
189
+ * mapping that produces them is pinned by spec/test-vectors/swap-state.json;
190
+ * `provider_completed_without_wallet_settlement` is reserved and not emitted yet.
179
191
  */
180
- type SwapAttentionReason = "provider_completed_without_wallet_settlement" | "provider_order_creation_stale" | "provider_order_creation_failed" | "provider_order_creation_needs_reconcile" | "provider_reported_emergency" | "provider_status_unrecognized" | "provider_order_expires_after_shadow_invoice";
192
+ type SwapAttentionReason = GeneratedSwapAttentionReason;
181
193
  /**
182
194
  * Why a swap attempt entered the refund path (`refund_required` → `refunded`).
183
195
  * Mapped from FixedFloat `emergency.status` (LESS / MORE / EXPIRED). An overpay
@@ -185,7 +197,7 @@ type SwapAttentionReason = "provider_completed_without_wallet_settlement" | "pro
185
197
  * fixed-amount bolt11, so there is nothing to exchange the surplus into and
186
198
  * `choice=EXCHANGE` is not a path this client takes.
187
199
  */
188
- type SwapRefundReason = "underpaid" | "overpaid" | "late_deposit" | "underpaid_and_late" | "overpaid_and_late";
200
+ type SwapRefundReason = GeneratedSwapRefundReason;
189
201
  interface SwapQuote {
190
202
  readonly pay_amount?: string;
191
203
  readonly minimum_pay_amount?: string;
@@ -407,8 +419,12 @@ declare function fixedFloatCompatibleSwapProvider(options: FixedFloatCompatibleS
407
419
  * - `refund` — a refund is required, staged, or in flight.
408
420
  * - `attention` — needs operator/support review (funds may be stuck).
409
421
  * - `terminal` — the attempt is over and will not change (expired/refunded/failed).
422
+ *
423
+ * The state → phase/terminal table itself is kernel vocabulary
424
+ * (spec/data/kernel-tables.json) generated into every engine; this module adds the
425
+ * payer-facing copy, which only the JS checkout renders.
410
426
  */
411
- type SwapPhase = "preparing" | "awaiting_deposit" | "processing" | "settling" | "refund" | "attention" | "terminal";
427
+ type SwapPhase = GeneratedSwapPhase;
412
428
  interface SwapStateInfo {
413
429
  /** The provider state this describes. */
414
430
  readonly state: SwapProviderState;
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  HEX_64,
3
3
  OPENRECEIVE_SWAP_PAY_IN_ASSETS,
4
+ OPENRECEIVE_SWAP_PROVIDER_STATES,
5
+ OPENRECEIVE_SWAP_STATE_TABLE,
4
6
  ReceiveCheckoutValidationError,
5
7
  TransientSwapCache,
6
8
  WalletPreflightError,
@@ -17,7 +19,7 @@ import {
17
19
  summarizeReconcilePass,
18
20
  summarizeSwapProviderApiRequest,
19
21
  summarizeSwapProviderApiResponse
20
- } from "./chunk-LWN4JJ24.js";
22
+ } from "./chunk-65UPROSE.js";
21
23
 
22
24
  // src/index.ts
23
25
  import { OpenReceiveError as OpenReceiveError2 } from "@openreceive/core";
@@ -778,92 +780,65 @@ function toSafeInteger(value, field) {
778
780
  import { isValidSwapAddressForPayInAsset } from "@openreceive/core";
779
781
 
780
782
  // src/swap/state.ts
781
- var OPENRECEIVE_SWAP_STATES = {
783
+ var SWAP_STATE_COPY = {
782
784
  creating_provider_order: {
783
- state: "creating_provider_order",
784
785
  label: "Preparing payment address",
785
- detail: "Creating a payment address.",
786
- phase: "preparing",
787
- terminal: false
786
+ detail: "Creating a payment address."
788
787
  },
789
788
  awaiting_deposit: {
790
- state: "awaiting_deposit",
791
789
  label: "Waiting for your payment",
792
- detail: "Send exactly the amount shown below.",
793
- phase: "awaiting_deposit",
794
- terminal: false
790
+ detail: "Send exactly the amount shown below."
795
791
  },
796
792
  confirming: {
797
- state: "confirming",
798
793
  label: "Confirming payment",
799
- detail: "Your payment was detected and is confirming on-chain.",
800
- phase: "processing",
801
- terminal: false
794
+ detail: "Your payment was detected and is confirming on-chain."
802
795
  },
803
796
  exchanging: {
804
- state: "exchanging",
805
797
  label: "Converting payment",
806
- detail: "Your payment is confirmed and being converted. This usually finishes within a minute.",
807
- phase: "processing",
808
- terminal: false
798
+ detail: "Your payment is confirmed and being converted. This usually finishes within a minute."
809
799
  },
810
800
  paying_invoice: {
811
- state: "paying_invoice",
812
801
  label: "Finalizing checkout",
813
- detail: "The provider is sending the Lightning payment. This usually takes a few seconds.",
814
- phase: "processing",
815
- terminal: false
802
+ detail: "The provider is sending the Lightning payment. This usually takes a few seconds."
816
803
  },
817
804
  completed: {
818
- state: "completed",
819
805
  label: "Finalizing checkout",
820
- detail: "The provider is sending the Lightning payment. This usually takes a few seconds.",
821
- phase: "settling",
822
- terminal: false
806
+ detail: "The provider is sending the Lightning payment. This usually takes a few seconds."
823
807
  },
824
808
  expired: {
825
- state: "expired",
826
809
  label: "Expired",
827
- detail: "No payment was received before the payment window closed.",
828
- phase: "terminal",
829
- terminal: true
810
+ detail: "No payment was received before the payment window closed."
830
811
  },
831
812
  refund_required: {
832
- state: "refund_required",
833
813
  label: "Refund needed",
834
- detail: "Enter an address you control to request a refund.",
835
- phase: "refund",
836
- terminal: false
814
+ detail: "Enter an address you control to request a refund."
837
815
  },
838
816
  refund_pending: {
839
- state: "refund_pending",
840
817
  label: "Refund pending",
841
- detail: "Your refund request has been sent.",
842
- phase: "refund",
843
- terminal: false
818
+ detail: "Your refund request has been sent."
844
819
  },
845
820
  refunded: {
846
- state: "refunded",
847
821
  label: "Refunded",
848
- detail: "The provider reports the refund was sent.",
849
- phase: "terminal",
850
- terminal: true
822
+ detail: "The provider reports the refund was sent."
851
823
  },
852
824
  attention: {
853
- state: "attention",
854
825
  label: "Needs attention",
855
- detail: "This payment needs support review.",
856
- phase: "attention",
857
- terminal: true
826
+ detail: "This payment needs support review."
858
827
  },
859
828
  failed: {
860
- state: "failed",
861
829
  label: "Failed",
862
- detail: "This payment address can no longer be used.",
863
- phase: "terminal",
864
- terminal: true
830
+ detail: "This payment address can no longer be used."
865
831
  }
866
832
  };
833
+ function swapStateInfo(state) {
834
+ const { phase, terminal } = OPENRECEIVE_SWAP_STATE_TABLE[state];
835
+ return { state, ...SWAP_STATE_COPY[state], phase, terminal };
836
+ }
837
+ var OPENRECEIVE_SWAP_STATES = Object.freeze(
838
+ Object.fromEntries(
839
+ OPENRECEIVE_SWAP_PROVIDER_STATES.map((state) => [state, swapStateInfo(state)])
840
+ )
841
+ );
867
842
 
868
843
  // src/swap/weight-budget.ts
869
844
  import { compact as compact3 } from "@openreceive/core";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openreceive/node",
3
- "version": "0.4.3",
4
- "description": "The OpenReceive Node service: receive-only NWC wallet client, checkout and swap service, pricing, and the openreceive CLI.",
3
+ "version": "0.4.6",
4
+ "description": "Accept Bitcoin Lightning payments in Node.js with your own wallet and optional USDT, USDC, SOL and ETH swaps.",
5
5
  "keywords": [
6
6
  "bitcoin",
7
7
  "lightning",
@@ -17,7 +17,7 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "dependencies": {
19
19
  "@getalby/sdk": "^8.0.3",
20
- "@openreceive/core": "0.4.3"
20
+ "@openreceive/core": "0.4.6"
21
21
  },
22
22
  "bin": {
23
23
  "openreceive": "./bin/openreceive.mjs"
@@ -5,7 +5,8 @@ description: >
5
5
  Use when adding Bitcoin, Lightning, or crypto checkout to a Node.js, Express,
6
6
  Fastify, Next.js, Rails, React, Vue, Svelte, Angular, or plain-HTML
7
7
  application with OpenReceive (the @openreceive/* npm packages or the
8
- openreceive-rails gem).
8
+ openreceive-rails gem), or when connecting a BTCPay Server store to a
9
+ receive-only NWC wallet with the OpenReceive plugin.
9
10
  license: MIT
10
11
  ---
11
12
 
@@ -23,15 +24,25 @@ code** (`NWC_URI`).
23
24
  1. Identify the server stack of the application you are in.
24
25
  2. Open the matching reference — it is complete (quickstart inlined) and needs
25
26
  no network access:
26
- - Node (Express / Fastify / Next.js): [references/node.md](references/node.md)
27
+ - Node, Express: [references/node.md](references/node.md)
28
+ - Node, Fastify: [references/fastify.md](references/fastify.md)
29
+ - Node, Next.js App Router: [references/next.md](references/next.md)
27
30
  - Rails: [references/rails.md](references/rails.md)
31
+ - Django: [references/django.md](references/django.md)
32
+ - Laravel: [references/laravel.md](references/laravel.md)
33
+ - WordPress + WooCommerce: [references/woocommerce.md](references/woocommerce.md) — the packaged gateway and merchant settings.
34
+ - BTCPay Server: [references/btcpay.md](references/btcpay.md) — a plugin,
35
+ configured in BTCPay's store UI or Greenfield API; no application code,
36
+ no npm packages, no gem. The rest of this file is about the library.
28
37
  3. Follow its **Step 0** first: confirm `NWC_URI` is set in the server
29
38
  environment before writing code. Never print the value; never invent a
30
39
  placeholder.
31
40
 
32
- Install (Node): `npm install @openreceive/express @openreceive/react` — swap
33
- the adapter (`fastify`, `next`) and UI package (`vue`, `svelte`, `angular`,
34
- `elements`) for the stack. Install (Rails): `bundle add openreceive-rails`.
41
+ Install, per adapter — Express: `npm install @openreceive/express @openreceive/react`;
42
+ Fastify: `npm install @openreceive/fastify @openreceive/react`; Next.js:
43
+ `npm install @openreceive/next @openreceive/react`. Swap the UI package (`vue`,
44
+ `svelte`, `angular`, `elements`) for the frontend the app already has. Install
45
+ (Rails): `bundle add openreceive-rails`.
35
46
 
36
47
  ## The three server objects
37
48