@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.
@@ -11442,6 +11442,7 @@ function computeHistoryDedupKey(type, tokenId, transferId) {
11442
11442
  }
11443
11443
  var MAX_SYNCED_HISTORY_ENTRIES = 5e3;
11444
11444
  var MAX_HISTORY_HYDRATION_PAGES = 100;
11445
+ var MAX_PR_HYDRATION_PAGES = 100;
11445
11446
  var SEND_ENGINE_OP_TIMEOUT_MS = 6e4;
11446
11447
  var DELIVERY_POLL_INTERVAL_MS = 3e4;
11447
11448
  function enrichWithRegistry(info) {
@@ -11769,10 +11770,13 @@ var PaymentsModule = class _PaymentsModule {
11769
11770
  /** Coalesces concurrent payment-request pump runs. */
11770
11771
  prPumpInFlight = null;
11771
11772
  /**
11772
- * Set after the once-per-session `status=open` bootstrap scan: the surfaced
11773
- * incoming list is in-memory only, so still-open requests BELOW the
11774
- * persisted cursor must be recovered at session start (bounded by the §5.5
11775
- * per-payer open cap).
11773
+ * Set after the once-per-session full incoming hydration (#556): the surfaced
11774
+ * incoming list is in-memory only, so on a fresh engine the CURRENT state of
11775
+ * ALL incoming requests — open AND resolved (paid/declined/expired) — must be
11776
+ * rebuilt from a `role=incoming&since=0` pull, not just the still-open ones.
11777
+ * A status-filtered bootstrap (the pre-#556 `status=open` scan) dropped
11778
+ * requests resolved in a PRIOR session, so the payer reopened and the
11779
+ * 'Paid Successfully' request was gone (twin of #521/#549).
11776
11780
  */
11777
11781
  prBootstrapped = false;
11778
11782
  // Guard: ensure load() completes before processing incoming bundles
@@ -14621,22 +14625,28 @@ var PaymentsModule = class _PaymentsModule {
14621
14625
  * mailbox-cursor pattern: the persisted `{cursor, syncEpoch}` is the resume
14622
14626
  * point; a `syncEpoch` change (server restore — §5.4) voids cursor
14623
14627
  * continuity, so the tail re-pulls from 0 and the id-dedup in
14624
- * {@link surfaceIncomingPaymentRequest} absorbs the replays. Because the
14625
- * surfaced list is in-memory only, each session ALSO starts with one
14626
- * `status=open` bootstrap scan from 0 — still-open requests below the
14627
- * cursor (bounded by the §5.5 per-payer open cap) are recovered; resolved
14628
- * ones are not re-surfaced.
14628
+ * {@link surfaceIncomingPaymentRequest} absorbs the replays.
14629
+ *
14630
+ * Because the surfaced list is in-memory only, each session FIRST runs one
14631
+ * full incoming hydration from `since=0` with NO status filter (#556 —
14632
+ * mirrors {@link hydrateHistoryFromServer}): a fresh engine rebuilds the
14633
+ * CURRENT state of ALL incoming requests — open AND resolved — so a request
14634
+ * paid/declined/expired in a PRIOR session is still present (with its
14635
+ * resolved status) instead of vanishing once the cursor advanced past it.
14636
+ * Hydration NEVER fires the new-incoming handlers/events for resolved
14637
+ * requests — only `open` ones notify (the status-aware
14638
+ * {@link surfaceIncomingPaymentRequest}) — so reopening can't spam stale
14639
+ * 'new request' notifications. After hydration the `since`-cursor delta poll
14640
+ * picks up live updates from the resume point.
14629
14641
  */
14630
14642
  async pumpIncomingPaymentRequests(api) {
14631
14643
  const persisted = await this.readPrCursorState();
14632
14644
  if (!this.prBootstrapped) {
14633
- if (persisted !== null && persisted.cursor > 0n) {
14634
- let scan = await api.listPaymentRequests({ role: "incoming", status: "open", since: 0n });
14635
- for (; ; ) {
14636
- for (const wire of scan.requests) this.surfaceIncomingPaymentRequest(wire);
14637
- if (!scan.more) break;
14638
- scan = await api.listPaymentRequests({ role: "incoming", status: "open", since: scan.cursor });
14639
- }
14645
+ let scan = await api.listPaymentRequests({ role: "incoming", since: 0n });
14646
+ for (let pageCount = 0; pageCount < MAX_PR_HYDRATION_PAGES; pageCount++) {
14647
+ for (const wire of scan.requests) this.surfaceIncomingPaymentRequest(wire);
14648
+ if (!scan.more) break;
14649
+ scan = await api.listPaymentRequests({ role: "incoming", since: scan.cursor });
14640
14650
  }
14641
14651
  this.prBootstrapped = true;
14642
14652
  }
@@ -14670,17 +14680,23 @@ var PaymentsModule = class _PaymentsModule {
14670
14680
  return {};
14671
14681
  }
14672
14682
  }
14683
+ /** §16 wire status → the public {@link PaymentRequestStatus} display status. */
14684
+ static PR_WIRE_STATUS = { open: "pending", paid: "paid", declined: "rejected", expired: "expired" };
14673
14685
  /**
14674
14686
  * Map a §16 wire request onto the public {@link IncomingPaymentRequest}
14675
- * surface and notify (event + handlers). Only `open` requests are
14676
- * actionable; the in-memory id-dedup doubles as the replay guard for
14677
- * cursor resets. Multi-asset requests surface their first asset (the
14678
- * module's request surface is single-asset; module-created requests
14679
- * always are).
14687
+ * surface, deduped by id (the in-memory id-dedup doubles as the replay guard
14688
+ * for cursor resets). Requests of EVERY status are surfaced so a reloaded
14689
+ * thin wallet rebuilds the CURRENT state of its incoming view (#556) — open
14690
+ * ones land as actionable `pending`, resolved ones carry their paid/declined
14691
+ * (→ `rejected`)/expired status. Only `open` requests fire the new-incoming
14692
+ * event + handlers; resolved requests are folded into the list silently, so a
14693
+ * reload (or a `syncEpoch` re-pull) never re-notifies for already-resolved
14694
+ * requests. Multi-asset requests surface their first asset (the module's
14695
+ * request surface is single-asset; module-created requests always are).
14680
14696
  */
14681
14697
  surfaceIncomingPaymentRequest(wire) {
14682
- if (wire.status !== "open") return;
14683
14698
  if (this.paymentRequests.some((r) => r.id === wire.id)) return;
14699
+ const status = _PaymentsModule.PR_WIRE_STATUS[wire.status];
14684
14700
  const coinId = wire.assets[0]?.coinId ?? "";
14685
14701
  const coinDef = TokenRegistry.getInstance().getDefinition(coinId);
14686
14702
  const { memo: message, senderNametag } = this.decryptPaymentRequestMemo(wire);
@@ -14694,18 +14710,20 @@ var PaymentsModule = class _PaymentsModule {
14694
14710
  ...message !== void 0 ? { message } : {},
14695
14711
  requestId: wire.id,
14696
14712
  timestamp: wire.createdAt,
14697
- status: "pending"
14713
+ status
14698
14714
  };
14699
14715
  this.paymentRequests.unshift(request);
14700
- this.deps?.emitEvent("payment_request:incoming", request);
14701
- for (const handler of this.paymentRequestHandlers) {
14702
- try {
14703
- handler(request);
14704
- } catch (error) {
14705
- logger.debug("Payments", "Payment request handler error:", error);
14716
+ if (status === "pending") {
14717
+ this.deps?.emitEvent("payment_request:incoming", request);
14718
+ for (const handler of this.paymentRequestHandlers) {
14719
+ try {
14720
+ handler(request);
14721
+ } catch (error) {
14722
+ logger.debug("Payments", "Payment request handler error:", error);
14723
+ }
14706
14724
  }
14707
14725
  }
14708
- logger.debug("Payments", `Incoming payment request: ${request.id} for ${request.amount} ${request.symbol}`);
14726
+ logger.debug("Payments", `Incoming payment request: ${request.id} (${status}) for ${request.amount} ${request.symbol}`);
14709
14727
  }
14710
14728
  /**
14711
14729
  * Outgoing requests are a `?before=` backfill view (§16 — newest-first, no