@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
|
@@ -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 {
|
|
@@ -3228,6 +3231,7 @@ async function loadIpnsModule() {
|
|
|
3228
3231
|
async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
|
|
3229
3232
|
const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
|
|
3230
3233
|
const record = await createIPNSRecord(
|
|
3234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3231
3235
|
keyPair,
|
|
3232
3236
|
`/ipfs/${cid}`,
|
|
3233
3237
|
sequenceNumber,
|
|
@@ -4972,26 +4976,37 @@ var CoinGeckoPriceProvider = class {
|
|
|
4972
4976
|
timeout;
|
|
4973
4977
|
debug;
|
|
4974
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;
|
|
4975
4988
|
constructor(config) {
|
|
4976
4989
|
this.apiKey = config?.apiKey;
|
|
4977
4990
|
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
4978
4991
|
this.timeout = config?.timeout ?? 1e4;
|
|
4979
4992
|
this.debug = config?.debug ?? false;
|
|
4993
|
+
this.storage = config?.storage ?? null;
|
|
4980
4994
|
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
4981
4995
|
}
|
|
4982
4996
|
async getPrices(tokenNames) {
|
|
4983
4997
|
if (tokenNames.length === 0) {
|
|
4984
4998
|
return /* @__PURE__ */ new Map();
|
|
4985
4999
|
}
|
|
5000
|
+
if (!this.persistentCacheLoaded && this.storage) {
|
|
5001
|
+
await this.loadFromStorage();
|
|
5002
|
+
}
|
|
4986
5003
|
const now = Date.now();
|
|
4987
5004
|
const result = /* @__PURE__ */ new Map();
|
|
4988
5005
|
const uncachedNames = [];
|
|
4989
5006
|
for (const name of tokenNames) {
|
|
4990
5007
|
const cached = this.cache.get(name);
|
|
4991
5008
|
if (cached && cached.expiresAt > now) {
|
|
4992
|
-
|
|
4993
|
-
result.set(name, cached.price);
|
|
4994
|
-
}
|
|
5009
|
+
result.set(name, cached.price);
|
|
4995
5010
|
} else {
|
|
4996
5011
|
uncachedNames.push(name);
|
|
4997
5012
|
}
|
|
@@ -4999,6 +5014,41 @@ var CoinGeckoPriceProvider = class {
|
|
|
4999
5014
|
if (uncachedNames.length === 0) {
|
|
5000
5015
|
return result;
|
|
5001
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();
|
|
5002
5052
|
try {
|
|
5003
5053
|
const ids = uncachedNames.join(",");
|
|
5004
5054
|
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
@@ -5014,6 +5064,9 @@ var CoinGeckoPriceProvider = class {
|
|
|
5014
5064
|
signal: AbortSignal.timeout(this.timeout)
|
|
5015
5065
|
});
|
|
5016
5066
|
if (!response.ok) {
|
|
5067
|
+
if (response.status === 429) {
|
|
5068
|
+
this.extendCacheOnRateLimit(uncachedNames);
|
|
5069
|
+
}
|
|
5017
5070
|
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
5018
5071
|
}
|
|
5019
5072
|
const data = await response.json();
|
|
@@ -5032,25 +5085,113 @@ var CoinGeckoPriceProvider = class {
|
|
|
5032
5085
|
}
|
|
5033
5086
|
for (const name of uncachedNames) {
|
|
5034
5087
|
if (!result.has(name)) {
|
|
5035
|
-
|
|
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);
|
|
5036
5097
|
}
|
|
5037
5098
|
}
|
|
5038
5099
|
if (this.debug) {
|
|
5039
5100
|
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
5040
5101
|
}
|
|
5102
|
+
this.saveToStorage();
|
|
5041
5103
|
} catch (error) {
|
|
5042
5104
|
if (this.debug) {
|
|
5043
5105
|
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
5044
5106
|
}
|
|
5045
5107
|
for (const name of uncachedNames) {
|
|
5046
5108
|
const stale = this.cache.get(name);
|
|
5047
|
-
if (stale
|
|
5109
|
+
if (stale) {
|
|
5048
5110
|
result.set(name, stale.price);
|
|
5049
5111
|
}
|
|
5050
5112
|
}
|
|
5051
5113
|
}
|
|
5052
5114
|
return result;
|
|
5053
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
|
+
}
|
|
5054
5195
|
async getPrice(tokenName) {
|
|
5055
5196
|
const prices = await this.getPrices([tokenName]);
|
|
5056
5197
|
return prices.get(tokenName) ?? null;
|
|
@@ -5084,6 +5225,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5084
5225
|
refreshTimer = null;
|
|
5085
5226
|
lastRefreshAt = 0;
|
|
5086
5227
|
refreshPromise = null;
|
|
5228
|
+
initialLoadPromise = null;
|
|
5087
5229
|
constructor() {
|
|
5088
5230
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
5089
5231
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
@@ -5122,13 +5264,8 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5122
5264
|
if (options.refreshIntervalMs !== void 0) {
|
|
5123
5265
|
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
5124
5266
|
}
|
|
5125
|
-
if (instance.storage) {
|
|
5126
|
-
instance.loadFromCache();
|
|
5127
|
-
}
|
|
5128
5267
|
const autoRefresh = options.autoRefresh ?? true;
|
|
5129
|
-
|
|
5130
|
-
instance.startAutoRefresh();
|
|
5131
|
-
}
|
|
5268
|
+
instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
|
|
5132
5269
|
}
|
|
5133
5270
|
/**
|
|
5134
5271
|
* Reset the singleton instance (useful for testing).
|
|
@@ -5146,6 +5283,53 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
5146
5283
|
static destroy() {
|
|
5147
5284
|
_TokenRegistry.resetInstance();
|
|
5148
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
|
+
}
|
|
5149
5333
|
// ===========================================================================
|
|
5150
5334
|
// Cache (StorageProvider)
|
|
5151
5335
|
// ===========================================================================
|
|
@@ -5475,7 +5659,7 @@ function resolveL1Config(network, config) {
|
|
|
5475
5659
|
enableVesting: config.enableVesting
|
|
5476
5660
|
};
|
|
5477
5661
|
}
|
|
5478
|
-
function resolvePriceConfig(config) {
|
|
5662
|
+
function resolvePriceConfig(config, storage) {
|
|
5479
5663
|
if (config === void 0) {
|
|
5480
5664
|
return void 0;
|
|
5481
5665
|
}
|
|
@@ -5485,7 +5669,8 @@ function resolvePriceConfig(config) {
|
|
|
5485
5669
|
baseUrl: config.baseUrl,
|
|
5486
5670
|
cacheTtlMs: config.cacheTtlMs,
|
|
5487
5671
|
timeout: config.timeout,
|
|
5488
|
-
debug: config.debug
|
|
5672
|
+
debug: config.debug,
|
|
5673
|
+
storage
|
|
5489
5674
|
};
|
|
5490
5675
|
}
|
|
5491
5676
|
function resolveArrayConfig(defaults, replace, additional) {
|
|
@@ -5512,16 +5697,6 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5512
5697
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5513
5698
|
};
|
|
5514
5699
|
}
|
|
5515
|
-
function resolveMarketConfig(config) {
|
|
5516
|
-
if (!config) return void 0;
|
|
5517
|
-
if (config === true) {
|
|
5518
|
-
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5519
|
-
}
|
|
5520
|
-
return {
|
|
5521
|
-
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5522
|
-
timeout: config.timeout
|
|
5523
|
-
};
|
|
5524
|
-
}
|
|
5525
5700
|
|
|
5526
5701
|
// impl/browser/index.ts
|
|
5527
5702
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5579,8 +5754,8 @@ function createBrowserProviders(config) {
|
|
|
5579
5754
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5580
5755
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5581
5756
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5582
|
-
const priceConfig = resolvePriceConfig(config?.price);
|
|
5583
5757
|
const storage = createLocalStorageProvider(config?.storage);
|
|
5758
|
+
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5584
5759
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5585
5760
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
5586
5761
|
gateways: ipfsConfig.gateways,
|
|
@@ -5590,11 +5765,9 @@ function createBrowserProviders(config) {
|
|
|
5590
5765
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5591
5766
|
const networkConfig = getNetworkConfig(network);
|
|
5592
5767
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5593
|
-
const market = resolveMarketConfig(config?.market);
|
|
5594
5768
|
return {
|
|
5595
5769
|
storage,
|
|
5596
5770
|
groupChat,
|
|
5597
|
-
market,
|
|
5598
5771
|
transport: createNostrTransportProvider({
|
|
5599
5772
|
relays: transportConfig.relays,
|
|
5600
5773
|
timeout: transportConfig.timeout,
|