@unicitylabs/sphere-sdk 0.9.1-dev.12 → 0.9.1-dev.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.
@@ -2125,10 +2125,13 @@ declare class PaymentsModule {
2125
2125
  /** Coalesces concurrent payment-request pump runs. */
2126
2126
  private prPumpInFlight;
2127
2127
  /**
2128
- * Set after the once-per-session `status=open` bootstrap scan: the surfaced
2129
- * incoming list is in-memory only, so still-open requests BELOW the
2130
- * persisted cursor must be recovered at session start (bounded by the §5.5
2131
- * per-payer open cap).
2128
+ * Set after the once-per-session full incoming hydration (#556): the surfaced
2129
+ * incoming list is in-memory only, so on a fresh engine the CURRENT state of
2130
+ * ALL incoming requests — open AND resolved (paid/declined/expired) — must be
2131
+ * rebuilt from a `role=incoming&since=0` pull, not just the still-open ones.
2132
+ * A status-filtered bootstrap (the pre-#556 `status=open` scan) dropped
2133
+ * requests resolved in a PRIOR session, so the payer reopened and the
2134
+ * 'Paid Successfully' request was gone (twin of #521/#549).
2132
2135
  */
2133
2136
  private prBootstrapped;
2134
2137
  private loadedPromise;
@@ -2865,11 +2868,19 @@ declare class PaymentsModule {
2865
2868
  * mailbox-cursor pattern: the persisted `{cursor, syncEpoch}` is the resume
2866
2869
  * point; a `syncEpoch` change (server restore — §5.4) voids cursor
2867
2870
  * continuity, so the tail re-pulls from 0 and the id-dedup in
2868
- * {@link surfaceIncomingPaymentRequest} absorbs the replays. Because the
2869
- * surfaced list is in-memory only, each session ALSO starts with one
2870
- * `status=open` bootstrap scan from 0 — still-open requests below the
2871
- * cursor (bounded by the §5.5 per-payer open cap) are recovered; resolved
2872
- * ones are not re-surfaced.
2871
+ * {@link surfaceIncomingPaymentRequest} absorbs the replays.
2872
+ *
2873
+ * Because the surfaced list is in-memory only, each session FIRST runs one
2874
+ * full incoming hydration from `since=0` with NO status filter (#556 —
2875
+ * mirrors {@link hydrateHistoryFromServer}): a fresh engine rebuilds the
2876
+ * CURRENT state of ALL incoming requests — open AND resolved — so a request
2877
+ * paid/declined/expired in a PRIOR session is still present (with its
2878
+ * resolved status) instead of vanishing once the cursor advanced past it.
2879
+ * Hydration NEVER fires the new-incoming handlers/events for resolved
2880
+ * requests — only `open` ones notify (the status-aware
2881
+ * {@link surfaceIncomingPaymentRequest}) — so reopening can't spam stale
2882
+ * 'new request' notifications. After hydration the `since`-cursor delta poll
2883
+ * picks up live updates from the resume point.
2873
2884
  */
2874
2885
  private pumpIncomingPaymentRequests;
2875
2886
  /**
@@ -2882,13 +2893,19 @@ declare class PaymentsModule {
2882
2893
  * (PR twin of #546/#547).
2883
2894
  */
2884
2895
  private decryptPaymentRequestMemo;
2896
+ /** §16 wire status → the public {@link PaymentRequestStatus} display status. */
2897
+ private static readonly PR_WIRE_STATUS;
2885
2898
  /**
2886
2899
  * Map a §16 wire request onto the public {@link IncomingPaymentRequest}
2887
- * surface and notify (event + handlers). Only `open` requests are
2888
- * actionable; the in-memory id-dedup doubles as the replay guard for
2889
- * cursor resets. Multi-asset requests surface their first asset (the
2890
- * module's request surface is single-asset; module-created requests
2891
- * always are).
2900
+ * surface, deduped by id (the in-memory id-dedup doubles as the replay guard
2901
+ * for cursor resets). Requests of EVERY status are surfaced so a reloaded
2902
+ * thin wallet rebuilds the CURRENT state of its incoming view (#556) — open
2903
+ * ones land as actionable `pending`, resolved ones carry their paid/declined
2904
+ * (→ `rejected`)/expired status. Only `open` requests fire the new-incoming
2905
+ * event + handlers; resolved requests are folded into the list silently, so a
2906
+ * reload (or a `syncEpoch` re-pull) never re-notifies for already-resolved
2907
+ * requests. Multi-asset requests surface their first asset (the module's
2908
+ * request surface is single-asset; module-created requests always are).
2892
2909
  */
2893
2910
  private surfaceIncomingPaymentRequest;
2894
2911
  /**
@@ -2125,10 +2125,13 @@ declare class PaymentsModule {
2125
2125
  /** Coalesces concurrent payment-request pump runs. */
2126
2126
  private prPumpInFlight;
2127
2127
  /**
2128
- * Set after the once-per-session `status=open` bootstrap scan: the surfaced
2129
- * incoming list is in-memory only, so still-open requests BELOW the
2130
- * persisted cursor must be recovered at session start (bounded by the §5.5
2131
- * per-payer open cap).
2128
+ * Set after the once-per-session full incoming hydration (#556): the surfaced
2129
+ * incoming list is in-memory only, so on a fresh engine the CURRENT state of
2130
+ * ALL incoming requests — open AND resolved (paid/declined/expired) — must be
2131
+ * rebuilt from a `role=incoming&since=0` pull, not just the still-open ones.
2132
+ * A status-filtered bootstrap (the pre-#556 `status=open` scan) dropped
2133
+ * requests resolved in a PRIOR session, so the payer reopened and the
2134
+ * 'Paid Successfully' request was gone (twin of #521/#549).
2132
2135
  */
2133
2136
  private prBootstrapped;
2134
2137
  private loadedPromise;
@@ -2865,11 +2868,19 @@ declare class PaymentsModule {
2865
2868
  * mailbox-cursor pattern: the persisted `{cursor, syncEpoch}` is the resume
2866
2869
  * point; a `syncEpoch` change (server restore — §5.4) voids cursor
2867
2870
  * continuity, so the tail re-pulls from 0 and the id-dedup in
2868
- * {@link surfaceIncomingPaymentRequest} absorbs the replays. Because the
2869
- * surfaced list is in-memory only, each session ALSO starts with one
2870
- * `status=open` bootstrap scan from 0 — still-open requests below the
2871
- * cursor (bounded by the §5.5 per-payer open cap) are recovered; resolved
2872
- * ones are not re-surfaced.
2871
+ * {@link surfaceIncomingPaymentRequest} absorbs the replays.
2872
+ *
2873
+ * Because the surfaced list is in-memory only, each session FIRST runs one
2874
+ * full incoming hydration from `since=0` with NO status filter (#556 —
2875
+ * mirrors {@link hydrateHistoryFromServer}): a fresh engine rebuilds the
2876
+ * CURRENT state of ALL incoming requests — open AND resolved — so a request
2877
+ * paid/declined/expired in a PRIOR session is still present (with its
2878
+ * resolved status) instead of vanishing once the cursor advanced past it.
2879
+ * Hydration NEVER fires the new-incoming handlers/events for resolved
2880
+ * requests — only `open` ones notify (the status-aware
2881
+ * {@link surfaceIncomingPaymentRequest}) — so reopening can't spam stale
2882
+ * 'new request' notifications. After hydration the `since`-cursor delta poll
2883
+ * picks up live updates from the resume point.
2873
2884
  */
2874
2885
  private pumpIncomingPaymentRequests;
2875
2886
  /**
@@ -2882,13 +2893,19 @@ declare class PaymentsModule {
2882
2893
  * (PR twin of #546/#547).
2883
2894
  */
2884
2895
  private decryptPaymentRequestMemo;
2896
+ /** §16 wire status → the public {@link PaymentRequestStatus} display status. */
2897
+ private static readonly PR_WIRE_STATUS;
2885
2898
  /**
2886
2899
  * Map a §16 wire request onto the public {@link IncomingPaymentRequest}
2887
- * surface and notify (event + handlers). Only `open` requests are
2888
- * actionable; the in-memory id-dedup doubles as the replay guard for
2889
- * cursor resets. Multi-asset requests surface their first asset (the
2890
- * module's request surface is single-asset; module-created requests
2891
- * always are).
2900
+ * surface, deduped by id (the in-memory id-dedup doubles as the replay guard
2901
+ * for cursor resets). Requests of EVERY status are surfaced so a reloaded
2902
+ * thin wallet rebuilds the CURRENT state of its incoming view (#556) — open
2903
+ * ones land as actionable `pending`, resolved ones carry their paid/declined
2904
+ * (→ `rejected`)/expired status. Only `open` requests fire the new-incoming
2905
+ * event + handlers; resolved requests are folded into the list silently, so a
2906
+ * reload (or a `syncEpoch` re-pull) never re-notifies for already-resolved
2907
+ * requests. Multi-asset requests surface their first asset (the module's
2908
+ * request surface is single-asset; module-created requests always are).
2892
2909
  */
2893
2910
  private surfaceIncomingPaymentRequest;
2894
2911
  /**
@@ -11349,6 +11349,7 @@ function computeHistoryDedupKey(type, tokenId, transferId) {
11349
11349
  }
11350
11350
  var MAX_SYNCED_HISTORY_ENTRIES = 5e3;
11351
11351
  var MAX_HISTORY_HYDRATION_PAGES = 100;
11352
+ var MAX_PR_HYDRATION_PAGES = 100;
11352
11353
  var SEND_ENGINE_OP_TIMEOUT_MS = 6e4;
11353
11354
  var DELIVERY_POLL_INTERVAL_MS = 3e4;
11354
11355
  function enrichWithRegistry(info) {
@@ -11676,10 +11677,13 @@ var PaymentsModule = class _PaymentsModule {
11676
11677
  /** Coalesces concurrent payment-request pump runs. */
11677
11678
  prPumpInFlight = null;
11678
11679
  /**
11679
- * Set after the once-per-session `status=open` bootstrap scan: the surfaced
11680
- * incoming list is in-memory only, so still-open requests BELOW the
11681
- * persisted cursor must be recovered at session start (bounded by the §5.5
11682
- * per-payer open cap).
11680
+ * Set after the once-per-session full incoming hydration (#556): the surfaced
11681
+ * incoming list is in-memory only, so on a fresh engine the CURRENT state of
11682
+ * ALL incoming requests — open AND resolved (paid/declined/expired) — must be
11683
+ * rebuilt from a `role=incoming&since=0` pull, not just the still-open ones.
11684
+ * A status-filtered bootstrap (the pre-#556 `status=open` scan) dropped
11685
+ * requests resolved in a PRIOR session, so the payer reopened and the
11686
+ * 'Paid Successfully' request was gone (twin of #521/#549).
11683
11687
  */
11684
11688
  prBootstrapped = false;
11685
11689
  // Guard: ensure load() completes before processing incoming bundles
@@ -14528,22 +14532,28 @@ var PaymentsModule = class _PaymentsModule {
14528
14532
  * mailbox-cursor pattern: the persisted `{cursor, syncEpoch}` is the resume
14529
14533
  * point; a `syncEpoch` change (server restore — §5.4) voids cursor
14530
14534
  * continuity, so the tail re-pulls from 0 and the id-dedup in
14531
- * {@link surfaceIncomingPaymentRequest} absorbs the replays. Because the
14532
- * surfaced list is in-memory only, each session ALSO starts with one
14533
- * `status=open` bootstrap scan from 0 — still-open requests below the
14534
- * cursor (bounded by the §5.5 per-payer open cap) are recovered; resolved
14535
- * ones are not re-surfaced.
14535
+ * {@link surfaceIncomingPaymentRequest} absorbs the replays.
14536
+ *
14537
+ * Because the surfaced list is in-memory only, each session FIRST runs one
14538
+ * full incoming hydration from `since=0` with NO status filter (#556 —
14539
+ * mirrors {@link hydrateHistoryFromServer}): a fresh engine rebuilds the
14540
+ * CURRENT state of ALL incoming requests — open AND resolved — so a request
14541
+ * paid/declined/expired in a PRIOR session is still present (with its
14542
+ * resolved status) instead of vanishing once the cursor advanced past it.
14543
+ * Hydration NEVER fires the new-incoming handlers/events for resolved
14544
+ * requests — only `open` ones notify (the status-aware
14545
+ * {@link surfaceIncomingPaymentRequest}) — so reopening can't spam stale
14546
+ * 'new request' notifications. After hydration the `since`-cursor delta poll
14547
+ * picks up live updates from the resume point.
14536
14548
  */
14537
14549
  async pumpIncomingPaymentRequests(api) {
14538
14550
  const persisted = await this.readPrCursorState();
14539
14551
  if (!this.prBootstrapped) {
14540
- if (persisted !== null && persisted.cursor > 0n) {
14541
- let scan = await api.listPaymentRequests({ role: "incoming", status: "open", since: 0n });
14542
- for (; ; ) {
14543
- for (const wire of scan.requests) this.surfaceIncomingPaymentRequest(wire);
14544
- if (!scan.more) break;
14545
- scan = await api.listPaymentRequests({ role: "incoming", status: "open", since: scan.cursor });
14546
- }
14552
+ let scan = await api.listPaymentRequests({ role: "incoming", since: 0n });
14553
+ for (let pageCount = 0; pageCount < MAX_PR_HYDRATION_PAGES; pageCount++) {
14554
+ for (const wire of scan.requests) this.surfaceIncomingPaymentRequest(wire);
14555
+ if (!scan.more) break;
14556
+ scan = await api.listPaymentRequests({ role: "incoming", since: scan.cursor });
14547
14557
  }
14548
14558
  this.prBootstrapped = true;
14549
14559
  }
@@ -14577,17 +14587,23 @@ var PaymentsModule = class _PaymentsModule {
14577
14587
  return {};
14578
14588
  }
14579
14589
  }
14590
+ /** §16 wire status → the public {@link PaymentRequestStatus} display status. */
14591
+ static PR_WIRE_STATUS = { open: "pending", paid: "paid", declined: "rejected", expired: "expired" };
14580
14592
  /**
14581
14593
  * Map a §16 wire request onto the public {@link IncomingPaymentRequest}
14582
- * surface and notify (event + handlers). Only `open` requests are
14583
- * actionable; the in-memory id-dedup doubles as the replay guard for
14584
- * cursor resets. Multi-asset requests surface their first asset (the
14585
- * module's request surface is single-asset; module-created requests
14586
- * always are).
14594
+ * surface, deduped by id (the in-memory id-dedup doubles as the replay guard
14595
+ * for cursor resets). Requests of EVERY status are surfaced so a reloaded
14596
+ * thin wallet rebuilds the CURRENT state of its incoming view (#556) — open
14597
+ * ones land as actionable `pending`, resolved ones carry their paid/declined
14598
+ * (→ `rejected`)/expired status. Only `open` requests fire the new-incoming
14599
+ * event + handlers; resolved requests are folded into the list silently, so a
14600
+ * reload (or a `syncEpoch` re-pull) never re-notifies for already-resolved
14601
+ * requests. Multi-asset requests surface their first asset (the module's
14602
+ * request surface is single-asset; module-created requests always are).
14587
14603
  */
14588
14604
  surfaceIncomingPaymentRequest(wire) {
14589
- if (wire.status !== "open") return;
14590
14605
  if (this.paymentRequests.some((r) => r.id === wire.id)) return;
14606
+ const status = _PaymentsModule.PR_WIRE_STATUS[wire.status];
14591
14607
  const coinId = wire.assets[0]?.coinId ?? "";
14592
14608
  const coinDef = TokenRegistry.getInstance().getDefinition(coinId);
14593
14609
  const { memo: message, senderNametag } = this.decryptPaymentRequestMemo(wire);
@@ -14601,18 +14617,20 @@ var PaymentsModule = class _PaymentsModule {
14601
14617
  ...message !== void 0 ? { message } : {},
14602
14618
  requestId: wire.id,
14603
14619
  timestamp: wire.createdAt,
14604
- status: "pending"
14620
+ status
14605
14621
  };
14606
14622
  this.paymentRequests.unshift(request);
14607
- this.deps?.emitEvent("payment_request:incoming", request);
14608
- for (const handler of this.paymentRequestHandlers) {
14609
- try {
14610
- handler(request);
14611
- } catch (error) {
14612
- logger.debug("Payments", "Payment request handler error:", error);
14623
+ if (status === "pending") {
14624
+ this.deps?.emitEvent("payment_request:incoming", request);
14625
+ for (const handler of this.paymentRequestHandlers) {
14626
+ try {
14627
+ handler(request);
14628
+ } catch (error) {
14629
+ logger.debug("Payments", "Payment request handler error:", error);
14630
+ }
14613
14631
  }
14614
14632
  }
14615
- logger.debug("Payments", `Incoming payment request: ${request.id} for ${request.amount} ${request.symbol}`);
14633
+ logger.debug("Payments", `Incoming payment request: ${request.id} (${status}) for ${request.amount} ${request.symbol}`);
14616
14634
  }
14617
14635
  /**
14618
14636
  * Outgoing requests are a `?before=` backfill view (§16 — newest-first, no