@unicitylabs/sphere-sdk 0.3.7 → 0.3.8

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.
@@ -40,7 +40,11 @@ var STORAGE_KEYS_GLOBAL = {
40
40
  /** Cached token registry JSON (fetched from remote) */
41
41
  TOKEN_REGISTRY_CACHE: "token_registry_cache",
42
42
  /** Timestamp of last token registry cache update (ms since epoch) */
43
- TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts"
43
+ TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
44
+ /** Cached price data JSON (from CoinGecko or other provider) */
45
+ PRICE_CACHE: "price_cache",
46
+ /** Timestamp of last price cache update (ms since epoch) */
47
+ PRICE_CACHE_TS: "price_cache_ts"
44
48
  };
45
49
  var STORAGE_KEYS_ADDRESS = {
46
50
  /** Pending transfers for this address */
@@ -165,7 +169,6 @@ var TIMEOUTS = {
165
169
  /** Sync interval */
166
170
  SYNC_INTERVAL: 6e4
167
171
  };
168
- var DEFAULT_MARKET_API_URL = "https://market-api.unicity.network";
169
172
 
170
173
  // impl/browser/storage/LocalStorageProvider.ts
171
174
  var LocalStorageProvider = class {
@@ -533,61 +536,36 @@ var IndexedDBTokenStorageProvider = class {
533
536
  return meta !== null;
534
537
  }
535
538
  async clear() {
536
- const dbNames = [this.dbName];
539
+ if (this.db) {
540
+ this.db.close();
541
+ this.db = null;
542
+ }
543
+ this.status = "disconnected";
544
+ const CLEAR_TIMEOUT = 1500;
545
+ const withTimeout = (promise, ms, label) => Promise.race([
546
+ promise,
547
+ new Promise(
548
+ (_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
549
+ )
550
+ ]);
551
+ const deleteDb = (name) => new Promise((resolve) => {
552
+ const req = indexedDB.deleteDatabase(name);
553
+ req.onsuccess = () => resolve();
554
+ req.onerror = () => resolve();
555
+ req.onblocked = () => resolve();
556
+ });
537
557
  try {
538
- if (this.db) {
539
- await this.clearStore(STORE_TOKENS);
540
- await this.clearStore(STORE_META);
541
- this.db.close();
542
- this.db = null;
543
- }
544
- this.status = "disconnected";
545
558
  if (typeof indexedDB.databases === "function") {
546
- try {
547
- const dbs = await Promise.race([
548
- indexedDB.databases(),
549
- new Promise(
550
- (_, reject) => setTimeout(() => reject(new Error("timeout")), 1500)
551
- )
552
- ]);
553
- for (const dbInfo of dbs) {
554
- if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
555
- dbNames.push(dbInfo.name);
556
- try {
557
- const db = await new Promise((resolve, reject) => {
558
- const req = indexedDB.open(dbInfo.name, DB_VERSION);
559
- req.onsuccess = () => resolve(req.result);
560
- req.onerror = () => reject(req.error);
561
- req.onupgradeneeded = (e) => {
562
- const d = e.target.result;
563
- if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
564
- if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
565
- };
566
- });
567
- const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
568
- clearTx.objectStore(STORE_TOKENS).clear();
569
- clearTx.objectStore(STORE_META).clear();
570
- await new Promise((resolve) => {
571
- clearTx.oncomplete = () => resolve();
572
- clearTx.onerror = () => resolve();
573
- });
574
- db.close();
575
- } catch {
576
- }
577
- }
578
- }
579
- } catch {
580
- }
581
- }
582
- for (const name of dbNames) {
583
- try {
584
- const req = indexedDB.deleteDatabase(name);
585
- req.onerror = () => {
586
- };
587
- req.onblocked = () => {
588
- };
589
- } catch {
590
- }
559
+ const dbs = await withTimeout(
560
+ indexedDB.databases(),
561
+ CLEAR_TIMEOUT,
562
+ "indexedDB.databases()"
563
+ );
564
+ await Promise.all(
565
+ dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
566
+ );
567
+ } else {
568
+ await deleteDb(this.dbName);
591
569
  }
592
570
  return true;
593
571
  } catch (err) {
@@ -1136,9 +1114,7 @@ import {
1136
1114
  EventKinds,
1137
1115
  hashNametag,
1138
1116
  NostrClient,
1139
- Filter,
1140
- isChatMessage,
1141
- isReadReceipt
1117
+ Filter
1142
1118
  } from "@unicitylabs/nostr-js-sdk";
1143
1119
 
1144
1120
  // core/crypto.ts
@@ -1356,8 +1332,6 @@ var NostrTransportProvider = class {
1356
1332
  transferHandlers = /* @__PURE__ */ new Set();
1357
1333
  paymentRequestHandlers = /* @__PURE__ */ new Set();
1358
1334
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
1359
- readReceiptHandlers = /* @__PURE__ */ new Set();
1360
- typingIndicatorHandlers = /* @__PURE__ */ new Set();
1361
1335
  broadcastHandlers = /* @__PURE__ */ new Map();
1362
1336
  eventCallbacks = /* @__PURE__ */ new Set();
1363
1337
  constructor(config) {
@@ -1609,18 +1583,6 @@ var NostrTransportProvider = class {
1609
1583
  const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1610
1584
  const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1611
1585
  await this.publishEvent(giftWrap);
1612
- const selfWrapContent = JSON.stringify({
1613
- selfWrap: true,
1614
- originalId: giftWrap.id,
1615
- recipientPubkey,
1616
- senderNametag,
1617
- text: content
1618
- });
1619
- const selfPubkey = this.keyManager.getPublicKeyHex();
1620
- const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
1621
- this.publishEvent(selfGiftWrap).catch((err) => {
1622
- this.log("Self-wrap publish failed:", err);
1623
- });
1624
1586
  this.emitEvent({
1625
1587
  type: "message:sent",
1626
1588
  timestamp: Date.now(),
@@ -1719,37 +1681,6 @@ var NostrTransportProvider = class {
1719
1681
  this.paymentRequestResponseHandlers.add(handler);
1720
1682
  return () => this.paymentRequestResponseHandlers.delete(handler);
1721
1683
  }
1722
- // ===========================================================================
1723
- // Read Receipts
1724
- // ===========================================================================
1725
- async sendReadReceipt(recipientTransportPubkey, messageEventId) {
1726
- if (!this.keyManager) throw new Error("Not initialized");
1727
- const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1728
- const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
1729
- await this.publishEvent(event);
1730
- this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
1731
- }
1732
- onReadReceipt(handler) {
1733
- this.readReceiptHandlers.add(handler);
1734
- return () => this.readReceiptHandlers.delete(handler);
1735
- }
1736
- // ===========================================================================
1737
- // Typing Indicators
1738
- // ===========================================================================
1739
- async sendTypingIndicator(recipientTransportPubkey) {
1740
- if (!this.keyManager) throw new Error("Not initialized");
1741
- const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
1742
- const content = JSON.stringify({
1743
- type: "typing",
1744
- senderNametag: this.identity?.nametag
1745
- });
1746
- const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
1747
- await this.publishEvent(event);
1748
- }
1749
- onTypingIndicator(handler) {
1750
- this.typingIndicatorHandlers.add(handler);
1751
- return () => this.typingIndicatorHandlers.delete(handler);
1752
- }
1753
1684
  /**
1754
1685
  * Resolve any identifier to full peer information.
1755
1686
  * Routes to the appropriate specific resolve method based on identifier format.
@@ -2203,74 +2134,11 @@ var NostrTransportProvider = class {
2203
2134
  const pm = NIP17.unwrap(event, this.keyManager);
2204
2135
  this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
2205
2136
  if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
2206
- try {
2207
- const parsed = JSON.parse(pm.content);
2208
- if (parsed?.selfWrap && parsed.recipientPubkey) {
2209
- this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
2210
- const message2 = {
2211
- id: parsed.originalId || pm.eventId,
2212
- senderTransportPubkey: pm.senderPubkey,
2213
- senderNametag: parsed.senderNametag,
2214
- recipientTransportPubkey: parsed.recipientPubkey,
2215
- content: parsed.text ?? "",
2216
- timestamp: pm.timestamp * 1e3,
2217
- encrypted: true,
2218
- isSelfWrap: true
2219
- };
2220
- for (const handler of this.messageHandlers) {
2221
- try {
2222
- handler(message2);
2223
- } catch (e) {
2224
- this.log("Self-wrap handler error:", e);
2225
- }
2226
- }
2227
- return;
2228
- }
2229
- } catch {
2230
- }
2231
- this.log("Skipping own non-self-wrap message");
2137
+ this.log("Skipping own message");
2232
2138
  return;
2233
2139
  }
2234
- if (isReadReceipt(pm)) {
2235
- this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
2236
- if (pm.replyToEventId) {
2237
- const receipt = {
2238
- senderTransportPubkey: pm.senderPubkey,
2239
- messageEventId: pm.replyToEventId,
2240
- timestamp: pm.timestamp * 1e3
2241
- };
2242
- for (const handler of this.readReceiptHandlers) {
2243
- try {
2244
- handler(receipt);
2245
- } catch (e) {
2246
- this.log("Read receipt handler error:", e);
2247
- }
2248
- }
2249
- }
2250
- return;
2251
- }
2252
- try {
2253
- const parsed = JSON.parse(pm.content);
2254
- if (parsed?.type === "typing") {
2255
- this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
2256
- const indicator = {
2257
- senderTransportPubkey: pm.senderPubkey,
2258
- senderNametag: parsed.senderNametag,
2259
- timestamp: pm.timestamp * 1e3
2260
- };
2261
- for (const handler of this.typingIndicatorHandlers) {
2262
- try {
2263
- handler(indicator);
2264
- } catch (e) {
2265
- this.log("Typing handler error:", e);
2266
- }
2267
- }
2268
- return;
2269
- }
2270
- } catch {
2271
- }
2272
- if (!isChatMessage(pm)) {
2273
- this.log("Skipping unknown message kind:", pm.kind);
2140
+ if (pm.kind !== EventKinds.CHAT_MESSAGE) {
2141
+ this.log("Skipping non-chat message, kind:", pm.kind);
2274
2142
  return;
2275
2143
  }
2276
2144
  let content = pm.content;
@@ -2285,9 +2153,7 @@ var NostrTransportProvider = class {
2285
2153
  }
2286
2154
  this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
2287
2155
  const message = {
2288
- // Use outer gift wrap event.id so it matches the sender's stored giftWrap.id.
2289
- // This ensures read receipts reference an ID the sender recognizes.
2290
- id: event.id,
2156
+ id: pm.eventId,
2291
2157
  senderTransportPubkey: pm.senderPubkey,
2292
2158
  senderNametag,
2293
2159
  content,
@@ -3365,6 +3231,7 @@ async function loadIpnsModule() {
3365
3231
  async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
3366
3232
  const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
3367
3233
  const record = await createIPNSRecord(
3234
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3368
3235
  keyPair,
3369
3236
  `/ipfs/${cid}`,
3370
3237
  sequenceNumber,
@@ -5109,26 +4976,37 @@ var CoinGeckoPriceProvider = class {
5109
4976
  timeout;
5110
4977
  debug;
5111
4978
  baseUrl;
4979
+ storage;
4980
+ /** In-flight fetch promise for deduplication of concurrent getPrices() calls */
4981
+ fetchPromise = null;
4982
+ /** Token names being fetched in the current in-flight request */
4983
+ fetchNames = null;
4984
+ /** Whether persistent cache has been loaded into memory */
4985
+ persistentCacheLoaded = false;
4986
+ /** Promise for loading persistent cache (deduplication) */
4987
+ loadCachePromise = null;
5112
4988
  constructor(config) {
5113
4989
  this.apiKey = config?.apiKey;
5114
4990
  this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
5115
4991
  this.timeout = config?.timeout ?? 1e4;
5116
4992
  this.debug = config?.debug ?? false;
4993
+ this.storage = config?.storage ?? null;
5117
4994
  this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
5118
4995
  }
5119
4996
  async getPrices(tokenNames) {
5120
4997
  if (tokenNames.length === 0) {
5121
4998
  return /* @__PURE__ */ new Map();
5122
4999
  }
5000
+ if (!this.persistentCacheLoaded && this.storage) {
5001
+ await this.loadFromStorage();
5002
+ }
5123
5003
  const now = Date.now();
5124
5004
  const result = /* @__PURE__ */ new Map();
5125
5005
  const uncachedNames = [];
5126
5006
  for (const name of tokenNames) {
5127
5007
  const cached = this.cache.get(name);
5128
5008
  if (cached && cached.expiresAt > now) {
5129
- if (cached.price !== null) {
5130
- result.set(name, cached.price);
5131
- }
5009
+ result.set(name, cached.price);
5132
5010
  } else {
5133
5011
  uncachedNames.push(name);
5134
5012
  }
@@ -5136,6 +5014,41 @@ var CoinGeckoPriceProvider = class {
5136
5014
  if (uncachedNames.length === 0) {
5137
5015
  return result;
5138
5016
  }
5017
+ if (this.fetchPromise && this.fetchNames) {
5018
+ const allCovered = uncachedNames.every((n) => this.fetchNames.has(n));
5019
+ if (allCovered) {
5020
+ if (this.debug) {
5021
+ console.log(`[CoinGecko] Deduplicating request, reusing in-flight fetch`);
5022
+ }
5023
+ const fetched = await this.fetchPromise;
5024
+ for (const name of uncachedNames) {
5025
+ const price = fetched.get(name);
5026
+ if (price) {
5027
+ result.set(name, price);
5028
+ }
5029
+ }
5030
+ return result;
5031
+ }
5032
+ }
5033
+ const fetchPromise = this.doFetch(uncachedNames);
5034
+ this.fetchPromise = fetchPromise;
5035
+ this.fetchNames = new Set(uncachedNames);
5036
+ try {
5037
+ const fetched = await fetchPromise;
5038
+ for (const [name, price] of fetched) {
5039
+ result.set(name, price);
5040
+ }
5041
+ } finally {
5042
+ if (this.fetchPromise === fetchPromise) {
5043
+ this.fetchPromise = null;
5044
+ this.fetchNames = null;
5045
+ }
5046
+ }
5047
+ return result;
5048
+ }
5049
+ async doFetch(uncachedNames) {
5050
+ const result = /* @__PURE__ */ new Map();
5051
+ const now = Date.now();
5139
5052
  try {
5140
5053
  const ids = uncachedNames.join(",");
5141
5054
  const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
@@ -5151,6 +5064,9 @@ var CoinGeckoPriceProvider = class {
5151
5064
  signal: AbortSignal.timeout(this.timeout)
5152
5065
  });
5153
5066
  if (!response.ok) {
5067
+ if (response.status === 429) {
5068
+ this.extendCacheOnRateLimit(uncachedNames);
5069
+ }
5154
5070
  throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
5155
5071
  }
5156
5072
  const data = await response.json();
@@ -5169,25 +5085,113 @@ var CoinGeckoPriceProvider = class {
5169
5085
  }
5170
5086
  for (const name of uncachedNames) {
5171
5087
  if (!result.has(name)) {
5172
- this.cache.set(name, { price: null, expiresAt: now + this.cacheTtlMs });
5088
+ const zeroPrice = {
5089
+ tokenName: name,
5090
+ priceUsd: 0,
5091
+ priceEur: 0,
5092
+ change24h: 0,
5093
+ timestamp: now
5094
+ };
5095
+ this.cache.set(name, { price: zeroPrice, expiresAt: now + this.cacheTtlMs });
5096
+ result.set(name, zeroPrice);
5173
5097
  }
5174
5098
  }
5175
5099
  if (this.debug) {
5176
5100
  console.log(`[CoinGecko] Fetched ${result.size} prices`);
5177
5101
  }
5102
+ this.saveToStorage();
5178
5103
  } catch (error) {
5179
5104
  if (this.debug) {
5180
5105
  console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
5181
5106
  }
5182
5107
  for (const name of uncachedNames) {
5183
5108
  const stale = this.cache.get(name);
5184
- if (stale?.price) {
5109
+ if (stale) {
5185
5110
  result.set(name, stale.price);
5186
5111
  }
5187
5112
  }
5188
5113
  }
5189
5114
  return result;
5190
5115
  }
5116
+ // ===========================================================================
5117
+ // Persistent Storage
5118
+ // ===========================================================================
5119
+ /**
5120
+ * Load cached prices from StorageProvider into in-memory cache.
5121
+ * Only loads entries that are still within cacheTtlMs.
5122
+ */
5123
+ async loadFromStorage() {
5124
+ if (this.loadCachePromise) {
5125
+ return this.loadCachePromise;
5126
+ }
5127
+ this.loadCachePromise = this.doLoadFromStorage();
5128
+ try {
5129
+ await this.loadCachePromise;
5130
+ } finally {
5131
+ this.loadCachePromise = null;
5132
+ }
5133
+ }
5134
+ async doLoadFromStorage() {
5135
+ this.persistentCacheLoaded = true;
5136
+ if (!this.storage) return;
5137
+ try {
5138
+ const [cached, cachedTs] = await Promise.all([
5139
+ this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE),
5140
+ this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS)
5141
+ ]);
5142
+ if (!cached || !cachedTs) return;
5143
+ const ts = parseInt(cachedTs, 10);
5144
+ if (isNaN(ts)) return;
5145
+ const age = Date.now() - ts;
5146
+ if (age > this.cacheTtlMs) return;
5147
+ const data = JSON.parse(cached);
5148
+ const expiresAt = ts + this.cacheTtlMs;
5149
+ for (const [name, price] of Object.entries(data)) {
5150
+ if (!this.cache.has(name)) {
5151
+ this.cache.set(name, { price, expiresAt });
5152
+ }
5153
+ }
5154
+ if (this.debug) {
5155
+ console.log(`[CoinGecko] Loaded ${Object.keys(data).length} prices from persistent cache`);
5156
+ }
5157
+ } catch {
5158
+ }
5159
+ }
5160
+ /**
5161
+ * Save current prices to StorageProvider (fire-and-forget).
5162
+ */
5163
+ saveToStorage() {
5164
+ if (!this.storage) return;
5165
+ const data = {};
5166
+ for (const [name, entry] of this.cache) {
5167
+ data[name] = entry.price;
5168
+ }
5169
+ Promise.all([
5170
+ this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE, JSON.stringify(data)),
5171
+ this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS, String(Date.now()))
5172
+ ]).catch(() => {
5173
+ });
5174
+ }
5175
+ // ===========================================================================
5176
+ // Rate-limit handling
5177
+ // ===========================================================================
5178
+ /**
5179
+ * On 429 rate-limit, extend stale cache entries so subsequent calls
5180
+ * don't immediately retry and hammer the API.
5181
+ */
5182
+ extendCacheOnRateLimit(names) {
5183
+ const backoffMs = 6e4;
5184
+ const extendedExpiry = Date.now() + backoffMs;
5185
+ for (const name of names) {
5186
+ const existing = this.cache.get(name);
5187
+ if (existing) {
5188
+ existing.expiresAt = Math.max(existing.expiresAt, extendedExpiry);
5189
+ }
5190
+ }
5191
+ if (this.debug) {
5192
+ console.warn(`[CoinGecko] Rate-limited (429), extended cache TTL by ${backoffMs / 1e3}s`);
5193
+ }
5194
+ }
5191
5195
  async getPrice(tokenName) {
5192
5196
  const prices = await this.getPrices([tokenName]);
5193
5197
  return prices.get(tokenName) ?? null;
@@ -5221,6 +5225,7 @@ var TokenRegistry = class _TokenRegistry {
5221
5225
  refreshTimer = null;
5222
5226
  lastRefreshAt = 0;
5223
5227
  refreshPromise = null;
5228
+ initialLoadPromise = null;
5224
5229
  constructor() {
5225
5230
  this.definitionsById = /* @__PURE__ */ new Map();
5226
5231
  this.definitionsBySymbol = /* @__PURE__ */ new Map();
@@ -5259,13 +5264,8 @@ var TokenRegistry = class _TokenRegistry {
5259
5264
  if (options.refreshIntervalMs !== void 0) {
5260
5265
  instance.refreshIntervalMs = options.refreshIntervalMs;
5261
5266
  }
5262
- if (instance.storage) {
5263
- instance.loadFromCache();
5264
- }
5265
5267
  const autoRefresh = options.autoRefresh ?? true;
5266
- if (autoRefresh && instance.remoteUrl) {
5267
- instance.startAutoRefresh();
5268
- }
5268
+ instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
5269
5269
  }
5270
5270
  /**
5271
5271
  * Reset the singleton instance (useful for testing).
@@ -5283,6 +5283,53 @@ var TokenRegistry = class _TokenRegistry {
5283
5283
  static destroy() {
5284
5284
  _TokenRegistry.resetInstance();
5285
5285
  }
5286
+ /**
5287
+ * Wait for the initial data load (cache or remote) to complete.
5288
+ * Returns true if data was loaded, false if not (timeout or no data source).
5289
+ *
5290
+ * @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
5291
+ */
5292
+ static async waitForReady(timeoutMs = 1e4) {
5293
+ const instance = _TokenRegistry.getInstance();
5294
+ if (!instance.initialLoadPromise) {
5295
+ return instance.definitionsById.size > 0;
5296
+ }
5297
+ if (timeoutMs <= 0) {
5298
+ return instance.initialLoadPromise;
5299
+ }
5300
+ return Promise.race([
5301
+ instance.initialLoadPromise,
5302
+ new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
5303
+ ]);
5304
+ }
5305
+ // ===========================================================================
5306
+ // Initial Load
5307
+ // ===========================================================================
5308
+ /**
5309
+ * Perform initial data load: try cache first, fall back to remote fetch.
5310
+ * After initial data is available, start periodic auto-refresh if configured.
5311
+ */
5312
+ async performInitialLoad(autoRefresh) {
5313
+ let loaded = false;
5314
+ if (this.storage) {
5315
+ loaded = await this.loadFromCache();
5316
+ }
5317
+ if (loaded) {
5318
+ if (autoRefresh && this.remoteUrl) {
5319
+ this.startAutoRefresh();
5320
+ }
5321
+ return true;
5322
+ }
5323
+ if (autoRefresh && this.remoteUrl) {
5324
+ loaded = await this.refreshFromRemote();
5325
+ this.stopAutoRefresh();
5326
+ this.refreshTimer = setInterval(() => {
5327
+ this.refreshFromRemote();
5328
+ }, this.refreshIntervalMs);
5329
+ return loaded;
5330
+ }
5331
+ return false;
5332
+ }
5286
5333
  // ===========================================================================
5287
5334
  // Cache (StorageProvider)
5288
5335
  // ===========================================================================
@@ -5612,7 +5659,7 @@ function resolveL1Config(network, config) {
5612
5659
  enableVesting: config.enableVesting
5613
5660
  };
5614
5661
  }
5615
- function resolvePriceConfig(config) {
5662
+ function resolvePriceConfig(config, storage) {
5616
5663
  if (config === void 0) {
5617
5664
  return void 0;
5618
5665
  }
@@ -5622,7 +5669,8 @@ function resolvePriceConfig(config) {
5622
5669
  baseUrl: config.baseUrl,
5623
5670
  cacheTtlMs: config.cacheTtlMs,
5624
5671
  timeout: config.timeout,
5625
- debug: config.debug
5672
+ debug: config.debug,
5673
+ storage
5626
5674
  };
5627
5675
  }
5628
5676
  function resolveArrayConfig(defaults, replace, additional) {
@@ -5649,16 +5697,6 @@ function resolveGroupChatConfig(network, config) {
5649
5697
  relays: config.relays ?? [...netConfig.groupRelays]
5650
5698
  };
5651
5699
  }
5652
- function resolveMarketConfig(config) {
5653
- if (!config) return void 0;
5654
- if (config === true) {
5655
- return { apiUrl: DEFAULT_MARKET_API_URL };
5656
- }
5657
- return {
5658
- apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
5659
- timeout: config.timeout
5660
- };
5661
- }
5662
5700
 
5663
5701
  // impl/browser/index.ts
5664
5702
  if (typeof globalThis.Buffer === "undefined") {
@@ -5716,8 +5754,8 @@ function createBrowserProviders(config) {
5716
5754
  const oracleConfig = resolveOracleConfig(network, config?.oracle);
5717
5755
  const l1Config = resolveL1Config(network, config?.l1);
5718
5756
  const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
5719
- const priceConfig = resolvePriceConfig(config?.price);
5720
5757
  const storage = createLocalStorageProvider(config?.storage);
5758
+ const priceConfig = resolvePriceConfig(config?.price, storage);
5721
5759
  const ipfsConfig = tokenSyncConfig?.ipfs;
5722
5760
  const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
5723
5761
  gateways: ipfsConfig.gateways,
@@ -5727,11 +5765,9 @@ function createBrowserProviders(config) {
5727
5765
  const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5728
5766
  const networkConfig = getNetworkConfig(network);
5729
5767
  TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
5730
- const market = resolveMarketConfig(config?.market);
5731
5768
  return {
5732
5769
  storage,
5733
5770
  groupChat,
5734
- market,
5735
5771
  transport: createNostrTransportProvider({
5736
5772
  relays: transportConfig.relays,
5737
5773
  timeout: transportConfig.timeout,