@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
|
@@ -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 {
|
|
@@ -3042,6 +3045,7 @@ async function loadIpnsModule() {
|
|
|
3042
3045
|
async function createSignedRecord(keyPair, cid, sequenceNumber, lifetimeMs = DEFAULT_LIFETIME_MS) {
|
|
3043
3046
|
const { createIPNSRecord, marshalIPNSRecord } = await loadIpnsModule();
|
|
3044
3047
|
const record = await createIPNSRecord(
|
|
3048
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3045
3049
|
keyPair,
|
|
3046
3050
|
`/ipfs/${cid}`,
|
|
3047
3051
|
sequenceNumber,
|
|
@@ -4783,26 +4787,37 @@ var CoinGeckoPriceProvider = class {
|
|
|
4783
4787
|
timeout;
|
|
4784
4788
|
debug;
|
|
4785
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;
|
|
4786
4799
|
constructor(config) {
|
|
4787
4800
|
this.apiKey = config?.apiKey;
|
|
4788
4801
|
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
4789
4802
|
this.timeout = config?.timeout ?? 1e4;
|
|
4790
4803
|
this.debug = config?.debug ?? false;
|
|
4804
|
+
this.storage = config?.storage ?? null;
|
|
4791
4805
|
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
4792
4806
|
}
|
|
4793
4807
|
async getPrices(tokenNames) {
|
|
4794
4808
|
if (tokenNames.length === 0) {
|
|
4795
4809
|
return /* @__PURE__ */ new Map();
|
|
4796
4810
|
}
|
|
4811
|
+
if (!this.persistentCacheLoaded && this.storage) {
|
|
4812
|
+
await this.loadFromStorage();
|
|
4813
|
+
}
|
|
4797
4814
|
const now = Date.now();
|
|
4798
4815
|
const result = /* @__PURE__ */ new Map();
|
|
4799
4816
|
const uncachedNames = [];
|
|
4800
4817
|
for (const name of tokenNames) {
|
|
4801
4818
|
const cached = this.cache.get(name);
|
|
4802
4819
|
if (cached && cached.expiresAt > now) {
|
|
4803
|
-
|
|
4804
|
-
result.set(name, cached.price);
|
|
4805
|
-
}
|
|
4820
|
+
result.set(name, cached.price);
|
|
4806
4821
|
} else {
|
|
4807
4822
|
uncachedNames.push(name);
|
|
4808
4823
|
}
|
|
@@ -4810,6 +4825,41 @@ var CoinGeckoPriceProvider = class {
|
|
|
4810
4825
|
if (uncachedNames.length === 0) {
|
|
4811
4826
|
return result;
|
|
4812
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();
|
|
4813
4863
|
try {
|
|
4814
4864
|
const ids = uncachedNames.join(",");
|
|
4815
4865
|
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
@@ -4825,6 +4875,9 @@ var CoinGeckoPriceProvider = class {
|
|
|
4825
4875
|
signal: AbortSignal.timeout(this.timeout)
|
|
4826
4876
|
});
|
|
4827
4877
|
if (!response.ok) {
|
|
4878
|
+
if (response.status === 429) {
|
|
4879
|
+
this.extendCacheOnRateLimit(uncachedNames);
|
|
4880
|
+
}
|
|
4828
4881
|
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
4829
4882
|
}
|
|
4830
4883
|
const data = await response.json();
|
|
@@ -4843,25 +4896,113 @@ var CoinGeckoPriceProvider = class {
|
|
|
4843
4896
|
}
|
|
4844
4897
|
for (const name of uncachedNames) {
|
|
4845
4898
|
if (!result.has(name)) {
|
|
4846
|
-
|
|
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);
|
|
4847
4908
|
}
|
|
4848
4909
|
}
|
|
4849
4910
|
if (this.debug) {
|
|
4850
4911
|
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
4851
4912
|
}
|
|
4913
|
+
this.saveToStorage();
|
|
4852
4914
|
} catch (error) {
|
|
4853
4915
|
if (this.debug) {
|
|
4854
4916
|
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
4855
4917
|
}
|
|
4856
4918
|
for (const name of uncachedNames) {
|
|
4857
4919
|
const stale = this.cache.get(name);
|
|
4858
|
-
if (stale
|
|
4920
|
+
if (stale) {
|
|
4859
4921
|
result.set(name, stale.price);
|
|
4860
4922
|
}
|
|
4861
4923
|
}
|
|
4862
4924
|
}
|
|
4863
4925
|
return result;
|
|
4864
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
|
+
}
|
|
4865
5006
|
async getPrice(tokenName) {
|
|
4866
5007
|
const prices = await this.getPrices([tokenName]);
|
|
4867
5008
|
return prices.get(tokenName) ?? null;
|
|
@@ -4895,6 +5036,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
4895
5036
|
refreshTimer = null;
|
|
4896
5037
|
lastRefreshAt = 0;
|
|
4897
5038
|
refreshPromise = null;
|
|
5039
|
+
initialLoadPromise = null;
|
|
4898
5040
|
constructor() {
|
|
4899
5041
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
4900
5042
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
@@ -4933,13 +5075,8 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
4933
5075
|
if (options.refreshIntervalMs !== void 0) {
|
|
4934
5076
|
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
4935
5077
|
}
|
|
4936
|
-
if (instance.storage) {
|
|
4937
|
-
instance.loadFromCache();
|
|
4938
|
-
}
|
|
4939
5078
|
const autoRefresh = options.autoRefresh ?? true;
|
|
4940
|
-
|
|
4941
|
-
instance.startAutoRefresh();
|
|
4942
|
-
}
|
|
5079
|
+
instance.initialLoadPromise = instance.performInitialLoad(autoRefresh);
|
|
4943
5080
|
}
|
|
4944
5081
|
/**
|
|
4945
5082
|
* Reset the singleton instance (useful for testing).
|
|
@@ -4957,6 +5094,53 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
4957
5094
|
static destroy() {
|
|
4958
5095
|
_TokenRegistry.resetInstance();
|
|
4959
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
|
+
}
|
|
4960
5144
|
// ===========================================================================
|
|
4961
5145
|
// Cache (StorageProvider)
|
|
4962
5146
|
// ===========================================================================
|
|
@@ -5286,7 +5470,7 @@ function resolveL1Config(network, config) {
|
|
|
5286
5470
|
enableVesting: config.enableVesting
|
|
5287
5471
|
};
|
|
5288
5472
|
}
|
|
5289
|
-
function resolvePriceConfig(config) {
|
|
5473
|
+
function resolvePriceConfig(config, storage) {
|
|
5290
5474
|
if (config === void 0) {
|
|
5291
5475
|
return void 0;
|
|
5292
5476
|
}
|
|
@@ -5296,7 +5480,8 @@ function resolvePriceConfig(config) {
|
|
|
5296
5480
|
baseUrl: config.baseUrl,
|
|
5297
5481
|
cacheTtlMs: config.cacheTtlMs,
|
|
5298
5482
|
timeout: config.timeout,
|
|
5299
|
-
debug: config.debug
|
|
5483
|
+
debug: config.debug,
|
|
5484
|
+
storage
|
|
5300
5485
|
};
|
|
5301
5486
|
}
|
|
5302
5487
|
function resolveGroupChatConfig(network, config) {
|
|
@@ -5313,16 +5498,6 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5313
5498
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5314
5499
|
};
|
|
5315
5500
|
}
|
|
5316
|
-
function resolveMarketConfig(config) {
|
|
5317
|
-
if (!config) return void 0;
|
|
5318
|
-
if (config === true) {
|
|
5319
|
-
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5320
|
-
}
|
|
5321
|
-
return {
|
|
5322
|
-
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5323
|
-
timeout: config.timeout
|
|
5324
|
-
};
|
|
5325
|
-
}
|
|
5326
5501
|
|
|
5327
5502
|
// impl/nodejs/index.ts
|
|
5328
5503
|
function createNodeProviders(config) {
|
|
@@ -5330,21 +5505,19 @@ function createNodeProviders(config) {
|
|
|
5330
5505
|
const transportConfig = resolveTransportConfig(network, config?.transport);
|
|
5331
5506
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5332
5507
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5333
|
-
const priceConfig = resolvePriceConfig(config?.price);
|
|
5334
5508
|
const storage = createFileStorageProvider({
|
|
5335
5509
|
dataDir: config?.dataDir ?? "./sphere-data",
|
|
5336
5510
|
...config?.walletFileName ? { fileName: config.walletFileName } : {}
|
|
5337
5511
|
});
|
|
5512
|
+
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5338
5513
|
const ipfsSync = config?.tokenSync?.ipfs;
|
|
5339
5514
|
const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
|
|
5340
5515
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5341
5516
|
const networkConfig = getNetworkConfig(network);
|
|
5342
5517
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5343
|
-
const market = resolveMarketConfig(config?.market);
|
|
5344
5518
|
return {
|
|
5345
5519
|
storage,
|
|
5346
5520
|
groupChat,
|
|
5347
|
-
market,
|
|
5348
5521
|
tokenStorage: createFileTokenStorageProvider({
|
|
5349
5522
|
tokensDir: config?.tokensDir ?? "./sphere-tokens"
|
|
5350
5523
|
}),
|