@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.
- package/dist/core/index.cjs +97 -2589
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +11 -222
- package/dist/core/index.d.ts +11 -222
- package/dist/core/index.js +93 -2585
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +233 -195
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +234 -198
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +6 -1
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +6 -1
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +205 -142
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +2 -70
- package/dist/impl/nodejs/index.d.ts +2 -70
- package/dist/impl/nodejs/index.js +206 -145
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +239 -2600
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -226
- package/dist/index.d.ts +60 -226
- package/dist/index.js +235 -2593
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -41,7 +41,11 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
41
41
|
/** Cached token registry JSON (fetched from remote) */
|
|
42
42
|
TOKEN_REGISTRY_CACHE: "token_registry_cache",
|
|
43
43
|
/** Timestamp of last token registry cache update (ms since epoch) */
|
|
44
|
-
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts"
|
|
44
|
+
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
|
|
45
|
+
/** Cached price data JSON (from CoinGecko or other provider) */
|
|
46
|
+
PRICE_CACHE: "price_cache",
|
|
47
|
+
/** Timestamp of last price cache update (ms since epoch) */
|
|
48
|
+
PRICE_CACHE_TS: "price_cache_ts"
|
|
45
49
|
};
|
|
46
50
|
var STORAGE_KEYS_ADDRESS = {
|
|
47
51
|
/** Pending transfers for this address */
|
|
@@ -166,7 +170,6 @@ var TIMEOUTS = {
|
|
|
166
170
|
/** Sync interval */
|
|
167
171
|
SYNC_INTERVAL: 6e4
|
|
168
172
|
};
|
|
169
|
-
var DEFAULT_MARKET_API_URL = "https://market-api.unicity.network";
|
|
170
173
|
|
|
171
174
|
// impl/nodejs/storage/FileStorageProvider.ts
|
|
172
175
|
var FileStorageProvider = class {
|
|
@@ -938,9 +941,7 @@ import {
|
|
|
938
941
|
EventKinds,
|
|
939
942
|
hashNametag,
|
|
940
943
|
NostrClient,
|
|
941
|
-
Filter
|
|
942
|
-
isChatMessage,
|
|
943
|
-
isReadReceipt
|
|
944
|
+
Filter
|
|
944
945
|
} from "@unicitylabs/nostr-js-sdk";
|
|
945
946
|
|
|
946
947
|
// core/crypto.ts
|
|
@@ -1158,8 +1159,6 @@ var NostrTransportProvider = class {
|
|
|
1158
1159
|
transferHandlers = /* @__PURE__ */ new Set();
|
|
1159
1160
|
paymentRequestHandlers = /* @__PURE__ */ new Set();
|
|
1160
1161
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1161
|
-
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1162
|
-
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1163
1162
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1164
1163
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1165
1164
|
constructor(config) {
|
|
@@ -1411,18 +1410,6 @@ var NostrTransportProvider = class {
|
|
|
1411
1410
|
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1412
1411
|
const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
|
|
1413
1412
|
await this.publishEvent(giftWrap);
|
|
1414
|
-
const selfWrapContent = JSON.stringify({
|
|
1415
|
-
selfWrap: true,
|
|
1416
|
-
originalId: giftWrap.id,
|
|
1417
|
-
recipientPubkey,
|
|
1418
|
-
senderNametag,
|
|
1419
|
-
text: content
|
|
1420
|
-
});
|
|
1421
|
-
const selfPubkey = this.keyManager.getPublicKeyHex();
|
|
1422
|
-
const selfGiftWrap = NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
|
|
1423
|
-
this.publishEvent(selfGiftWrap).catch((err) => {
|
|
1424
|
-
this.log("Self-wrap publish failed:", err);
|
|
1425
|
-
});
|
|
1426
1413
|
this.emitEvent({
|
|
1427
1414
|
type: "message:sent",
|
|
1428
1415
|
timestamp: Date.now(),
|
|
@@ -1521,37 +1508,6 @@ var NostrTransportProvider = class {
|
|
|
1521
1508
|
this.paymentRequestResponseHandlers.add(handler);
|
|
1522
1509
|
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1523
1510
|
}
|
|
1524
|
-
// ===========================================================================
|
|
1525
|
-
// Read Receipts
|
|
1526
|
-
// ===========================================================================
|
|
1527
|
-
async sendReadReceipt(recipientTransportPubkey, messageEventId) {
|
|
1528
|
-
if (!this.keyManager) throw new Error("Not initialized");
|
|
1529
|
-
const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
|
|
1530
|
-
const event = NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
|
|
1531
|
-
await this.publishEvent(event);
|
|
1532
|
-
this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
|
|
1533
|
-
}
|
|
1534
|
-
onReadReceipt(handler) {
|
|
1535
|
-
this.readReceiptHandlers.add(handler);
|
|
1536
|
-
return () => this.readReceiptHandlers.delete(handler);
|
|
1537
|
-
}
|
|
1538
|
-
// ===========================================================================
|
|
1539
|
-
// Typing Indicators
|
|
1540
|
-
// ===========================================================================
|
|
1541
|
-
async sendTypingIndicator(recipientTransportPubkey) {
|
|
1542
|
-
if (!this.keyManager) throw new Error("Not initialized");
|
|
1543
|
-
const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
|
|
1544
|
-
const content = JSON.stringify({
|
|
1545
|
-
type: "typing",
|
|
1546
|
-
senderNametag: this.identity?.nametag
|
|
1547
|
-
});
|
|
1548
|
-
const event = NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
|
|
1549
|
-
await this.publishEvent(event);
|
|
1550
|
-
}
|
|
1551
|
-
onTypingIndicator(handler) {
|
|
1552
|
-
this.typingIndicatorHandlers.add(handler);
|
|
1553
|
-
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1554
|
-
}
|
|
1555
1511
|
/**
|
|
1556
1512
|
* Resolve any identifier to full peer information.
|
|
1557
1513
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2005,74 +1961,11 @@ var NostrTransportProvider = class {
|
|
|
2005
1961
|
const pm = NIP17.unwrap(event, this.keyManager);
|
|
2006
1962
|
this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
|
|
2007
1963
|
if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
|
|
2008
|
-
|
|
2009
|
-
const parsed = JSON.parse(pm.content);
|
|
2010
|
-
if (parsed?.selfWrap && parsed.recipientPubkey) {
|
|
2011
|
-
this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
|
|
2012
|
-
const message2 = {
|
|
2013
|
-
id: parsed.originalId || pm.eventId,
|
|
2014
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2015
|
-
senderNametag: parsed.senderNametag,
|
|
2016
|
-
recipientTransportPubkey: parsed.recipientPubkey,
|
|
2017
|
-
content: parsed.text ?? "",
|
|
2018
|
-
timestamp: pm.timestamp * 1e3,
|
|
2019
|
-
encrypted: true,
|
|
2020
|
-
isSelfWrap: true
|
|
2021
|
-
};
|
|
2022
|
-
for (const handler of this.messageHandlers) {
|
|
2023
|
-
try {
|
|
2024
|
-
handler(message2);
|
|
2025
|
-
} catch (e) {
|
|
2026
|
-
this.log("Self-wrap handler error:", e);
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
} catch {
|
|
2032
|
-
}
|
|
2033
|
-
this.log("Skipping own non-self-wrap message");
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
if (isReadReceipt(pm)) {
|
|
2037
|
-
this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
|
|
2038
|
-
if (pm.replyToEventId) {
|
|
2039
|
-
const receipt = {
|
|
2040
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2041
|
-
messageEventId: pm.replyToEventId,
|
|
2042
|
-
timestamp: pm.timestamp * 1e3
|
|
2043
|
-
};
|
|
2044
|
-
for (const handler of this.readReceiptHandlers) {
|
|
2045
|
-
try {
|
|
2046
|
-
handler(receipt);
|
|
2047
|
-
} catch (e) {
|
|
2048
|
-
this.log("Read receipt handler error:", e);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
1964
|
+
this.log("Skipping own message");
|
|
2052
1965
|
return;
|
|
2053
1966
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
if (parsed?.type === "typing") {
|
|
2057
|
-
this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
|
|
2058
|
-
const indicator = {
|
|
2059
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2060
|
-
senderNametag: parsed.senderNametag,
|
|
2061
|
-
timestamp: pm.timestamp * 1e3
|
|
2062
|
-
};
|
|
2063
|
-
for (const handler of this.typingIndicatorHandlers) {
|
|
2064
|
-
try {
|
|
2065
|
-
handler(indicator);
|
|
2066
|
-
} catch (e) {
|
|
2067
|
-
this.log("Typing handler error:", e);
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
} catch {
|
|
2073
|
-
}
|
|
2074
|
-
if (!isChatMessage(pm)) {
|
|
2075
|
-
this.log("Skipping unknown message kind:", pm.kind);
|
|
1967
|
+
if (pm.kind !== EventKinds.CHAT_MESSAGE) {
|
|
1968
|
+
this.log("Skipping non-chat message, kind:", pm.kind);
|
|
2076
1969
|
return;
|
|
2077
1970
|
}
|
|
2078
1971
|
let content = pm.content;
|
|
@@ -2087,9 +1980,7 @@ var NostrTransportProvider = class {
|
|
|
2087
1980
|
}
|
|
2088
1981
|
this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
|
|
2089
1982
|
const message = {
|
|
2090
|
-
|
|
2091
|
-
// This ensures read receipts reference an ID the sender recognizes.
|
|
2092
|
-
id: event.id,
|
|
1983
|
+
id: pm.eventId,
|
|
2093
1984
|
senderTransportPubkey: pm.senderPubkey,
|
|
2094
1985
|
senderNametag,
|
|
2095
1986
|
content,
|
|
@@ -3114,6 +3005,7 @@ async function loadIpnsModule() {
|
|
|
3114
3005
|
async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
|
|
3115
3006
|
const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
|
|
3116
3007
|
const record = await createIPNSRecord(
|
|
3008
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3117
3009
|
keyPair,
|
|
3118
3010
|
`/ipfs/${cid}`,
|
|
3119
3011
|
sequenceNumber,
|
|
@@ -4855,26 +4747,37 @@ var CoinGeckoPriceProvider = class {
|
|
|
4855
4747
|
timeout;
|
|
4856
4748
|
debug;
|
|
4857
4749
|
baseUrl;
|
|
4750
|
+
storage;
|
|
4751
|
+
/** In-flight fetch promise for deduplication of concurrent getPrices() calls */
|
|
4752
|
+
fetchPromise = null;
|
|
4753
|
+
/** Token names being fetched in the current in-flight request */
|
|
4754
|
+
fetchNames = null;
|
|
4755
|
+
/** Whether persistent cache has been loaded into memory */
|
|
4756
|
+
persistentCacheLoaded = false;
|
|
4757
|
+
/** Promise for loading persistent cache (deduplication) */
|
|
4758
|
+
loadCachePromise = null;
|
|
4858
4759
|
constructor(config) {
|
|
4859
4760
|
this.apiKey = config?.apiKey;
|
|
4860
4761
|
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
4861
4762
|
this.timeout = config?.timeout ?? 1e4;
|
|
4862
4763
|
this.debug = config?.debug ?? false;
|
|
4764
|
+
this.storage = config?.storage ?? null;
|
|
4863
4765
|
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
4864
4766
|
}
|
|
4865
4767
|
async getPrices(tokenNames) {
|
|
4866
4768
|
if (tokenNames.length === 0) {
|
|
4867
4769
|
return /* @__PURE__ */ new Map();
|
|
4868
4770
|
}
|
|
4771
|
+
if (!this.persistentCacheLoaded && this.storage) {
|
|
4772
|
+
await this.loadFromStorage();
|
|
4773
|
+
}
|
|
4869
4774
|
const now = Date.now();
|
|
4870
4775
|
const result = /* @__PURE__ */ new Map();
|
|
4871
4776
|
const uncachedNames = [];
|
|
4872
4777
|
for (const name of tokenNames) {
|
|
4873
4778
|
const cached = this.cache.get(name);
|
|
4874
4779
|
if (cached && cached.expiresAt > now) {
|
|
4875
|
-
|
|
4876
|
-
result.set(name, cached.price);
|
|
4877
|
-
}
|
|
4780
|
+
result.set(name, cached.price);
|
|
4878
4781
|
} else {
|
|
4879
4782
|
uncachedNames.push(name);
|
|
4880
4783
|
}
|
|
@@ -4882,6 +4785,41 @@ var CoinGeckoPriceProvider = class {
|
|
|
4882
4785
|
if (uncachedNames.length === 0) {
|
|
4883
4786
|
return result;
|
|
4884
4787
|
}
|
|
4788
|
+
if (this.fetchPromise && this.fetchNames) {
|
|
4789
|
+
const allCovered = uncachedNames.every((n) => this.fetchNames.has(n));
|
|
4790
|
+
if (allCovered) {
|
|
4791
|
+
if (this.debug) {
|
|
4792
|
+
console.log(`[CoinGecko] Deduplicating request, reusing in-flight fetch`);
|
|
4793
|
+
}
|
|
4794
|
+
const fetched = await this.fetchPromise;
|
|
4795
|
+
for (const name of uncachedNames) {
|
|
4796
|
+
const price = fetched.get(name);
|
|
4797
|
+
if (price) {
|
|
4798
|
+
result.set(name, price);
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
return result;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
const fetchPromise = this.doFetch(uncachedNames);
|
|
4805
|
+
this.fetchPromise = fetchPromise;
|
|
4806
|
+
this.fetchNames = new Set(uncachedNames);
|
|
4807
|
+
try {
|
|
4808
|
+
const fetched = await fetchPromise;
|
|
4809
|
+
for (const [name, price] of fetched) {
|
|
4810
|
+
result.set(name, price);
|
|
4811
|
+
}
|
|
4812
|
+
} finally {
|
|
4813
|
+
if (this.fetchPromise === fetchPromise) {
|
|
4814
|
+
this.fetchPromise = null;
|
|
4815
|
+
this.fetchNames = null;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
return result;
|
|
4819
|
+
}
|
|
4820
|
+
async doFetch(uncachedNames) {
|
|
4821
|
+
const result = /* @__PURE__ */ new Map();
|
|
4822
|
+
const now = Date.now();
|
|
4885
4823
|
try {
|
|
4886
4824
|
const ids = uncachedNames.join(",");
|
|
4887
4825
|
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
@@ -4897,6 +4835,9 @@ var CoinGeckoPriceProvider = class {
|
|
|
4897
4835
|
signal: AbortSignal.timeout(this.timeout)
|
|
4898
4836
|
});
|
|
4899
4837
|
if (!response.ok) {
|
|
4838
|
+
if (response.status === 429) {
|
|
4839
|
+
this.extendCacheOnRateLimit(uncachedNames);
|
|
4840
|
+
}
|
|
4900
4841
|
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
4901
4842
|
}
|
|
4902
4843
|
const data = await response.json();
|
|
@@ -4915,25 +4856,113 @@ var CoinGeckoPriceProvider = class {
|
|
|
4915
4856
|
}
|
|
4916
4857
|
for (const name of uncachedNames) {
|
|
4917
4858
|
if (!result.has(name)) {
|
|
4918
|
-
|
|
4859
|
+
const zeroPrice = {
|
|
4860
|
+
tokenName: name,
|
|
4861
|
+
priceUsd: 0,
|
|
4862
|
+
priceEur: 0,
|
|
4863
|
+
change24h: 0,
|
|
4864
|
+
timestamp: now
|
|
4865
|
+
};
|
|
4866
|
+
this.cache.set(name, { price: zeroPrice, expiresAt: now + this.cacheTtlMs });
|
|
4867
|
+
result.set(name, zeroPrice);
|
|
4919
4868
|
}
|
|
4920
4869
|
}
|
|
4921
4870
|
if (this.debug) {
|
|
4922
4871
|
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
4923
4872
|
}
|
|
4873
|
+
this.saveToStorage();
|
|
4924
4874
|
} catch (error) {
|
|
4925
4875
|
if (this.debug) {
|
|
4926
4876
|
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
4927
4877
|
}
|
|
4928
4878
|
for (const name of uncachedNames) {
|
|
4929
4879
|
const stale = this.cache.get(name);
|
|
4930
|
-
if (stale
|
|
4880
|
+
if (stale) {
|
|
4931
4881
|
result.set(name, stale.price);
|
|
4932
4882
|
}
|
|
4933
4883
|
}
|
|
4934
4884
|
}
|
|
4935
4885
|
return result;
|
|
4936
4886
|
}
|
|
4887
|
+
// ===========================================================================
|
|
4888
|
+
// Persistent Storage
|
|
4889
|
+
// ===========================================================================
|
|
4890
|
+
/**
|
|
4891
|
+
* Load cached prices from StorageProvider into in-memory cache.
|
|
4892
|
+
* Only loads entries that are still within cacheTtlMs.
|
|
4893
|
+
*/
|
|
4894
|
+
async loadFromStorage() {
|
|
4895
|
+
if (this.loadCachePromise) {
|
|
4896
|
+
return this.loadCachePromise;
|
|
4897
|
+
}
|
|
4898
|
+
this.loadCachePromise = this.doLoadFromStorage();
|
|
4899
|
+
try {
|
|
4900
|
+
await this.loadCachePromise;
|
|
4901
|
+
} finally {
|
|
4902
|
+
this.loadCachePromise = null;
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
async doLoadFromStorage() {
|
|
4906
|
+
this.persistentCacheLoaded = true;
|
|
4907
|
+
if (!this.storage) return;
|
|
4908
|
+
try {
|
|
4909
|
+
const [cached, cachedTs] = await Promise.all([
|
|
4910
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE),
|
|
4911
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS)
|
|
4912
|
+
]);
|
|
4913
|
+
if (!cached || !cachedTs) return;
|
|
4914
|
+
const ts = parseInt(cachedTs, 10);
|
|
4915
|
+
if (isNaN(ts)) return;
|
|
4916
|
+
const age = Date.now() - ts;
|
|
4917
|
+
if (age > this.cacheTtlMs) return;
|
|
4918
|
+
const data = JSON.parse(cached);
|
|
4919
|
+
const expiresAt = ts + this.cacheTtlMs;
|
|
4920
|
+
for (const [name, price] of Object.entries(data)) {
|
|
4921
|
+
if (!this.cache.has(name)) {
|
|
4922
|
+
this.cache.set(name, { price, expiresAt });
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
if (this.debug) {
|
|
4926
|
+
console.log(`[CoinGecko] Loaded ${Object.keys(data).length} prices from persistent cache`);
|
|
4927
|
+
}
|
|
4928
|
+
} catch {
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
/**
|
|
4932
|
+
* Save current prices to StorageProvider (fire-and-forget).
|
|
4933
|
+
*/
|
|
4934
|
+
saveToStorage() {
|
|
4935
|
+
if (!this.storage) return;
|
|
4936
|
+
const data = {};
|
|
4937
|
+
for (const [name, entry] of this.cache) {
|
|
4938
|
+
data[name] = entry.price;
|
|
4939
|
+
}
|
|
4940
|
+
Promise.all([
|
|
4941
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE, JSON.stringify(data)),
|
|
4942
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS, String(Date.now()))
|
|
4943
|
+
]).catch(() => {
|
|
4944
|
+
});
|
|
4945
|
+
}
|
|
4946
|
+
// ===========================================================================
|
|
4947
|
+
// Rate-limit handling
|
|
4948
|
+
// ===========================================================================
|
|
4949
|
+
/**
|
|
4950
|
+
* On 429 rate-limit, extend stale cache entries so subsequent calls
|
|
4951
|
+
* don't immediately retry and hammer the API.
|
|
4952
|
+
*/
|
|
4953
|
+
extendCacheOnRateLimit(names) {
|
|
4954
|
+
const backoffMs = 6e4;
|
|
4955
|
+
const extendedExpiry = Date.now() + backoffMs;
|
|
4956
|
+
for (const name of names) {
|
|
4957
|
+
const existing = this.cache.get(name);
|
|
4958
|
+
if (existing) {
|
|
4959
|
+
existing.expiresAt = Math.max(existing.expiresAt, extendedExpiry);
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
if (this.debug) {
|
|
4963
|
+
console.warn(`[CoinGecko] Rate-limited (429), extended cache TTL by ${backoffMs / 1e3}s`);
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4937
4966
|
async getPrice(tokenName) {
|
|
4938
4967
|
const prices = await this.getPrices([tokenName]);
|
|
4939
4968
|
return prices.get(tokenName) ?? null;
|
|
@@ -4967,6 +4996,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
4967
4996
|
refreshTimer = null;
|
|
4968
4997
|
lastRefreshAt = 0;
|
|
4969
4998
|
refreshPromise = null;
|
|
4999
|
+
initialLoadPromise = null;
|
|
4970
5000
|
constructor() {
|
|
4971
5001
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
4972
5002
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
@@ -5005,13 +5035,8 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5005
5035
|
if (options.refreshIntervalMs !== void 0) {
|
|
5006
5036
|
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
5007
5037
|
}
|
|
5008
|
-
if (instance.storage) {
|
|
5009
|
-
instance.loadFromCache();
|
|
5010
|
-
}
|
|
5011
5038
|
const autoRefresh = options.autoRefresh ?? true;
|
|
5012
|
-
|
|
5013
|
-
instance.startAutoRefresh();
|
|
5014
|
-
}
|
|
5039
|
+
instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
|
|
5015
5040
|
}
|
|
5016
5041
|
/**
|
|
5017
5042
|
* Reset the singleton instance (useful for testing).
|
|
@@ -5029,6 +5054,53 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5029
5054
|
static destroy() {
|
|
5030
5055
|
_TokenRegistry.resetInstance();
|
|
5031
5056
|
}
|
|
5057
|
+
/**
|
|
5058
|
+
* Wait for the initial data load (cache or remote) to complete.
|
|
5059
|
+
* Returns true if data was loaded, false if not (timeout or no data source).
|
|
5060
|
+
*
|
|
5061
|
+
* @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
|
|
5062
|
+
*/
|
|
5063
|
+
static async waitForReady(timeoutMs = 1e4) {
|
|
5064
|
+
const instance = _TokenRegistry.getInstance();
|
|
5065
|
+
if (!instance.initialLoadPromise) {
|
|
5066
|
+
return instance.definitionsById.size > 0;
|
|
5067
|
+
}
|
|
5068
|
+
if (timeoutMs <= 0) {
|
|
5069
|
+
return instance.initialLoadPromise;
|
|
5070
|
+
}
|
|
5071
|
+
return Promise.race([
|
|
5072
|
+
instance.initialLoadPromise,
|
|
5073
|
+
new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
|
|
5074
|
+
]);
|
|
5075
|
+
}
|
|
5076
|
+
// ===========================================================================
|
|
5077
|
+
// Initial Load
|
|
5078
|
+
// ===========================================================================
|
|
5079
|
+
/**
|
|
5080
|
+
* Perform initial data load: try cache first, fall back to remote fetch.
|
|
5081
|
+
* After initial data is available, start periodic auto-refresh if configured.
|
|
5082
|
+
*/
|
|
5083
|
+
async performInitialLoad(autoRefresh) {
|
|
5084
|
+
let loaded = false;
|
|
5085
|
+
if (this.storage) {
|
|
5086
|
+
loaded = await this.loadFromCache();
|
|
5087
|
+
}
|
|
5088
|
+
if (loaded) {
|
|
5089
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5090
|
+
this.startAutoRefresh();
|
|
5091
|
+
}
|
|
5092
|
+
return true;
|
|
5093
|
+
}
|
|
5094
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5095
|
+
loaded = await this.refreshFromRemote();
|
|
5096
|
+
this.stopAutoRefresh();
|
|
5097
|
+
this.refreshTimer = setInterval(() => {
|
|
5098
|
+
this.refreshFromRemote();
|
|
5099
|
+
}, this.refreshIntervalMs);
|
|
5100
|
+
return loaded;
|
|
5101
|
+
}
|
|
5102
|
+
return false;
|
|
5103
|
+
}
|
|
5032
5104
|
// ===========================================================================
|
|
5033
5105
|
// Cache (StorageProvider)
|
|
5034
5106
|
// ===========================================================================
|
|
@@ -5358,7 +5430,7 @@ function resolveL1Config(network, config) {
|
|
|
5358
5430
|
enableVesting: config.enableVesting
|
|
5359
5431
|
};
|
|
5360
5432
|
}
|
|
5361
|
-
function resolvePriceConfig(config) {
|
|
5433
|
+
function resolvePriceConfig(config, storage) {
|
|
5362
5434
|
if (config === void 0) {
|
|
5363
5435
|
return void 0;
|
|
5364
5436
|
}
|
|
@@ -5368,7 +5440,8 @@ function resolvePriceConfig(config) {
|
|
|
5368
5440
|
baseUrl: config.baseUrl,
|
|
5369
5441
|
cacheTtlMs: config.cacheTtlMs,
|
|
5370
5442
|
timeout: config.timeout,
|
|
5371
|
-
debug: config.debug
|
|
5443
|
+
debug: config.debug,
|
|
5444
|
+
storage
|
|
5372
5445
|
};
|
|
5373
5446
|
}
|
|
5374
5447
|
function resolveGroupChatConfig(network, config) {
|
|
@@ -5385,16 +5458,6 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5385
5458
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5386
5459
|
};
|
|
5387
5460
|
}
|
|
5388
|
-
function resolveMarketConfig(config) {
|
|
5389
|
-
if (!config) return void 0;
|
|
5390
|
-
if (config === true) {
|
|
5391
|
-
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5392
|
-
}
|
|
5393
|
-
return {
|
|
5394
|
-
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5395
|
-
timeout: config.timeout
|
|
5396
|
-
};
|
|
5397
|
-
}
|
|
5398
5461
|
|
|
5399
5462
|
// impl/nodejs/index.ts
|
|
5400
5463
|
function createNodeProviders(config) {
|
|
@@ -5402,21 +5465,19 @@ function createNodeProviders(config) {
|
|
|
5402
5465
|
const transportConfig = resolveTransportConfig(network, config?.transport);
|
|
5403
5466
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5404
5467
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5405
|
-
const priceConfig = resolvePriceConfig(config?.price);
|
|
5406
5468
|
const storage = createFileStorageProvider({
|
|
5407
5469
|
dataDir: config?.dataDir ?? "./sphere-data",
|
|
5408
5470
|
...config?.walletFileName ? { fileName: config.walletFileName } : {}
|
|
5409
5471
|
});
|
|
5472
|
+
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5410
5473
|
const ipfsSync = config?.tokenSync?.ipfs;
|
|
5411
5474
|
const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
|
|
5412
5475
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5413
5476
|
const networkConfig = getNetworkConfig(network);
|
|
5414
5477
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5415
|
-
const market = resolveMarketConfig(config?.market);
|
|
5416
5478
|
return {
|
|
5417
5479
|
storage,
|
|
5418
5480
|
groupChat,
|
|
5419
|
-
market,
|
|
5420
5481
|
tokenStorage: createFileTokenStorageProvider({
|
|
5421
5482
|
tokensDir: config?.tokensDir ?? "./sphere-tokens"
|
|
5422
5483
|
}),
|