@openreceive/browser 0.4.10 → 0.4.12

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.
@@ -1,7 +1,7 @@
1
1
  import { AssetIndexEntry, PaymentWizardRoute } from '@openreceive/provider-data';
2
2
  export { PaymentWizardRoute, PaymentWizardRouteRequest, getPaymentWizardRoutes, loadPayTutorialImages, payTutorialImage } from '@openreceive/provider-data';
3
- import { P as PaymentIconId, a as ParseOpenReceiveOptionalIntegerOptions, b as PaymentMethod, R as ResolvedTheme, T as ThemePreference, c as PaymentMethodOption, d as TickingValueOptions, e as TickingValueController, f as TransientFeedbackOptions, g as TransientFeedbackController, C as CheckoutInvoiceSnapshot, U as UnixSeconds, S as SwapDisplayModel, h as CheckoutSnapshot, i as SwapDepositRisk, j as CheckoutState, k as TransactionDetailsInput, l as TransactionDetailRow, m as CreateCheckoutStateOptions, n as CheckoutStatusModelInput, o as CheckoutStatusModel, p as CheckoutPaymentMethod, B as BrowserLoggerOption, q as ThemeAttributeTarget, r as CheckoutElementAttributes, s as CheckoutElementAttributeOptions, t as CheckoutElementEventHandlers, u as CheckoutElementListeners, v as CreateCheckoutShellOptions, w as CheckoutShellElements, x as CheckoutShellOptions, y as CheckoutShellModel, z as CreateOpenReceiveThemeToggleElementOptions, A as ThemeToggleElementAttributeOptions, D as ThemeToggleElementAttributes, E as StoredThemeModelOptions, F as ThemeModel, G as ThemeModelOptions, H as ReadThemePreferenceOptions, I as ThemeControlTargets, J as ThemeStorageOptions, K as PaymentWizardControllerOptions, L as PaymentWizardController, M as PaymentWizardSelection, N as PaymentWizardModel, W as WizardRouteAssetDisplay, O as WizardRouteDisplay, Q as PaymentWizardSelectionAction } from './status-Cnx1Vv-w.js';
4
- export { V as BrowserLogContext, X as BrowserLogger, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, $ as CheckoutPhase, a0 as CheckoutShellRootAttributes, a1 as CheckoutStatusRefresh, a2 as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, a3 as OPENRECEIVE_CHECKOUT_DATA_SELECTORS, a4 as OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES, a5 as OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS, a6 as OPENRECEIVE_CHECKOUT_ELEMENT_PARTS, a7 as OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS, a8 as OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS, a9 as OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME, aa as OPENRECEIVE_COPY_FEEDBACK_MS, ab as OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS, ac as OPENRECEIVE_DEFAULT_PREFIX, ad as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES, ae as OPENRECEIVE_PAYMENT_WIZARD_SELECTORS, af as OPENRECEIVE_PROVIDER_PREVIEW_LIMIT, ag as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE, ah as OPENRECEIVE_STYLE_ROOT_SELECTOR, ai as OPENRECEIVE_THEME_STORAGE_KEY, aj as OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES, ak as OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS, al as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS, am as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS, an as OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME, ao as QrEncoder, ap as QrSvgController, aq as QrSvgControllerOptions, ar as Status, as as SwapCopyRow, at as SwapRefundStaging, au as WizardProviderDisplay, av as assertDisplayInvoice, aw as copyInvoice, ax as createCheckoutActionEvent, ay as createCheckoutController, az as createCheckoutErrorEvent, aA as createCheckoutProviderCopyEvent, aB as createCheckoutStateEvent, aC as createQrPayloadSvg, aD as createQrSvg, aE as createQrSvgController, aF as createStatusFetcher, aG as createThemeChangeEvent, aH as currentCheckoutUrl, aI as deriveCheckoutStateLabels, aJ as deriveStatus, aK as enterCheckoutResumePath, aL as escapeHtml, aM as formatAmountCaption, aN as formatDepositAmount, aO as formatFiatAmount, aP as formatMsats, aQ as formatSwapLimit, aR as formatUnixTime, aS as openWallet, aT as paymentIconSvgs, aU as prepareCheckout, aV as requestCheckout } from './status-Cnx1Vv-w.js';
3
+ import { P as PaymentIconId, a as ParseOpenReceiveOptionalIntegerOptions, b as PaymentMethod, R as ResolvedTheme, T as ThemePreference, c as PaymentMethodOption, d as TickingValueOptions, e as TickingValueController, f as TransientFeedbackOptions, g as TransientFeedbackController, C as CheckoutInvoiceSnapshot, U as UnixSeconds, S as SwapDisplayModel, h as CheckoutSnapshot, i as SwapDepositRisk, j as CheckoutState, k as TransactionDetailsInput, l as TransactionDetailRow, m as CreateCheckoutStateOptions, n as CheckoutStatusModelInput, o as CheckoutStatusModel, p as CheckoutPaymentMethod, B as BrowserLoggerOption, q as ThemeAttributeTarget, r as CheckoutElementAttributes, s as CheckoutElementAttributeOptions, t as CheckoutElementEventHandlers, u as CheckoutElementListeners, v as CreateCheckoutShellOptions, w as CheckoutShellElements, x as CheckoutShellOptions, y as CheckoutShellModel, z as CreateOpenReceiveThemeToggleElementOptions, A as ThemeToggleElementAttributeOptions, D as ThemeToggleElementAttributes, E as StoredThemeModelOptions, F as ThemeModel, G as ThemeModelOptions, H as ReadThemePreferenceOptions, I as ThemeControlTargets, J as ThemeStorageOptions, K as PaymentWizardControllerOptions, L as PaymentWizardController, M as PaymentWizardSelection, N as PaymentWizardModel, W as WizardRouteAssetDisplay, O as WizardRouteDisplay, Q as PaymentWizardSelectionAction } from './status-Q1CJqaYo.js';
4
+ export { V as BrowserLogContext, X as BrowserLogger, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, $ as CheckoutPhase, a0 as CheckoutShellRootAttributes, a1 as CheckoutStatusRefresh, a2 as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, a3 as OPENRECEIVE_CHECKOUT_DATA_SELECTORS, a4 as OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES, a5 as OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS, a6 as OPENRECEIVE_CHECKOUT_ELEMENT_PARTS, a7 as OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS, a8 as OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS, a9 as OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME, aa as OPENRECEIVE_COPY_FEEDBACK_MS, ab as OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS, ac as OPENRECEIVE_DEFAULT_PREFIX, ad as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES, ae as OPENRECEIVE_PAYMENT_WIZARD_SELECTORS, af as OPENRECEIVE_PROVIDER_PREVIEW_LIMIT, ag as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE, ah as OPENRECEIVE_STYLE_ROOT_SELECTOR, ai as OPENRECEIVE_THEME_STORAGE_KEY, aj as OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES, ak as OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS, al as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS, am as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS, an as OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME, ao as QrEncoder, ap as QrSvgController, aq as QrSvgControllerOptions, ar as Status, as as SwapCopyRow, at as SwapRefundStaging, au as WizardProviderDisplay, av as assertDisplayInvoice, aw as copyInvoice, ax as createCheckoutActionEvent, ay as createCheckoutController, az as createCheckoutErrorEvent, aA as createCheckoutProviderCopyEvent, aB as createCheckoutStateEvent, aC as createQrPayloadSvg, aD as createQrSvg, aE as createQrSvgController, aF as createStatusFetcher, aG as createThemeChangeEvent, aH as currentCheckoutUrl, aI as deriveCheckoutStateLabels, aJ as deriveStatus, aK as enterCheckoutResumePath, aL as escapeHtml, aM as formatAmountCaption, aN as formatDepositAmount, aO as formatFiatAmount, aP as formatMsats, aQ as formatSwapLimit, aR as formatUnixTime, aS as openWallet, aT as paymentIconSvgs, aU as prepareCheckout, aV as requestCheckout } from './status-Q1CJqaYo.js';
5
5
  import '@openreceive/core';
6
6
 
7
7
  /** Each packaged payment icon as a `data:image/svg+xml` URI, keyed by id. */
@@ -437,8 +437,14 @@ declare function createSwapDisplayModel(invoice: CheckoutInvoiceSnapshot, option
437
437
  * renderers used to keep a near-identical copy of this; a headless UI holding
438
438
  * its own snapshot store wants the same three rules.
439
439
  *
440
- * `dismissedInvoiceId` is the "back to Lightning" exit: a dismissed attempt is
441
- * invisible until a new start or a refund clears the dismissal.
440
+ * `dismissedInvoiceId` is the "back to Lightning" / "switch payment method"
441
+ * exit: while it is set, NO attempt is current until a new start or a refund
442
+ * clears it. The payer walked away from the swap they were looking at, and every
443
+ * older attempt in the snapshot is one they walked away from before that. The
444
+ * snapshot copy is the fallback only when nothing was dismissed (a resumed
445
+ * attempt this session never started). Falling back to "the first other swap"
446
+ * reopened the previous coin's deposit once an order had two attempts, and
447
+ * every further click re-dismissed the same one, so the exit stopped working.
442
448
  */
443
449
  declare function selectCurrentSwapInvoice(snapshot: CheckoutSnapshot | undefined, options?: {
444
450
  readonly started?: CheckoutInvoiceSnapshot | null;
@@ -747,13 +753,15 @@ interface CheckoutSessionOptions {
747
753
  snapshot(): CheckoutSnapshot | undefined;
748
754
  /** The order being paid, read at call time (the element's is an attribute). */
749
755
  reference(): string | undefined;
756
+ /** Endpoint is part of checkout identity; trailing slashes are equivalent. */
757
+ prefix?(): string | undefined;
750
758
  /**
751
759
  * Mint a Lightning invoice for this reference (POST `${prefix}/checkouts`).
752
760
  * The host closes over its own prefix, metadata and fetch. Answer `undefined`
753
761
  * to say "this host cannot mint" — React's payment wizard is such a host: it
754
762
  * asks its parent for Lightning through `onRequestLightning` instead.
755
763
  */
756
- requestCheckout?(reference: string): Promise<CheckoutSnapshot> | undefined;
764
+ requestCheckout?(reference: string, signal?: AbortSignal): Promise<CheckoutSnapshot> | undefined;
757
765
  /** Publish a snapshot that now carries the Lightning attempt. */
758
766
  onSnapshot?(snapshot: CheckoutSnapshot): void;
759
767
  /**
@@ -785,6 +793,14 @@ interface CheckoutSession {
785
793
  * carries the accepted range the host renders in its unavailable panel.
786
794
  */
787
795
  readonly swapQuotes: Readonly<Record<string, CheckoutPaymentMethod>>;
796
+ /** Invalidate every outstanding action when identity changes. */
797
+ syncIdentity(): void;
798
+ reset(): void;
799
+ dispose(): void;
800
+ capture(): {
801
+ readonly signal: AbortSignal;
802
+ isCurrent(): boolean;
803
+ };
788
804
  ensureLightning(): Promise<void>;
789
805
  /**
790
806
  * Quote the pay-in asset, then start the swap when the quote confirms the
@@ -825,6 +841,7 @@ declare function createCheckoutShell(snapshot: CheckoutSnapshot, options?: Creat
825
841
  * URL input — no call in this module takes a route of its own.
826
842
  */
827
843
  interface SwapRequestOptions {
844
+ readonly signal?: AbortSignal;
828
845
  readonly fetch: typeof globalThis.fetch;
829
846
  readonly prefix: string;
830
847
  readonly logger?: BrowserLoggerOption;
package/dist/headless.js CHANGED
@@ -119,7 +119,7 @@ import {
119
119
  swapOptionLimitSentence,
120
120
  swapPickerKey,
121
121
  updatePaymentWizardSelection
122
- } from "./chunk-FIAYCDZO.js";
122
+ } from "./chunk-XDFN6BT3.js";
123
123
 
124
124
  // src/headless.ts
125
125
  import {
@@ -177,7 +177,43 @@ function createCheckoutSession(options) {
177
177
  let mintingLightning = false;
178
178
  let startingSwapAsset = null;
179
179
  let swapQuotes = {};
180
+ let generation = 0;
181
+ let abort = new AbortController();
182
+ const identityKey = () => JSON.stringify([
183
+ options.reference(),
184
+ (options.prefix?.() ?? options.swap?.prefix() ?? "").replace(/\/+$/, "")
185
+ ]);
186
+ let identity = identityKey();
187
+ function invalidateActions() {
188
+ abort.abort();
189
+ abort = new AbortController();
190
+ generation += 1;
191
+ mintingLightning = false;
192
+ startingSwapAsset = null;
193
+ }
194
+ function reset() {
195
+ invalidateActions();
196
+ identity = identityKey();
197
+ wizardError = void 0;
198
+ swapStartError = void 0;
199
+ lightningRequested = false;
200
+ mintingLightning = false;
201
+ startingSwapAsset = null;
202
+ swapQuotes = {};
203
+ }
204
+ function syncIdentity() {
205
+ if (identityKey() !== identity) reset();
206
+ }
207
+ function capture() {
208
+ syncIdentity();
209
+ const captured = generation;
210
+ return {
211
+ signal: abort.signal,
212
+ isCurrent: () => captured === generation && identityKey() === identity
213
+ };
214
+ }
180
215
  async function ensureLightning() {
216
+ const action = capture();
181
217
  if (mintingLightning) return;
182
218
  const reference = options.reference();
183
219
  if (reference === void 0 || reference.length === 0) return;
@@ -195,20 +231,25 @@ function createCheckoutSession(options) {
195
231
  wizardError = void 0;
196
232
  options.onChange();
197
233
  try {
198
- const pending = options.requestCheckout?.(reference);
234
+ const pending = options.requestCheckout?.(reference, action.signal);
199
235
  if (pending === void 0) return;
200
236
  const checkout = await pending;
237
+ if (!action.isCurrent()) return;
201
238
  lightningRequested = true;
202
239
  options.onSnapshot?.(mergeMintedCheckout(checkout, options.snapshot()));
203
240
  } catch (error) {
241
+ if (!action.isCurrent()) return;
204
242
  wizardError = payerFacingMessage(error, MINT_FAILED);
205
243
  options.onError(error);
206
244
  } finally {
207
- mintingLightning = false;
208
- options.onChange();
245
+ if (action.isCurrent()) {
246
+ mintingLightning = false;
247
+ options.onChange();
248
+ }
209
249
  }
210
250
  }
211
251
  async function startSwap(payInAsset) {
252
+ const action = capture();
212
253
  if (startingSwapAsset !== null) return;
213
254
  const swap = options.swap;
214
255
  if (swap === void 0) {
@@ -239,9 +280,18 @@ function createCheckoutSession(options) {
239
280
  swapStartError = void 0;
240
281
  options.onChange();
241
282
  try {
242
- const quote = await quoteSwapAsset(payInAsset, prefix, fetcher, reference, csrfHeader);
283
+ const quote = await quoteSwapAsset(
284
+ payInAsset,
285
+ prefix,
286
+ fetcher,
287
+ reference,
288
+ csrfHeader,
289
+ action
290
+ );
291
+ if (!action.isCurrent()) return;
243
292
  if (quote !== void 0 && quote.available === false) return;
244
293
  const started = await startSwapRequest({
294
+ signal: action.signal,
245
295
  fetch: fetcher,
246
296
  prefix,
247
297
  ...csrfHeader === void 0 ? {} : { csrfHeader },
@@ -249,12 +299,14 @@ function createCheckoutSession(options) {
249
299
  payInAsset,
250
300
  ...options.logger === void 0 ? {} : { logger: options.logger }
251
301
  });
302
+ if (!action.isCurrent()) return;
252
303
  selection.setStarted(started);
253
304
  selection.setDismissedInvoiceId(null);
254
305
  swap.onStarted?.(started);
255
306
  selection.setSelectedAsset(payInAsset);
256
307
  options.onChange();
257
308
  } catch (error) {
309
+ if (!action.isCurrent()) return;
258
310
  const landed = selection.started();
259
311
  if (landed?.swap?.pay_in_asset === payInAsset && landed.invoice_id !== selection.dismissedInvoiceId()) {
260
312
  selection.setSelectedAsset(payInAsset);
@@ -264,17 +316,22 @@ function createCheckoutSession(options) {
264
316
  selection.setSelectedAsset(payInAsset);
265
317
  failSwapStart(error);
266
318
  } finally {
267
- startingSwapAsset = null;
319
+ if (action.isCurrent()) {
320
+ startingSwapAsset = null;
321
+ options.onChange();
322
+ }
268
323
  }
269
324
  }
270
- async function quoteSwapAsset(payInAsset, prefix, fetcher, reference, csrfHeader) {
325
+ async function quoteSwapAsset(payInAsset, prefix, fetcher, reference, csrfHeader, action) {
271
326
  const body = await postJson({
327
+ signal: action.signal,
272
328
  fetch: fetcher,
273
329
  prefix,
274
330
  ...csrfHeader === void 0 ? {} : { csrfHeader },
275
331
  ...options.logger === void 0 ? {} : { logger: options.logger },
276
332
  body: { reference, action: "swap_quote", pay_in_asset: payInAsset }
277
333
  });
334
+ if (!action.isCurrent()) return void 0;
278
335
  const quote = normalizeSwapQuote(body);
279
336
  if (quote === void 0) return void 0;
280
337
  swapQuotes = { ...swapQuotes, [quote.pay_in_asset]: quote };
@@ -304,12 +361,17 @@ function createCheckoutSession(options) {
304
361
  get swapQuotes() {
305
362
  return swapQuotes;
306
363
  },
364
+ syncIdentity,
365
+ reset,
366
+ dispose: reset,
367
+ capture,
307
368
  ensureLightning,
308
369
  startSwap,
309
370
  resetLightningRequest() {
310
- lightningRequested = false;
371
+ reset();
311
372
  },
312
373
  clearSwapStartError() {
374
+ invalidateActions();
313
375
  swapStartError = void 0;
314
376
  }
315
377
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { aW as BrowserLogLevel } from './status-Cnx1Vv-w.js';
2
- export { aX as BrowserLogEntry, X as BrowserLogger, B as BrowserLoggerOption, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, h as CheckoutSnapshot, j as CheckoutState, aY as CopyInvoiceOptions, aZ as GuestCheckoutResumeController, a_ as GuestCheckoutResumeOptions, a$ as OpenWalletOptions, b0 as PrepareCheckoutOptions, b1 as QrOptions, b2 as RequestCheckoutOptions, ar as Status, b3 as StatusInvoiceLike, U as UnixSeconds, aw as copyInvoice, ay as createCheckoutController, b4 as createGuestCheckoutResume, b5 as createGuestOrderFetcher, b6 as createLightningUri, b7 as createQrPngDataUrl, aD as createQrSvg, aJ as deriveStatus, aK as enterCheckoutResumePath, aS as openWallet, aU as prepareCheckout, aV as requestCheckout } from './status-Cnx1Vv-w.js';
1
+ import { aW as BrowserLogLevel } from './status-Q1CJqaYo.js';
2
+ export { aX as BrowserLogEntry, X as BrowserLogger, B as BrowserLoggerOption, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, h as CheckoutSnapshot, j as CheckoutState, aY as CopyInvoiceOptions, aZ as GuestCheckoutResumeController, a_ as GuestCheckoutResumeOptions, a$ as OpenWalletOptions, b0 as PrepareCheckoutOptions, b1 as QrOptions, b2 as RequestCheckoutOptions, ar as Status, b3 as StatusInvoiceLike, U as UnixSeconds, aw as copyInvoice, ay as createCheckoutController, b4 as createGuestCheckoutResume, b5 as createGuestOrderFetcher, b6 as createLightningUri, b7 as createQrPngDataUrl, aD as createQrSvg, aJ as deriveStatus, aK as enterCheckoutResumePath, aS as openWallet, aU as prepareCheckout, aV as requestCheckout } from './status-Q1CJqaYo.js';
3
3
  import '@openreceive/provider-data';
4
4
  import '@openreceive/core';
5
5
 
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  openWallet,
14
14
  prepareCheckout,
15
15
  requestCheckout
16
- } from "./chunk-FIAYCDZO.js";
16
+ } from "./chunk-XDFN6BT3.js";
17
17
  export {
18
18
  BrowserRequestError,
19
19
  copyInvoice,
@@ -506,9 +506,7 @@ interface SwapDisplayModel {
506
506
  readonly networkWarningEmphasis: string;
507
507
  /**
508
508
  * Full plain-text network warning (accessible / non-HTML consumers). The
509
- * lost-funds sentence is present only when `depositRisk` is not `pinned`; the
510
- * "pay with one method only" sentence is on every rail, because double-paying
511
- * is reachable on all of them.
509
+ * lost-funds sentence is present only when `depositRisk` is not `pinned`.
512
510
  */
513
511
  readonly networkWarning: string;
514
512
  readonly depositAddress: string;
@@ -820,8 +818,9 @@ interface RequestCheckoutOptions extends RequestCheckoutBaseOptions {
820
818
  * `prepareCheckout` posts only the order id — it locks the amount and lists
821
819
  * payment methods without minting — so there is no memo or metadata to carry.
822
820
  */
823
- type PrepareCheckoutOptions = Pick<RequestCheckoutBaseOptions, "prefix" | "reference" | "fetch" | "headers" | "csrfHeader">;
821
+ type PrepareCheckoutOptions = Pick<RequestCheckoutBaseOptions, "prefix" | "reference" | "fetch" | "headers" | "csrfHeader" | "signal">;
824
822
  interface RequestCheckoutBaseOptions {
823
+ readonly signal?: AbortSignal;
825
824
  /**
826
825
  * Base path the shipped router is mounted at (e.g. `/openreceive`). The create and
827
826
  * prepare routes are derived from it — see {@link checkoutRoutes}. It is required
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openreceive/browser",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "Browser helpers and a headless UI engine for Bitcoin Lightning checkout and optional USDT, USDC, SOL and ETH swaps.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -30,8 +30,8 @@
30
30
  "./styles.css": "./dist/styles.css"
31
31
  },
32
32
  "dependencies": {
33
- "@openreceive/core": "0.4.10",
34
- "@openreceive/provider-data": "0.4.10",
33
+ "@openreceive/core": "0.4.12",
34
+ "@openreceive/provider-data": "0.4.12",
35
35
  "qrcode": "^1.5.4"
36
36
  },
37
37
  "devDependencies": {
@@ -34,9 +34,10 @@ code** (`NWC_URI`).
34
34
  - BTCPay Server: [references/btcpay.md](references/btcpay.md) — a plugin,
35
35
  configured in BTCPay's store UI or Greenfield API; no application code,
36
36
  no npm packages, no gem. The rest of this file is about the library.
37
- 3. Follow its **Step 0** first: confirm `NWC_URI` is set in the server
38
- environment before writing code. Never print the value; never invent a
39
- placeholder.
37
+ 3. Follow its **Step 0** first: before writing code or searching the machine,
38
+ ask the user for the receive-only NWC code (then the swap URI), one question
39
+ per message, and store each pasted code in the project's env file yourself.
40
+ Never print the value; never invent a placeholder.
40
41
 
41
42
  Install, per adapter — Express: `npm install @openreceive/express @openreceive/react`;
42
43
  Fastify: `npm install @openreceive/fastify @openreceive/react`; Next.js:
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (BTCPay Server)
2
2
 
3
- These directions describe OpenReceive 0.4.10.
3
+ These directions describe OpenReceive 0.4.12.
4
4
 
5
5
  Connect a BTCPay Server store to a receive-only NWC wallet with the OpenReceive
6
6
  plugin, and optionally let payers pay BTCPay invoices with USDT, USDC, ETH or
@@ -35,10 +35,12 @@ refund path on the same checkout screen.
35
35
  1. Confirm the BTCPay Server version is 2.4.4 or later (Server Settings →
36
36
  About, or `GET /api/v1/server/info`). The plugin declares that minimum and
37
37
  BTCPay refuses to load it below.
38
- 2. Check whether the plugin is installed (Server Settings → Plugins, or the
39
- store navigation shows an "OpenReceive" entry). If not, install it from the
40
- BTCPay plugin directory (Server Settings → Plugins, search "OpenReceive"),
41
- as the quickstart says; do not invent an installer command.
38
+ 2. Check whether the plugin is installed (the Plugins menu — the plug icon in
39
+ the top-right corner — under Installed Plugins, or the store navigation
40
+ shows an "OpenReceive" entry). If not, install it from the BTCPay plugin
41
+ directory (the same Plugins menu → Plugin Directory, search "openreceive",
42
+ then Install and Restart now), as the quickstart says; do not invent an
43
+ installer command.
42
44
  3. Check whether the store already has an OpenReceive connection:
43
45
  `GET /api/v1/stores/{storeId}/openreceive/settings` returns
44
46
  `lightningNodeIsOpenReceive`. If true, the wallet step is done — go to
@@ -119,6 +121,8 @@ enough; drop the `.md` for the same page a person would read.
119
121
  Questions, or a problem with the plugin itself:
120
122
  https://openreceive.org/contact
121
123
 
124
+ - https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
125
+
122
126
  ---
123
127
 
124
128
  ## The quickstart, in full
@@ -131,15 +135,15 @@ passes. The page it comes from is https://openreceive.org/guides/quickstart-btcp
131
135
  Requires BTCPay Server ≥ 2.4.4.
132
136
 
133
137
  The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
134
- BTCPay store. BTCPay mints every Lightning invoice in that wallet and records
135
- payments through its own settlement machinery. Optionally, payers can pay a
136
- BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
137
- provider; the swap settles into the same wallet. The store's internal node is
138
+ BTCPay store. BTCPay creates every Lightning invoice in that wallet. It records
139
+ payments the same way it records any other payment. You can also let payers pay
140
+ a BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
141
+ provider. The swap pays into the same wallet. The store's internal node is
138
142
  never used.
139
143
 
140
144
  This is not the Node or Rails library. There are no hooks, no
141
- `openreceive_payments` table and no OpenReceive HTTP routes: BTCPay's
142
- invoices, checkout, webhooks and Greenfield API are the host.
145
+ `openreceive_payments` table and no OpenReceive HTTP routes. BTCPay's own
146
+ invoices, checkout, webhooks and Greenfield API do that work.
143
147
 
144
148
  ### 1. Prerequisites
145
149
 
@@ -155,39 +159,66 @@ invoices, checkout, webhooks and Greenfield API are the host.
155
159
 
156
160
  ### 2. Install the plugin
157
161
 
158
- In BTCPay, open **Server Settings → Plugins**, search the plugin directory
159
- for **OpenReceive**, click **Install**, and restart BTCPay when prompted.
160
- BTCPay creates the plugin's one table (`openreceive_swaps`, schema
161
- `BTCPayServer.Plugins.OpenReceive`) in its own Postgres at startup; nothing
162
- else is created.
162
+ Sign in as a **server administrator**. If someone else hosts your server, ask
163
+ them to install the plugin for you.
164
+
165
+ **1. Open the Plugins menu.** It is the plug icon in the top-right corner.
166
+
167
+ **2. Click Plugin Directory.**
168
+
169
+ **3. Search for `openreceive`** and click the **OpenReceive** result.
170
+
171
+ **4. Click Install in BTCPay Server.** Confirm when prompted, then click
172
+ **Restart now** and wait for BTCPay to come back.
173
+
174
+ At startup, BTCPay creates the plugin's two tables in its own Postgres
175
+ database: `openreceive_invoices` and `openreceive_swaps`, in the schema
176
+ `BTCPayServer.Plugins.OpenReceive`. Nothing else is created.
163
177
 
164
178
  To build the plugin from source instead, follow
165
179
  [the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
166
180
 
167
181
  ### 3. Connect the wallet
168
182
 
169
- Follow the plugin README's illustrated walkthrough:
170
- [OpenReceive for BTCPay Server](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/BTCPayServer.Plugins.OpenReceive/README.md).
171
- It opens the **OpenReceive** page in the store's sidebar, saves the
172
- receive-only NWC code, optionally saves the LSC code to turn swaps on, and
173
- creates a first test invoice. There is nothing else to configure: you never
174
- open BTCPay's Lightning node screen, and the plugin never reads the internal
175
- node.
183
+ 1. Select your store and open **OpenReceive** in its sidebar, under Wallets.
184
+ 2. Paste your receive-only NWC code. To see what the wallet supports first,
185
+ click **Test connection**.
186
+ 3. Click **Save NWC Code**.
187
+ 4. To turn swaps on, paste a Lightning Swap Connect code and click **Save swap
188
+ settings**.
176
189
 
177
- Saving fails closed if the wallet advertises a spend method such as
178
- `pay_invoice`. Mint a receive-only code instead; the override for a wallet
179
- that cannot is a deliberate, logged choice. Swaps raise the store's invoice
180
- expiration to 60 minutes when it is shorter, because a swap needs at least 45
181
- minutes of invoice life.
190
+ The page then shows **Wallet connected**. If you set up a provider, it also
191
+ shows **Swaps on**. There is nothing else to configure. You never open BTCPay's
192
+ Lightning node screen, and the plugin never reads the internal node.
193
+
194
+ Screenshots for each of those steps, and for creating a first test invoice,
195
+ are in the plugin's
196
+ [README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/BTCPayServer.Plugins.OpenReceive/README.md).
197
+
198
+ The plugin refuses to save a code whose wallet advertises a spend method such
199
+ as `pay_invoice`. Create a receive-only code instead. If your wallet cannot
200
+ make one, there is an override, but using it is a deliberate choice and the
201
+ plugin logs it.
202
+
203
+ Turning swaps on raises the store's invoice expiration to 60 minutes if it is
204
+ shorter. A swap needs the invoice to stay open for at least 45 minutes.
182
205
 
183
206
  ### 4. Check it
184
207
 
185
- **Run a health check** on the OpenReceive page runs every probe in place:
186
- the connection, the wallet preflight, payment notifications, the last wallet
187
- scan, the swap provider and its assets, the invoice expiration, and swaps
188
- that need a human. Each failing probe carries a fix link.
208
+ Click **Run a health check** on the OpenReceive page. It runs every check
209
+ right there:
210
+
211
+ - the connection
212
+ - the wallet preflight
213
+ - payment notifications
214
+ - the last wallet scan
215
+ - the swap provider and its assets
216
+ - the invoice expiration
217
+ - swaps that need a human
218
+
219
+ Each failing check comes with a link to the fix.
189
220
 
190
- Every setting, Greenfield route, swap state, log event and probe is in the
191
- [BTCPay plugin reference](https://openreceive.org/guides/btcpay-reference.md), including what is
192
- unsupported by design: every send-side feature, top-up invoices, and a bare
193
- `nostr+walletconnect://` string in BTCPay's Lightning node screen.
221
+ The [BTCPay plugin reference](https://openreceive.org/guides/btcpay-reference.md) lists every setting,
222
+ Greenfield route, swap state, log event and check. It also lists what the
223
+ plugin does not support by design: every send-side feature, top-up invoices,
224
+ and a bare `nostr+walletconnect://` string in BTCPay's Lightning node screen.