crypull 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +154 -18
- package/dist/index.d.ts +154 -18
- package/dist/index.js +905 -236
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +894 -237
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -15
- package/README.md +0 -228
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -1094
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -1069
- package/dist/cli.mjs.map +0 -1
- package/examples/basic.ts +0 -28
- package/src/Crypull.ts +0 -288
- package/src/cli.ts +0 -440
- package/src/index.ts +0 -14
- package/src/providers/binance.ts +0 -74
- package/src/providers/coincap.ts +0 -65
- package/src/providers/coingecko.ts +0 -92
- package/src/providers/coinpaprika.ts +0 -82
- package/src/providers/cryptocompare.ts +0 -56
- package/src/providers/dexscreener.ts +0 -82
- package/src/providers/geckoterminal.ts +0 -70
- package/src/types.ts +0 -130
- package/tests/index.test.ts +0 -15
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -11
package/dist/index.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(
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
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")
|
|
43
|
+
if (!data[resolved.id] || typeof data[resolved.id].usd === "undefined") {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
32
46
|
return {
|
|
33
|
-
symbol:
|
|
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
|
|
52
|
+
} catch {
|
|
39
53
|
return null;
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
|
-
async getTokenInfo(
|
|
56
|
+
async getTokenInfo(symbolOrId) {
|
|
43
57
|
try {
|
|
44
|
-
const
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
119
|
-
const
|
|
120
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
223
|
-
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${
|
|
224
|
-
if (res.ok)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
233
|
-
} catch (e) {
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
} catch {
|
|
234
379
|
return [];
|
|
235
380
|
}
|
|
236
381
|
}
|
|
237
382
|
async getPrice(symbol) {
|
|
238
383
|
try {
|
|
239
|
-
|
|
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:
|
|
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
|
|
395
|
+
} catch {
|
|
253
396
|
return null;
|
|
254
397
|
}
|
|
255
398
|
}
|
|
256
399
|
async getTokenInfo(symbol) {
|
|
257
400
|
try {
|
|
258
|
-
|
|
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:
|
|
267
|
-
symbol:
|
|
408
|
+
name: base,
|
|
409
|
+
symbol: base,
|
|
268
410
|
priceUsd: parseFloat(data.lastPrice) || 0,
|
|
269
411
|
volume24h: parseFloat(data.quoteVolume) || 0,
|
|
270
|
-
|
|
412
|
+
priceChangePercentage24h: finiteOrUndefined(data.priceChangePercent),
|
|
271
413
|
source: this.name,
|
|
272
414
|
lastUpdated: Date.now()
|
|
273
415
|
};
|
|
274
|
-
} catch
|
|
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(
|
|
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
|
|
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
|
|
316
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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" ||
|
|
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
|
|
585
|
+
} catch {
|
|
432
586
|
return null;
|
|
433
587
|
}
|
|
434
588
|
}
|
|
435
589
|
async getTokenInfo(symbol) {
|
|
436
590
|
try {
|
|
437
|
-
const sym = symbol.toUpperCase();
|
|
438
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
482
|
-
return
|
|
1188
|
+
coinGecko() {
|
|
1189
|
+
return this.providers.find((p) => p instanceof CoinGeckoProvider);
|
|
483
1190
|
}
|
|
484
1191
|
/**
|
|
485
|
-
* Search for a token or coin across
|
|
1192
|
+
* Search for a token or coin across symbol + DEX providers in parallel.
|
|
486
1193
|
*/
|
|
487
1194
|
async search(query) {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
this.
|
|
492
|
-
|
|
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
|
|
512
|
-
* @param network Optional network for DEX addresses (e.g
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
res = await
|
|
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
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
568
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|