@unicitylabs/sphere-sdk 0.9.1-dev.10 → 0.9.1-dev.11

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.
@@ -9643,6 +9643,7 @@ function computeHistoryDedupKey(type, tokenId, transferId) {
9643
9643
  return `${type}_${crypto.randomUUID()}`;
9644
9644
  }
9645
9645
  var MAX_SYNCED_HISTORY_ENTRIES = 5e3;
9646
+ var MAX_HISTORY_HYDRATION_PAGES = 100;
9646
9647
  var SEND_ENGINE_OP_TIMEOUT_MS = 6e4;
9647
9648
  var DELIVERY_POLL_INTERVAL_MS = 3e4;
9648
9649
  function enrichWithRegistry(info) {
@@ -11932,10 +11933,20 @@ var PaymentsModule = class _PaymentsModule {
11932
11933
  this.deps.emitEvent("history:updated", historyEntry);
11933
11934
  }
11934
11935
  /**
11935
- * Load history from the local token storage provider into the in-memory cache.
11936
- * Also performs one-time migration from legacy KV storage.
11936
+ * Load history into the in-memory cache.
11937
+ *
11938
+ * In the wallet-api composition (the `walletApi` client is present) the
11939
+ * durable §10 history log lives on the SERVER — the thin storage provider
11940
+ * keeps none — so the cache is rebuilt from `walletApi.listHistory()`. The
11941
+ * twin of the #521 inventory reload bug: `_historyCache` is process-lifetime,
11942
+ * so a reload (tab refresh) must re-pull it or render an empty history.
11943
+ * Compositions WITHOUT `walletApi` keep the legacy local path below.
11937
11944
  */
11938
11945
  async loadHistory() {
11946
+ if (this.deps.walletApi?.listHistory) {
11947
+ await this.hydrateHistoryFromServer(this.deps.walletApi);
11948
+ return;
11949
+ }
11939
11950
  const provider = this.getLocalTokenStorageProvider();
11940
11951
  if (provider?.getHistoryEntries) {
11941
11952
  this._historyCache = await provider.getHistoryEntries();
@@ -11967,6 +11978,75 @@ var PaymentsModule = class _PaymentsModule {
11967
11978
  }
11968
11979
  }
11969
11980
  }
11981
+ /**
11982
+ * Rebuild `_historyCache` from the server's §10 history log (the wallet-api
11983
+ * composition). Pages newest-first via the keyset cursor until `more:false`
11984
+ * or the page cap; dedups by `dedupKey` (a hydrate-then-receive in the same
11985
+ * session must not double-list). The S6 `memo` / `counterpartyNametag`
11986
+ * envelopes are decrypted with the owner's own field key on the way in.
11987
+ *
11988
+ * Best-effort, like the §10 history POST: history is untrusted DISPLAY data,
11989
+ * so a backend outage during hydration must NEVER fail `load()` (the money
11990
+ * path) — the in-session cache is left intact and the pull retries next load.
11991
+ */
11992
+ async hydrateHistoryFromServer(api) {
11993
+ const byDedupKey = /* @__PURE__ */ new Map();
11994
+ try {
11995
+ let before;
11996
+ for (let page = 0; page < MAX_HISTORY_HYDRATION_PAGES; page++) {
11997
+ const result = await api.listHistory(before !== void 0 ? { before } : {});
11998
+ for (const wire of result.records) {
11999
+ if (!byDedupKey.has(wire.dedupKey)) {
12000
+ byDedupKey.set(wire.dedupKey, this.historyEntryFromWire(wire));
12001
+ }
12002
+ }
12003
+ if (!result.more || result.cursor === null) break;
12004
+ before = result.cursor;
12005
+ }
12006
+ } catch (err) {
12007
+ logger.warn("Payments", "history hydration from server failed (kept in-memory; retries next load):", err);
12008
+ return;
12009
+ }
12010
+ this._historyCache = [...byDedupKey.values()];
12011
+ }
12012
+ /**
12013
+ * Map one §16 history wire record onto the display
12014
+ * {@link TransactionHistoryEntry}. `counterpartyNametag` lands on the role-
12015
+ * appropriate field (sender for RECEIVED, recipient otherwise); the S6 memo +
12016
+ * nametag envelopes decrypt under THIS wallet's field key (self-scoped at
12017
+ * rest — §8.3), surfaced as absent if they don't decrypt rather than as
12018
+ * ciphertext (same rule as mailbox/payment-request memos).
12019
+ */
12020
+ historyEntryFromWire(wire) {
12021
+ const asset = wire.assets[0];
12022
+ const coinId = asset?.coinId ?? "";
12023
+ const received = wire.type === "RECEIVED";
12024
+ const memo = this.tryDecryptField(wire.memo);
12025
+ const nametag = this.tryDecryptField(wire.counterpartyNametag);
12026
+ return {
12027
+ id: wire.id,
12028
+ dedupKey: wire.dedupKey,
12029
+ type: wire.type,
12030
+ amount: asset?.amount ?? "0",
12031
+ coinId,
12032
+ symbol: this.getCoinSymbol(coinId),
12033
+ timestamp: Date.parse(wire.ts),
12034
+ ...wire.transferId !== void 0 ? { transferId: wire.transferId } : {},
12035
+ ...wire.tokenId !== void 0 ? { tokenId: wire.tokenId } : {},
12036
+ ...wire.counterpartyPubkey !== void 0 ? received ? { senderPubkey: wire.counterpartyPubkey } : { recipientPubkey: wire.counterpartyPubkey } : {},
12037
+ ...nametag !== void 0 ? received ? { senderNametag: nametag } : { recipientNametag: nametag } : {},
12038
+ ...memo !== void 0 ? { memo } : {}
12039
+ };
12040
+ }
12041
+ /** S6 field decrypt that surfaces an undecryptable envelope as absent (§8.3). */
12042
+ tryDecryptField(envelope) {
12043
+ if (envelope === void 0) return void 0;
12044
+ try {
12045
+ return decryptField(this.getFieldEncryptionKey(), envelope);
12046
+ } catch {
12047
+ return void 0;
12048
+ }
12049
+ }
11970
12050
  /**
11971
12051
  * Import history entries from remote TXF data into local store.
11972
12052
  * Delegates to the local TokenStorageProvider's importHistoryEntries() for