@unicitylabs/sphere-sdk 0.3.4 → 0.3.6
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 +2722 -153
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +169 -0
- package/dist/core/index.d.ts +169 -0
- package/dist/core/index.js +2718 -149
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +385 -4
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +385 -4
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +5 -1
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +5 -1
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +385 -4
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +23 -0
- package/dist/impl/nodejs/index.d.ts +23 -0
- package/dist/impl/nodejs/index.js +385 -4
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +2728 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -6
- package/dist/index.d.ts +270 -6
- package/dist/index.js +2721 -149
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1112,6 +1112,7 @@ declare const NETWORKS: {
|
|
|
1112
1112
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1113
1113
|
readonly electrumUrl: "wss://fulcrum.alpha.unicity.network:50004";
|
|
1114
1114
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1115
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1115
1116
|
};
|
|
1116
1117
|
readonly testnet: {
|
|
1117
1118
|
readonly name: "Testnet";
|
|
@@ -1120,6 +1121,7 @@ declare const NETWORKS: {
|
|
|
1120
1121
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1121
1122
|
readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
1122
1123
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1124
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1123
1125
|
};
|
|
1124
1126
|
readonly dev: {
|
|
1125
1127
|
readonly name: "Development";
|
|
@@ -1128,6 +1130,7 @@ declare const NETWORKS: {
|
|
|
1128
1130
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1129
1131
|
readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
1130
1132
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1133
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1131
1134
|
};
|
|
1132
1135
|
};
|
|
1133
1136
|
type NetworkType = keyof typeof NETWORKS;
|
|
@@ -1312,6 +1315,15 @@ interface BasePriceConfig {
|
|
|
1312
1315
|
/** Enable debug logging */
|
|
1313
1316
|
debug?: boolean;
|
|
1314
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Base market module configuration
|
|
1320
|
+
*/
|
|
1321
|
+
interface BaseMarketConfig {
|
|
1322
|
+
/** Market API base URL (default: https://market-api.unicity.network) */
|
|
1323
|
+
apiUrl?: string;
|
|
1324
|
+
/** Request timeout in ms (default: 30000) */
|
|
1325
|
+
timeout?: number;
|
|
1326
|
+
}
|
|
1315
1327
|
/**
|
|
1316
1328
|
* Base providers result
|
|
1317
1329
|
* Common structure for all platforms
|
|
@@ -1327,6 +1339,13 @@ interface BaseProviders {
|
|
|
1327
1339
|
price?: PriceProvider;
|
|
1328
1340
|
}
|
|
1329
1341
|
|
|
1342
|
+
interface MarketModuleConfig {
|
|
1343
|
+
/** Market API base URL (default: https://market-api.unicity.network) */
|
|
1344
|
+
apiUrl?: string;
|
|
1345
|
+
/** Request timeout in ms (default: 30000) */
|
|
1346
|
+
timeout?: number;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1330
1349
|
/** IPFS storage provider configuration */
|
|
1331
1350
|
interface IpfsStorageConfig {
|
|
1332
1351
|
/** Gateway URLs for HTTP API (defaults to Unicity dedicated nodes) */
|
|
@@ -1419,6 +1438,8 @@ interface NodeProvidersConfig {
|
|
|
1419
1438
|
enabled?: boolean;
|
|
1420
1439
|
relays?: string[];
|
|
1421
1440
|
} | boolean;
|
|
1441
|
+
/** Market module configuration. true = enable with defaults, object = custom config */
|
|
1442
|
+
market?: BaseMarketConfig | boolean;
|
|
1422
1443
|
}
|
|
1423
1444
|
interface NodeProviders {
|
|
1424
1445
|
storage: StorageProvider;
|
|
@@ -1433,6 +1454,8 @@ interface NodeProviders {
|
|
|
1433
1454
|
ipfsTokenStorage?: TokenStorageProvider<TxfStorageDataBase>;
|
|
1434
1455
|
/** Group chat config (resolved, for passing to Sphere.init) */
|
|
1435
1456
|
groupChat?: GroupChatModuleConfig | boolean;
|
|
1457
|
+
/** Market module config (resolved, for passing to Sphere.init) */
|
|
1458
|
+
market?: MarketModuleConfig | boolean;
|
|
1436
1459
|
}
|
|
1437
1460
|
/**
|
|
1438
1461
|
* Create all Node.js providers with default configuration
|
|
@@ -1112,6 +1112,7 @@ declare const NETWORKS: {
|
|
|
1112
1112
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1113
1113
|
readonly electrumUrl: "wss://fulcrum.alpha.unicity.network:50004";
|
|
1114
1114
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1115
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1115
1116
|
};
|
|
1116
1117
|
readonly testnet: {
|
|
1117
1118
|
readonly name: "Testnet";
|
|
@@ -1120,6 +1121,7 @@ declare const NETWORKS: {
|
|
|
1120
1121
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1121
1122
|
readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
1122
1123
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1124
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1123
1125
|
};
|
|
1124
1126
|
readonly dev: {
|
|
1125
1127
|
readonly name: "Development";
|
|
@@ -1128,6 +1130,7 @@ declare const NETWORKS: {
|
|
|
1128
1130
|
readonly ipfsGateways: readonly ["https://unicity-ipfs1.dyndns.org"];
|
|
1129
1131
|
readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
1130
1132
|
readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
|
|
1133
|
+
readonly tokenRegistryUrl: "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
1131
1134
|
};
|
|
1132
1135
|
};
|
|
1133
1136
|
type NetworkType = keyof typeof NETWORKS;
|
|
@@ -1312,6 +1315,15 @@ interface BasePriceConfig {
|
|
|
1312
1315
|
/** Enable debug logging */
|
|
1313
1316
|
debug?: boolean;
|
|
1314
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Base market module configuration
|
|
1320
|
+
*/
|
|
1321
|
+
interface BaseMarketConfig {
|
|
1322
|
+
/** Market API base URL (default: https://market-api.unicity.network) */
|
|
1323
|
+
apiUrl?: string;
|
|
1324
|
+
/** Request timeout in ms (default: 30000) */
|
|
1325
|
+
timeout?: number;
|
|
1326
|
+
}
|
|
1315
1327
|
/**
|
|
1316
1328
|
* Base providers result
|
|
1317
1329
|
* Common structure for all platforms
|
|
@@ -1327,6 +1339,13 @@ interface BaseProviders {
|
|
|
1327
1339
|
price?: PriceProvider;
|
|
1328
1340
|
}
|
|
1329
1341
|
|
|
1342
|
+
interface MarketModuleConfig {
|
|
1343
|
+
/** Market API base URL (default: https://market-api.unicity.network) */
|
|
1344
|
+
apiUrl?: string;
|
|
1345
|
+
/** Request timeout in ms (default: 30000) */
|
|
1346
|
+
timeout?: number;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1330
1349
|
/** IPFS storage provider configuration */
|
|
1331
1350
|
interface IpfsStorageConfig {
|
|
1332
1351
|
/** Gateway URLs for HTTP API (defaults to Unicity dedicated nodes) */
|
|
@@ -1419,6 +1438,8 @@ interface NodeProvidersConfig {
|
|
|
1419
1438
|
enabled?: boolean;
|
|
1420
1439
|
relays?: string[];
|
|
1421
1440
|
} | boolean;
|
|
1441
|
+
/** Market module configuration. true = enable with defaults, object = custom config */
|
|
1442
|
+
market?: BaseMarketConfig | boolean;
|
|
1422
1443
|
}
|
|
1423
1444
|
interface NodeProviders {
|
|
1424
1445
|
storage: StorageProvider;
|
|
@@ -1433,6 +1454,8 @@ interface NodeProviders {
|
|
|
1433
1454
|
ipfsTokenStorage?: TokenStorageProvider<TxfStorageDataBase>;
|
|
1434
1455
|
/** Group chat config (resolved, for passing to Sphere.init) */
|
|
1435
1456
|
groupChat?: GroupChatModuleConfig | boolean;
|
|
1457
|
+
/** Market module config (resolved, for passing to Sphere.init) */
|
|
1458
|
+
market?: MarketModuleConfig | boolean;
|
|
1436
1459
|
}
|
|
1437
1460
|
/**
|
|
1438
1461
|
* Create all Node.js providers with default configuration
|
|
@@ -37,7 +37,11 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
37
37
|
/** Group chat: processed event IDs for deduplication */
|
|
38
38
|
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
|
|
39
39
|
/** Group chat: last used relay URL (stale data detection) */
|
|
40
|
-
GROUP_CHAT_RELAY_URL: "group_chat_relay_url"
|
|
40
|
+
GROUP_CHAT_RELAY_URL: "group_chat_relay_url",
|
|
41
|
+
/** Cached token registry JSON (fetched from remote) */
|
|
42
|
+
TOKEN_REGISTRY_CACHE: "token_registry_cache",
|
|
43
|
+
/** Timestamp of last token registry cache update (ms since epoch) */
|
|
44
|
+
TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts"
|
|
41
45
|
};
|
|
42
46
|
var STORAGE_KEYS_ADDRESS = {
|
|
43
47
|
/** Pending transfers for this address */
|
|
@@ -113,6 +117,8 @@ var DEFAULT_BASE_PATH = "m/44'/0'/0'";
|
|
|
113
117
|
var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
|
|
114
118
|
var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
|
|
115
119
|
var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
|
|
120
|
+
var TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
|
|
121
|
+
var TOKEN_REGISTRY_REFRESH_INTERVAL = 36e5;
|
|
116
122
|
var TEST_NOSTR_RELAYS = [
|
|
117
123
|
"wss://nostr-relay.testnet.unicity.network"
|
|
118
124
|
];
|
|
@@ -126,7 +132,8 @@ var NETWORKS = {
|
|
|
126
132
|
nostrRelays: DEFAULT_NOSTR_RELAYS,
|
|
127
133
|
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
128
134
|
electrumUrl: DEFAULT_ELECTRUM_URL,
|
|
129
|
-
groupRelays: DEFAULT_GROUP_RELAYS
|
|
135
|
+
groupRelays: DEFAULT_GROUP_RELAYS,
|
|
136
|
+
tokenRegistryUrl: TOKEN_REGISTRY_URL
|
|
130
137
|
},
|
|
131
138
|
testnet: {
|
|
132
139
|
name: "Testnet",
|
|
@@ -134,7 +141,8 @@ var NETWORKS = {
|
|
|
134
141
|
nostrRelays: TEST_NOSTR_RELAYS,
|
|
135
142
|
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
136
143
|
electrumUrl: TEST_ELECTRUM_URL,
|
|
137
|
-
groupRelays: DEFAULT_GROUP_RELAYS
|
|
144
|
+
groupRelays: DEFAULT_GROUP_RELAYS,
|
|
145
|
+
tokenRegistryUrl: TOKEN_REGISTRY_URL
|
|
138
146
|
},
|
|
139
147
|
dev: {
|
|
140
148
|
name: "Development",
|
|
@@ -142,7 +150,8 @@ var NETWORKS = {
|
|
|
142
150
|
nostrRelays: TEST_NOSTR_RELAYS,
|
|
143
151
|
ipfsGateways: DEFAULT_IPFS_GATEWAYS,
|
|
144
152
|
electrumUrl: TEST_ELECTRUM_URL,
|
|
145
|
-
groupRelays: DEFAULT_GROUP_RELAYS
|
|
153
|
+
groupRelays: DEFAULT_GROUP_RELAYS,
|
|
154
|
+
tokenRegistryUrl: TOKEN_REGISTRY_URL
|
|
146
155
|
}
|
|
147
156
|
};
|
|
148
157
|
var TIMEOUTS = {
|
|
@@ -157,6 +166,7 @@ var TIMEOUTS = {
|
|
|
157
166
|
/** Sync interval */
|
|
158
167
|
SYNC_INTERVAL: 6e4
|
|
159
168
|
};
|
|
169
|
+
var DEFAULT_MARKET_API_URL = "https://market-api.unicity.network";
|
|
160
170
|
|
|
161
171
|
// impl/nodejs/storage/FileStorageProvider.ts
|
|
162
172
|
var FileStorageProvider = class {
|
|
@@ -4831,6 +4841,363 @@ function createPriceProvider(config) {
|
|
|
4831
4841
|
}
|
|
4832
4842
|
}
|
|
4833
4843
|
|
|
4844
|
+
// registry/TokenRegistry.ts
|
|
4845
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
4846
|
+
var TokenRegistry = class _TokenRegistry {
|
|
4847
|
+
static instance = null;
|
|
4848
|
+
definitionsById;
|
|
4849
|
+
definitionsBySymbol;
|
|
4850
|
+
definitionsByName;
|
|
4851
|
+
// Remote refresh state
|
|
4852
|
+
remoteUrl = null;
|
|
4853
|
+
storage = null;
|
|
4854
|
+
refreshIntervalMs = TOKEN_REGISTRY_REFRESH_INTERVAL;
|
|
4855
|
+
refreshTimer = null;
|
|
4856
|
+
lastRefreshAt = 0;
|
|
4857
|
+
refreshPromise = null;
|
|
4858
|
+
constructor() {
|
|
4859
|
+
this.definitionsById = /* @__PURE__ */ new Map();
|
|
4860
|
+
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
4861
|
+
this.definitionsByName = /* @__PURE__ */ new Map();
|
|
4862
|
+
}
|
|
4863
|
+
/**
|
|
4864
|
+
* Get singleton instance of TokenRegistry
|
|
4865
|
+
*/
|
|
4866
|
+
static getInstance() {
|
|
4867
|
+
if (!_TokenRegistry.instance) {
|
|
4868
|
+
_TokenRegistry.instance = new _TokenRegistry();
|
|
4869
|
+
}
|
|
4870
|
+
return _TokenRegistry.instance;
|
|
4871
|
+
}
|
|
4872
|
+
/**
|
|
4873
|
+
* Configure remote registry refresh with persistent caching.
|
|
4874
|
+
*
|
|
4875
|
+
* On first call:
|
|
4876
|
+
* 1. Loads cached data from StorageProvider (if available and fresh)
|
|
4877
|
+
* 2. Starts periodic remote fetch (if autoRefresh is true, which is default)
|
|
4878
|
+
*
|
|
4879
|
+
* @param options - Configuration options
|
|
4880
|
+
* @param options.remoteUrl - Remote URL to fetch definitions from
|
|
4881
|
+
* @param options.storage - StorageProvider for persistent caching
|
|
4882
|
+
* @param options.refreshIntervalMs - Refresh interval in ms (default: 1 hour)
|
|
4883
|
+
* @param options.autoRefresh - Start auto-refresh immediately (default: true)
|
|
4884
|
+
*/
|
|
4885
|
+
static configure(options) {
|
|
4886
|
+
const instance = _TokenRegistry.getInstance();
|
|
4887
|
+
if (options.remoteUrl !== void 0) {
|
|
4888
|
+
instance.remoteUrl = options.remoteUrl;
|
|
4889
|
+
}
|
|
4890
|
+
if (options.storage !== void 0) {
|
|
4891
|
+
instance.storage = options.storage;
|
|
4892
|
+
}
|
|
4893
|
+
if (options.refreshIntervalMs !== void 0) {
|
|
4894
|
+
instance.refreshIntervalMs = options.refreshIntervalMs;
|
|
4895
|
+
}
|
|
4896
|
+
if (instance.storage) {
|
|
4897
|
+
instance.loadFromCache();
|
|
4898
|
+
}
|
|
4899
|
+
const autoRefresh = options.autoRefresh ?? true;
|
|
4900
|
+
if (autoRefresh && instance.remoteUrl) {
|
|
4901
|
+
instance.startAutoRefresh();
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
/**
|
|
4905
|
+
* Reset the singleton instance (useful for testing).
|
|
4906
|
+
* Stops auto-refresh if running.
|
|
4907
|
+
*/
|
|
4908
|
+
static resetInstance() {
|
|
4909
|
+
if (_TokenRegistry.instance) {
|
|
4910
|
+
_TokenRegistry.instance.stopAutoRefresh();
|
|
4911
|
+
}
|
|
4912
|
+
_TokenRegistry.instance = null;
|
|
4913
|
+
}
|
|
4914
|
+
/**
|
|
4915
|
+
* Destroy the singleton: stop auto-refresh and reset.
|
|
4916
|
+
*/
|
|
4917
|
+
static destroy() {
|
|
4918
|
+
_TokenRegistry.resetInstance();
|
|
4919
|
+
}
|
|
4920
|
+
// ===========================================================================
|
|
4921
|
+
// Cache (StorageProvider)
|
|
4922
|
+
// ===========================================================================
|
|
4923
|
+
/**
|
|
4924
|
+
* Load definitions from StorageProvider cache.
|
|
4925
|
+
* Only applies if cache exists and is fresh (within refreshIntervalMs).
|
|
4926
|
+
*/
|
|
4927
|
+
async loadFromCache() {
|
|
4928
|
+
if (!this.storage) return false;
|
|
4929
|
+
try {
|
|
4930
|
+
const [cached, cachedTs] = await Promise.all([
|
|
4931
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE),
|
|
4932
|
+
this.storage.get(STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE_TS)
|
|
4933
|
+
]);
|
|
4934
|
+
if (!cached || !cachedTs) return false;
|
|
4935
|
+
const ts = parseInt(cachedTs, 10);
|
|
4936
|
+
if (isNaN(ts)) return false;
|
|
4937
|
+
const age = Date.now() - ts;
|
|
4938
|
+
if (age > this.refreshIntervalMs) return false;
|
|
4939
|
+
if (this.lastRefreshAt > ts) return false;
|
|
4940
|
+
const data = JSON.parse(cached);
|
|
4941
|
+
if (!this.isValidDefinitionsArray(data)) return false;
|
|
4942
|
+
this.applyDefinitions(data);
|
|
4943
|
+
this.lastRefreshAt = ts;
|
|
4944
|
+
return true;
|
|
4945
|
+
} catch {
|
|
4946
|
+
return false;
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
/**
|
|
4950
|
+
* Save definitions to StorageProvider cache.
|
|
4951
|
+
*/
|
|
4952
|
+
async saveToCache(definitions) {
|
|
4953
|
+
if (!this.storage) return;
|
|
4954
|
+
try {
|
|
4955
|
+
await Promise.all([
|
|
4956
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE, JSON.stringify(definitions)),
|
|
4957
|
+
this.storage.set(STORAGE_KEYS_GLOBAL.TOKEN_REGISTRY_CACHE_TS, String(Date.now()))
|
|
4958
|
+
]);
|
|
4959
|
+
} catch {
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
// ===========================================================================
|
|
4963
|
+
// Remote Refresh
|
|
4964
|
+
// ===========================================================================
|
|
4965
|
+
/**
|
|
4966
|
+
* Apply an array of token definitions to the internal maps.
|
|
4967
|
+
* Clears existing data before applying.
|
|
4968
|
+
*/
|
|
4969
|
+
applyDefinitions(definitions) {
|
|
4970
|
+
this.definitionsById.clear();
|
|
4971
|
+
this.definitionsBySymbol.clear();
|
|
4972
|
+
this.definitionsByName.clear();
|
|
4973
|
+
for (const def of definitions) {
|
|
4974
|
+
const idLower = def.id.toLowerCase();
|
|
4975
|
+
this.definitionsById.set(idLower, def);
|
|
4976
|
+
if (def.symbol) {
|
|
4977
|
+
this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
|
|
4978
|
+
}
|
|
4979
|
+
this.definitionsByName.set(def.name.toLowerCase(), def);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
/**
|
|
4983
|
+
* Validate that data is an array of objects with 'id' field
|
|
4984
|
+
*/
|
|
4985
|
+
isValidDefinitionsArray(data) {
|
|
4986
|
+
return Array.isArray(data) && data.every((item) => item && typeof item === "object" && "id" in item);
|
|
4987
|
+
}
|
|
4988
|
+
/**
|
|
4989
|
+
* Fetch token definitions from the remote URL and update the registry.
|
|
4990
|
+
* On success, also persists to StorageProvider cache.
|
|
4991
|
+
* Returns true on success, false on failure. On failure, existing data is preserved.
|
|
4992
|
+
* Concurrent calls are deduplicated — only one fetch runs at a time.
|
|
4993
|
+
*/
|
|
4994
|
+
async refreshFromRemote() {
|
|
4995
|
+
if (!this.remoteUrl) {
|
|
4996
|
+
return false;
|
|
4997
|
+
}
|
|
4998
|
+
if (this.refreshPromise) {
|
|
4999
|
+
return this.refreshPromise;
|
|
5000
|
+
}
|
|
5001
|
+
this.refreshPromise = this.doRefresh();
|
|
5002
|
+
try {
|
|
5003
|
+
return await this.refreshPromise;
|
|
5004
|
+
} finally {
|
|
5005
|
+
this.refreshPromise = null;
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
async doRefresh() {
|
|
5009
|
+
try {
|
|
5010
|
+
const controller = new AbortController();
|
|
5011
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
5012
|
+
let response;
|
|
5013
|
+
try {
|
|
5014
|
+
response = await fetch(this.remoteUrl, {
|
|
5015
|
+
headers: { Accept: "application/json" },
|
|
5016
|
+
signal: controller.signal
|
|
5017
|
+
});
|
|
5018
|
+
} finally {
|
|
5019
|
+
clearTimeout(timer);
|
|
5020
|
+
}
|
|
5021
|
+
if (!response.ok) {
|
|
5022
|
+
console.warn(
|
|
5023
|
+
`[TokenRegistry] Remote fetch failed: HTTP ${response.status} ${response.statusText}`
|
|
5024
|
+
);
|
|
5025
|
+
return false;
|
|
5026
|
+
}
|
|
5027
|
+
const data = await response.json();
|
|
5028
|
+
if (!this.isValidDefinitionsArray(data)) {
|
|
5029
|
+
console.warn("[TokenRegistry] Remote data is not a valid token definitions array");
|
|
5030
|
+
return false;
|
|
5031
|
+
}
|
|
5032
|
+
const definitions = data;
|
|
5033
|
+
this.applyDefinitions(definitions);
|
|
5034
|
+
this.lastRefreshAt = Date.now();
|
|
5035
|
+
this.saveToCache(definitions);
|
|
5036
|
+
return true;
|
|
5037
|
+
} catch (error) {
|
|
5038
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5039
|
+
console.warn(`[TokenRegistry] Remote refresh failed: ${message}`);
|
|
5040
|
+
return false;
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Start periodic auto-refresh from the remote URL.
|
|
5045
|
+
* Does an immediate fetch, then repeats at the configured interval.
|
|
5046
|
+
*/
|
|
5047
|
+
startAutoRefresh(intervalMs) {
|
|
5048
|
+
this.stopAutoRefresh();
|
|
5049
|
+
if (intervalMs !== void 0) {
|
|
5050
|
+
this.refreshIntervalMs = intervalMs;
|
|
5051
|
+
}
|
|
5052
|
+
this.refreshFromRemote();
|
|
5053
|
+
this.refreshTimer = setInterval(() => {
|
|
5054
|
+
this.refreshFromRemote();
|
|
5055
|
+
}, this.refreshIntervalMs);
|
|
5056
|
+
}
|
|
5057
|
+
/**
|
|
5058
|
+
* Stop periodic auto-refresh
|
|
5059
|
+
*/
|
|
5060
|
+
stopAutoRefresh() {
|
|
5061
|
+
if (this.refreshTimer !== null) {
|
|
5062
|
+
clearInterval(this.refreshTimer);
|
|
5063
|
+
this.refreshTimer = null;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Timestamp of the last successful remote refresh (0 if never refreshed)
|
|
5068
|
+
*/
|
|
5069
|
+
getLastRefreshAt() {
|
|
5070
|
+
return this.lastRefreshAt;
|
|
5071
|
+
}
|
|
5072
|
+
// ===========================================================================
|
|
5073
|
+
// Lookup Methods
|
|
5074
|
+
// ===========================================================================
|
|
5075
|
+
/**
|
|
5076
|
+
* Get token definition by hex coin ID
|
|
5077
|
+
* @param coinId - 64-character hex string
|
|
5078
|
+
* @returns Token definition or undefined if not found
|
|
5079
|
+
*/
|
|
5080
|
+
getDefinition(coinId) {
|
|
5081
|
+
if (!coinId) return void 0;
|
|
5082
|
+
return this.definitionsById.get(coinId.toLowerCase());
|
|
5083
|
+
}
|
|
5084
|
+
/**
|
|
5085
|
+
* Get token definition by symbol (e.g., "UCT", "BTC")
|
|
5086
|
+
* @param symbol - Token symbol (case-insensitive)
|
|
5087
|
+
* @returns Token definition or undefined if not found
|
|
5088
|
+
*/
|
|
5089
|
+
getDefinitionBySymbol(symbol) {
|
|
5090
|
+
if (!symbol) return void 0;
|
|
5091
|
+
return this.definitionsBySymbol.get(symbol.toUpperCase());
|
|
5092
|
+
}
|
|
5093
|
+
/**
|
|
5094
|
+
* Get token definition by name (e.g., "bitcoin", "ethereum")
|
|
5095
|
+
* @param name - Token name (case-insensitive)
|
|
5096
|
+
* @returns Token definition or undefined if not found
|
|
5097
|
+
*/
|
|
5098
|
+
getDefinitionByName(name) {
|
|
5099
|
+
if (!name) return void 0;
|
|
5100
|
+
return this.definitionsByName.get(name.toLowerCase());
|
|
5101
|
+
}
|
|
5102
|
+
/**
|
|
5103
|
+
* Get token symbol for a coin ID
|
|
5104
|
+
* @param coinId - 64-character hex string
|
|
5105
|
+
* @returns Symbol (e.g., "UCT") or truncated ID if not found
|
|
5106
|
+
*/
|
|
5107
|
+
getSymbol(coinId) {
|
|
5108
|
+
const def = this.getDefinition(coinId);
|
|
5109
|
+
if (def?.symbol) {
|
|
5110
|
+
return def.symbol;
|
|
5111
|
+
}
|
|
5112
|
+
return coinId.slice(0, 6).toUpperCase();
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* Get token name for a coin ID
|
|
5116
|
+
* @param coinId - 64-character hex string
|
|
5117
|
+
* @returns Name (e.g., "Bitcoin") or coin ID if not found
|
|
5118
|
+
*/
|
|
5119
|
+
getName(coinId) {
|
|
5120
|
+
const def = this.getDefinition(coinId);
|
|
5121
|
+
if (def?.name) {
|
|
5122
|
+
return def.name.charAt(0).toUpperCase() + def.name.slice(1);
|
|
5123
|
+
}
|
|
5124
|
+
return coinId;
|
|
5125
|
+
}
|
|
5126
|
+
/**
|
|
5127
|
+
* Get decimal places for a coin ID
|
|
5128
|
+
* @param coinId - 64-character hex string
|
|
5129
|
+
* @returns Decimals or 0 if not found
|
|
5130
|
+
*/
|
|
5131
|
+
getDecimals(coinId) {
|
|
5132
|
+
const def = this.getDefinition(coinId);
|
|
5133
|
+
return def?.decimals ?? 0;
|
|
5134
|
+
}
|
|
5135
|
+
/**
|
|
5136
|
+
* Get icon URL for a coin ID
|
|
5137
|
+
* @param coinId - 64-character hex string
|
|
5138
|
+
* @param preferPng - Prefer PNG format over SVG
|
|
5139
|
+
* @returns Icon URL or null if not found
|
|
5140
|
+
*/
|
|
5141
|
+
getIconUrl(coinId, preferPng = true) {
|
|
5142
|
+
const def = this.getDefinition(coinId);
|
|
5143
|
+
if (!def?.icons || def.icons.length === 0) {
|
|
5144
|
+
return null;
|
|
5145
|
+
}
|
|
5146
|
+
if (preferPng) {
|
|
5147
|
+
const pngIcon = def.icons.find((i) => i.url.toLowerCase().includes(".png"));
|
|
5148
|
+
if (pngIcon) return pngIcon.url;
|
|
5149
|
+
}
|
|
5150
|
+
return def.icons[0].url;
|
|
5151
|
+
}
|
|
5152
|
+
/**
|
|
5153
|
+
* Check if a coin ID is known in the registry
|
|
5154
|
+
* @param coinId - 64-character hex string
|
|
5155
|
+
* @returns true if the coin is in the registry
|
|
5156
|
+
*/
|
|
5157
|
+
isKnown(coinId) {
|
|
5158
|
+
return this.definitionsById.has(coinId.toLowerCase());
|
|
5159
|
+
}
|
|
5160
|
+
/**
|
|
5161
|
+
* Get all token definitions
|
|
5162
|
+
* @returns Array of all token definitions
|
|
5163
|
+
*/
|
|
5164
|
+
getAllDefinitions() {
|
|
5165
|
+
return Array.from(this.definitionsById.values());
|
|
5166
|
+
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Get all fungible token definitions
|
|
5169
|
+
* @returns Array of fungible token definitions
|
|
5170
|
+
*/
|
|
5171
|
+
getFungibleTokens() {
|
|
5172
|
+
return this.getAllDefinitions().filter((def) => def.assetKind === "fungible");
|
|
5173
|
+
}
|
|
5174
|
+
/**
|
|
5175
|
+
* Get all non-fungible token definitions
|
|
5176
|
+
* @returns Array of non-fungible token definitions
|
|
5177
|
+
*/
|
|
5178
|
+
getNonFungibleTokens() {
|
|
5179
|
+
return this.getAllDefinitions().filter((def) => def.assetKind === "non-fungible");
|
|
5180
|
+
}
|
|
5181
|
+
/**
|
|
5182
|
+
* Get coin ID by symbol
|
|
5183
|
+
* @param symbol - Token symbol (e.g., "UCT")
|
|
5184
|
+
* @returns Coin ID hex string or undefined if not found
|
|
5185
|
+
*/
|
|
5186
|
+
getCoinIdBySymbol(symbol) {
|
|
5187
|
+
const def = this.getDefinitionBySymbol(symbol);
|
|
5188
|
+
return def?.id;
|
|
5189
|
+
}
|
|
5190
|
+
/**
|
|
5191
|
+
* Get coin ID by name
|
|
5192
|
+
* @param name - Token name (e.g., "bitcoin")
|
|
5193
|
+
* @returns Coin ID hex string or undefined if not found
|
|
5194
|
+
*/
|
|
5195
|
+
getCoinIdByName(name) {
|
|
5196
|
+
const def = this.getDefinitionByName(name);
|
|
5197
|
+
return def?.id;
|
|
5198
|
+
}
|
|
5199
|
+
};
|
|
5200
|
+
|
|
4834
5201
|
// impl/shared/resolvers.ts
|
|
4835
5202
|
function getNetworkConfig(network = "mainnet") {
|
|
4836
5203
|
return NETWORKS[network];
|
|
@@ -4906,6 +5273,16 @@ function resolveGroupChatConfig(network, config) {
|
|
|
4906
5273
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
4907
5274
|
};
|
|
4908
5275
|
}
|
|
5276
|
+
function resolveMarketConfig(config) {
|
|
5277
|
+
if (!config) return void 0;
|
|
5278
|
+
if (config === true) {
|
|
5279
|
+
return { apiUrl: DEFAULT_MARKET_API_URL };
|
|
5280
|
+
}
|
|
5281
|
+
return {
|
|
5282
|
+
apiUrl: config.apiUrl ?? DEFAULT_MARKET_API_URL,
|
|
5283
|
+
timeout: config.timeout
|
|
5284
|
+
};
|
|
5285
|
+
}
|
|
4909
5286
|
|
|
4910
5287
|
// impl/nodejs/index.ts
|
|
4911
5288
|
function createNodeProviders(config) {
|
|
@@ -4921,9 +5298,13 @@ function createNodeProviders(config) {
|
|
|
4921
5298
|
const ipfsSync = config?.tokenSync?.ipfs;
|
|
4922
5299
|
const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
|
|
4923
5300
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
5301
|
+
const networkConfig = getNetworkConfig(network);
|
|
5302
|
+
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5303
|
+
const market = resolveMarketConfig(config?.market);
|
|
4924
5304
|
return {
|
|
4925
5305
|
storage,
|
|
4926
5306
|
groupChat,
|
|
5307
|
+
market,
|
|
4927
5308
|
tokenStorage: createFileTokenStorageProvider({
|
|
4928
5309
|
tokensDir: config?.tokensDir ?? "./sphere-tokens"
|
|
4929
5310
|
}),
|