@unicitylabs/sphere-sdk 0.3.6 → 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 +96 -2528
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +10 -165
- package/dist/core/index.d.ts +10 -165
- package/dist/core/index.js +92 -2524
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +201 -28
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +201 -28
- 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 +201 -28
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +2 -21
- package/dist/impl/nodejs/index.d.ts +2 -21
- package/dist/impl/nodejs/index.js +201 -28
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +238 -2539
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -169
- package/dist/index.d.ts +59 -169
- package/dist/index.js +234 -2532
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -98,7 +98,11 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
98
98
|
/** Cached token registry JSON (fetched from remote) */
|
|
99
99
|
TOKEN_REGISTRY_CACHE: "token_registry_cache",
|
|
100
100
|
/** Timestamp of last token registry cache update (ms since epoch) */
|
|
101
|
-
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts"
|
|
101
|
+
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
|
|
102
|
+
/** Cached price data JSON (from CoinGecko or other provider) */
|
|
103
|
+
PRICE_CACHE: "price_cache",
|
|
104
|
+
/** Timestamp of last price cache update (ms since epoch) */
|
|
105
|
+
PRICE_CACHE_TS: "price_cache_ts"
|
|
102
106
|
};
|
|
103
107
|
var STORAGE_KEYS_ADDRESS = {
|
|
104
108
|
/** Pending transfers for this address */
|
|
@@ -223,7 +227,6 @@ var TIMEOUTS = {
|
|
|
223
227
|
/** Sync interval */
|
|
224
228
|
SYNC_INTERVAL: 6e4
|
|
225
229
|
};
|
|
226
|
-
var DEFAULT_MARKET_API_URL = "https://market-api.unicity.network";
|
|
227
230
|
|
|
228
231
|
// impl/browser/storage/LocalStorageProvider.ts
|
|
229
232
|
var LocalStorageProvider = class {
|
|
@@ -3277,6 +3280,7 @@ async function loadIpnsModule() {
|
|
|
3277
3280
|
async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
|
|
3278
3281
|
const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
|
|
3279
3282
|
const record = await createIPNSRecord(
|
|
3283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3280
3284
|
keyPair,
|
|
3281
3285
|
`/ipfs/${cid}`,
|
|
3282
3286
|
sequenceNumber,
|
|
@@ -5021,26 +5025,37 @@ var CoinGeckoPriceProvider = class {
|
|
|
5021
5025
|
timeout;
|
|
5022
5026
|
debug;
|
|
5023
5027
|
baseUrl;
|
|
5028
|
+
storage;
|
|
5029
|
+
/** In-flight fetch promise for deduplication of concurrent getPrices() calls */
|
|
5030
|
+
fetchPromise = null;
|
|
5031
|
+
/** Token names being fetched in the current in-flight request */
|
|
5032
|
+
fetchNames = null;
|
|
5033
|
+
/** Whether persistent cache has been loaded into memory */
|
|
5034
|
+
persistentCacheLoaded = false;
|
|
5035
|
+
/** Promise for loading persistent cache (deduplication) */
|
|
5036
|
+
loadCachePromise = null;
|
|
5024
5037
|
constructor(config) {
|
|
5025
5038
|
this.apiKey = config?.apiKey;
|
|
5026
5039
|
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
5027
5040
|
this.timeout = config?.timeout ?? 1e4;
|
|
5028
5041
|
this.debug = config?.debug ?? false;
|
|
5042
|
+
this.storage = config?.storage ?? null;
|
|
5029
5043
|
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
5030
5044
|
}
|
|
5031
5045
|
async getPrices(tokenNames) {
|
|
5032
5046
|
if (tokenNames.length === 0) {
|
|
5033
5047
|
return /* @__PURE__ */ new Map();
|
|
5034
5048
|
}
|
|
5049
|
+
if (!this.persistentCacheLoaded && this.storage) {
|
|
5050
|
+
await this.loadFromStorage();
|
|
5051
|
+
}
|
|
5035
5052
|
const now = Date.now();
|
|
5036
5053
|
const result = /* @__PURE__ */ new Map();
|
|
5037
5054
|
const uncachedNames = [];
|
|
5038
5055
|
for (const name of tokenNames) {
|
|
5039
5056
|
const cached = this.cache.get(name);
|
|
5040
5057
|
if (cached && cached.expiresAt > now) {
|
|
5041
|
-
|
|
5042
|
-
result.set(name, cached.price);
|
|
5043
|
-
}
|
|
5058
|
+
result.set(name, cached.price);
|
|
5044
5059
|
} else {
|
|
5045
5060
|
uncachedNames.push(name);
|
|
5046
5061
|
}
|
|
@@ -5048,6 +5063,41 @@ var CoinGeckoPriceProvider = class {
|
|
|
5048
5063
|
if (uncachedNames.length === 0) {
|
|
5049
5064
|
return result;
|
|
5050
5065
|
}
|
|
5066
|
+
if (this.fetchPromise && this.fetchNames) {
|
|
5067
|
+
const allCovered = uncachedNames.every((n) => this.fetchNames.has(n));
|
|
5068
|
+
if (allCovered) {
|
|
5069
|
+
if (this.debug) {
|
|
5070
|
+
console.log(`[CoinGecko] Deduplicating request, reusing in-flight fetch`);
|
|
5071
|
+
}
|
|
5072
|
+
const fetched = await this.fetchPromise;
|
|
5073
|
+
for (const name of uncachedNames) {
|
|
5074
|
+
const price = fetched.get(name);
|
|
5075
|
+
if (price) {
|
|
5076
|
+
result.set(name, price);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
return result;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
const fetchPromise = this.doFetch(uncachedNames);
|
|
5083
|
+
this.fetchPromise = fetchPromise;
|
|
5084
|
+
this.fetchNames = new Set(uncachedNames);
|
|
5085
|
+
try {
|
|
5086
|
+
const fetched = await fetchPromise;
|
|
5087
|
+
for (const [name, price] of fetched) {
|
|
5088
|
+
result.set(name, price);
|
|
5089
|
+
}
|
|
5090
|
+
} finally {
|
|
5091
|
+
if (this.fetchPromise === fetchPromise) {
|
|
5092
|
+
this.fetchPromise = null;
|
|
5093
|
+
this.fetchNames = null;
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
return result;
|
|
5097
|
+
}
|
|
5098
|
+
async doFetch(uncachedNames) {
|
|
5099
|
+
const result = /* @__PURE__ */ new Map();
|
|
5100
|
+
const now = Date.now();
|
|
5051
5101
|
try {
|
|
5052
5102
|
const ids = uncachedNames.join(",");
|
|
5053
5103
|
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
@@ -5063,6 +5113,9 @@ var CoinGeckoPriceProvider = class {
|
|
|
5063
5113
|
signal: AbortSignal.timeout(this.timeout)
|
|
5064
5114
|
});
|
|
5065
5115
|
if (!response.ok) {
|
|
5116
|
+
if (response.status === 429) {
|
|
5117
|
+
this.extendCacheOnRateLimit(uncachedNames);
|
|
5118
|
+
}
|
|
5066
5119
|
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
5067
5120
|
}
|
|
5068
5121
|
const data = await response.json();
|
|
@@ -5081,25 +5134,113 @@ var CoinGeckoPriceProvider = class {
|
|
|
5081
5134
|
}
|
|
5082
5135
|
for (const name of uncachedNames) {
|
|
5083
5136
|
if (!result.has(name)) {
|
|
5084
|
-
|
|
5137
|
+
const zeroPrice = {
|
|
5138
|
+
tokenName: name,
|
|
5139
|
+
priceUsd: 0,
|
|
5140
|
+
priceEur: 0,
|
|
5141
|
+
change24h: 0,
|
|
5142
|
+
timestamp: now
|
|
5143
|
+
};
|
|
5144
|
+
this.cache.set(name, { price: zeroPrice, expiresAt: now + this.cacheTtlMs });
|
|
5145
|
+
result.set(name, zeroPrice);
|
|
5085
5146
|
}
|
|
5086
5147
|
}
|
|
5087
5148
|
if (this.debug) {
|
|
5088
5149
|
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
5089
5150
|
}
|
|
5151
|
+
this.saveToStorage();
|
|
5090
5152
|
} catch (error) {
|
|
5091
5153
|
if (this.debug) {
|
|
5092
5154
|
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
5093
5155
|
}
|
|
5094
5156
|
for (const name of uncachedNames) {
|
|
5095
5157
|
const stale = this.cache.get(name);
|
|
5096
|
-
if (stale
|
|
5158
|
+
if (stale) {
|
|
5097
5159
|
result.set(name, stale.price);
|
|
5098
5160
|
}
|
|
5099
5161
|
}
|
|
5100
5162
|
}
|
|
5101
5163
|
return result;
|
|
5102
5164
|
}
|
|
5165
|
+
// ===========================================================================
|
|
5166
|
+
// Persistent Storage
|
|
5167
|
+
// ===========================================================================
|
|
5168
|
+
/**
|
|
5169
|
+
* Load cached prices from StorageProvider into in-memory cache.
|
|
5170
|
+
* Only loads entries that are still within cacheTtlMs.
|
|
5171
|
+
*/
|
|
5172
|
+
async loadFromStorage() {
|
|
5173
|
+
if (this.loadCachePromise) {
|
|
5174
|
+
return this.loadCachePromise;
|
|
5175
|
+
}
|
|
5176
|
+
this.loadCachePromise = this.doLoadFromStorage();
|
|
5177
|
+
try {
|
|
5178
|
+
await this.loadCachePromise;
|
|
5179
|
+
} finally {
|
|
5180
|
+
this.loadCachePromise = null;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
async doLoadFromStorage() {
|
|
5184
|
+
this.persistentCacheLoaded = true;
|
|
5185
|
+
if (!this.storage) return;
|
|
5186
|
+
try {
|
|
5187
|
+
const [cached, cachedTs] = await Promise.all([
|
|
5188
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE),
|
|
5189
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS)
|
|
5190
|
+
]);
|
|
5191
|
+
if (!cached || !cachedTs) return;
|
|
5192
|
+
const ts = parseInt(cachedTs, 10);
|
|
5193
|
+
if (isNaN(ts)) return;
|
|
5194
|
+
const age = Date.now() - ts;
|
|
5195
|
+
if (age > this.cacheTtlMs) return;
|
|
5196
|
+
const data = JSON.parse(cached);
|
|
5197
|
+
const expiresAt = ts + this.cacheTtlMs;
|
|
5198
|
+
for (const [name, price] of Object.entries(data)) {
|
|
5199
|
+
if (!this.cache.has(name)) {
|
|
5200
|
+
this.cache.set(name, { price, expiresAt });
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
if (this.debug) {
|
|
5204
|
+
console.log(`[CoinGecko] Loaded ${Object.keys(data).length} prices from persistent cache`);
|
|
5205
|
+
}
|
|
5206
|
+
} catch {
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
/**
|
|
5210
|
+
* Save current prices to StorageProvider (fire-and-forget).
|
|
5211
|
+
*/
|
|
5212
|
+
saveToStorage() {
|
|
5213
|
+
if (!this.storage) return;
|
|
5214
|
+
const data = {};
|
|
5215
|
+
for (const [name, entry] of this.cache) {
|
|
5216
|
+
data[name] = entry.price;
|
|
5217
|
+
}
|
|
5218
|
+
Promise.all([
|
|
5219
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE, JSON.stringify(data)),
|
|
5220
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.PRICE_CACHE_TS, String(Date.now()))
|
|
5221
|
+
]).catch(() => {
|
|
5222
|
+
});
|
|
5223
|
+
}
|
|
5224
|
+
// ===========================================================================
|
|
5225
|
+
// Rate-limit handling
|
|
5226
|
+
// ===========================================================================
|
|
5227
|
+
/**
|
|
5228
|
+
* On 429 rate-limit, extend stale cache entries so subsequent calls
|
|
5229
|
+
* don't immediately retry and hammer the API.
|
|
5230
|
+
*/
|
|
5231
|
+
extendCacheOnRateLimit(names) {
|
|
5232
|
+
const backoffMs = 6e4;
|
|
5233
|
+
const extendedExpiry = Date.now() + backoffMs;
|
|
5234
|
+
for (const name of names) {
|
|
5235
|
+
const existing = this.cache.get(name);
|
|
5236
|
+
if (existing) {
|
|
5237
|
+
existing.expiresAt = Math.max(existing.expiresAt, extendedExpiry);
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
if (this.debug) {
|
|
5241
|
+
console.warn(`[CoinGecko] Rate-limited (429), extended cache TTL by ${backoffMs / 1e3}s`);
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5103
5244
|
async getPrice(tokenName) {
|
|
5104
5245
|
const prices = await this.getPrices([tokenName]);
|
|
5105
5246
|
return prices.get(tokenName) ?? null;
|
|
@@ -5133,6 +5274,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5133
5274
|
refreshTimer = null;
|
|
5134
5275
|
lastRefreshAt = 0;
|
|
5135
5276
|
refreshPromise = null;
|
|
5277
|
+
initialLoadPromise = null;
|
|
5136
5278
|
constructor() {
|
|
5137
5279
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
5138
5280
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
@@ -5171,13 +5313,8 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5171
5313
|
if (options.refreshIntervalMs !== void 0) {
|
|
5172
5314
|
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
5173
5315
|
}
|
|
5174
|
-
if (instance.storage) {
|
|
5175
|
-
instance.loadFromCache();
|
|
5176
|
-
}
|
|
5177
5316
|
const autoRefresh = options.autoRefresh ?? true;
|
|
5178
|
-
|
|
5179
|
-
instance.startAutoRefresh();
|
|
5180
|
-
}
|
|
5317
|
+
instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
|
|
5181
5318
|
}
|
|
5182
5319
|
/**
|
|
5183
5320
|
* Reset the singleton instance (useful for testing).
|
|
@@ -5195,6 +5332,53 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5195
5332
|
static destroy() {
|
|
5196
5333
|
_TokenRegistry.resetInstance();
|
|
5197
5334
|
}
|
|
5335
|
+
/**
|
|
5336
|
+
* Wait for the initial data load (cache or remote) to complete.
|
|
5337
|
+
* Returns true if data was loaded, false if not (timeout or no data source).
|
|
5338
|
+
*
|
|
5339
|
+
* @param timeoutMs - Maximum wait time in ms (default: 10s). Set to 0 for no timeout.
|
|
5340
|
+
*/
|
|
5341
|
+
static async waitForReady(timeoutMs = 1e4) {
|
|
5342
|
+
const instance = _TokenRegistry.getInstance();
|
|
5343
|
+
if (!instance.initialLoadPromise) {
|
|
5344
|
+
return instance.definitionsById.size > 0;
|
|
5345
|
+
}
|
|
5346
|
+
if (timeoutMs <= 0) {
|
|
5347
|
+
return instance.initialLoadPromise;
|
|
5348
|
+
}
|
|
5349
|
+
return Promise.race([
|
|
5350
|
+
instance.initialLoadPromise,
|
|
5351
|
+
new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs))
|
|
5352
|
+
]);
|
|
5353
|
+
}
|
|
5354
|
+
// ===========================================================================
|
|
5355
|
+
// Initial Load
|
|
5356
|
+
// ===========================================================================
|
|
5357
|
+
/**
|
|
5358
|
+
* Perform initial data load: try cache first, fall back to remote fetch.
|
|
5359
|
+
* After initial data is available, start periodic auto-refresh if configured.
|
|
5360
|
+
*/
|
|
5361
|
+
async performInitialLoad(autoRefresh) {
|
|
5362
|
+
let loaded = false;
|
|
5363
|
+
if (this.storage) {
|
|
5364
|
+
loaded = await this.loadFromCache();
|
|
5365
|
+
}
|
|
5366
|
+
if (loaded) {
|
|
5367
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5368
|
+
this.startAutoRefresh();
|
|
5369
|
+
}
|
|
5370
|
+
return true;
|
|
5371
|
+
}
|
|
5372
|
+
if (autoRefresh && this.remoteUrl) {
|
|
5373
|
+
loaded = await this.refreshFromRemote();
|
|
5374
|
+
this.stopAutoRefresh();
|
|
5375
|
+
this.refreshTimer = setInterval(() => {
|
|
5376
|
+
this.refreshFromRemote();
|
|
5377
|
+
}, this.refreshIntervalMs);
|
|
5378
|
+
return loaded;
|
|
5379
|
+
}
|
|
5380
|
+
return false;
|
|
5381
|
+
}
|
|
5198
5382
|
// ===========================================================================
|
|
5199
5383
|
// Cache (StorageProvider)
|
|
5200
5384
|
// ===========================================================================
|
|
@@ -5524,7 +5708,7 @@ function resolveL1Config(network, config) {
|
|
|
5524
5708
|
enableVesting: config.enableVesting
|
|
5525
5709
|
};
|
|
5526
5710
|
}
|
|
5527
|
-
function resolvePriceConfig(config) {
|
|
5711
|
+
function resolvePriceConfig(config, storage) {
|
|
5528
5712
|
if (config === void 0) {
|
|
5529
5713
|
return void 0;
|
|
5530
5714
|
}
|
|
@@ -5534,7 +5718,8 @@ function resolvePriceConfig(config) {
|
|
|
5534
5718
|
baseUrl: config.baseUrl,
|
|
5535
5719
|
cacheTtlMs: config.cacheTtlMs,
|
|
5536
5720
|
timeout: config.timeout,
|
|
5537
|
-
debug: config.debug
|
|
5721
|
+
debug: config.debug,
|
|
5722
|
+
storage
|
|
5538
5723
|
};
|
|
5539
5724
|
}
|
|
5540
5725
|
function resolveArrayConfig(defaults, replace, additional) {
|
|
@@ -5561,16 +5746,6 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5561
5746
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5562
5747
|
};
|
|
5563
5748
|
}
|
|
5564
|
-
function resolveMarketConfig(config) {
|
|
5565
|
-
if (!config) return void 0;
|
|
5566
|
-
if (config === true) {
|
|
5567
|
-
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5568
|
-
}
|
|
5569
|
-
return {
|
|
5570
|
-
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5571
|
-
timeout: config.timeout
|
|
5572
|
-
};
|
|
5573
|
-
}
|
|
5574
5749
|
|
|
5575
5750
|
// impl/browser/index.ts
|
|
5576
5751
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5628,8 +5803,8 @@ function createBrowserProviders(config) {
|
|
|
5628
5803
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5629
5804
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5630
5805
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5631
|
-
const priceConfig = resolvePriceConfig(config?.price);
|
|
5632
5806
|
const storage = createLocalStorageProvider(config?.storage);
|
|
5807
|
+
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5633
5808
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5634
5809
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
5635
5810
|
gateways: ipfsConfig.gateways,
|
|
@@ -5639,11 +5814,9 @@ function createBrowserProviders(config) {
|
|
|
5639
5814
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5640
5815
|
const networkConfig = getNetworkConfig(network);
|
|
5641
5816
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5642
|
-
const market = resolveMarketConfig(config?.market);
|
|
5643
5817
|
return {
|
|
5644
5818
|
storage,
|
|
5645
5819
|
groupChat,
|
|
5646
|
-
market,
|
|
5647
5820
|
transport: createNostrTransportProvider({
|
|
5648
5821
|
relays: transportConfig.relays,
|
|
5649
5822
|
timeout: transportConfig.timeout,
|