@unicitylabs/sphere-sdk 0.1.9 → 0.2.1
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/README.md +47 -2
- package/dist/core/index.cjs +2044 -369
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +612 -58
- package/dist/core/index.d.ts +612 -58
- package/dist/core/index.js +2043 -368
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +307 -13
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +307 -13
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +4 -2
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +4 -2
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +326 -15
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +227 -17
- package/dist/impl/nodejs/index.d.ts +227 -17
- package/dist/impl/nodejs/index.js +326 -15
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +2299 -396
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1027 -7605
- package/dist/index.d.ts +1027 -7605
- package/dist/index.js +2286 -393
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.d.cts +7 -0
- package/dist/l1/index.d.ts +7 -0
- package/dist/l1/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,8 +22,10 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
22
22
|
WALLET_EXISTS: "wallet_exists",
|
|
23
23
|
/** Current active address index */
|
|
24
24
|
CURRENT_ADDRESS_INDEX: "current_address_index",
|
|
25
|
-
/**
|
|
26
|
-
ADDRESS_NAMETAGS: "address_nametags"
|
|
25
|
+
/** Nametag cache per address (separate from tracked addresses registry) */
|
|
26
|
+
ADDRESS_NAMETAGS: "address_nametags",
|
|
27
|
+
/** Active addresses registry (JSON: TrackedAddressesStorage) */
|
|
28
|
+
TRACKED_ADDRESSES: "tracked_addresses"
|
|
27
29
|
};
|
|
28
30
|
var STORAGE_KEYS_ADDRESS = {
|
|
29
31
|
/** Pending transfers for this address */
|
|
@@ -213,6 +215,19 @@ var FileStorageProvider = class {
|
|
|
213
215
|
}
|
|
214
216
|
await this.save();
|
|
215
217
|
}
|
|
218
|
+
async saveTrackedAddresses(entries) {
|
|
219
|
+
await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
|
|
220
|
+
}
|
|
221
|
+
async loadTrackedAddresses() {
|
|
222
|
+
const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
|
|
223
|
+
if (!data) return [];
|
|
224
|
+
try {
|
|
225
|
+
const parsed = JSON.parse(data);
|
|
226
|
+
return parsed.addresses ?? [];
|
|
227
|
+
} catch {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
216
231
|
/**
|
|
217
232
|
* Get full storage key with address prefix for per-address keys
|
|
218
233
|
*/
|
|
@@ -296,7 +311,12 @@ var FileTokenStorageProvider = class {
|
|
|
296
311
|
}
|
|
297
312
|
};
|
|
298
313
|
try {
|
|
299
|
-
const files = fs2.readdirSync(this.tokensDir).filter(
|
|
314
|
+
const files = fs2.readdirSync(this.tokensDir).filter(
|
|
315
|
+
(f) => f.endsWith(".json") && f !== "_meta.json" && f !== "_tombstones.json" && !f.startsWith("archived_") && // Skip archived tokens
|
|
316
|
+
!f.startsWith("token-") && // Skip legacy token format
|
|
317
|
+
!f.startsWith("nametag-")
|
|
318
|
+
// Skip nametag files (not tokens)
|
|
319
|
+
);
|
|
300
320
|
for (const file of files) {
|
|
301
321
|
try {
|
|
302
322
|
const basename2 = path2.basename(file, ".json");
|
|
@@ -314,6 +334,14 @@ var FileTokenStorageProvider = class {
|
|
|
314
334
|
} catch {
|
|
315
335
|
}
|
|
316
336
|
}
|
|
337
|
+
const tombstonesPath = path2.join(this.tokensDir, "_tombstones.json");
|
|
338
|
+
if (fs2.existsSync(tombstonesPath)) {
|
|
339
|
+
try {
|
|
340
|
+
const content = fs2.readFileSync(tombstonesPath, "utf-8");
|
|
341
|
+
data._tombstones = JSON.parse(content);
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
317
345
|
return {
|
|
318
346
|
success: true,
|
|
319
347
|
data,
|
|
@@ -358,6 +386,10 @@ var FileTokenStorageProvider = class {
|
|
|
358
386
|
fs2.unlinkSync(filePath);
|
|
359
387
|
}
|
|
360
388
|
}
|
|
389
|
+
fs2.writeFileSync(
|
|
390
|
+
path2.join(this.tokensDir, "_tombstones.json"),
|
|
391
|
+
JSON.stringify(data._tombstones, null, 2)
|
|
392
|
+
);
|
|
361
393
|
}
|
|
362
394
|
return {
|
|
363
395
|
success: true,
|
|
@@ -1006,6 +1038,10 @@ function defaultUUIDGenerator() {
|
|
|
1006
1038
|
|
|
1007
1039
|
// transport/NostrTransportProvider.ts
|
|
1008
1040
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1041
|
+
function hashAddressForTag(address) {
|
|
1042
|
+
const bytes = new TextEncoder().encode("unicity:address:" + address);
|
|
1043
|
+
return Buffer2.from(sha256(bytes)).toString("hex");
|
|
1044
|
+
}
|
|
1009
1045
|
function deriveNametagEncryptionKey(privateKeyHex) {
|
|
1010
1046
|
const privateKeyBytes = Buffer2.from(privateKeyHex, "hex");
|
|
1011
1047
|
const saltInput = new TextEncoder().encode("sphere-nametag-salt");
|
|
@@ -1259,7 +1295,7 @@ var NostrTransportProvider = class {
|
|
|
1259
1295
|
// ===========================================================================
|
|
1260
1296
|
// TransportProvider Implementation
|
|
1261
1297
|
// ===========================================================================
|
|
1262
|
-
setIdentity(identity) {
|
|
1298
|
+
async setIdentity(identity) {
|
|
1263
1299
|
this.identity = identity;
|
|
1264
1300
|
const secretKey = Buffer2.from(identity.privateKey, "hex");
|
|
1265
1301
|
this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
|
|
@@ -1289,12 +1325,9 @@ var NostrTransportProvider = class {
|
|
|
1289
1325
|
this.log("NostrClient reconnected to relay:", url);
|
|
1290
1326
|
}
|
|
1291
1327
|
});
|
|
1292
|
-
this.nostrClient.connect(...this.config.relays)
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}).catch((err) => {
|
|
1296
|
-
this.log("Failed to reconnect with new identity:", err);
|
|
1297
|
-
});
|
|
1328
|
+
await this.nostrClient.connect(...this.config.relays);
|
|
1329
|
+
this.subscribeToEvents();
|
|
1330
|
+
oldClient.disconnect();
|
|
1298
1331
|
} else if (this.isConnected()) {
|
|
1299
1332
|
this.subscribeToEvents();
|
|
1300
1333
|
}
|
|
@@ -1414,6 +1447,28 @@ var NostrTransportProvider = class {
|
|
|
1414
1447
|
this.paymentRequestResponseHandlers.add(handler);
|
|
1415
1448
|
return () => this.paymentRequestResponseHandlers.delete(handler);
|
|
1416
1449
|
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Resolve any identifier to full peer information.
|
|
1452
|
+
* Routes to the appropriate specific resolve method based on identifier format.
|
|
1453
|
+
*/
|
|
1454
|
+
async resolve(identifier) {
|
|
1455
|
+
if (identifier.startsWith("@")) {
|
|
1456
|
+
return this.resolveNametagInfo(identifier.slice(1));
|
|
1457
|
+
}
|
|
1458
|
+
if (identifier.startsWith("DIRECT:") || identifier.startsWith("PROXY:")) {
|
|
1459
|
+
return this.resolveAddressInfo(identifier);
|
|
1460
|
+
}
|
|
1461
|
+
if (identifier.startsWith("alpha1") || identifier.startsWith("alphat1")) {
|
|
1462
|
+
return this.resolveAddressInfo(identifier);
|
|
1463
|
+
}
|
|
1464
|
+
if (/^0[23][0-9a-f]{64}$/i.test(identifier)) {
|
|
1465
|
+
return this.resolveAddressInfo(identifier);
|
|
1466
|
+
}
|
|
1467
|
+
if (/^[0-9a-f]{64}$/i.test(identifier)) {
|
|
1468
|
+
return this.resolveTransportPubkeyInfo(identifier);
|
|
1469
|
+
}
|
|
1470
|
+
return this.resolveNametagInfo(identifier);
|
|
1471
|
+
}
|
|
1417
1472
|
async resolveNametag(nametag) {
|
|
1418
1473
|
this.ensureReady();
|
|
1419
1474
|
const hashedNametag = hashNametag(nametag);
|
|
@@ -1510,6 +1565,77 @@ var NostrTransportProvider = class {
|
|
|
1510
1565
|
};
|
|
1511
1566
|
}
|
|
1512
1567
|
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Resolve a DIRECT://, PROXY://, or L1 address to full peer info.
|
|
1570
|
+
* Performs reverse lookup: hash(address) → query '#t' tag → parse binding event.
|
|
1571
|
+
* Works with both new identity binding events and legacy nametag binding events.
|
|
1572
|
+
*/
|
|
1573
|
+
async resolveAddressInfo(address) {
|
|
1574
|
+
this.ensureReady();
|
|
1575
|
+
const addressHash = hashAddressForTag(address);
|
|
1576
|
+
const events = await this.queryEvents({
|
|
1577
|
+
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1578
|
+
"#t": [addressHash],
|
|
1579
|
+
limit: 1
|
|
1580
|
+
});
|
|
1581
|
+
if (events.length === 0) return null;
|
|
1582
|
+
const bindingEvent = events[0];
|
|
1583
|
+
try {
|
|
1584
|
+
const content = JSON.parse(bindingEvent.content);
|
|
1585
|
+
return {
|
|
1586
|
+
nametag: content.nametag || void 0,
|
|
1587
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1588
|
+
chainPubkey: content.public_key || "",
|
|
1589
|
+
l1Address: content.l1_address || "",
|
|
1590
|
+
directAddress: content.direct_address || "",
|
|
1591
|
+
proxyAddress: content.proxy_address || void 0,
|
|
1592
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1593
|
+
};
|
|
1594
|
+
} catch {
|
|
1595
|
+
return {
|
|
1596
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1597
|
+
chainPubkey: "",
|
|
1598
|
+
l1Address: "",
|
|
1599
|
+
directAddress: "",
|
|
1600
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Resolve transport pubkey (Nostr pubkey) to full peer info.
|
|
1606
|
+
* Queries binding events authored by the given pubkey.
|
|
1607
|
+
*/
|
|
1608
|
+
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1609
|
+
this.ensureReady();
|
|
1610
|
+
const events = await this.queryEvents({
|
|
1611
|
+
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1612
|
+
authors: [transportPubkey],
|
|
1613
|
+
limit: 5
|
|
1614
|
+
});
|
|
1615
|
+
if (events.length === 0) return null;
|
|
1616
|
+
events.sort((a, b) => b.created_at - a.created_at);
|
|
1617
|
+
const bindingEvent = events[0];
|
|
1618
|
+
try {
|
|
1619
|
+
const content = JSON.parse(bindingEvent.content);
|
|
1620
|
+
return {
|
|
1621
|
+
nametag: content.nametag || void 0,
|
|
1622
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1623
|
+
chainPubkey: content.public_key || "",
|
|
1624
|
+
l1Address: content.l1_address || "",
|
|
1625
|
+
directAddress: content.direct_address || "",
|
|
1626
|
+
proxyAddress: content.proxy_address || void 0,
|
|
1627
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1628
|
+
};
|
|
1629
|
+
} catch {
|
|
1630
|
+
return {
|
|
1631
|
+
transportPubkey: bindingEvent.pubkey,
|
|
1632
|
+
chainPubkey: "",
|
|
1633
|
+
l1Address: "",
|
|
1634
|
+
directAddress: "",
|
|
1635
|
+
timestamp: bindingEvent.created_at * 1e3
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1513
1639
|
/**
|
|
1514
1640
|
* Recover nametag for the current identity by searching for encrypted nametag events
|
|
1515
1641
|
* Used after wallet import to recover associated nametag
|
|
@@ -1553,6 +1679,63 @@ var NostrTransportProvider = class {
|
|
|
1553
1679
|
this.log("Could not decrypt nametag from any event");
|
|
1554
1680
|
return null;
|
|
1555
1681
|
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Publish identity binding event on Nostr.
|
|
1684
|
+
* Without nametag: publishes base binding (chainPubkey, l1Address, directAddress).
|
|
1685
|
+
* With nametag: also publishes nametag hash, proxy address, encrypted nametag for recovery.
|
|
1686
|
+
*
|
|
1687
|
+
* Uses kind 30078 parameterized replaceable event with d=SHA256('unicity:identity:' + nostrPubkey).
|
|
1688
|
+
* Each HD address index has its own Nostr key → its own binding event.
|
|
1689
|
+
*
|
|
1690
|
+
* @returns true if successful, false if nametag is taken by another pubkey
|
|
1691
|
+
*/
|
|
1692
|
+
async publishIdentityBinding(chainPubkey, l1Address, directAddress, nametag) {
|
|
1693
|
+
this.ensureReady();
|
|
1694
|
+
if (!this.identity) {
|
|
1695
|
+
throw new Error("Identity not set");
|
|
1696
|
+
}
|
|
1697
|
+
const nostrPubkey = this.getNostrPubkey();
|
|
1698
|
+
const dTagBytes = new TextEncoder().encode("unicity:identity:" + nostrPubkey);
|
|
1699
|
+
const dTag = Buffer2.from(sha256(dTagBytes)).toString("hex");
|
|
1700
|
+
const contentObj = {
|
|
1701
|
+
public_key: chainPubkey,
|
|
1702
|
+
l1_address: l1Address,
|
|
1703
|
+
direct_address: directAddress
|
|
1704
|
+
};
|
|
1705
|
+
const tags = [
|
|
1706
|
+
["d", dTag],
|
|
1707
|
+
["t", hashAddressForTag(chainPubkey)],
|
|
1708
|
+
["t", hashAddressForTag(directAddress)],
|
|
1709
|
+
["t", hashAddressForTag(l1Address)]
|
|
1710
|
+
];
|
|
1711
|
+
if (nametag) {
|
|
1712
|
+
const existing = await this.resolveNametag(nametag);
|
|
1713
|
+
if (existing && existing !== nostrPubkey) {
|
|
1714
|
+
this.log("Nametag already taken:", nametag, "- owner:", existing);
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
|
|
1718
|
+
const proxyAddr = await ProxyAddress.fromNameTag(nametag);
|
|
1719
|
+
const proxyAddress = proxyAddr.toString();
|
|
1720
|
+
const encryptedNametag = await encryptNametag(nametag, this.identity.privateKey);
|
|
1721
|
+
const hashedNametag = hashNametag(nametag);
|
|
1722
|
+
contentObj.nametag = nametag;
|
|
1723
|
+
contentObj.encrypted_nametag = encryptedNametag;
|
|
1724
|
+
contentObj.proxy_address = proxyAddress;
|
|
1725
|
+
tags.push(["t", hashedNametag]);
|
|
1726
|
+
tags.push(["t", hashAddressForTag(proxyAddress)]);
|
|
1727
|
+
}
|
|
1728
|
+
const content = JSON.stringify(contentObj);
|
|
1729
|
+
const event = await this.createEvent(EVENT_KINDS.NAMETAG_BINDING, content, tags);
|
|
1730
|
+
await this.publishEvent(event);
|
|
1731
|
+
if (nametag) {
|
|
1732
|
+
this.log("Published identity binding with nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...");
|
|
1733
|
+
} else {
|
|
1734
|
+
this.log("Published identity binding (no nametag) for pubkey:", nostrPubkey.slice(0, 16) + "...");
|
|
1735
|
+
}
|
|
1736
|
+
return true;
|
|
1737
|
+
}
|
|
1738
|
+
/** @deprecated Use publishIdentityBinding instead */
|
|
1556
1739
|
async publishNametag(nametag, address) {
|
|
1557
1740
|
this.ensureReady();
|
|
1558
1741
|
const hashedNametag = hashNametag(nametag);
|
|
@@ -1579,6 +1762,9 @@ var NostrTransportProvider = class {
|
|
|
1579
1762
|
const compressedPubkey = getPublicKey(privateKeyHex, true);
|
|
1580
1763
|
const l1Address = publicKeyToAddress(compressedPubkey, "alpha");
|
|
1581
1764
|
const encryptedNametag = await encryptNametag(nametag, privateKeyHex);
|
|
1765
|
+
const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
|
|
1766
|
+
const proxyAddr = await ProxyAddress.fromNameTag(nametag);
|
|
1767
|
+
const proxyAddress = proxyAddr.toString();
|
|
1582
1768
|
const hashedNametag = hashNametag(nametag);
|
|
1583
1769
|
const content = JSON.stringify({
|
|
1584
1770
|
nametag_hash: hashedNametag,
|
|
@@ -1588,17 +1774,20 @@ var NostrTransportProvider = class {
|
|
|
1588
1774
|
encrypted_nametag: encryptedNametag,
|
|
1589
1775
|
public_key: compressedPubkey,
|
|
1590
1776
|
l1_address: l1Address,
|
|
1591
|
-
direct_address: directAddress
|
|
1777
|
+
direct_address: directAddress,
|
|
1778
|
+
proxy_address: proxyAddress
|
|
1592
1779
|
});
|
|
1593
|
-
const
|
|
1780
|
+
const tags = [
|
|
1594
1781
|
["d", hashedNametag],
|
|
1595
1782
|
["nametag", hashedNametag],
|
|
1596
1783
|
["t", hashedNametag],
|
|
1784
|
+
["t", hashAddressForTag(directAddress)],
|
|
1785
|
+
["t", hashAddressForTag(proxyAddress)],
|
|
1597
1786
|
["address", nostrPubkey],
|
|
1598
|
-
// Extended tags for indexing
|
|
1599
1787
|
["pubkey", compressedPubkey],
|
|
1600
1788
|
["l1", l1Address]
|
|
1601
|
-
]
|
|
1789
|
+
];
|
|
1790
|
+
const event = await this.createEvent(EVENT_KINDS.NAMETAG_BINDING, content, tags);
|
|
1602
1791
|
await this.publishEvent(event);
|
|
1603
1792
|
this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...", "l1:", l1Address.slice(0, 12) + "...");
|
|
1604
1793
|
return true;
|
|
@@ -2550,6 +2739,113 @@ function createUnicityAggregatorProvider(config) {
|
|
|
2550
2739
|
}
|
|
2551
2740
|
var createUnicityOracleProvider = createUnicityAggregatorProvider;
|
|
2552
2741
|
|
|
2742
|
+
// price/CoinGeckoPriceProvider.ts
|
|
2743
|
+
var CoinGeckoPriceProvider = class {
|
|
2744
|
+
platform = "coingecko";
|
|
2745
|
+
cache = /* @__PURE__ */ new Map();
|
|
2746
|
+
apiKey;
|
|
2747
|
+
cacheTtlMs;
|
|
2748
|
+
timeout;
|
|
2749
|
+
debug;
|
|
2750
|
+
baseUrl;
|
|
2751
|
+
constructor(config) {
|
|
2752
|
+
this.apiKey = config?.apiKey;
|
|
2753
|
+
this.cacheTtlMs = config?.cacheTtlMs ?? 6e4;
|
|
2754
|
+
this.timeout = config?.timeout ?? 1e4;
|
|
2755
|
+
this.debug = config?.debug ?? false;
|
|
2756
|
+
this.baseUrl = config?.baseUrl ?? (this.apiKey ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3");
|
|
2757
|
+
}
|
|
2758
|
+
async getPrices(tokenNames) {
|
|
2759
|
+
if (tokenNames.length === 0) {
|
|
2760
|
+
return /* @__PURE__ */ new Map();
|
|
2761
|
+
}
|
|
2762
|
+
const now = Date.now();
|
|
2763
|
+
const result = /* @__PURE__ */ new Map();
|
|
2764
|
+
const uncachedNames = [];
|
|
2765
|
+
for (const name of tokenNames) {
|
|
2766
|
+
const cached = this.cache.get(name);
|
|
2767
|
+
if (cached && cached.expiresAt > now) {
|
|
2768
|
+
if (cached.price !== null) {
|
|
2769
|
+
result.set(name, cached.price);
|
|
2770
|
+
}
|
|
2771
|
+
} else {
|
|
2772
|
+
uncachedNames.push(name);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
if (uncachedNames.length === 0) {
|
|
2776
|
+
return result;
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
const ids = uncachedNames.join(",");
|
|
2780
|
+
const url = `${this.baseUrl}/simple/price?ids=${encodeURIComponent(ids)}&vs_currencies=usd,eur&include_24hr_change=true`;
|
|
2781
|
+
const headers = { Accept: "application/json" };
|
|
2782
|
+
if (this.apiKey) {
|
|
2783
|
+
headers["x-cg-pro-api-key"] = this.apiKey;
|
|
2784
|
+
}
|
|
2785
|
+
if (this.debug) {
|
|
2786
|
+
console.log(`[CoinGecko] Fetching prices for: ${uncachedNames.join(", ")}`);
|
|
2787
|
+
}
|
|
2788
|
+
const response = await fetch(url, {
|
|
2789
|
+
headers,
|
|
2790
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
2791
|
+
});
|
|
2792
|
+
if (!response.ok) {
|
|
2793
|
+
throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
|
|
2794
|
+
}
|
|
2795
|
+
const data = await response.json();
|
|
2796
|
+
for (const [name, values] of Object.entries(data)) {
|
|
2797
|
+
if (values && typeof values === "object") {
|
|
2798
|
+
const price = {
|
|
2799
|
+
tokenName: name,
|
|
2800
|
+
priceUsd: values.usd ?? 0,
|
|
2801
|
+
priceEur: values.eur,
|
|
2802
|
+
change24h: values.usd_24h_change,
|
|
2803
|
+
timestamp: now
|
|
2804
|
+
};
|
|
2805
|
+
this.cache.set(name, { price, expiresAt: now + this.cacheTtlMs });
|
|
2806
|
+
result.set(name, price);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
for (const name of uncachedNames) {
|
|
2810
|
+
if (!result.has(name)) {
|
|
2811
|
+
this.cache.set(name, { price: null, expiresAt: now + this.cacheTtlMs });
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
if (this.debug) {
|
|
2815
|
+
console.log(`[CoinGecko] Fetched ${result.size} prices`);
|
|
2816
|
+
}
|
|
2817
|
+
} catch (error) {
|
|
2818
|
+
if (this.debug) {
|
|
2819
|
+
console.warn("[CoinGecko] Fetch failed, using stale cache:", error);
|
|
2820
|
+
}
|
|
2821
|
+
for (const name of uncachedNames) {
|
|
2822
|
+
const stale = this.cache.get(name);
|
|
2823
|
+
if (stale?.price) {
|
|
2824
|
+
result.set(name, stale.price);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
return result;
|
|
2829
|
+
}
|
|
2830
|
+
async getPrice(tokenName) {
|
|
2831
|
+
const prices = await this.getPrices([tokenName]);
|
|
2832
|
+
return prices.get(tokenName) ?? null;
|
|
2833
|
+
}
|
|
2834
|
+
clearCache() {
|
|
2835
|
+
this.cache.clear();
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
|
|
2839
|
+
// price/index.ts
|
|
2840
|
+
function createPriceProvider(config) {
|
|
2841
|
+
switch (config.platform) {
|
|
2842
|
+
case "coingecko":
|
|
2843
|
+
return new CoinGeckoPriceProvider(config);
|
|
2844
|
+
default:
|
|
2845
|
+
throw new Error(`Unsupported price platform: ${String(config.platform)}`);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2553
2849
|
// impl/shared/resolvers.ts
|
|
2554
2850
|
function getNetworkConfig(network = "mainnet") {
|
|
2555
2851
|
return NETWORKS[network];
|
|
@@ -2598,6 +2894,19 @@ function resolveL1Config(network, config) {
|
|
|
2598
2894
|
enableVesting: config.enableVesting
|
|
2599
2895
|
};
|
|
2600
2896
|
}
|
|
2897
|
+
function resolvePriceConfig(config) {
|
|
2898
|
+
if (config === void 0) {
|
|
2899
|
+
return void 0;
|
|
2900
|
+
}
|
|
2901
|
+
return {
|
|
2902
|
+
platform: config.platform ?? "coingecko",
|
|
2903
|
+
apiKey: config.apiKey,
|
|
2904
|
+
baseUrl: config.baseUrl,
|
|
2905
|
+
cacheTtlMs: config.cacheTtlMs,
|
|
2906
|
+
timeout: config.timeout,
|
|
2907
|
+
debug: config.debug
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2601
2910
|
|
|
2602
2911
|
// impl/nodejs/index.ts
|
|
2603
2912
|
function createNodeProviders(config) {
|
|
@@ -2605,6 +2914,7 @@ function createNodeProviders(config) {
|
|
|
2605
2914
|
const transportConfig = resolveTransportConfig(network, config?.transport);
|
|
2606
2915
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
2607
2916
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
2917
|
+
const priceConfig = resolvePriceConfig(config?.price);
|
|
2608
2918
|
return {
|
|
2609
2919
|
storage: createFileStorageProvider({
|
|
2610
2920
|
dataDir: config?.dataDir ?? "./sphere-data"
|
|
@@ -2627,7 +2937,8 @@ function createNodeProviders(config) {
|
|
|
2627
2937
|
debug: oracleConfig.debug,
|
|
2628
2938
|
network
|
|
2629
2939
|
}),
|
|
2630
|
-
l1: l1Config
|
|
2940
|
+
l1: l1Config,
|
|
2941
|
+
price: priceConfig ? createPriceProvider(priceConfig) : void 0
|
|
2631
2942
|
};
|
|
2632
2943
|
}
|
|
2633
2944
|
export {
|