@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
|
@@ -90,7 +90,11 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
90
90
|
/** Cached token registry JSON (fetched from remote) */
|
|
91
91
|
TOKEN_REGISTRY_CACHE: "token_registry_cache",
|
|
92
92
|
/** Timestamp of last token registry cache update (ms since epoch) */
|
|
93
|
-
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts"
|
|
93
|
+
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
|
|
94
|
+
/** Cached price data JSON (from CoinGecko or other provider) */
|
|
95
|
+
PRICE_CACHE: "price_cache",
|
|
96
|
+
/** Timestamp of last price cache update (ms since epoch) */
|
|
97
|
+
PRICE_CACHE_TS: "price_cache_ts"
|
|
94
98
|
};
|
|
95
99
|
var STORAGE_KEYS_ADDRESS = {
|
|
96
100
|
/** Pending transfers for this address */
|
|
@@ -215,7 +219,6 @@ var TIMEOUTS = {
|
|
|
215
219
|
/** Sync interval */
|
|
216
220
|
SYNC_INTERVAL: 6e4
|
|
217
221
|
};
|
|
218
|
-
var DEFAULT_MARKET_API_URL = "https://market-api.unicity.network";
|
|
219
222
|
|
|
220
223
|
// impl/nodejs/storage/FileStorageProvider.ts
|
|
221
224
|
var FileStorageProvider = class {
|
|
@@ -1196,8 +1199,6 @@ var NostrTransportProvider = class {
|
|
|
1196
1199
|
transferHandlers = /* @__PURE__ */ new Set();
|
|
1197
1200
|
paymentRequestHandlers = /* @__PURE__ */ new Set();
|
|
1198
1201
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1199
|
-
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1200
|
-
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1201
1202
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1202
1203
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1203
1204
|
constructor(config) {
|
|
@@ -1449,18 +1450,6 @@ var NostrTransportProvider = class {
|
|
|
1449
1450
|
const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
|
|
1450
1451
|
const giftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
|
|
1451
1452
|
await this.publishEvent(giftWrap);
|
|
1452
|
-
const selfWrapContent = JSON.stringify({
|
|
1453
|
-
selfWrap: true,
|
|
1454
|
-
originalId: giftWrap.id,
|
|
1455
|
-
recipientPubkey,
|
|
1456
|
-
senderNametag,
|
|
1457
|
-
text: content
|
|
1458
|
-
});
|
|
1459
|
-
const selfPubkey = this.keyManager.getPublicKeyHex();
|
|
1460
|
-
const selfGiftWrap = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, selfPubkey, selfWrapContent);
|
|
1461
|
-
this.publishEvent(selfGiftWrap).catch((err) => {
|
|
1462
|
-
this.log("Self-wrap publish failed:", err);
|
|
1463
|
-
});
|
|
1464
1453
|
this.emitEvent({
|
|
1465
1454
|
type: "message:sent",
|
|
1466
1455
|
timestamp: Date.now(),
|
|
@@ -1559,37 +1548,6 @@ var NostrTransportProvider = class {
|
|
|
1559
1548
|
this.paymentRequestResponseHandlers.add(handler);
|
|
1560
1549
|
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1561
1550
|
}
|
|
1562
|
-
// ===========================================================================
|
|
1563
|
-
// Read Receipts
|
|
1564
|
-
// ===========================================================================
|
|
1565
|
-
async sendReadReceipt(recipientTransportPubkey, messageEventId) {
|
|
1566
|
-
if (!this.keyManager) throw new Error("Not initialized");
|
|
1567
|
-
const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
|
|
1568
|
-
const event = import_nostr_js_sdk.NIP17.createReadReceipt(this.keyManager, nostrRecipient, messageEventId);
|
|
1569
|
-
await this.publishEvent(event);
|
|
1570
|
-
this.log("Sent read receipt for:", messageEventId, "to:", nostrRecipient.slice(0, 16));
|
|
1571
|
-
}
|
|
1572
|
-
onReadReceipt(handler) {
|
|
1573
|
-
this.readReceiptHandlers.add(handler);
|
|
1574
|
-
return () => this.readReceiptHandlers.delete(handler);
|
|
1575
|
-
}
|
|
1576
|
-
// ===========================================================================
|
|
1577
|
-
// Typing Indicators
|
|
1578
|
-
// ===========================================================================
|
|
1579
|
-
async sendTypingIndicator(recipientTransportPubkey) {
|
|
1580
|
-
if (!this.keyManager) throw new Error("Not initialized");
|
|
1581
|
-
const nostrRecipient = recipientTransportPubkey.length === 66 ? recipientTransportPubkey.slice(2) : recipientTransportPubkey;
|
|
1582
|
-
const content = JSON.stringify({
|
|
1583
|
-
type: "typing",
|
|
1584
|
-
senderNametag: this.identity?.nametag
|
|
1585
|
-
});
|
|
1586
|
-
const event = import_nostr_js_sdk.NIP17.createGiftWrap(this.keyManager, nostrRecipient, content);
|
|
1587
|
-
await this.publishEvent(event);
|
|
1588
|
-
}
|
|
1589
|
-
onTypingIndicator(handler) {
|
|
1590
|
-
this.typingIndicatorHandlers.add(handler);
|
|
1591
|
-
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1592
|
-
}
|
|
1593
1551
|
/**
|
|
1594
1552
|
* Resolve any identifier to full peer information.
|
|
1595
1553
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2043,74 +2001,11 @@ var NostrTransportProvider = class {
|
|
|
2043
2001
|
const pm = import_nostr_js_sdk.NIP17.unwrap(event, this.keyManager);
|
|
2044
2002
|
this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
|
|
2045
2003
|
if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
|
|
2046
|
-
|
|
2047
|
-
const parsed = JSON.parse(pm.content);
|
|
2048
|
-
if (parsed?.selfWrap && parsed.recipientPubkey) {
|
|
2049
|
-
this.log("Self-wrap replay for recipient:", parsed.recipientPubkey?.slice(0, 16));
|
|
2050
|
-
const message2 = {
|
|
2051
|
-
id: parsed.originalId || pm.eventId,
|
|
2052
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2053
|
-
senderNametag: parsed.senderNametag,
|
|
2054
|
-
recipientTransportPubkey: parsed.recipientPubkey,
|
|
2055
|
-
content: parsed.text ?? "",
|
|
2056
|
-
timestamp: pm.timestamp * 1e3,
|
|
2057
|
-
encrypted: true,
|
|
2058
|
-
isSelfWrap: true
|
|
2059
|
-
};
|
|
2060
|
-
for (const handler of this.messageHandlers) {
|
|
2061
|
-
try {
|
|
2062
|
-
handler(message2);
|
|
2063
|
-
} catch (e) {
|
|
2064
|
-
this.log("Self-wrap handler error:", e);
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
return;
|
|
2068
|
-
}
|
|
2069
|
-
} catch {
|
|
2070
|
-
}
|
|
2071
|
-
this.log("Skipping own non-self-wrap message");
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
if ((0, import_nostr_js_sdk.isReadReceipt)(pm)) {
|
|
2075
|
-
this.log("Read receipt from:", pm.senderPubkey?.slice(0, 16), "for:", pm.replyToEventId);
|
|
2076
|
-
if (pm.replyToEventId) {
|
|
2077
|
-
const receipt = {
|
|
2078
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2079
|
-
messageEventId: pm.replyToEventId,
|
|
2080
|
-
timestamp: pm.timestamp * 1e3
|
|
2081
|
-
};
|
|
2082
|
-
for (const handler of this.readReceiptHandlers) {
|
|
2083
|
-
try {
|
|
2084
|
-
handler(receipt);
|
|
2085
|
-
} catch (e) {
|
|
2086
|
-
this.log("Read receipt handler error:", e);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2004
|
+
this.log("Skipping own message");
|
|
2090
2005
|
return;
|
|
2091
2006
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
if (parsed?.type === "typing") {
|
|
2095
|
-
this.log("Typing indicator from:", pm.senderPubkey?.slice(0, 16));
|
|
2096
|
-
const indicator = {
|
|
2097
|
-
senderTransportPubkey: pm.senderPubkey,
|
|
2098
|
-
senderNametag: parsed.senderNametag,
|
|
2099
|
-
timestamp: pm.timestamp * 1e3
|
|
2100
|
-
};
|
|
2101
|
-
for (const handler of this.typingIndicatorHandlers) {
|
|
2102
|
-
try {
|
|
2103
|
-
handler(indicator);
|
|
2104
|
-
} catch (e) {
|
|
2105
|
-
this.log("Typing handler error:", e);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
} catch {
|
|
2111
|
-
}
|
|
2112
|
-
if (!(0, import_nostr_js_sdk.isChatMessage)(pm)) {
|
|
2113
|
-
this.log("Skipping unknown message kind:", pm.kind);
|
|
2007
|
+
if (pm.kind !== import_nostr_js_sdk.EventKinds.CHAT_MESSAGE) {
|
|
2008
|
+
this.log("Skipping non-chat message, kind:", pm.kind);
|
|
2114
2009
|
return;
|
|
2115
2010
|
}
|
|
2116
2011
|
let content = pm.content;
|
|
@@ -2125,9 +2020,7 @@ var NostrTransportProvider = class {
|
|
|
2125
2020
|
}
|
|
2126
2021
|
this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
|
|
2127
2022
|
const message = {
|
|
2128
|
-
|
|
2129
|
-
// This ensures read receipts reference an ID the sender recognizes.
|
|
2130
|
-
id: event.id,
|
|
2023
|
+
id: pm.eventId,
|
|
2131
2024
|
senderTransportPubkey: pm.senderPubkey,
|
|
2132
2025
|
senderNametag,
|
|
2133
2026
|
content,
|
|
@@ -3152,6 +3045,7 @@ async function loadIpnsModule() {
|
|
|
3152
3045
|
async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
|
|
3153
3046
|
const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
|
|
3154
3047
|
const record = await createIPNSRecord(
|
|
3048
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3155
3049
|
keyPair,
|
|
3156
3050
|
`/ipfs/${cid}`,
|
|
3157
3051
|
sequenceNumber,
|
|
@@ -4893,26 +4787,37 @@ var CoinGeckoPriceProvider = class {
|
|
|
4893
4787
|
timeout;
|
|
4894
4788
|
debug;
|
|
4895
4789
|
baseUrl;
|
|
4790
|
+
storage;
|
|
4791
|
+
/** In-flight fetch promise for deduplication of concurrent getPrices() calls */
|
|
4792
|
+
fetchPromise = null;
|
|
4793
|
+
/** Token names being fetched in the current in-flight request */
|
|
4794
|
+
fetchNames = null;
|
|
4795
|
+
/** Whether persistent cache has been loaded into memory */
|
|
4796
|
+
persistentCacheLoaded = false;
|
|
4797
|
+
/** Promise for loading persistent cache (deduplication) */
|
|
4798
|
+
loadCachePromise = null;
|
|
4896
4799
|
constructor(config) {
|
|
4897
4800
|
this.apiKey = config?.apiKey;
|
|
4898
4801
|
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
4899
4802
|
this.timeout = config?.timeout ?? 1e4;
|
|
4900
4803
|
this.debug = config?.debug ?? false;
|
|
4804
|
+
this.storage = config?.storage ?? null;
|
|
4901
4805
|
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
4902
4806
|
}
|
|
4903
4807
|
async getPrices(tokenNames) {
|
|
4904
4808
|
if (tokenNames.length === 0) {
|
|
4905
4809
|
return /* @__PURE__ */ new Map();
|
|
4906
4810
|
}
|
|
4811
|
+
if (!this.persistentCacheLoaded && this.storage) {
|
|
4812
|
+
await this.loadFromStorage();
|
|
4813
|
+
}
|
|
4907
4814
|
const now = Date.now();
|
|
4908
4815
|
const result = /* @__PURE__ */ new Map();
|
|
4909
4816
|
const uncachedNames = [];
|
|
4910
4817
|
for (const name of tokenNames) {
|
|
4911
4818
|
const cached = this.cache.get(name);
|
|
4912
4819
|
if (cached && cached.expiresAt > now) {
|
|
4913
|
-
|
|
4914
|
-
result.set(name, cached.price);
|
|
4915
|
-
}
|
|
4820
|
+
result.set(name, cached.price);
|
|
4916
4821
|
} else {
|
|
4917
4822
|
uncachedNames.push(name);
|
|
4918
4823
|
}
|
|
@@ -4920,6 +4825,41 @@ var CoinGeckoPriceProvider = class {
|
|
|
4920
4825
|
if (uncachedNames.length === 0) {
|
|
4921
4826
|
return result;
|
|
4922
4827
|
}
|
|
4828
|
+
if (this.fetchPromise && this.fetchNames) {
|
|
4829
|
+
const allCovered = uncachedNames.every((n) => this.fetchNames.has(n));
|
|
4830
|
+
if (allCovered) {
|
|
4831
|
+
if (this.debug) {
|
|
4832
|
+
console.log(`[CoinGecko] Deduplicating request, reusing in-flight fetch`);
|
|
4833
|
+
}
|
|
4834
|
+
const fetched = await this.fetchPromise;
|
|
4835
|
+
for (const name of uncachedNames) {
|
|
4836
|
+
const price = fetched.get(name);
|
|
4837
|
+
if (price) {
|
|
4838
|
+
result.set(name, price);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
return result;
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
const fetchPromise = this.doFetch(uncachedNames);
|
|
4845
|
+
this.fetchPromise = fetchPromise;
|
|
4846
|
+
this.fetchNames = new Set(uncachedNames);
|
|
4847
|
+
try {
|
|
4848
|
+
const fetched = await fetchPromise;
|
|
4849
|
+
for (const [name, price] of fetched) {
|
|
4850
|
+
result.set(name, price);
|
|
4851
|
+
}
|
|
4852
|
+
} finally {
|
|
4853
|
+
if (this.fetchPromise === fetchPromise) {
|
|
4854
|
+
this.fetchPromise = null;
|
|
4855
|
+
this.fetchNames = null;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
return result;
|
|
4859
|
+
}
|
|
4860
|
+
async doFetch(uncachedNames) {
|
|
4861
|
+
const result = /* @__PURE__ */ new Map();
|
|
4862
|
+
const now = Date.now();
|
|
4923
4863
|
try {
|
|
4924
4864
|
const ids = uncachedNames.join(",");
|
|
4925
4865
|
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
@@ -4935,6 +4875,9 @@ var CoinGeckoPriceProvider = class {
|
|
|
4935
4875
|
signal: AbortSignal.timeout(this.timeout)
|
|
4936
4876
|
});
|
|
4937
4877
|
if (!response.ok) {
|
|
4878
|
+
if (response.status === 429) {
|
|
4879
|
+
this.extendCacheOnRateLimit(uncachedNames);
|
|
4880
|
+
}
|
|
4938
4881
|
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
4939
4882
|
}
|
|
4940
4883
|
const data = await response.json();
|
|
@@ -4953,25 +4896,113 @@ var CoinGeckoPriceProvider = class {
|
|
|
4953
4896
|
}
|
|
4954
4897
|
for (const name of uncachedNames) {
|
|
4955
4898
|
if (!result.has(name)) {
|
|
4956
|
-
|
|
4899
|
+
const zeroPrice = {
|
|
4900
|
+
tokenName: name,
|
|
4901
|
+
priceUsd: 0,
|
|
4902
|
+
priceEur: 0,
|
|
4903
|
+
change24h: 0,
|
|
4904
|
+
timestamp: now
|
|
4905
|
+
};
|
|
4906
|
+
this.cache.set(name, { price: zeroPrice, expiresAt: now + this.cacheTtlMs });
|
|
4907
|
+
result.set(name, zeroPrice);
|
|
4957
4908
|
}
|
|
4958
4909
|
}
|
|
4959
4910
|
if (this.debug) {
|
|
4960
4911
|
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
4961
4912
|
}
|
|
4913
|
+
this.saveToStorage();
|
|
4962
4914
|
} catch (error) {
|
|
4963
4915
|
if (this.debug) {
|
|
4964
4916
|
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
4965
4917
|
}
|
|
4966
4918
|
for (const name of uncachedNames) {
|
|
4967
4919
|
const stale = this.cache.get(name);
|
|
4968
|
-
if (stale
|
|
4920
|
+
if (stale) {
|
|
4969
4921
|
result.set(name, stale.price);
|
|
4970
4922
|
}
|
|
4971
4923
|
}
|
|
4972
4924
|
}
|
|
4973
4925
|
return result;
|
|
4974
4926
|
}
|
|
4927
|
+
// ===========================================================================
|
|
4928
|
+
// Persistent Storage
|
|
4929
|
+
// ===========================================================================
|
|
4930
|
+
/**
|
|
4931
|
+
* Load cached prices from StorageProvider into in-memory cache.
|
|
4932
|
+
* Only loads entries that are still within cacheTtlMs.
|
|
4933
|
+
*/
|
|
4934
|
+
async loadFromStorage() {
|
|
4935
|
+
if (this.loadCachePromise) {
|
|
4936
|
+
return this.loadCachePromise;
|
|
4937
|
+
}
|
|
4938
|
+
this.loadCachePromise = this.doLoadFromStorage();
|
|
4939
|
+
try {
|
|
4940
|
+
await this.loadCachePromise;
|
|
4941
|
+
} finally {
|
|
4942
|
+
this.loadCachePromise = null;
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
async doLoadFromStorage() {
|
|
4946
|
+
this.persistentCacheLoaded = true;
|
|
4947
|
+
if (!this.storage) return;
|
|
4948
|
+
try {
|
|
4949
|
+
const [cached, cachedTs] = await Promise.all([
|
|
4950
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE),
|
|
4951
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS)
|
|
4952
|
+
]);
|
|
4953
|
+
if (!cached || !cachedTs) return;
|
|
4954
|
+
const ts = parseInt(cachedTs, 10);
|
|
4955
|
+
if (isNaN(ts)) return;
|
|
4956
|
+
const age = Date.now() - ts;
|
|
4957
|
+
if (age > this.cacheTtlMs) return;
|
|
4958
|
+
const data = JSON.parse(cached);
|
|
4959
|
+
const expiresAt = ts + this.cacheTtlMs;
|
|
4960
|
+
for (const [name, price] of Object.entries(data)) {
|
|
4961
|
+
if (!this.cache.has(name)) {
|
|
4962
|
+
this.cache.set(name, { price, expiresAt });
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
if (this.debug) {
|
|
4966
|
+
console.log(`[CoinGecko] Loaded ${Object.keys(data).length} prices from persistent cache`);
|
|
4967
|
+
}
|
|
4968
|
+
} catch {
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Save current prices to StorageProvider (fire-and-forget).
|
|
4973
|
+
*/
|
|
4974
|
+
saveToStorage() {
|
|
4975
|
+
if (!this.storage) return;
|
|
4976
|
+
const data = {};
|
|
4977
|
+
for (const [name, entry] of this.cache) {
|
|
4978
|
+
data[name] = entry.price;
|
|
4979
|
+
}
|
|
4980
|
+
Promise.all([
|
|
4981
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE, JSON.stringify(data)),
|
|
4982
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS, String(Date.now()))
|
|
4983
|
+
]).catch(() => {
|
|
4984
|
+
});
|
|
4985
|
+
}
|
|
4986
|
+
// ===========================================================================
|
|
4987
|
+
// Rate-limit handling
|
|
4988
|
+
// ===========================================================================
|
|
4989
|
+
/**
|
|
4990
|
+
* On 429 rate-limit, extend stale cache entries so subsequent calls
|
|
4991
|
+
* don't immediately retry and hammer the API.
|
|
4992
|
+
*/
|
|
4993
|
+
extendCacheOnRateLimit(names) {
|
|
4994
|
+
const backoffMs = 6e4;
|
|
4995
|
+
const extendedExpiry = Date.now() + backoffMs;
|
|
4996
|
+
for (const name of names) {
|
|
4997
|
+
const existing = this.cache.get(name);
|
|
4998
|
+
if (existing) {
|
|
4999
|
+
existing.expiresAt = Math.max(existing.expiresAt, extendedExpiry);
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
if (this.debug) {
|
|
5003
|
+
console.warn(`[CoinGecko] Rate-limited (429), extended cache TTL by ${backoffMs / 1e3}s`);
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
4975
5006
|
async getPrice(tokenName) {
|
|
4976
5007
|
const prices = await this.getPrices([tokenName]);
|
|
4977
5008
|
return prices.get(tokenName) ?? null;
|
|
@@ -5005,6 +5036,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5005
5036
|
refreshTimer = null;
|
|
5006
5037
|
lastRefreshAt = 0;
|
|
5007
5038
|
refreshPromise = null;
|
|
5039
|
+
initialLoadPromise = null;
|
|
5008
5040
|
constructor() {
|
|
5009
5041
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
5010
5042
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
@@ -5043,13 +5075,8 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5043
5075
|
if (options.refreshIntervalMs !== void 0) {
|
|
5044
5076
|
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
5045
5077
|
}
|
|
5046
|
-
if (instance.storage) {
|
|
5047
|
-
instance.loadFromCache();
|
|
5048
|
-
}
|
|
5049
5078
|
const autoRefresh = options.autoRefresh ?? true;
|
|
5050
|
-
|
|
5051
|
-
instance.startAutoRefresh();
|
|
5052
|
-
}
|
|
5079
|
+
instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
|
|
5053
5080
|
}
|
|
5054
5081
|
/**
|
|
5055
5082
|
* Reset the singleton instance (useful for testing).
|
|
@@ -5067,6 +5094,53 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5067
5094
|
static destroy() {
|
|
5068
5095
|
_TokenRegistry.resetInstance();
|
|
5069
5096
|
}
|
|
5097
|
+
/**
|
|
5098
|
+
* Wait for the initial data load (cache or remote) to complete.
|
|
5099
|
+
* Returns true if data was loaded, false if not (timeout or no data source).
|
|
5100
|
+
*
|
|
5101
|
+
* @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
|
|
5102
|
+
*/
|
|
5103
|
+
static async waitForReady(timeoutMs = 1e4) {
|
|
5104
|
+
const instance = _TokenRegistry.getInstance();
|
|
5105
|
+
if (!instance.initialLoadPromise) {
|
|
5106
|
+
return instance.definitionsById.size > 0;
|
|
5107
|
+
}
|
|
5108
|
+
if (timeoutMs <= 0) {
|
|
5109
|
+
return instance.initialLoadPromise;
|
|
5110
|
+
}
|
|
5111
|
+
return Promise.race([
|
|
5112
|
+
instance.initialLoadPromise,
|
|
5113
|
+
new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
|
|
5114
|
+
]);
|
|
5115
|
+
}
|
|
5116
|
+
// ===========================================================================
|
|
5117
|
+
// Initial Load
|
|
5118
|
+
// ===========================================================================
|
|
5119
|
+
/**
|
|
5120
|
+
* Perform initial data load: try cache first, fall back to remote fetch.
|
|
5121
|
+
* After initial data is available, start periodic auto-refresh if configured.
|
|
5122
|
+
*/
|
|
5123
|
+
async performInitialLoad(autoRefresh) {
|
|
5124
|
+
let loaded = false;
|
|
5125
|
+
if (this.storage) {
|
|
5126
|
+
loaded = await this.loadFromCache();
|
|
5127
|
+
}
|
|
5128
|
+
if (loaded) {
|
|
5129
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5130
|
+
this.startAutoRefresh();
|
|
5131
|
+
}
|
|
5132
|
+
return true;
|
|
5133
|
+
}
|
|
5134
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5135
|
+
loaded = await this.refreshFromRemote();
|
|
5136
|
+
this.stopAutoRefresh();
|
|
5137
|
+
this.refreshTimer = setInterval(() => {
|
|
5138
|
+
this.refreshFromRemote();
|
|
5139
|
+
}, this.refreshIntervalMs);
|
|
5140
|
+
return loaded;
|
|
5141
|
+
}
|
|
5142
|
+
return false;
|
|
5143
|
+
}
|
|
5070
5144
|
// ===========================================================================
|
|
5071
5145
|
// Cache (StorageProvider)
|
|
5072
5146
|
// ===========================================================================
|
|
@@ -5396,7 +5470,7 @@ function resolveL1Config(network, config) {
|
|
|
5396
5470
|
enableVesting: config.enableVesting
|
|
5397
5471
|
};
|
|
5398
5472
|
}
|
|
5399
|
-
function resolvePriceConfig(config) {
|
|
5473
|
+
function resolvePriceConfig(config, storage) {
|
|
5400
5474
|
if (config === void 0) {
|
|
5401
5475
|
return void 0;
|
|
5402
5476
|
}
|
|
@@ -5406,7 +5480,8 @@ function resolvePriceConfig(config) {
|
|
|
5406
5480
|
baseUrl: config.baseUrl,
|
|
5407
5481
|
cacheTtlMs: config.cacheTtlMs,
|
|
5408
5482
|
timeout: config.timeout,
|
|
5409
|
-
debug: config.debug
|
|
5483
|
+
debug: config.debug,
|
|
5484
|
+
storage
|
|
5410
5485
|
};
|
|
5411
5486
|
}
|
|
5412
5487
|
function resolveGroupChatConfig(network, config) {
|
|
@@ -5423,16 +5498,6 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5423
5498
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5424
5499
|
};
|
|
5425
5500
|
}
|
|
5426
|
-
function resolveMarketConfig(config) {
|
|
5427
|
-
if (!config) return void 0;
|
|
5428
|
-
if (config === true) {
|
|
5429
|
-
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5430
|
-
}
|
|
5431
|
-
return {
|
|
5432
|
-
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5433
|
-
timeout: config.timeout
|
|
5434
|
-
};
|
|
5435
|
-
}
|
|
5436
5501
|
|
|
5437
5502
|
// impl/nodejs/index.ts
|
|
5438
5503
|
function createNodeProviders(config) {
|
|
@@ -5440,21 +5505,19 @@ function createNodeProviders(config) {
|
|
|
5440
5505
|
const transportConfig = resolveTransportConfig(network, config?.transport);
|
|
5441
5506
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5442
5507
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5443
|
-
const priceConfig = resolvePriceConfig(config?.price);
|
|
5444
5508
|
const storage = createFileStorageProvider({
|
|
5445
5509
|
dataDir: config?.dataDir ?? "./sphere-data",
|
|
5446
5510
|
...config?.walletFileName ? { fileName: config.walletFileName } : {}
|
|
5447
5511
|
});
|
|
5512
|
+
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5448
5513
|
const ipfsSync = config?.tokenSync?.ipfs;
|
|
5449
5514
|
const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
|
|
5450
5515
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5451
5516
|
const networkConfig = getNetworkConfig(network);
|
|
5452
5517
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5453
|
-
const market = resolveMarketConfig(config?.market);
|
|
5454
5518
|
return {
|
|
5455
5519
|
storage,
|
|
5456
5520
|
groupChat,
|
|
5457
|
-
market,
|
|
5458
5521
|
tokenStorage: createFileTokenStorageProvider({
|
|
5459
5522
|
tokensDir: config?.tokensDir ?? "./sphere-tokens"
|
|
5460
5523
|
}),
|