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.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(`${this.baseUrl}/search?query=${query}`);
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 (e) {
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
- let id = symbolOrId.toLowerCase();
25
- const searchRes = await this.search(id);
26
- const match = searchRes.find((s) => s.symbol.toLowerCase() === id || s.id === id);
27
- if (match && match.id) {
28
- id = match.id;
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") return null;
45
+ if (!data[resolved.id] || typeof data[resolved.id].usd === "undefined") {
46
+ return null;
47
+ }
34
48
  return {
35
- symbol: match ? match.symbol : id.toUpperCase(),
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 (e) {
54
+ } catch {
41
55
  return null;
42
56
  }
43
57
  }
44
- async getTokenInfo(id) {
58
+ async getTokenInfo(symbolOrId) {
45
59
  try {
46
- const res = await fetch(`${this.baseUrl}/coins/${id.toLowerCase()}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`);
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((url) => url.includes("discord")),
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 (e) {
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(`${this.baseUrl}/search?q=${query}`);
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 (e) {
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(`${this.baseUrl}/tokens/${address}`);
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 || data.pairs.length === 0) return null;
121
- const pair = data.pairs[0];
122
- const mappedPairs = data.pairs.slice(0, 10).map((p) => ({
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 (e) {
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(`${this.baseUrl}/search/pools?query=${query}`);
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.network.data.id,
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 (e) {
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(`${this.baseUrl}/networks/${network}/tokens/${address}`);
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 || !json.data.attributes) return null;
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 (e) {
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 symbol = query.toUpperCase();
225
- const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${symbol}USDT`);
226
- if (res.ok) {
227
- return [{
228
- id: `${symbol}USDT`,
229
- name: symbol,
230
- symbol,
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
- return [];
235
- } catch (e) {
378
+ }
379
+ ];
380
+ } catch {
236
381
  return [];
237
382
  }
238
383
  }
239
384
  async getPrice(symbol) {
240
385
  try {
241
- let querySymbol = symbol.toUpperCase();
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: symbol.toUpperCase(),
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 (e) {
397
+ } catch {
255
398
  return null;
256
399
  }
257
400
  }
258
401
  async getTokenInfo(symbol) {
259
402
  try {
260
- let querySymbol = symbol.toUpperCase();
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: symbol.toUpperCase(),
269
- symbol: symbol.toUpperCase(),
410
+ name: base,
411
+ symbol: base,
270
412
  priceUsd: parseFloat(data.lastPrice) || 0,
271
413
  volume24h: parseFloat(data.quoteVolume) || 0,
272
- // Quote volume in USDT roughly = USD volume
414
+ priceChangePercentage24h: finiteOrUndefined(data.priceChangePercent),
273
415
  source: this.name,
274
416
  lastUpdated: Date.now()
275
417
  };
276
- } catch (e) {
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(`${this.baseUrl}/search?q=${query}&c=currencies&limit=10`);
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 (e) {
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 && match.id) {
318
- id = match.id;
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 (e) {
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(`${this.baseUrl}/assets?search=${query}&limit=10`);
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 (e) {
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 && match.id) {
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 (e) {
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
- async search(query) {
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
- const res = await fetch(`${this.baseUrl}/price?fsym=${sym}&tsyms=USD`);
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" || !data.USD) return null;
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 (e) {
587
+ } catch {
434
588
  return null;
435
589
  }
436
590
  }
437
591
  async getTokenInfo(symbol) {
438
592
  try {
439
- const sym = symbol.toUpperCase();
440
- const res = await fetch(`${this.baseUrl}/pricemultifull?fsyms=${sym}&tsyms=USD`);
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
- if (!data.RAW || !data.RAW[sym] || !data.RAW[sym].USD) return null;
444
- const raw = data.RAW[sym].USD;
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 (e) {
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
- this.binance,
475
- this.coinGecko,
476
- this.coinpaprika,
477
- this.coincap,
478
- this.dexScreener,
479
- this.geckoTerminal,
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
- isAddress(query) {
484
- return query.startsWith("0x") && query.length === 42 || query.length > 30;
1190
+ coinGecko() {
1191
+ return this.providers.find((p) => p instanceof CoinGeckoProvider);
485
1192
  }
486
1193
  /**
487
- * Search for a token or coin across multiple providers in parallel
1194
+ * Search for a token or coin across symbol + DEX providers in parallel.
488
1195
  */
489
1196
  async search(query) {
490
- const promises = [
491
- this.coinGecko.search(query),
492
- this.dexScreener.search(query),
493
- this.coinpaprika.search(query),
494
- this.coincap.search(query)
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 Can be a symbol (e.g., 'BTC') or a contract address
514
- * @param network Optional network for DEX addresses (e.g., 'eth', 'bsc')
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
- if (this.isAddress(query)) {
518
- let res = await this.dexScreener.getPrice(query);
519
- if (res) return res;
520
- if (network) {
521
- res = await this.geckoTerminal.getPrice(query, network);
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
- } else {
525
- let res = await this.binance.getPrice(query);
526
- if (res) return res;
527
- res = await this.coincap.getPrice(query);
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 Can be a symbol (e.g., 'BTC') or a contract address
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
- if (this.isAddress(query)) {
545
- let res = await this.dexScreener.getTokenInfo(query);
546
- if (res) return res;
547
- if (network) {
548
- res = await this.geckoTerminal.getTokenInfo(query, network);
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
- } else {
552
- let res = await this.coinGecko.getTokenInfo(query);
553
- if (res) return res;
554
- res = await this.coinpaprika.getTokenInfo(query);
555
- if (res) return res;
556
- res = await this.coincap.getTokenInfo(query);
557
- if (res) return res;
558
- res = await this.binance.getTokenInfo(query);
559
- if (res) return res;
560
- res = await this.cryptoCompare.getTokenInfo(query);
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
- try {
570
- const res = await fetch(`https://api.coingecko.com/api/v3/global`);
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
- try {
591
- const res = await fetch(`https://api.coingecko.com/api/v3/search/trending`);
592
- if (!res.ok) return [];
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
- try {
612
- const res = await fetch(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=50&page=1&sparkline=false`);
613
- if (!res.ok) return [];
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(`https://api.alternative.me/fng/?limit=1`);
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 (e) {
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(`https://api.etherscan.io/v2/api?chainid=1&module=gastracker&action=gasoracle`);
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 (e) {
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
- try {
674
- let id = query.toLowerCase();
675
- const searchRes = await this.coinGecko.search(id);
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