crypull 1.0.2 → 1.0.3
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/index.d.mts +154 -18
- package/dist/index.d.ts +154 -18
- package/dist/index.js +905 -236
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +894 -237
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -15
- package/README.md +0 -228
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -1094
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -1069
- package/dist/cli.mjs.map +0 -1
- package/examples/basic.ts +0 -28
- package/src/Crypull.ts +0 -288
- package/src/cli.ts +0 -440
- package/src/index.ts +0 -14
- package/src/providers/binance.ts +0 -74
- package/src/providers/coincap.ts +0 -65
- package/src/providers/coingecko.ts +0 -92
- package/src/providers/coinpaprika.ts +0 -82
- package/src/providers/cryptocompare.ts +0 -56
- package/src/providers/dexscreener.ts +0 -82
- package/src/providers/geckoterminal.ts +0 -70
- package/src/types.ts +0 -130
- package/tests/index.test.ts +0 -15
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
// src/providers/coingecko.ts
|
|
4
4
|
var CoinGeckoProvider = class {
|
|
5
5
|
name = "CoinGecko";
|
|
6
|
+
addressBased = false;
|
|
6
7
|
baseUrl = "https://api.coingecko.com/api/v3";
|
|
7
8
|
async search(query) {
|
|
8
9
|
try {
|
|
9
|
-
const res = await fetch(
|
|
10
|
+
const res = await fetch(
|
|
11
|
+
`${this.baseUrl}/search?query=${encodeURIComponent(query)}`
|
|
12
|
+
);
|
|
10
13
|
if (!res.ok) return [];
|
|
11
14
|
const data = await res.json();
|
|
12
15
|
return (data.coins || []).slice(0, 10).map((coin) => ({
|
|
@@ -15,38 +18,53 @@ var CoinGeckoProvider = class {
|
|
|
15
18
|
symbol: coin.symbol.toUpperCase(),
|
|
16
19
|
source: this.name
|
|
17
20
|
}));
|
|
18
|
-
} catch
|
|
21
|
+
} catch {
|
|
19
22
|
return [];
|
|
20
23
|
}
|
|
21
24
|
}
|
|
25
|
+
/** Resolve a symbol or slug to a CoinGecko coin id. */
|
|
26
|
+
async resolveId(symbolOrId) {
|
|
27
|
+
const q = symbolOrId.toLowerCase().trim();
|
|
28
|
+
if (!q) return null;
|
|
29
|
+
const searchRes = await this.search(q);
|
|
30
|
+
const match = searchRes.find((s) => s.id === q) || searchRes.find((s) => s.symbol.toLowerCase() === q);
|
|
31
|
+
if (match?.id) {
|
|
32
|
+
return { id: match.id, symbol: match.symbol };
|
|
33
|
+
}
|
|
34
|
+
return { id: q, symbol: q.toUpperCase() };
|
|
35
|
+
}
|
|
22
36
|
async getPrice(symbolOrId) {
|
|
23
37
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
const res = await fetch(`${this.baseUrl}/simple/price?ids=${id}&vs_currencies=usd`);
|
|
38
|
+
const resolved = await this.resolveId(symbolOrId);
|
|
39
|
+
if (!resolved) return null;
|
|
40
|
+
const res = await fetch(
|
|
41
|
+
`${this.baseUrl}/simple/price?ids=${encodeURIComponent(resolved.id)}&vs_currencies=usd`
|
|
42
|
+
);
|
|
31
43
|
if (!res.ok) return null;
|
|
32
44
|
const data = await res.json();
|
|
33
|
-
if (!data[id] || typeof data[id].usd === "undefined")
|
|
45
|
+
if (!data[resolved.id] || typeof data[resolved.id].usd === "undefined") {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
34
48
|
return {
|
|
35
|
-
symbol:
|
|
36
|
-
priceUsd: data[id].usd,
|
|
49
|
+
symbol: resolved.symbol,
|
|
50
|
+
priceUsd: data[resolved.id].usd,
|
|
37
51
|
source: this.name,
|
|
38
52
|
lastUpdated: Date.now()
|
|
39
53
|
};
|
|
40
|
-
} catch
|
|
54
|
+
} catch {
|
|
41
55
|
return null;
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
|
-
async getTokenInfo(
|
|
58
|
+
async getTokenInfo(symbolOrId) {
|
|
45
59
|
try {
|
|
46
|
-
const
|
|
60
|
+
const resolved = await this.resolveId(symbolOrId);
|
|
61
|
+
if (!resolved) return null;
|
|
62
|
+
const res = await fetch(
|
|
63
|
+
`${this.baseUrl}/coins/${encodeURIComponent(resolved.id)}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`
|
|
64
|
+
);
|
|
47
65
|
if (!res.ok) return null;
|
|
48
66
|
const data = await res.json();
|
|
49
|
-
if (data.error) return null;
|
|
67
|
+
if (data.error || !data.id) return null;
|
|
50
68
|
return {
|
|
51
69
|
name: data.name,
|
|
52
70
|
symbol: data.symbol.toUpperCase(),
|
|
@@ -67,16 +85,98 @@ var CoinGeckoProvider = class {
|
|
|
67
85
|
atl: data.market_data?.atl?.usd,
|
|
68
86
|
atlDate: data.market_data?.atl_date?.usd,
|
|
69
87
|
links: {
|
|
70
|
-
website: data.links?.homepage?.[0],
|
|
88
|
+
website: data.links?.homepage?.[0] || void 0,
|
|
71
89
|
twitter: data.links?.twitter_screen_name ? `https://twitter.com/${data.links.twitter_screen_name}` : void 0,
|
|
72
90
|
telegram: data.links?.telegram_channel_identifier ? `https://t.me/${data.links.telegram_channel_identifier}` : void 0,
|
|
73
|
-
discord: data.links?.chat_url?.find(
|
|
91
|
+
discord: data.links?.chat_url?.find(
|
|
92
|
+
(url) => url.includes("discord")
|
|
93
|
+
),
|
|
74
94
|
github: data.links?.repos_url?.github?.[0]
|
|
75
95
|
},
|
|
76
96
|
source: this.name,
|
|
77
97
|
lastUpdated: Date.now()
|
|
78
98
|
};
|
|
79
|
-
} catch
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async getGlobalMarket() {
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(`${this.baseUrl}/global`);
|
|
106
|
+
if (!res.ok) return null;
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
const d = data.data;
|
|
109
|
+
if (!d) return null;
|
|
110
|
+
return {
|
|
111
|
+
totalMarketCapUsd: d.total_market_cap.usd,
|
|
112
|
+
totalVolume24hUsd: d.total_volume.usd,
|
|
113
|
+
bitcoinDominancePercentage: d.market_cap_percentage.btc,
|
|
114
|
+
ethereumDominancePercentage: d.market_cap_percentage.eth,
|
|
115
|
+
activeCryptocurrencies: d.active_cryptocurrencies,
|
|
116
|
+
lastUpdated: d.updated_at * 1e3
|
|
117
|
+
};
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async getTrending() {
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`${this.baseUrl}/search/trending`);
|
|
125
|
+
if (!res.ok) return [];
|
|
126
|
+
const data = await res.json();
|
|
127
|
+
return (data.coins || []).slice(0, 10).map((c) => ({
|
|
128
|
+
id: c.item.id,
|
|
129
|
+
name: c.item.name,
|
|
130
|
+
symbol: c.item.symbol.toUpperCase(),
|
|
131
|
+
marketCapRank: c.item.market_cap_rank,
|
|
132
|
+
priceUsd: c.item.data?.price,
|
|
133
|
+
priceChange24h: c.item.data?.price_change_percentage_24h?.usd,
|
|
134
|
+
volume24h: c.item.data?.total_volume
|
|
135
|
+
}));
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async getTop(limit = 50) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(
|
|
143
|
+
`${this.baseUrl}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=${limit}&page=1&sparkline=false`
|
|
144
|
+
);
|
|
145
|
+
if (!res.ok) return [];
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
return data.map((c) => ({
|
|
148
|
+
id: c.id,
|
|
149
|
+
name: c.name,
|
|
150
|
+
symbol: c.symbol.toUpperCase(),
|
|
151
|
+
marketCapRank: c.market_cap_rank,
|
|
152
|
+
priceUsd: c.current_price,
|
|
153
|
+
marketCap: c.market_cap,
|
|
154
|
+
volume24h: c.total_volume,
|
|
155
|
+
priceChange24h: c.price_change_percentage_24h
|
|
156
|
+
}));
|
|
157
|
+
} catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async getChart(query, days = 7) {
|
|
162
|
+
try {
|
|
163
|
+
const resolved = await this.resolveId(query);
|
|
164
|
+
if (!resolved) return null;
|
|
165
|
+
const res = await fetch(
|
|
166
|
+
`${this.baseUrl}/coins/${encodeURIComponent(resolved.id)}/market_chart?vs_currency=usd&days=${days}`
|
|
167
|
+
);
|
|
168
|
+
if (!res.ok) return null;
|
|
169
|
+
const data = await res.json();
|
|
170
|
+
if (!data.prices?.length) return null;
|
|
171
|
+
const prices = data.prices.map((p) => p[1]);
|
|
172
|
+
const timestamps = data.prices.map((p) => p[0]);
|
|
173
|
+
return {
|
|
174
|
+
prices,
|
|
175
|
+
timestamps,
|
|
176
|
+
minPrice: Math.min(...prices),
|
|
177
|
+
maxPrice: Math.max(...prices)
|
|
178
|
+
};
|
|
179
|
+
} catch {
|
|
80
180
|
return null;
|
|
81
181
|
}
|
|
82
182
|
}
|
|
@@ -85,10 +185,13 @@ var CoinGeckoProvider = class {
|
|
|
85
185
|
// src/providers/dexscreener.ts
|
|
86
186
|
var DexScreenerProvider = class {
|
|
87
187
|
name = "DexScreener";
|
|
188
|
+
addressBased = true;
|
|
88
189
|
baseUrl = "https://api.dexscreener.com/latest/dex";
|
|
89
190
|
async search(query) {
|
|
90
191
|
try {
|
|
91
|
-
const res = await fetch(
|
|
192
|
+
const res = await fetch(
|
|
193
|
+
`${this.baseUrl}/search?q=${encodeURIComponent(query)}`
|
|
194
|
+
);
|
|
92
195
|
if (!res.ok) return [];
|
|
93
196
|
const data = await res.json();
|
|
94
197
|
return (data.pairs || []).slice(0, 10).map((pair) => ({
|
|
@@ -98,7 +201,7 @@ var DexScreenerProvider = class {
|
|
|
98
201
|
network: pair.chainId,
|
|
99
202
|
source: this.name
|
|
100
203
|
}));
|
|
101
|
-
} catch
|
|
204
|
+
} catch {
|
|
102
205
|
return [];
|
|
103
206
|
}
|
|
104
207
|
}
|
|
@@ -114,12 +217,17 @@ var DexScreenerProvider = class {
|
|
|
114
217
|
}
|
|
115
218
|
async getTokenInfo(address) {
|
|
116
219
|
try {
|
|
117
|
-
const res = await fetch(
|
|
220
|
+
const res = await fetch(
|
|
221
|
+
`${this.baseUrl}/tokens/${encodeURIComponent(address)}`
|
|
222
|
+
);
|
|
118
223
|
if (!res.ok) return null;
|
|
119
224
|
const data = await res.json();
|
|
120
|
-
if (!data.pairs
|
|
121
|
-
const
|
|
122
|
-
|
|
225
|
+
if (!data.pairs?.length) return null;
|
|
226
|
+
const sorted = [...data.pairs].sort(
|
|
227
|
+
(a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0)
|
|
228
|
+
);
|
|
229
|
+
const pair = sorted[0];
|
|
230
|
+
const mappedPairs = sorted.slice(0, 10).map((p) => ({
|
|
123
231
|
dexId: p.dexId,
|
|
124
232
|
url: p.url,
|
|
125
233
|
pairAddress: p.pairAddress,
|
|
@@ -149,7 +257,7 @@ var DexScreenerProvider = class {
|
|
|
149
257
|
source: this.name,
|
|
150
258
|
lastUpdated: Date.now()
|
|
151
259
|
};
|
|
152
|
-
} catch
|
|
260
|
+
} catch {
|
|
153
261
|
return null;
|
|
154
262
|
}
|
|
155
263
|
}
|
|
@@ -158,26 +266,28 @@ var DexScreenerProvider = class {
|
|
|
158
266
|
// src/providers/geckoterminal.ts
|
|
159
267
|
var GeckoTerminalProvider = class {
|
|
160
268
|
name = "GeckoTerminal";
|
|
269
|
+
addressBased = true;
|
|
161
270
|
baseUrl = "https://api.geckoterminal.com/api/v2";
|
|
162
271
|
async search(query) {
|
|
163
272
|
try {
|
|
164
|
-
const res = await fetch(
|
|
273
|
+
const res = await fetch(
|
|
274
|
+
`${this.baseUrl}/search/pools?query=${encodeURIComponent(query)}`
|
|
275
|
+
);
|
|
165
276
|
if (!res.ok) return [];
|
|
166
277
|
const data = await res.json();
|
|
167
278
|
const pools = data.data || [];
|
|
168
279
|
return pools.slice(0, 10).map((pool) => {
|
|
169
280
|
const attributes = pool.attributes;
|
|
170
|
-
const baseToken = attributes.name.split(" / ")[0];
|
|
281
|
+
const baseToken = (attributes.name || "").split(" / ")[0] || attributes.name;
|
|
171
282
|
return {
|
|
172
283
|
id: pool.id,
|
|
173
|
-
// e.g. "eth_0x..."
|
|
174
284
|
name: attributes.name,
|
|
175
|
-
symbol: baseToken,
|
|
176
|
-
network: pool.relationships
|
|
285
|
+
symbol: typeof baseToken === "string" ? baseToken.toUpperCase() : String(baseToken),
|
|
286
|
+
network: pool.relationships?.network?.data?.id,
|
|
177
287
|
source: this.name
|
|
178
288
|
};
|
|
179
289
|
});
|
|
180
|
-
} catch
|
|
290
|
+
} catch {
|
|
181
291
|
return [];
|
|
182
292
|
}
|
|
183
293
|
}
|
|
@@ -193,10 +303,12 @@ var GeckoTerminalProvider = class {
|
|
|
193
303
|
}
|
|
194
304
|
async getTokenInfo(address, network = "eth") {
|
|
195
305
|
try {
|
|
196
|
-
const res = await fetch(
|
|
306
|
+
const res = await fetch(
|
|
307
|
+
`${this.baseUrl}/networks/${encodeURIComponent(network)}/tokens/${encodeURIComponent(address)}`
|
|
308
|
+
);
|
|
197
309
|
if (!res.ok) return null;
|
|
198
310
|
const json = await res.json();
|
|
199
|
-
if (!json.data
|
|
311
|
+
if (!json.data?.attributes) return null;
|
|
200
312
|
const attrs = json.data.attributes;
|
|
201
313
|
return {
|
|
202
314
|
name: attrs.name,
|
|
@@ -204,76 +316,106 @@ var GeckoTerminalProvider = class {
|
|
|
204
316
|
address: attrs.address,
|
|
205
317
|
network,
|
|
206
318
|
priceUsd: parseFloat(attrs.price_usd) || 0,
|
|
207
|
-
fdv: parseFloat(attrs.fdv_usd),
|
|
208
|
-
volume24h: parseFloat(attrs.volume_usd?.h24 || 0),
|
|
319
|
+
fdv: parseFloat(attrs.fdv_usd) || void 0,
|
|
320
|
+
volume24h: parseFloat(attrs.volume_usd?.h24 || 0) || void 0,
|
|
209
321
|
source: this.name,
|
|
210
322
|
lastUpdated: Date.now()
|
|
211
323
|
};
|
|
212
|
-
} catch
|
|
324
|
+
} catch {
|
|
213
325
|
return null;
|
|
214
326
|
}
|
|
215
327
|
}
|
|
216
328
|
};
|
|
217
329
|
|
|
330
|
+
// src/utils.ts
|
|
331
|
+
function isContractAddress(query) {
|
|
332
|
+
const q = query.trim();
|
|
333
|
+
if (!q) return false;
|
|
334
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(q)) return true;
|
|
335
|
+
if (q.length >= 32 && q.length <= 44 && !q.includes(" ") && /^[1-9A-HJ-NP-Za-km-z]+$/.test(q)) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
function toBinancePair(symbol) {
|
|
341
|
+
const upper = symbol.toUpperCase().trim();
|
|
342
|
+
if (!upper) return upper;
|
|
343
|
+
if (upper.endsWith("USDT") || upper.endsWith("BUSD")) return upper;
|
|
344
|
+
return `${upper}USDT`;
|
|
345
|
+
}
|
|
346
|
+
function fromBinancePair(pair) {
|
|
347
|
+
const upper = pair.toUpperCase();
|
|
348
|
+
if (upper.endsWith("USDT") && upper.length > 4) return upper.slice(0, -4);
|
|
349
|
+
if (upper.endsWith("BUSD") && upper.length > 4) return upper.slice(0, -4);
|
|
350
|
+
return upper;
|
|
351
|
+
}
|
|
352
|
+
function defaultDexNetwork(address) {
|
|
353
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(address.trim())) return "eth";
|
|
354
|
+
return "solana";
|
|
355
|
+
}
|
|
356
|
+
function finiteOrUndefined(value) {
|
|
357
|
+
const n = typeof value === "number" ? value : parseFloat(String(value));
|
|
358
|
+
return Number.isFinite(n) ? n : void 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
218
361
|
// src/providers/binance.ts
|
|
219
362
|
var BinanceProvider = class {
|
|
220
363
|
name = "Binance";
|
|
364
|
+
addressBased = false;
|
|
221
365
|
baseUrl = "https://api.binance.com/api/v3";
|
|
222
366
|
async search(query) {
|
|
223
367
|
try {
|
|
224
|
-
const
|
|
225
|
-
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${
|
|
226
|
-
if (res.ok)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
368
|
+
const pair = toBinancePair(query);
|
|
369
|
+
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${pair}`);
|
|
370
|
+
if (!res.ok) return [];
|
|
371
|
+
const base = fromBinancePair(pair);
|
|
372
|
+
return [
|
|
373
|
+
{
|
|
374
|
+
id: pair,
|
|
375
|
+
name: base,
|
|
376
|
+
symbol: base,
|
|
231
377
|
source: this.name
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
} catch (e) {
|
|
378
|
+
}
|
|
379
|
+
];
|
|
380
|
+
} catch {
|
|
236
381
|
return [];
|
|
237
382
|
}
|
|
238
383
|
}
|
|
239
384
|
async getPrice(symbol) {
|
|
240
385
|
try {
|
|
241
|
-
|
|
242
|
-
if (!querySymbol.endsWith("USDT") && !querySymbol.endsWith("BUSD") && !querySymbol.endsWith("BTC")) {
|
|
243
|
-
querySymbol += "USDT";
|
|
244
|
-
}
|
|
386
|
+
const querySymbol = toBinancePair(symbol);
|
|
245
387
|
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${querySymbol}`);
|
|
246
388
|
if (!res.ok) return null;
|
|
247
389
|
const data = await res.json();
|
|
390
|
+
if (!data.price) return null;
|
|
248
391
|
return {
|
|
249
|
-
symbol:
|
|
392
|
+
symbol: fromBinancePair(querySymbol),
|
|
250
393
|
priceUsd: parseFloat(data.price) || 0,
|
|
251
394
|
source: this.name,
|
|
252
395
|
lastUpdated: Date.now()
|
|
253
396
|
};
|
|
254
|
-
} catch
|
|
397
|
+
} catch {
|
|
255
398
|
return null;
|
|
256
399
|
}
|
|
257
400
|
}
|
|
258
401
|
async getTokenInfo(symbol) {
|
|
259
402
|
try {
|
|
260
|
-
|
|
261
|
-
if (!querySymbol.endsWith("USDT") && !querySymbol.endsWith("BUSD") && !querySymbol.endsWith("BTC")) {
|
|
262
|
-
querySymbol += "USDT";
|
|
263
|
-
}
|
|
403
|
+
const querySymbol = toBinancePair(symbol);
|
|
264
404
|
const res = await fetch(`${this.baseUrl}/ticker/24hr?symbol=${querySymbol}`);
|
|
265
405
|
if (!res.ok) return null;
|
|
266
406
|
const data = await res.json();
|
|
407
|
+
if (!data.lastPrice) return null;
|
|
408
|
+
const base = fromBinancePair(querySymbol);
|
|
267
409
|
return {
|
|
268
|
-
name:
|
|
269
|
-
symbol:
|
|
410
|
+
name: base,
|
|
411
|
+
symbol: base,
|
|
270
412
|
priceUsd: parseFloat(data.lastPrice) || 0,
|
|
271
413
|
volume24h: parseFloat(data.quoteVolume) || 0,
|
|
272
|
-
|
|
414
|
+
priceChangePercentage24h: finiteOrUndefined(data.priceChangePercent),
|
|
273
415
|
source: this.name,
|
|
274
416
|
lastUpdated: Date.now()
|
|
275
417
|
};
|
|
276
|
-
} catch
|
|
418
|
+
} catch {
|
|
277
419
|
return null;
|
|
278
420
|
}
|
|
279
421
|
}
|
|
@@ -282,10 +424,13 @@ var BinanceProvider = class {
|
|
|
282
424
|
// src/providers/coinpaprika.ts
|
|
283
425
|
var CoinpaprikaProvider = class {
|
|
284
426
|
name = "Coinpaprika";
|
|
427
|
+
addressBased = false;
|
|
285
428
|
baseUrl = "https://api.coinpaprika.com/v1";
|
|
286
429
|
async search(query) {
|
|
287
430
|
try {
|
|
288
|
-
const res = await fetch(
|
|
431
|
+
const res = await fetch(
|
|
432
|
+
`${this.baseUrl}/search?q=${encodeURIComponent(query)}&c=currencies&limit=10`
|
|
433
|
+
);
|
|
289
434
|
if (!res.ok) return [];
|
|
290
435
|
const data = await res.json();
|
|
291
436
|
return (data.currencies || []).map((coin) => ({
|
|
@@ -294,7 +439,7 @@ var CoinpaprikaProvider = class {
|
|
|
294
439
|
symbol: coin.symbol.toUpperCase(),
|
|
295
440
|
source: this.name
|
|
296
441
|
}));
|
|
297
|
-
} catch
|
|
442
|
+
} catch {
|
|
298
443
|
return [];
|
|
299
444
|
}
|
|
300
445
|
}
|
|
@@ -310,19 +455,18 @@ var CoinpaprikaProvider = class {
|
|
|
310
455
|
}
|
|
311
456
|
async getTokenInfo(symbolOrId) {
|
|
312
457
|
try {
|
|
313
|
-
let id = symbolOrId.toLowerCase();
|
|
458
|
+
let id = symbolOrId.toLowerCase().trim();
|
|
459
|
+
if (!id) return null;
|
|
314
460
|
if (!id.includes("-")) {
|
|
315
461
|
const searchRes = await this.search(id);
|
|
316
|
-
const match = searchRes.find((s) => s.symbol.toLowerCase() === id || s.id === id);
|
|
317
|
-
if (match
|
|
318
|
-
|
|
319
|
-
} else if (searchRes.length > 0 && searchRes[0].id) {
|
|
320
|
-
id = searchRes[0].id;
|
|
321
|
-
}
|
|
462
|
+
const match = searchRes.find((s) => s.symbol.toLowerCase() === id) || searchRes.find((s) => s.id === id);
|
|
463
|
+
if (!match?.id) return null;
|
|
464
|
+
id = match.id;
|
|
322
465
|
}
|
|
323
|
-
const res = await fetch(`${this.baseUrl}/tickers/${id}`);
|
|
466
|
+
const res = await fetch(`${this.baseUrl}/tickers/${encodeURIComponent(id)}`);
|
|
324
467
|
if (!res.ok) return null;
|
|
325
468
|
const data = await res.json();
|
|
469
|
+
if (!data.symbol) return null;
|
|
326
470
|
return {
|
|
327
471
|
name: data.name,
|
|
328
472
|
symbol: data.symbol.toUpperCase(),
|
|
@@ -347,7 +491,7 @@ var CoinpaprikaProvider = class {
|
|
|
347
491
|
source: this.name,
|
|
348
492
|
lastUpdated: Date.now()
|
|
349
493
|
};
|
|
350
|
-
} catch
|
|
494
|
+
} catch {
|
|
351
495
|
return null;
|
|
352
496
|
}
|
|
353
497
|
}
|
|
@@ -356,10 +500,13 @@ var CoinpaprikaProvider = class {
|
|
|
356
500
|
// src/providers/coincap.ts
|
|
357
501
|
var CoinCapProvider = class {
|
|
358
502
|
name = "CoinCap";
|
|
503
|
+
addressBased = false;
|
|
359
504
|
baseUrl = "https://api.coincap.io/v2";
|
|
360
505
|
async search(query) {
|
|
361
506
|
try {
|
|
362
|
-
const res = await fetch(
|
|
507
|
+
const res = await fetch(
|
|
508
|
+
`${this.baseUrl}/assets?search=${encodeURIComponent(query)}&limit=10`
|
|
509
|
+
);
|
|
363
510
|
if (!res.ok) return [];
|
|
364
511
|
const data = await res.json();
|
|
365
512
|
return (data.data || []).map((coin) => ({
|
|
@@ -368,7 +515,7 @@ var CoinCapProvider = class {
|
|
|
368
515
|
symbol: coin.symbol.toUpperCase(),
|
|
369
516
|
source: this.name
|
|
370
517
|
}));
|
|
371
|
-
} catch
|
|
518
|
+
} catch {
|
|
372
519
|
return [];
|
|
373
520
|
}
|
|
374
521
|
}
|
|
@@ -384,13 +531,14 @@ var CoinCapProvider = class {
|
|
|
384
531
|
}
|
|
385
532
|
async getTokenInfo(symbolOrId) {
|
|
386
533
|
try {
|
|
387
|
-
let id = symbolOrId.toLowerCase();
|
|
534
|
+
let id = symbolOrId.toLowerCase().trim();
|
|
535
|
+
if (!id) return null;
|
|
388
536
|
const searchRes = await this.search(id);
|
|
389
|
-
const match = searchRes.find((s) => s.symbol.toLowerCase() === id || s.id === id);
|
|
390
|
-
if (match
|
|
537
|
+
const match = searchRes.find((s) => s.symbol.toLowerCase() === id) || searchRes.find((s) => s.id === id);
|
|
538
|
+
if (match?.id) {
|
|
391
539
|
id = match.id;
|
|
392
540
|
}
|
|
393
|
-
const res = await fetch(`${this.baseUrl}/assets/${id}`);
|
|
541
|
+
const res = await fetch(`${this.baseUrl}/assets/${encodeURIComponent(id)}`);
|
|
394
542
|
if (!res.ok) return null;
|
|
395
543
|
const data = await res.json();
|
|
396
544
|
const asset = data.data;
|
|
@@ -399,12 +547,13 @@ var CoinCapProvider = class {
|
|
|
399
547
|
name: asset.name,
|
|
400
548
|
symbol: asset.symbol.toUpperCase(),
|
|
401
549
|
priceUsd: parseFloat(asset.priceUsd) || 0,
|
|
402
|
-
marketCap: parseFloat(asset.marketCapUsd),
|
|
403
|
-
volume24h: parseFloat(asset.volumeUsd24Hr),
|
|
550
|
+
marketCap: parseFloat(asset.marketCapUsd) || void 0,
|
|
551
|
+
volume24h: parseFloat(asset.volumeUsd24Hr) || void 0,
|
|
552
|
+
priceChangePercentage24h: parseFloat(asset.changePercent24Hr) || void 0,
|
|
404
553
|
source: this.name,
|
|
405
554
|
lastUpdated: Date.now()
|
|
406
555
|
};
|
|
407
|
-
} catch
|
|
556
|
+
} catch {
|
|
408
557
|
return null;
|
|
409
558
|
}
|
|
410
559
|
}
|
|
@@ -413,151 +562,716 @@ var CoinCapProvider = class {
|
|
|
413
562
|
// src/providers/cryptocompare.ts
|
|
414
563
|
var CryptoCompareProvider = class {
|
|
415
564
|
name = "CryptoCompare";
|
|
565
|
+
addressBased = false;
|
|
416
566
|
baseUrl = "https://min-api.cryptocompare.com/data";
|
|
417
|
-
|
|
567
|
+
// CryptoCompare coin list is heavy; search is handled by other providers.
|
|
568
|
+
async search(_query) {
|
|
418
569
|
return [];
|
|
419
570
|
}
|
|
420
571
|
async getPrice(symbol) {
|
|
421
572
|
try {
|
|
422
|
-
const sym = symbol.toUpperCase();
|
|
423
|
-
|
|
573
|
+
const sym = symbol.toUpperCase().trim();
|
|
574
|
+
if (!sym) return null;
|
|
575
|
+
const res = await fetch(
|
|
576
|
+
`${this.baseUrl}/price?fsym=${encodeURIComponent(sym)}&tsyms=USD`
|
|
577
|
+
);
|
|
424
578
|
if (!res.ok) return null;
|
|
425
579
|
const data = await res.json();
|
|
426
|
-
if (data.Response === "Error" ||
|
|
580
|
+
if (data.Response === "Error" || typeof data.USD !== "number") return null;
|
|
427
581
|
return {
|
|
428
582
|
symbol: sym,
|
|
429
583
|
priceUsd: data.USD,
|
|
430
584
|
source: this.name,
|
|
431
585
|
lastUpdated: Date.now()
|
|
432
586
|
};
|
|
433
|
-
} catch
|
|
587
|
+
} catch {
|
|
434
588
|
return null;
|
|
435
589
|
}
|
|
436
590
|
}
|
|
437
591
|
async getTokenInfo(symbol) {
|
|
438
592
|
try {
|
|
439
|
-
const sym = symbol.toUpperCase();
|
|
440
|
-
|
|
593
|
+
const sym = symbol.toUpperCase().trim();
|
|
594
|
+
if (!sym) return null;
|
|
595
|
+
const res = await fetch(
|
|
596
|
+
`${this.baseUrl}/pricemultifull?fsyms=${encodeURIComponent(sym)}&tsyms=USD`
|
|
597
|
+
);
|
|
441
598
|
if (!res.ok) return null;
|
|
442
599
|
const data = await res.json();
|
|
443
|
-
|
|
444
|
-
|
|
600
|
+
const raw = data.RAW?.[sym]?.USD;
|
|
601
|
+
if (!raw) return null;
|
|
445
602
|
return {
|
|
446
603
|
name: sym,
|
|
447
|
-
// CryptoCompare doesn't give full name in this endpoint
|
|
448
604
|
symbol: sym,
|
|
449
605
|
priceUsd: raw.PRICE || 0,
|
|
450
606
|
marketCap: raw.MKTCAP,
|
|
451
607
|
volume24h: raw.TOTALVOLUME24HTO,
|
|
608
|
+
priceChangePercentage24h: raw.CHANGEPCT24HOUR,
|
|
609
|
+
source: this.name,
|
|
610
|
+
lastUpdated: Date.now()
|
|
611
|
+
};
|
|
612
|
+
} catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/providers/coinmarketcap.ts
|
|
619
|
+
var CoinMarketCapProvider = class {
|
|
620
|
+
name = "CoinMarketCap";
|
|
621
|
+
addressBased = false;
|
|
622
|
+
baseUrl = "https://pro-api.coinmarketcap.com/public-api";
|
|
623
|
+
headers = {
|
|
624
|
+
Accept: "application/json",
|
|
625
|
+
"User-Agent": "crypull"
|
|
626
|
+
};
|
|
627
|
+
async search(query) {
|
|
628
|
+
try {
|
|
629
|
+
const q = query.trim();
|
|
630
|
+
if (!q) return [];
|
|
631
|
+
const res = await fetch(
|
|
632
|
+
`${this.baseUrl}/v1/cryptocurrency/map?symbol=${encodeURIComponent(q.toUpperCase())}`,
|
|
633
|
+
{ headers: this.headers }
|
|
634
|
+
);
|
|
635
|
+
if (!res.ok) return [];
|
|
636
|
+
const data = await res.json();
|
|
637
|
+
const list = (data.data || []).filter((c) => c.is_active === 1).sort((a, b) => (a.rank || 999999) - (b.rank || 999999)).slice(0, 10);
|
|
638
|
+
return list.map((c) => ({
|
|
639
|
+
id: c.slug || String(c.id),
|
|
640
|
+
name: c.name,
|
|
641
|
+
symbol: c.symbol.toUpperCase(),
|
|
642
|
+
source: this.name
|
|
643
|
+
}));
|
|
644
|
+
} catch {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async getPrice(symbol) {
|
|
649
|
+
const info = await this.getTokenInfo(symbol);
|
|
650
|
+
if (!info) return null;
|
|
651
|
+
return {
|
|
652
|
+
symbol: info.symbol,
|
|
653
|
+
priceUsd: info.priceUsd,
|
|
654
|
+
source: this.name,
|
|
655
|
+
lastUpdated: info.lastUpdated
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async getTokenInfo(symbolOrSlug) {
|
|
659
|
+
try {
|
|
660
|
+
const q = symbolOrSlug.trim();
|
|
661
|
+
if (!q) return null;
|
|
662
|
+
let url = `${this.baseUrl}/v3/cryptocurrency/quotes/latest?symbol=${encodeURIComponent(q.toUpperCase())}`;
|
|
663
|
+
let res = await fetch(url, { headers: this.headers });
|
|
664
|
+
let data = res.ok ? await res.json() : null;
|
|
665
|
+
if (!data?.data?.length) {
|
|
666
|
+
url = `${this.baseUrl}/v3/cryptocurrency/quotes/latest?slug=${encodeURIComponent(q.toLowerCase())}`;
|
|
667
|
+
res = await fetch(url, { headers: this.headers });
|
|
668
|
+
if (!res.ok) return null;
|
|
669
|
+
data = await res.json();
|
|
670
|
+
}
|
|
671
|
+
const coin = data?.data?.[0];
|
|
672
|
+
if (!coin) return null;
|
|
673
|
+
const quote = Array.isArray(coin.quote) ? coin.quote.find((x) => x.symbol === "USD") || coin.quote[0] : coin.quote?.USD;
|
|
674
|
+
if (!quote || typeof quote.price !== "number") return null;
|
|
675
|
+
return {
|
|
676
|
+
name: coin.name,
|
|
677
|
+
symbol: coin.symbol.toUpperCase(),
|
|
678
|
+
priceUsd: quote.price,
|
|
679
|
+
marketCap: quote.market_cap,
|
|
680
|
+
marketCapRank: coin.cmc_rank,
|
|
681
|
+
fdv: quote.fully_diluted_market_cap,
|
|
682
|
+
circulatingSupply: coin.circulating_supply,
|
|
683
|
+
totalSupply: coin.total_supply,
|
|
684
|
+
maxSupply: coin.max_supply ?? void 0,
|
|
685
|
+
volume24h: quote.volume_24h,
|
|
686
|
+
priceChangePercentage24h: quote.percent_change_24h,
|
|
687
|
+
priceChangePercentage7d: quote.percent_change_7d,
|
|
688
|
+
source: this.name,
|
|
689
|
+
lastUpdated: quote.last_updated ? Date.parse(quote.last_updated) : Date.now()
|
|
690
|
+
};
|
|
691
|
+
} catch {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// src/providers/coinlore.ts
|
|
698
|
+
var CoinLoreProvider = class {
|
|
699
|
+
name = "CoinLore";
|
|
700
|
+
addressBased = false;
|
|
701
|
+
baseUrl = "https://api.coinlore.net/api";
|
|
702
|
+
/** Scan top tickers pages to resolve symbol → coin id. */
|
|
703
|
+
async findTicker(query) {
|
|
704
|
+
const q = query.toLowerCase().trim();
|
|
705
|
+
if (!q) return null;
|
|
706
|
+
for (let start = 0; start < 300; start += 100) {
|
|
707
|
+
const res = await fetch(
|
|
708
|
+
`${this.baseUrl}/tickers/?start=${start}&limit=100`
|
|
709
|
+
);
|
|
710
|
+
if (!res.ok) return null;
|
|
711
|
+
const data = await res.json();
|
|
712
|
+
const list = data.data || [];
|
|
713
|
+
if (!list.length) break;
|
|
714
|
+
const exact = list.find((c) => c.symbol?.toLowerCase() === q) || list.find((c) => c.nameid?.toLowerCase() === q) || list.find((c) => c.name?.toLowerCase() === q);
|
|
715
|
+
if (exact) return exact;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
async search(query) {
|
|
720
|
+
try {
|
|
721
|
+
const q = query.toLowerCase().trim();
|
|
722
|
+
if (!q) return [];
|
|
723
|
+
const matches = [];
|
|
724
|
+
for (let start = 0; start < 200; start += 100) {
|
|
725
|
+
const res = await fetch(
|
|
726
|
+
`${this.baseUrl}/tickers/?start=${start}&limit=100`
|
|
727
|
+
);
|
|
728
|
+
if (!res.ok) break;
|
|
729
|
+
const data = await res.json();
|
|
730
|
+
const list = data.data || [];
|
|
731
|
+
if (!list.length) break;
|
|
732
|
+
for (const c of list) {
|
|
733
|
+
const sym = (c.symbol || "").toLowerCase();
|
|
734
|
+
const name = (c.name || "").toLowerCase();
|
|
735
|
+
const nameid = (c.nameid || "").toLowerCase();
|
|
736
|
+
if (sym.includes(q) || name.includes(q) || nameid.includes(q)) {
|
|
737
|
+
matches.push({
|
|
738
|
+
id: c.id,
|
|
739
|
+
name: c.name,
|
|
740
|
+
symbol: c.symbol.toUpperCase(),
|
|
741
|
+
source: this.name
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (matches.length >= 10) return matches;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return matches;
|
|
748
|
+
} catch {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async getPrice(symbol) {
|
|
753
|
+
const info = await this.getTokenInfo(symbol);
|
|
754
|
+
if (!info) return null;
|
|
755
|
+
return {
|
|
756
|
+
symbol: info.symbol,
|
|
757
|
+
priceUsd: info.priceUsd,
|
|
758
|
+
source: this.name,
|
|
759
|
+
lastUpdated: info.lastUpdated
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async getTokenInfo(symbolOrId) {
|
|
763
|
+
try {
|
|
764
|
+
const q = symbolOrId.trim();
|
|
765
|
+
if (!q) return null;
|
|
766
|
+
let ticker = null;
|
|
767
|
+
if (/^\d+$/.test(q)) {
|
|
768
|
+
const res = await fetch(`${this.baseUrl}/ticker/?id=${q}`);
|
|
769
|
+
if (res.ok) {
|
|
770
|
+
const data = await res.json();
|
|
771
|
+
ticker = Array.isArray(data) ? data[0] : null;
|
|
772
|
+
}
|
|
773
|
+
} else {
|
|
774
|
+
ticker = await this.findTicker(q);
|
|
775
|
+
}
|
|
776
|
+
if (!ticker) return null;
|
|
777
|
+
return {
|
|
778
|
+
name: ticker.name,
|
|
779
|
+
symbol: ticker.symbol.toUpperCase(),
|
|
780
|
+
priceUsd: parseFloat(ticker.price_usd) || 0,
|
|
781
|
+
marketCap: finiteOrUndefined(ticker.market_cap_usd),
|
|
782
|
+
marketCapRank: finiteOrUndefined(ticker.rank),
|
|
783
|
+
circulatingSupply: finiteOrUndefined(ticker.csupply),
|
|
784
|
+
totalSupply: finiteOrUndefined(ticker.tsupply),
|
|
785
|
+
maxSupply: finiteOrUndefined(ticker.msupply),
|
|
786
|
+
volume24h: finiteOrUndefined(ticker.volume24),
|
|
787
|
+
priceChangePercentage24h: finiteOrUndefined(ticker.percent_change_24h),
|
|
788
|
+
priceChangePercentage7d: finiteOrUndefined(ticker.percent_change_7d),
|
|
789
|
+
source: this.name,
|
|
790
|
+
lastUpdated: Date.now()
|
|
791
|
+
};
|
|
792
|
+
} catch {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// src/providers/coinbase.ts
|
|
799
|
+
var CoinbaseProvider = class {
|
|
800
|
+
name = "Coinbase";
|
|
801
|
+
addressBased = false;
|
|
802
|
+
baseUrl = "https://api.coinbase.com/v2";
|
|
803
|
+
async search(query) {
|
|
804
|
+
try {
|
|
805
|
+
const sym = query.toUpperCase().trim();
|
|
806
|
+
if (!sym) return [];
|
|
807
|
+
const res = await fetch(`${this.baseUrl}/prices/${encodeURIComponent(sym)}-USD/spot`);
|
|
808
|
+
if (!res.ok) return [];
|
|
809
|
+
const data = await res.json();
|
|
810
|
+
if (!data.data?.amount) return [];
|
|
811
|
+
return [
|
|
812
|
+
{
|
|
813
|
+
id: sym,
|
|
814
|
+
name: sym,
|
|
815
|
+
symbol: sym,
|
|
816
|
+
source: this.name
|
|
817
|
+
}
|
|
818
|
+
];
|
|
819
|
+
} catch {
|
|
820
|
+
return [];
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async getPrice(symbol) {
|
|
824
|
+
try {
|
|
825
|
+
const sym = symbol.toUpperCase().trim();
|
|
826
|
+
if (!sym) return null;
|
|
827
|
+
const res = await fetch(
|
|
828
|
+
`${this.baseUrl}/prices/${encodeURIComponent(sym)}-USD/spot`
|
|
829
|
+
);
|
|
830
|
+
if (!res.ok) return null;
|
|
831
|
+
const data = await res.json();
|
|
832
|
+
const amount = parseFloat(data.data?.amount);
|
|
833
|
+
if (!amount) return null;
|
|
834
|
+
return {
|
|
835
|
+
symbol: sym,
|
|
836
|
+
priceUsd: amount,
|
|
837
|
+
source: this.name,
|
|
838
|
+
lastUpdated: Date.now()
|
|
839
|
+
};
|
|
840
|
+
} catch {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async getTokenInfo(symbol) {
|
|
845
|
+
const price = await this.getPrice(symbol);
|
|
846
|
+
if (!price) return null;
|
|
847
|
+
return {
|
|
848
|
+
name: price.symbol,
|
|
849
|
+
symbol: price.symbol,
|
|
850
|
+
priceUsd: price.priceUsd,
|
|
851
|
+
source: this.name,
|
|
852
|
+
lastUpdated: price.lastUpdated
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/providers/kraken.ts
|
|
858
|
+
var KrakenProvider = class {
|
|
859
|
+
name = "Kraken";
|
|
860
|
+
addressBased = false;
|
|
861
|
+
baseUrl = "https://api.kraken.com/0/public";
|
|
862
|
+
toPair(symbol) {
|
|
863
|
+
let base = symbol.toUpperCase().trim();
|
|
864
|
+
if (base.endsWith("USDT") && base.length > 4) {
|
|
865
|
+
base = base.slice(0, -4);
|
|
866
|
+
} else if (base.endsWith("USD") && base.length >= 6) {
|
|
867
|
+
base = base.slice(0, -3);
|
|
868
|
+
}
|
|
869
|
+
if (base === "BTC") base = "XBT";
|
|
870
|
+
return `${base}USD`;
|
|
871
|
+
}
|
|
872
|
+
fromPairKey(pairKey, fallback) {
|
|
873
|
+
const key = pairKey.toUpperCase();
|
|
874
|
+
if (key.includes("XBT")) return "BTC";
|
|
875
|
+
const base = fallback.toUpperCase().trim().replace(/USDT?$/, "");
|
|
876
|
+
return base === "XBT" ? "BTC" : base || key;
|
|
877
|
+
}
|
|
878
|
+
async search(query) {
|
|
879
|
+
const price = await this.getPrice(query);
|
|
880
|
+
if (!price) return [];
|
|
881
|
+
return [
|
|
882
|
+
{
|
|
883
|
+
id: price.symbol,
|
|
884
|
+
name: price.symbol,
|
|
885
|
+
symbol: price.symbol,
|
|
886
|
+
source: this.name
|
|
887
|
+
}
|
|
888
|
+
];
|
|
889
|
+
}
|
|
890
|
+
async getPrice(symbol) {
|
|
891
|
+
try {
|
|
892
|
+
const pair = this.toPair(symbol);
|
|
893
|
+
const res = await fetch(
|
|
894
|
+
`${this.baseUrl}/Ticker?pair=${encodeURIComponent(pair)}`
|
|
895
|
+
);
|
|
896
|
+
if (!res.ok) return null;
|
|
897
|
+
const data = await res.json();
|
|
898
|
+
if (data.error?.length || !data.result) return null;
|
|
899
|
+
const keys = Object.keys(data.result);
|
|
900
|
+
if (!keys.length) return null;
|
|
901
|
+
const ticker = data.result[keys[0]];
|
|
902
|
+
const last = parseFloat(ticker.c?.[0]);
|
|
903
|
+
if (!last) return null;
|
|
904
|
+
return {
|
|
905
|
+
symbol: this.fromPairKey(keys[0], symbol),
|
|
906
|
+
priceUsd: last,
|
|
907
|
+
source: this.name,
|
|
908
|
+
lastUpdated: Date.now()
|
|
909
|
+
};
|
|
910
|
+
} catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async getTokenInfo(symbol) {
|
|
915
|
+
try {
|
|
916
|
+
const pair = this.toPair(symbol);
|
|
917
|
+
const res = await fetch(
|
|
918
|
+
`${this.baseUrl}/Ticker?pair=${encodeURIComponent(pair)}`
|
|
919
|
+
);
|
|
920
|
+
if (!res.ok) return null;
|
|
921
|
+
const data = await res.json();
|
|
922
|
+
if (data.error?.length || !data.result) return null;
|
|
923
|
+
const keys = Object.keys(data.result);
|
|
924
|
+
if (!keys.length) return null;
|
|
925
|
+
const ticker = data.result[keys[0]];
|
|
926
|
+
const last = parseFloat(ticker.c?.[0]);
|
|
927
|
+
if (!last) return null;
|
|
928
|
+
const vol = parseFloat(ticker.v?.[1]);
|
|
929
|
+
const vwap = parseFloat(ticker.p?.[1]);
|
|
930
|
+
const sym = this.fromPairKey(keys[0], symbol);
|
|
931
|
+
return {
|
|
932
|
+
name: sym,
|
|
933
|
+
symbol: sym,
|
|
934
|
+
priceUsd: last,
|
|
935
|
+
volume24h: vol && vwap ? vol * vwap : void 0,
|
|
452
936
|
source: this.name,
|
|
453
937
|
lastUpdated: Date.now()
|
|
454
938
|
};
|
|
455
|
-
} catch
|
|
939
|
+
} catch {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
// src/providers/kucoin.ts
|
|
946
|
+
var KuCoinProvider = class {
|
|
947
|
+
name = "KuCoin";
|
|
948
|
+
addressBased = false;
|
|
949
|
+
baseUrl = "https://api.kucoin.com/api/v1";
|
|
950
|
+
toSymbol(symbol) {
|
|
951
|
+
const upper = symbol.toUpperCase().trim();
|
|
952
|
+
if (upper.includes("-")) return upper;
|
|
953
|
+
if (upper.endsWith("USDT") && upper.length > 4) {
|
|
954
|
+
return `${upper.slice(0, -4)}-USDT`;
|
|
955
|
+
}
|
|
956
|
+
return `${upper}-USDT`;
|
|
957
|
+
}
|
|
958
|
+
baseAsset(pair) {
|
|
959
|
+
return pair.split("-")[0] || pair;
|
|
960
|
+
}
|
|
961
|
+
async search(query) {
|
|
962
|
+
const price = await this.getPrice(query);
|
|
963
|
+
if (!price) return [];
|
|
964
|
+
return [
|
|
965
|
+
{
|
|
966
|
+
id: `${price.symbol}-USDT`,
|
|
967
|
+
name: price.symbol,
|
|
968
|
+
symbol: price.symbol,
|
|
969
|
+
source: this.name
|
|
970
|
+
}
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
async getPrice(symbol) {
|
|
974
|
+
try {
|
|
975
|
+
const pair = this.toSymbol(symbol);
|
|
976
|
+
const res = await fetch(
|
|
977
|
+
`${this.baseUrl}/market/orderbook/level1?symbol=${encodeURIComponent(pair)}`
|
|
978
|
+
);
|
|
979
|
+
if (!res.ok) return null;
|
|
980
|
+
const data = await res.json();
|
|
981
|
+
if (data.code !== "200000" || !data.data?.price) return null;
|
|
982
|
+
return {
|
|
983
|
+
symbol: this.baseAsset(pair),
|
|
984
|
+
priceUsd: parseFloat(data.data.price) || 0,
|
|
985
|
+
source: this.name,
|
|
986
|
+
lastUpdated: Date.now()
|
|
987
|
+
};
|
|
988
|
+
} catch {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async getTokenInfo(symbol) {
|
|
993
|
+
try {
|
|
994
|
+
const pair = this.toSymbol(symbol);
|
|
995
|
+
const res = await fetch(
|
|
996
|
+
`${this.baseUrl}/market/stats?symbol=${encodeURIComponent(pair)}`
|
|
997
|
+
);
|
|
998
|
+
if (!res.ok) return null;
|
|
999
|
+
const data = await res.json();
|
|
1000
|
+
if (data.code !== "200000" || !data.data?.last) return null;
|
|
1001
|
+
const d = data.data;
|
|
1002
|
+
return {
|
|
1003
|
+
name: this.baseAsset(pair),
|
|
1004
|
+
symbol: this.baseAsset(pair),
|
|
1005
|
+
priceUsd: parseFloat(d.last) || 0,
|
|
1006
|
+
volume24h: finiteOrUndefined(d.volValue),
|
|
1007
|
+
priceChangePercentage24h: (() => {
|
|
1008
|
+
const rate = finiteOrUndefined(d.changeRate);
|
|
1009
|
+
return rate === void 0 ? void 0 : rate * 100;
|
|
1010
|
+
})(),
|
|
1011
|
+
source: this.name,
|
|
1012
|
+
lastUpdated: Date.now()
|
|
1013
|
+
};
|
|
1014
|
+
} catch {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
// src/providers/bybit.ts
|
|
1021
|
+
var BybitProvider = class {
|
|
1022
|
+
name = "Bybit";
|
|
1023
|
+
addressBased = false;
|
|
1024
|
+
baseUrl = "https://api.bybit.com/v5/market";
|
|
1025
|
+
async search(query) {
|
|
1026
|
+
const price = await this.getPrice(query);
|
|
1027
|
+
if (!price) return [];
|
|
1028
|
+
return [
|
|
1029
|
+
{
|
|
1030
|
+
id: toBinancePair(price.symbol),
|
|
1031
|
+
name: price.symbol,
|
|
1032
|
+
symbol: price.symbol,
|
|
1033
|
+
source: this.name
|
|
1034
|
+
}
|
|
1035
|
+
];
|
|
1036
|
+
}
|
|
1037
|
+
async getPrice(symbol) {
|
|
1038
|
+
try {
|
|
1039
|
+
const pair = toBinancePair(symbol);
|
|
1040
|
+
const res = await fetch(
|
|
1041
|
+
`${this.baseUrl}/tickers?category=spot&symbol=${encodeURIComponent(pair)}`
|
|
1042
|
+
);
|
|
1043
|
+
if (!res.ok) return null;
|
|
1044
|
+
const data = await res.json();
|
|
1045
|
+
const item = data.result?.list?.[0];
|
|
1046
|
+
if (!item?.lastPrice) return null;
|
|
1047
|
+
return {
|
|
1048
|
+
symbol: fromBinancePair(pair),
|
|
1049
|
+
priceUsd: parseFloat(item.lastPrice) || 0,
|
|
1050
|
+
source: this.name,
|
|
1051
|
+
lastUpdated: Date.now()
|
|
1052
|
+
};
|
|
1053
|
+
} catch {
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async getTokenInfo(symbol) {
|
|
1058
|
+
try {
|
|
1059
|
+
const pair = toBinancePair(symbol);
|
|
1060
|
+
const res = await fetch(
|
|
1061
|
+
`${this.baseUrl}/tickers?category=spot&symbol=${encodeURIComponent(pair)}`
|
|
1062
|
+
);
|
|
1063
|
+
if (!res.ok) return null;
|
|
1064
|
+
const data = await res.json();
|
|
1065
|
+
const item = data.result?.list?.[0];
|
|
1066
|
+
if (!item?.lastPrice) return null;
|
|
1067
|
+
const base = fromBinancePair(pair);
|
|
1068
|
+
return {
|
|
1069
|
+
name: base,
|
|
1070
|
+
symbol: base,
|
|
1071
|
+
priceUsd: parseFloat(item.lastPrice) || 0,
|
|
1072
|
+
volume24h: finiteOrUndefined(item.turnover24h),
|
|
1073
|
+
priceChangePercentage24h: (() => {
|
|
1074
|
+
const pct = finiteOrUndefined(item.price24hPcnt);
|
|
1075
|
+
return pct === void 0 ? void 0 : pct * 100;
|
|
1076
|
+
})(),
|
|
1077
|
+
source: this.name,
|
|
1078
|
+
lastUpdated: Date.now()
|
|
1079
|
+
};
|
|
1080
|
+
} catch {
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// src/providers/gateio.ts
|
|
1087
|
+
var GateIoProvider = class {
|
|
1088
|
+
name = "Gate.io";
|
|
1089
|
+
addressBased = false;
|
|
1090
|
+
baseUrl = "https://api.gateio.ws/api/v4";
|
|
1091
|
+
toPair(symbol) {
|
|
1092
|
+
const upper = symbol.toUpperCase().trim();
|
|
1093
|
+
if (upper.includes("_")) return upper;
|
|
1094
|
+
if (upper.endsWith("USDT") && upper.length > 4) {
|
|
1095
|
+
return `${upper.slice(0, -4)}_USDT`;
|
|
1096
|
+
}
|
|
1097
|
+
return `${upper}_USDT`;
|
|
1098
|
+
}
|
|
1099
|
+
baseAsset(pair) {
|
|
1100
|
+
return pair.split("_")[0] || pair;
|
|
1101
|
+
}
|
|
1102
|
+
async search(query) {
|
|
1103
|
+
const price = await this.getPrice(query);
|
|
1104
|
+
if (!price) return [];
|
|
1105
|
+
return [
|
|
1106
|
+
{
|
|
1107
|
+
id: this.toPair(price.symbol),
|
|
1108
|
+
name: price.symbol,
|
|
1109
|
+
symbol: price.symbol,
|
|
1110
|
+
source: this.name
|
|
1111
|
+
}
|
|
1112
|
+
];
|
|
1113
|
+
}
|
|
1114
|
+
async getPrice(symbol) {
|
|
1115
|
+
try {
|
|
1116
|
+
const pair = this.toPair(symbol);
|
|
1117
|
+
const res = await fetch(
|
|
1118
|
+
`${this.baseUrl}/spot/tickers?currency_pair=${encodeURIComponent(pair)}`
|
|
1119
|
+
);
|
|
1120
|
+
if (!res.ok) return null;
|
|
1121
|
+
const data = await res.json();
|
|
1122
|
+
const item = Array.isArray(data) ? data[0] : null;
|
|
1123
|
+
if (!item?.last) return null;
|
|
1124
|
+
return {
|
|
1125
|
+
symbol: this.baseAsset(pair),
|
|
1126
|
+
priceUsd: parseFloat(item.last) || 0,
|
|
1127
|
+
source: this.name,
|
|
1128
|
+
lastUpdated: Date.now()
|
|
1129
|
+
};
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
async getTokenInfo(symbol) {
|
|
1135
|
+
try {
|
|
1136
|
+
const pair = this.toPair(symbol);
|
|
1137
|
+
const res = await fetch(
|
|
1138
|
+
`${this.baseUrl}/spot/tickers?currency_pair=${encodeURIComponent(pair)}`
|
|
1139
|
+
);
|
|
1140
|
+
if (!res.ok) return null;
|
|
1141
|
+
const data = await res.json();
|
|
1142
|
+
const item = Array.isArray(data) ? data[0] : null;
|
|
1143
|
+
if (!item?.last) return null;
|
|
1144
|
+
const base = this.baseAsset(pair);
|
|
1145
|
+
return {
|
|
1146
|
+
name: base,
|
|
1147
|
+
symbol: base,
|
|
1148
|
+
priceUsd: parseFloat(item.last) || 0,
|
|
1149
|
+
volume24h: finiteOrUndefined(item.quote_volume),
|
|
1150
|
+
priceChangePercentage24h: finiteOrUndefined(item.change_percentage),
|
|
1151
|
+
source: this.name,
|
|
1152
|
+
lastUpdated: Date.now()
|
|
1153
|
+
};
|
|
1154
|
+
} catch {
|
|
456
1155
|
return null;
|
|
457
1156
|
}
|
|
458
1157
|
}
|
|
459
1158
|
};
|
|
460
1159
|
|
|
461
1160
|
// src/Crypull.ts
|
|
1161
|
+
function createDefaultProviders() {
|
|
1162
|
+
return [
|
|
1163
|
+
new BinanceProvider(),
|
|
1164
|
+
new BybitProvider(),
|
|
1165
|
+
new KuCoinProvider(),
|
|
1166
|
+
new GateIoProvider(),
|
|
1167
|
+
new CoinbaseProvider(),
|
|
1168
|
+
new KrakenProvider(),
|
|
1169
|
+
new CoinCapProvider(),
|
|
1170
|
+
new CoinMarketCapProvider(),
|
|
1171
|
+
new CoinGeckoProvider(),
|
|
1172
|
+
new CoinpaprikaProvider(),
|
|
1173
|
+
new CoinLoreProvider(),
|
|
1174
|
+
new CryptoCompareProvider(),
|
|
1175
|
+
new DexScreenerProvider(),
|
|
1176
|
+
new GeckoTerminalProvider()
|
|
1177
|
+
];
|
|
1178
|
+
}
|
|
462
1179
|
var Crypull = class {
|
|
463
1180
|
providers;
|
|
464
|
-
// Standard free providers
|
|
465
|
-
coinGecko = new CoinGeckoProvider();
|
|
466
|
-
dexScreener = new DexScreenerProvider();
|
|
467
|
-
geckoTerminal = new GeckoTerminalProvider();
|
|
468
|
-
binance = new BinanceProvider();
|
|
469
|
-
coinpaprika = new CoinpaprikaProvider();
|
|
470
|
-
coincap = new CoinCapProvider();
|
|
471
|
-
cryptoCompare = new CryptoCompareProvider();
|
|
472
1181
|
constructor(options) {
|
|
473
|
-
this.providers = options?.providers
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
this.cryptoCompare
|
|
481
|
-
];
|
|
1182
|
+
this.providers = options?.providers?.length ? options.providers : createDefaultProviders();
|
|
1183
|
+
}
|
|
1184
|
+
symbolProviders() {
|
|
1185
|
+
return this.providers.filter((p) => !p.addressBased);
|
|
1186
|
+
}
|
|
1187
|
+
addressProviders() {
|
|
1188
|
+
return this.providers.filter((p) => p.addressBased);
|
|
482
1189
|
}
|
|
483
|
-
|
|
484
|
-
return
|
|
1190
|
+
coinGecko() {
|
|
1191
|
+
return this.providers.find((p) => p instanceof CoinGeckoProvider);
|
|
485
1192
|
}
|
|
486
1193
|
/**
|
|
487
|
-
* Search for a token or coin across
|
|
1194
|
+
* Search for a token or coin across symbol + DEX providers in parallel.
|
|
488
1195
|
*/
|
|
489
1196
|
async search(query) {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
this.
|
|
494
|
-
|
|
495
|
-
];
|
|
496
|
-
const results = await Promise.allSettled(promises);
|
|
1197
|
+
const q = query.trim();
|
|
1198
|
+
if (!q) return [];
|
|
1199
|
+
const results = await Promise.allSettled(
|
|
1200
|
+
this.providers.map((p) => p.search(q))
|
|
1201
|
+
);
|
|
497
1202
|
const combined = [];
|
|
498
1203
|
for (const res of results) {
|
|
499
|
-
if (res.status === "fulfilled" && res.value) {
|
|
1204
|
+
if (res.status === "fulfilled" && res.value.length) {
|
|
500
1205
|
combined.push(...res.value);
|
|
501
1206
|
}
|
|
502
1207
|
}
|
|
503
1208
|
const seen = /* @__PURE__ */ new Set();
|
|
504
1209
|
return combined.filter((item) => {
|
|
505
|
-
const key = `${item.symbol}`;
|
|
1210
|
+
const key = item.id ? `${item.source}:${item.id}` : `${item.symbol}:${item.network ?? ""}:${item.source}`;
|
|
506
1211
|
if (seen.has(key)) return false;
|
|
507
1212
|
seen.add(key);
|
|
508
1213
|
return true;
|
|
509
1214
|
});
|
|
510
1215
|
}
|
|
511
1216
|
/**
|
|
512
|
-
* Get the current price of a cryptocurrency
|
|
513
|
-
* @param query
|
|
514
|
-
* @param network Optional network for DEX addresses (e.g
|
|
1217
|
+
* Get the current price of a cryptocurrency.
|
|
1218
|
+
* @param query Symbol (e.g. 'BTC') or contract address
|
|
1219
|
+
* @param network Optional network for DEX addresses (e.g. 'eth', 'bsc', 'solana')
|
|
515
1220
|
*/
|
|
516
1221
|
async price(query, network) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1222
|
+
const q = query.trim();
|
|
1223
|
+
if (!q) return null;
|
|
1224
|
+
if (isContractAddress(q)) {
|
|
1225
|
+
for (const provider of this.addressProviders()) {
|
|
1226
|
+
const net = network ?? (provider instanceof GeckoTerminalProvider ? defaultDexNetwork(q) : void 0);
|
|
1227
|
+
const res = await provider.getPrice(q, net);
|
|
522
1228
|
if (res) return res;
|
|
523
1229
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
res = await
|
|
528
|
-
if (res) return res;
|
|
529
|
-
res = await this.coinGecko.getPrice(query);
|
|
530
|
-
if (res) return res;
|
|
531
|
-
res = await this.coinpaprika.getPrice(query);
|
|
532
|
-
if (res) return res;
|
|
533
|
-
res = await this.cryptoCompare.getPrice(query);
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
for (const provider of this.symbolProviders()) {
|
|
1233
|
+
const res = await provider.getPrice(q);
|
|
534
1234
|
if (res) return res;
|
|
535
1235
|
}
|
|
536
1236
|
return null;
|
|
537
1237
|
}
|
|
538
1238
|
/**
|
|
539
1239
|
* Get detailed token info (market cap, volume, liquidity, etc.)
|
|
540
|
-
* @param query
|
|
1240
|
+
* @param query Symbol (e.g. 'BTC') or contract address
|
|
541
1241
|
* @param network Optional network for DEX addresses
|
|
542
1242
|
*/
|
|
543
1243
|
async info(query, network) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1244
|
+
const q = query.trim();
|
|
1245
|
+
if (!q) return null;
|
|
1246
|
+
if (isContractAddress(q)) {
|
|
1247
|
+
for (const provider of this.addressProviders()) {
|
|
1248
|
+
const net = network ?? (provider instanceof GeckoTerminalProvider ? defaultDexNetwork(q) : void 0);
|
|
1249
|
+
const res = await provider.getTokenInfo(q, net);
|
|
549
1250
|
if (res) return res;
|
|
550
1251
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
const preferred = [
|
|
1255
|
+
"CoinGecko",
|
|
1256
|
+
"CoinMarketCap",
|
|
1257
|
+
"Coinpaprika",
|
|
1258
|
+
"CoinLore",
|
|
1259
|
+
"CoinCap",
|
|
1260
|
+
"Binance",
|
|
1261
|
+
"Bybit",
|
|
1262
|
+
"KuCoin",
|
|
1263
|
+
"Gate.io",
|
|
1264
|
+
"Kraken",
|
|
1265
|
+
"Coinbase",
|
|
1266
|
+
"CryptoCompare"
|
|
1267
|
+
];
|
|
1268
|
+
const providers = [...this.symbolProviders()].sort((a, b) => {
|
|
1269
|
+
const ai = preferred.indexOf(a.name);
|
|
1270
|
+
const bi = preferred.indexOf(b.name);
|
|
1271
|
+
return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
|
|
1272
|
+
});
|
|
1273
|
+
for (const provider of providers) {
|
|
1274
|
+
const res = await provider.getTokenInfo(q);
|
|
561
1275
|
if (res) return res;
|
|
562
1276
|
}
|
|
563
1277
|
return null;
|
|
@@ -566,81 +1280,41 @@ var Crypull = class {
|
|
|
566
1280
|
* Get global market data
|
|
567
1281
|
*/
|
|
568
1282
|
async market() {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (!res.ok) return null;
|
|
572
|
-
const data = await res.json();
|
|
573
|
-
const d = data.data;
|
|
574
|
-
return {
|
|
575
|
-
totalMarketCapUsd: d.total_market_cap.usd,
|
|
576
|
-
totalVolume24hUsd: d.total_volume.usd,
|
|
577
|
-
bitcoinDominancePercentage: d.market_cap_percentage.btc,
|
|
578
|
-
ethereumDominancePercentage: d.market_cap_percentage.eth,
|
|
579
|
-
activeCryptocurrencies: d.active_cryptocurrencies,
|
|
580
|
-
lastUpdated: d.updated_at * 1e3
|
|
581
|
-
};
|
|
582
|
-
} catch (e) {
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
1283
|
+
const cg = this.coinGecko();
|
|
1284
|
+
return cg ? cg.getGlobalMarket() : null;
|
|
585
1285
|
}
|
|
586
1286
|
/**
|
|
587
1287
|
* Get trending coins
|
|
588
1288
|
*/
|
|
589
1289
|
async trending() {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const data = await res.json();
|
|
594
|
-
return data.coins.slice(0, 10).map((c) => ({
|
|
595
|
-
id: c.item.id,
|
|
596
|
-
name: c.item.name,
|
|
597
|
-
symbol: c.item.symbol.toUpperCase(),
|
|
598
|
-
marketCapRank: c.item.market_cap_rank,
|
|
599
|
-
priceUsd: c.item.data?.price,
|
|
600
|
-
priceChange24h: c.item.data?.price_change_percentage_24h?.usd,
|
|
601
|
-
volume24h: c.item.data?.total_volume
|
|
602
|
-
}));
|
|
603
|
-
} catch (e) {
|
|
604
|
-
return [];
|
|
605
|
-
}
|
|
1290
|
+
const cg = this.coinGecko();
|
|
1291
|
+
if (cg) return cg.getTrending();
|
|
1292
|
+
return [];
|
|
606
1293
|
}
|
|
607
1294
|
/**
|
|
608
1295
|
* Get top 50 coins by market cap
|
|
609
1296
|
*/
|
|
610
1297
|
async top() {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const data = await res.json();
|
|
615
|
-
return data.map((c) => ({
|
|
616
|
-
id: c.id,
|
|
617
|
-
name: c.name,
|
|
618
|
-
symbol: c.symbol.toUpperCase(),
|
|
619
|
-
marketCapRank: c.market_cap_rank,
|
|
620
|
-
priceUsd: c.current_price,
|
|
621
|
-
marketCap: c.market_cap,
|
|
622
|
-
volume24h: c.total_volume,
|
|
623
|
-
priceChange24h: c.price_change_percentage_24h
|
|
624
|
-
}));
|
|
625
|
-
} catch (e) {
|
|
626
|
-
return [];
|
|
627
|
-
}
|
|
1298
|
+
const cg = this.coinGecko();
|
|
1299
|
+
if (cg) return cg.getTop(50);
|
|
1300
|
+
return [];
|
|
628
1301
|
}
|
|
629
1302
|
/**
|
|
630
1303
|
* Get fear and greed index
|
|
631
1304
|
*/
|
|
632
1305
|
async sentiment() {
|
|
633
1306
|
try {
|
|
634
|
-
const res = await fetch(
|
|
1307
|
+
const res = await fetch("https://api.alternative.me/fng/?limit=1");
|
|
635
1308
|
if (!res.ok) return null;
|
|
636
1309
|
const data = await res.json();
|
|
637
|
-
const item = data.data[0];
|
|
1310
|
+
const item = data.data?.[0];
|
|
1311
|
+
if (!item) return null;
|
|
638
1312
|
return {
|
|
639
|
-
value: parseInt(item.value),
|
|
1313
|
+
value: parseInt(item.value, 10),
|
|
640
1314
|
classification: item.value_classification,
|
|
641
|
-
lastUpdated: parseInt(item.timestamp) * 1e3
|
|
1315
|
+
lastUpdated: parseInt(item.timestamp, 10) * 1e3
|
|
642
1316
|
};
|
|
643
|
-
} catch
|
|
1317
|
+
} catch {
|
|
644
1318
|
return null;
|
|
645
1319
|
}
|
|
646
1320
|
}
|
|
@@ -649,10 +1323,12 @@ var Crypull = class {
|
|
|
649
1323
|
*/
|
|
650
1324
|
async gas() {
|
|
651
1325
|
try {
|
|
652
|
-
const res = await fetch(
|
|
1326
|
+
const res = await fetch(
|
|
1327
|
+
"https://api.etherscan.io/v2/api?chainid=1&module=gastracker&action=gasoracle"
|
|
1328
|
+
);
|
|
653
1329
|
if (!res.ok) return null;
|
|
654
1330
|
const data = await res.json();
|
|
655
|
-
if (data.status !== "1") return null;
|
|
1331
|
+
if (data.status !== "1" || !data.result) return null;
|
|
656
1332
|
return {
|
|
657
1333
|
network: "Ethereum",
|
|
658
1334
|
low: parseFloat(data.result.SafeGasPrice),
|
|
@@ -660,38 +1336,19 @@ var Crypull = class {
|
|
|
660
1336
|
high: parseFloat(data.result.FastGasPrice),
|
|
661
1337
|
baseFee: parseFloat(data.result.suggestBaseFee)
|
|
662
1338
|
};
|
|
663
|
-
} catch
|
|
1339
|
+
} catch {
|
|
664
1340
|
return null;
|
|
665
1341
|
}
|
|
666
1342
|
}
|
|
667
1343
|
/**
|
|
668
1344
|
* Get historical chart data for a coin
|
|
669
|
-
* @param query Coin symbol or ID
|
|
1345
|
+
* @param query Coin symbol or CoinGecko ID
|
|
670
1346
|
* @param days Number of days (e.g. 1, 7, 30)
|
|
671
1347
|
*/
|
|
672
1348
|
async chart(query, days = 7) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const match = searchRes.find((s) => s.symbol.toLowerCase() === id || s.id === id);
|
|
677
|
-
if (match && match.id) {
|
|
678
|
-
id = match.id;
|
|
679
|
-
}
|
|
680
|
-
const res = await fetch(`https://api.coingecko.com/api/v3/coins/${id}/market_chart?vs_currency=usd&days=${days}`);
|
|
681
|
-
if (!res.ok) return null;
|
|
682
|
-
const data = await res.json();
|
|
683
|
-
if (!data.prices || data.prices.length === 0) return null;
|
|
684
|
-
const prices = data.prices.map((p) => p[1]);
|
|
685
|
-
const timestamps = data.prices.map((p) => p[0]);
|
|
686
|
-
return {
|
|
687
|
-
prices,
|
|
688
|
-
timestamps,
|
|
689
|
-
minPrice: Math.min(...prices),
|
|
690
|
-
maxPrice: Math.max(...prices)
|
|
691
|
-
};
|
|
692
|
-
} catch (e) {
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
1349
|
+
const cg = this.coinGecko();
|
|
1350
|
+
if (!cg) return null;
|
|
1351
|
+
return cg.getChart(query.trim(), days);
|
|
695
1352
|
}
|
|
696
1353
|
};
|
|
697
1354
|
|
|
@@ -699,13 +1356,25 @@ var Crypull = class {
|
|
|
699
1356
|
var crypull = new Crypull();
|
|
700
1357
|
|
|
701
1358
|
exports.BinanceProvider = BinanceProvider;
|
|
1359
|
+
exports.BybitProvider = BybitProvider;
|
|
702
1360
|
exports.CoinCapProvider = CoinCapProvider;
|
|
703
1361
|
exports.CoinGeckoProvider = CoinGeckoProvider;
|
|
1362
|
+
exports.CoinLoreProvider = CoinLoreProvider;
|
|
1363
|
+
exports.CoinMarketCapProvider = CoinMarketCapProvider;
|
|
1364
|
+
exports.CoinbaseProvider = CoinbaseProvider;
|
|
704
1365
|
exports.CoinpaprikaProvider = CoinpaprikaProvider;
|
|
705
1366
|
exports.CryptoCompareProvider = CryptoCompareProvider;
|
|
706
1367
|
exports.Crypull = Crypull;
|
|
707
1368
|
exports.DexScreenerProvider = DexScreenerProvider;
|
|
1369
|
+
exports.GateIoProvider = GateIoProvider;
|
|
708
1370
|
exports.GeckoTerminalProvider = GeckoTerminalProvider;
|
|
1371
|
+
exports.KrakenProvider = KrakenProvider;
|
|
1372
|
+
exports.KuCoinProvider = KuCoinProvider;
|
|
709
1373
|
exports.crypull = crypull;
|
|
1374
|
+
exports.defaultDexNetwork = defaultDexNetwork;
|
|
1375
|
+
exports.finiteOrUndefined = finiteOrUndefined;
|
|
1376
|
+
exports.fromBinancePair = fromBinancePair;
|
|
1377
|
+
exports.isContractAddress = isContractAddress;
|
|
1378
|
+
exports.toBinancePair = toBinancePair;
|
|
710
1379
|
//# sourceMappingURL=index.js.map
|
|
711
1380
|
//# sourceMappingURL=index.js.map
|