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