crypull 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1005 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +980 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +227 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +711 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +701 -0
- package/dist/index.mjs.map +1 -0
- package/examples/basic.ts +28 -0
- package/package.json +52 -0
- package/src/Crypull.ts +288 -0
- package/src/cli.ts +327 -0
- package/src/index.ts +14 -0
- package/src/providers/binance.ts +74 -0
- package/src/providers/coincap.ts +65 -0
- package/src/providers/coingecko.ts +92 -0
- package/src/providers/coinpaprika.ts +82 -0
- package/src/providers/cryptocompare.ts +56 -0
- package/src/providers/dexscreener.ts +82 -0
- package/src/providers/geckoterminal.ts +70 -0
- package/src/types.ts +130 -0
- package/tests/index.test.ts +15 -0
- package/tsconfig.json +16 -0
- package/tsup.config.ts +11 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import * as asciichart from 'asciichart';
|
|
5
|
+
import { crypull } from './index.js';
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('crypull')
|
|
11
|
+
.description('A human-friendly CLI to fetch crypto prices and info from multiple providers')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command('price <query>')
|
|
16
|
+
.description('Get the current price of a cryptocurrency (symbol or contract address)')
|
|
17
|
+
.action(async (query: string) => {
|
|
18
|
+
console.log(pc.cyan(`\nFetching price for ${pc.bold(query)}...`));
|
|
19
|
+
|
|
20
|
+
const data = await crypull.price(query);
|
|
21
|
+
|
|
22
|
+
if (!data) {
|
|
23
|
+
console.log(pc.red(`\n✖ Could not find price for "${query}".`));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
|
|
28
|
+
console.log(pc.white(`----------------------------------------`));
|
|
29
|
+
console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
|
|
30
|
+
console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
|
|
31
|
+
console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
|
|
32
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command('info <query>')
|
|
37
|
+
.description('Get detailed info about a cryptocurrency (market cap, volume, FDV, etc.)')
|
|
38
|
+
.action(async (query: string) => {
|
|
39
|
+
console.log(pc.cyan(`\nFetching detailed info for ${pc.bold(query)}...`));
|
|
40
|
+
|
|
41
|
+
const data = await crypull.info(query);
|
|
42
|
+
|
|
43
|
+
if (!data) {
|
|
44
|
+
console.log(pc.red(`\n✖ Could not find info for "${query}".`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
|
|
49
|
+
console.log(pc.white(`----------------------------------------`));
|
|
50
|
+
console.log(`${pc.gray('Name:')} ${pc.white(pc.bold(data.name))}`);
|
|
51
|
+
console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
|
|
52
|
+
console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
|
|
53
|
+
|
|
54
|
+
if (data.marketCap) {
|
|
55
|
+
console.log(`${pc.gray('MarketCap:')} ${pc.white('$' + data.marketCap.toLocaleString())}`);
|
|
56
|
+
}
|
|
57
|
+
if (data.marketCapRank) {
|
|
58
|
+
console.log(`${pc.gray('Rank:')} ${pc.magenta('#' + data.marketCapRank)}`);
|
|
59
|
+
}
|
|
60
|
+
if (data.fdv) {
|
|
61
|
+
console.log(`${pc.gray('FDV:')} ${pc.white('$' + data.fdv.toLocaleString())}`);
|
|
62
|
+
}
|
|
63
|
+
if (data.circulatingSupply) {
|
|
64
|
+
console.log(`${pc.gray('Circ. Sup:')} ${pc.white(data.circulatingSupply.toLocaleString())}`);
|
|
65
|
+
}
|
|
66
|
+
if (data.totalSupply) {
|
|
67
|
+
console.log(`${pc.gray('Total Sup:')} ${pc.white(data.totalSupply.toLocaleString())}`);
|
|
68
|
+
}
|
|
69
|
+
if (data.maxSupply) {
|
|
70
|
+
console.log(`${pc.gray('Max Sup:')} ${pc.white(data.maxSupply.toLocaleString())}`);
|
|
71
|
+
}
|
|
72
|
+
if (data.volume24h) {
|
|
73
|
+
console.log(`${pc.gray('24h Vol:')} ${pc.white('$' + data.volume24h.toLocaleString())}`);
|
|
74
|
+
}
|
|
75
|
+
if (data.priceChangePercentage24h !== undefined) {
|
|
76
|
+
const pc24 = data.priceChangePercentage24h;
|
|
77
|
+
const color = pc24 >= 0 ? pc.green : pc.red;
|
|
78
|
+
console.log(`${pc.gray('24h %:')} ${color(pc24.toFixed(2) + '%')}`);
|
|
79
|
+
}
|
|
80
|
+
if (data.liquidityUsd) {
|
|
81
|
+
console.log(`${pc.gray('Liquidity:')} ${pc.white('$' + data.liquidityUsd.toLocaleString())}`);
|
|
82
|
+
}
|
|
83
|
+
if (data.ath) {
|
|
84
|
+
console.log(`${pc.gray('ATH:')} ${pc.green('$' + data.ath.toLocaleString())} ${data.athDate ? pc.gray(`(${new Date(data.athDate).toLocaleDateString()})`) : ''}`);
|
|
85
|
+
}
|
|
86
|
+
if (data.atl) {
|
|
87
|
+
console.log(`${pc.gray('ATL:')} ${pc.red('$' + data.atl.toLocaleString())} ${data.atlDate ? pc.gray(`(${new Date(data.atlDate).toLocaleDateString()})`) : ''}`);
|
|
88
|
+
}
|
|
89
|
+
if (data.network) {
|
|
90
|
+
console.log(`${pc.gray('Network:')} ${pc.magenta(data.network)}`);
|
|
91
|
+
}
|
|
92
|
+
if (data.address) {
|
|
93
|
+
console.log(`${pc.gray('Address:')} ${pc.gray(data.address)}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (data.links && Object.values(data.links).some(l => !!l)) {
|
|
97
|
+
console.log(pc.white(`\n--- Links ---`));
|
|
98
|
+
if (data.links.website) console.log(`${pc.gray('Website:')} ${pc.blue(data.links.website)}`);
|
|
99
|
+
if (data.links.twitter) console.log(`${pc.gray('Twitter:')} ${pc.blue(data.links.twitter)}`);
|
|
100
|
+
if (data.links.telegram) console.log(`${pc.gray('Telegram:')} ${pc.blue(data.links.telegram)}`);
|
|
101
|
+
if (data.links.discord) console.log(`${pc.gray('Discord:')} ${pc.blue(data.links.discord)}`);
|
|
102
|
+
if (data.links.github) console.log(`${pc.gray('GitHub:')} ${pc.blue(data.links.github)}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (data.description && data.description.length > 0) {
|
|
106
|
+
console.log(pc.white(`\n--- Description ---`));
|
|
107
|
+
// Truncate to 300 chars to not flood terminal
|
|
108
|
+
const desc = data.description.replace(/(<([^>]+)>)/gi, ""); // strip html
|
|
109
|
+
const truncated = desc.length > 300 ? desc.substring(0, 300) + '...' : desc;
|
|
110
|
+
console.log(pc.gray(truncated));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (data.pairs && data.pairs.length > 0) {
|
|
114
|
+
console.log(pc.white(`\n--- Top Trading Pairs ---`));
|
|
115
|
+
data.pairs.slice(0, 5).forEach((p, i) => {
|
|
116
|
+
const title = `${p.baseTokenSymbol}/${p.quoteTokenSymbol}`;
|
|
117
|
+
const priceStr = p.priceUsd ? `$${p.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
|
|
118
|
+
const volStr = p.volume24h ? `$${p.volume24h.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : 'N/A';
|
|
119
|
+
console.log(`${pc.blue(String(i + 1) + '.')} ${pc.yellow(title.padEnd(12, ' '))} | ${pc.green(priceStr.padEnd(12, ' '))} | Vol: ${pc.white(volStr)} | DEX: ${pc.magenta(p.dexId)}`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(pc.white(`\n----------------------------------------`));
|
|
124
|
+
console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
|
|
125
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
program
|
|
129
|
+
.command('top')
|
|
130
|
+
.description('Show top 50 cryptocurrencies by market cap')
|
|
131
|
+
.action(async () => {
|
|
132
|
+
console.log(pc.cyan(`\nFetching top 50 cryptocurrencies...`));
|
|
133
|
+
|
|
134
|
+
const results = await crypull.top();
|
|
135
|
+
|
|
136
|
+
if (!results || results.length === 0) {
|
|
137
|
+
console.log(pc.red(`\n✖ Could not fetch top coins at this time.`));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log(pc.green(`\n✔ Top 50 Cryptocurrencies:`));
|
|
142
|
+
console.log(pc.white(`----------------------------------------`));
|
|
143
|
+
|
|
144
|
+
results.forEach((res, i) => {
|
|
145
|
+
const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
|
|
146
|
+
const mcapStr = res.marketCap ? `$${res.marketCap.toLocaleString()}` : 'N/A';
|
|
147
|
+
|
|
148
|
+
let changeStr = '';
|
|
149
|
+
if (res.priceChange24h !== undefined && res.priceChange24h !== null) {
|
|
150
|
+
const color = res.priceChange24h >= 0 ? pc.green : pc.red;
|
|
151
|
+
changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | Mcap: ${pc.white(mcapStr)}`);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
program
|
|
161
|
+
.command('search <query>')
|
|
162
|
+
.description('Search for tokens or coins across multiple providers')
|
|
163
|
+
.action(async (query: string) => {
|
|
164
|
+
console.log(pc.cyan(`\nSearching for ${pc.bold(query)}...`));
|
|
165
|
+
|
|
166
|
+
const results = await crypull.search(query);
|
|
167
|
+
|
|
168
|
+
if (!results || results.length === 0) {
|
|
169
|
+
console.log(pc.red(`\n✖ No results found for "${query}".`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(pc.green(`\n✔ Found ${results.length} results:`));
|
|
174
|
+
console.log(pc.white(`----------------------------------------`));
|
|
175
|
+
|
|
176
|
+
results.forEach((res, i) => {
|
|
177
|
+
console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.white(res.name)} ${pc.gray(`(via ${res.source})`)}`);
|
|
178
|
+
if (res.network) {
|
|
179
|
+
console.log(` ${pc.gray('Network:')} ${pc.magenta(res.network)}`);
|
|
180
|
+
}
|
|
181
|
+
if (res.id && res.id.length > 20) {
|
|
182
|
+
console.log(` ${pc.gray('Address:')} ${pc.gray(res.id)}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
program
|
|
190
|
+
.command('trending')
|
|
191
|
+
.description('Show top 10 trending coins/tokens right now')
|
|
192
|
+
.action(async () => {
|
|
193
|
+
console.log(pc.cyan(`\nFetching top trending coins...`));
|
|
194
|
+
|
|
195
|
+
const results = await crypull.trending();
|
|
196
|
+
|
|
197
|
+
if (!results || results.length === 0) {
|
|
198
|
+
console.log(pc.red(`\n✖ Could not fetch trending data at this time.`));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(pc.green(`\n✔ Top 10 Trending Coins:`));
|
|
203
|
+
console.log(pc.white(`----------------------------------------`));
|
|
204
|
+
|
|
205
|
+
results.forEach((res, i) => {
|
|
206
|
+
const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
|
|
207
|
+
let changeStr = '';
|
|
208
|
+
if (res.priceChange24h !== undefined) {
|
|
209
|
+
const color = res.priceChange24h >= 0 ? pc.green : pc.red;
|
|
210
|
+
changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
|
|
211
|
+
}
|
|
212
|
+
const rankStr = res.marketCapRank ? pc.magenta(`#${res.marketCapRank}`) : '';
|
|
213
|
+
|
|
214
|
+
console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | ${rankStr}`);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
program
|
|
221
|
+
.command('market')
|
|
222
|
+
.description('Show global cryptocurrency market overview')
|
|
223
|
+
.action(async () => {
|
|
224
|
+
console.log(pc.cyan(`\nFetching global market data...`));
|
|
225
|
+
|
|
226
|
+
const data = await crypull.market();
|
|
227
|
+
|
|
228
|
+
if (!data) {
|
|
229
|
+
console.log(pc.red(`\n✖ Could not fetch global market data at this time.`));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(pc.green(`\n✔ Global Market Overview:`));
|
|
234
|
+
console.log(pc.white(`----------------------------------------`));
|
|
235
|
+
console.log(`${pc.gray('Total Market Cap:')} ${pc.white('$' + data.totalMarketCapUsd.toLocaleString())}`);
|
|
236
|
+
console.log(`${pc.gray('24h Volume:')} ${pc.white('$' + data.totalVolume24hUsd.toLocaleString())}`);
|
|
237
|
+
console.log(`${pc.gray('BTC Dominance:')} ${pc.yellow(data.bitcoinDominancePercentage.toFixed(2) + '%')}`);
|
|
238
|
+
console.log(`${pc.gray('ETH Dominance:')} ${pc.blue(data.ethereumDominancePercentage.toFixed(2) + '%')}`);
|
|
239
|
+
console.log(`${pc.gray('Active Coins:')} ${pc.white(data.activeCryptocurrencies.toLocaleString())}`);
|
|
240
|
+
console.log(pc.white(`----------------------------------------`));
|
|
241
|
+
console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
|
|
242
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
program
|
|
246
|
+
.command('sentiment')
|
|
247
|
+
.description('Show current Crypto Fear & Greed Index')
|
|
248
|
+
.action(async () => {
|
|
249
|
+
console.log(pc.cyan(`\nFetching Fear & Greed Index...`));
|
|
250
|
+
|
|
251
|
+
const data = await crypull.sentiment();
|
|
252
|
+
|
|
253
|
+
if (!data) {
|
|
254
|
+
console.log(pc.red(`\n✖ Could not fetch sentiment data at this time.`));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const value = data.value;
|
|
259
|
+
let color = pc.white;
|
|
260
|
+
if (value <= 25) color = pc.red;
|
|
261
|
+
else if (value <= 45) color = pc.yellow;
|
|
262
|
+
else if (value <= 55) color = pc.white;
|
|
263
|
+
else if (value <= 75) color = pc.green;
|
|
264
|
+
else color = pc.blue;
|
|
265
|
+
|
|
266
|
+
console.log(pc.green(`\n✔ Current Market Sentiment:`));
|
|
267
|
+
console.log(pc.white(`----------------------------------------`));
|
|
268
|
+
console.log(`${pc.gray('Score:')} ${color(pc.bold(value.toString()) + ' / 100')}`);
|
|
269
|
+
console.log(`${pc.gray('Classification:')} ${color(pc.bold(data.classification))}`);
|
|
270
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
program
|
|
274
|
+
.command('gas')
|
|
275
|
+
.description('Show current Ethereum network gas prices')
|
|
276
|
+
.action(async () => {
|
|
277
|
+
console.log(pc.cyan(`\nFetching network gas prices...`));
|
|
278
|
+
|
|
279
|
+
const data = await crypull.gas();
|
|
280
|
+
|
|
281
|
+
if (!data) {
|
|
282
|
+
console.log(pc.red(`\n✖ Could not fetch gas prices at this time.`));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log(pc.green(`\n✔ ${data.network} Gas Tracker (Gwei):`));
|
|
287
|
+
console.log(pc.white(`----------------------------------------`));
|
|
288
|
+
console.log(`${pc.gray('Low (Safe):')} ${pc.green(data.low + ' gwei')}`);
|
|
289
|
+
console.log(`${pc.gray('Average:')} ${pc.yellow(data.average + ' gwei')}`);
|
|
290
|
+
console.log(`${pc.gray('High (Fast):')} ${pc.red(data.high + ' gwei')}`);
|
|
291
|
+
if (data.baseFee) {
|
|
292
|
+
console.log(`${pc.gray('Base Fee:')} ${pc.white(data.baseFee + ' gwei')}`);
|
|
293
|
+
}
|
|
294
|
+
console.log(pc.white(`----------------------------------------\n`));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
program
|
|
298
|
+
.command('chart <query>')
|
|
299
|
+
.description('Draw a terminal ASCII chart of historical prices')
|
|
300
|
+
.option('-d, --days <days>', 'Number of days to chart', '7')
|
|
301
|
+
.action(async (query: string, options) => {
|
|
302
|
+
const days = parseInt(options.days) || 7;
|
|
303
|
+
console.log(pc.cyan(`\nFetching ${days}-day chart data for ${pc.bold(query)}...`));
|
|
304
|
+
|
|
305
|
+
const data = await crypull.chart(query, days);
|
|
306
|
+
|
|
307
|
+
if (!data || !data.prices || data.prices.length === 0) {
|
|
308
|
+
console.log(pc.red(`\n✖ Could not fetch chart data for "${query}". Note: Charts may only be available for major coins.`));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Determine color based on price action
|
|
313
|
+
const firstPrice = data.prices[0];
|
|
314
|
+
const lastPrice = data.prices[data.prices.length - 1];
|
|
315
|
+
const isUp = lastPrice >= firstPrice;
|
|
316
|
+
|
|
317
|
+
const chartConfig = {
|
|
318
|
+
colors: [isUp ? asciichart.lightgreen : asciichart.lightred],
|
|
319
|
+
height: 10
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
console.log(pc.green(`\n✔ ${days}-Day Chart for ${query.toUpperCase()}:`));
|
|
323
|
+
console.log(pc.gray(`Min: $${data.minPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })} | Max: $${data.maxPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })}`));
|
|
324
|
+
console.log(pc.white(`\n` + asciichart.plot(data.prices, chartConfig) + `\n`));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
program.parse(process.argv);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './Crypull.js';
|
|
3
|
+
export * from './providers/coingecko.js';
|
|
4
|
+
export * from './providers/dexscreener.js';
|
|
5
|
+
export * from './providers/geckoterminal.js';
|
|
6
|
+
export * from './providers/binance.js';
|
|
7
|
+
export * from './providers/coinpaprika.js';
|
|
8
|
+
export * from './providers/coincap.js';
|
|
9
|
+
export * from './providers/cryptocompare.js';
|
|
10
|
+
|
|
11
|
+
import { Crypull } from './Crypull.js';
|
|
12
|
+
|
|
13
|
+
// Export a default instance for quick usage
|
|
14
|
+
export const crypull = new Crypull();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ICryptoProvider, PriceData, SearchResult, TokenInfo } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export class BinanceProvider implements ICryptoProvider {
|
|
4
|
+
name = 'Binance';
|
|
5
|
+
private baseUrl = 'https://api.binance.com/api/v3';
|
|
6
|
+
|
|
7
|
+
async search(query: string): Promise<SearchResult[]> {
|
|
8
|
+
try {
|
|
9
|
+
// Binance doesn't have a direct "search" that returns symbols nicely for our format
|
|
10
|
+
// We can fetch exchangeInfo, but it's large. For speed, we return a mock match if they query
|
|
11
|
+
// or we just return an empty array and rely on other providers for search.
|
|
12
|
+
// But let's try a quick hack: if query is short, construct a symbol.
|
|
13
|
+
const symbol = query.toUpperCase();
|
|
14
|
+
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${symbol}USDT`);
|
|
15
|
+
if (res.ok) {
|
|
16
|
+
return [{
|
|
17
|
+
id: `${symbol}USDT`,
|
|
18
|
+
name: symbol,
|
|
19
|
+
symbol: symbol,
|
|
20
|
+
source: this.name
|
|
21
|
+
}];
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getPrice(symbol: string): Promise<PriceData | null> {
|
|
30
|
+
try {
|
|
31
|
+
let querySymbol = symbol.toUpperCase();
|
|
32
|
+
if (!querySymbol.endsWith('USDT') && !querySymbol.endsWith('BUSD') && !querySymbol.endsWith('BTC')) {
|
|
33
|
+
querySymbol += 'USDT';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const res = await fetch(`${this.baseUrl}/ticker/price?symbol=${querySymbol}`);
|
|
37
|
+
if (!res.ok) return null;
|
|
38
|
+
const data = await res.json();
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
symbol: symbol.toUpperCase(),
|
|
42
|
+
priceUsd: parseFloat(data.price) || 0,
|
|
43
|
+
source: this.name,
|
|
44
|
+
lastUpdated: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getTokenInfo(symbol: string): Promise<TokenInfo | null> {
|
|
52
|
+
try {
|
|
53
|
+
let querySymbol = symbol.toUpperCase();
|
|
54
|
+
if (!querySymbol.endsWith('USDT') && !querySymbol.endsWith('BUSD') && !querySymbol.endsWith('BTC')) {
|
|
55
|
+
querySymbol += 'USDT';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const res = await fetch(`${this.baseUrl}/ticker/24hr?symbol=${querySymbol}`);
|
|
59
|
+
if (!res.ok) return null;
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: symbol.toUpperCase(),
|
|
64
|
+
symbol: symbol.toUpperCase(),
|
|
65
|
+
priceUsd: parseFloat(data.lastPrice) || 0,
|
|
66
|
+
volume24h: parseFloat(data.quoteVolume) || 0, // Quote volume in USDT roughly = USD volume
|
|
67
|
+
source: this.name,
|
|
68
|
+
lastUpdated: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ICryptoProvider, PriceData, SearchResult, TokenInfo } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export class CoinCapProvider implements ICryptoProvider {
|
|
4
|
+
name = 'CoinCap';
|
|
5
|
+
private baseUrl = 'https://api.coincap.io/v2';
|
|
6
|
+
|
|
7
|
+
async search(query: string): Promise<SearchResult[]> {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${this.baseUrl}/assets?search=${query}&limit=10`);
|
|
10
|
+
if (!res.ok) return [];
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
|
|
13
|
+
return (data.data || []).map((coin: any) => ({
|
|
14
|
+
id: coin.id,
|
|
15
|
+
name: coin.name,
|
|
16
|
+
symbol: coin.symbol.toUpperCase(),
|
|
17
|
+
source: this.name,
|
|
18
|
+
}));
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getPrice(symbolOrId: string): Promise<PriceData | null> {
|
|
25
|
+
const info = await this.getTokenInfo(symbolOrId);
|
|
26
|
+
if (!info) return null;
|
|
27
|
+
return {
|
|
28
|
+
symbol: info.symbol,
|
|
29
|
+
priceUsd: info.priceUsd,
|
|
30
|
+
source: this.name,
|
|
31
|
+
lastUpdated: info.lastUpdated,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getTokenInfo(symbolOrId: string): Promise<TokenInfo | null> {
|
|
36
|
+
try {
|
|
37
|
+
let id = symbolOrId.toLowerCase();
|
|
38
|
+
|
|
39
|
+
const searchRes = await this.search(id);
|
|
40
|
+
const match = searchRes.find(s => s.symbol.toLowerCase() === id || s.id === id);
|
|
41
|
+
if (match && match.id) {
|
|
42
|
+
id = match.id;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const res = await fetch(`${this.baseUrl}/assets/${id}`);
|
|
46
|
+
if (!res.ok) return null;
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
const asset = data.data;
|
|
49
|
+
|
|
50
|
+
if (!asset) return null;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
name: asset.name,
|
|
54
|
+
symbol: asset.symbol.toUpperCase(),
|
|
55
|
+
priceUsd: parseFloat(asset.priceUsd) || 0,
|
|
56
|
+
marketCap: parseFloat(asset.marketCapUsd),
|
|
57
|
+
volume24h: parseFloat(asset.volumeUsd24Hr),
|
|
58
|
+
source: this.name,
|
|
59
|
+
lastUpdated: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ICryptoProvider, PriceData, SearchResult, TokenInfo } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export class CoinGeckoProvider implements ICryptoProvider {
|
|
4
|
+
name = 'CoinGecko';
|
|
5
|
+
private baseUrl = 'https://api.coingecko.com/api/v3';
|
|
6
|
+
|
|
7
|
+
async search(query: string): Promise<SearchResult[]> {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${this.baseUrl}/search?query=${query}`);
|
|
10
|
+
if (!res.ok) return [];
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
return (data.coins || []).slice(0, 10).map((coin: any) => ({
|
|
13
|
+
id: coin.id,
|
|
14
|
+
name: coin.name,
|
|
15
|
+
symbol: coin.symbol.toUpperCase(),
|
|
16
|
+
source: this.name,
|
|
17
|
+
}));
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getPrice(symbolOrId: string): Promise<PriceData | null> {
|
|
24
|
+
try {
|
|
25
|
+
let id = symbolOrId.toLowerCase();
|
|
26
|
+
|
|
27
|
+
const searchRes = await this.search(id);
|
|
28
|
+
const match = searchRes.find(s => s.symbol.toLowerCase() === id || s.id === id);
|
|
29
|
+
if (match && match.id) {
|
|
30
|
+
id = match.id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const res = await fetch(`${this.baseUrl}/simple/price?ids=${id}&vs_currencies=usd`);
|
|
34
|
+
if (!res.ok) return null;
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
|
|
37
|
+
if (!data[id] || typeof data[id].usd === 'undefined') return null;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
symbol: match ? match.symbol : id.toUpperCase(),
|
|
41
|
+
priceUsd: data[id].usd,
|
|
42
|
+
source: this.name,
|
|
43
|
+
lastUpdated: Date.now(),
|
|
44
|
+
};
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getTokenInfo(id: string): Promise<TokenInfo | null> {
|
|
51
|
+
try {
|
|
52
|
+
// By default, assuming id is the coingecko internal ID (like "bitcoin")
|
|
53
|
+
const res = await fetch(`${this.baseUrl}/coins/${id.toLowerCase()}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`);
|
|
54
|
+
if (!res.ok) return null;
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
|
|
57
|
+
if (data.error) return null;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
name: data.name,
|
|
61
|
+
symbol: data.symbol.toUpperCase(),
|
|
62
|
+
description: data.description?.en,
|
|
63
|
+
priceUsd: data.market_data?.current_price?.usd || 0,
|
|
64
|
+
marketCap: data.market_data?.market_cap?.usd,
|
|
65
|
+
marketCapRank: data.market_cap_rank,
|
|
66
|
+
fdv: data.market_data?.fully_diluted_valuation?.usd,
|
|
67
|
+
circulatingSupply: data.market_data?.circulating_supply,
|
|
68
|
+
totalSupply: data.market_data?.total_supply,
|
|
69
|
+
maxSupply: data.market_data?.max_supply,
|
|
70
|
+
volume24h: data.market_data?.total_volume?.usd,
|
|
71
|
+
priceChange24h: data.market_data?.price_change_24h,
|
|
72
|
+
priceChangePercentage24h: data.market_data?.price_change_percentage_24h,
|
|
73
|
+
priceChangePercentage7d: data.market_data?.price_change_percentage_7d,
|
|
74
|
+
ath: data.market_data?.ath?.usd,
|
|
75
|
+
athDate: data.market_data?.ath_date?.usd,
|
|
76
|
+
atl: data.market_data?.atl?.usd,
|
|
77
|
+
atlDate: data.market_data?.atl_date?.usd,
|
|
78
|
+
links: {
|
|
79
|
+
website: data.links?.homepage?.[0],
|
|
80
|
+
twitter: data.links?.twitter_screen_name ? `https://twitter.com/${data.links.twitter_screen_name}` : undefined,
|
|
81
|
+
telegram: data.links?.telegram_channel_identifier ? `https://t.me/${data.links.telegram_channel_identifier}` : undefined,
|
|
82
|
+
discord: data.links?.chat_url?.find((url: string) => url.includes('discord')),
|
|
83
|
+
github: data.links?.repos_url?.github?.[0],
|
|
84
|
+
},
|
|
85
|
+
source: this.name,
|
|
86
|
+
lastUpdated: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ICryptoProvider, PriceData, SearchResult, TokenInfo } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export class CoinpaprikaProvider implements ICryptoProvider {
|
|
4
|
+
name = 'Coinpaprika';
|
|
5
|
+
private baseUrl = 'https://api.coinpaprika.com/v1';
|
|
6
|
+
|
|
7
|
+
async search(query: string): Promise<SearchResult[]> {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${this.baseUrl}/search?q=${query}&c=currencies&limit=10`);
|
|
10
|
+
if (!res.ok) return [];
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
|
|
13
|
+
return (data.currencies || []).map((coin: any) => ({
|
|
14
|
+
id: coin.id,
|
|
15
|
+
name: coin.name,
|
|
16
|
+
symbol: coin.symbol.toUpperCase(),
|
|
17
|
+
source: this.name,
|
|
18
|
+
}));
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getPrice(symbolOrId: string): Promise<PriceData | null> {
|
|
25
|
+
const info = await this.getTokenInfo(symbolOrId);
|
|
26
|
+
if (!info) return null;
|
|
27
|
+
return {
|
|
28
|
+
symbol: info.symbol,
|
|
29
|
+
priceUsd: info.priceUsd,
|
|
30
|
+
source: this.name,
|
|
31
|
+
lastUpdated: info.lastUpdated,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getTokenInfo(symbolOrId: string): Promise<TokenInfo | null> {
|
|
36
|
+
try {
|
|
37
|
+
let id = symbolOrId.toLowerCase();
|
|
38
|
+
|
|
39
|
+
// If it doesn't look like a coinpaprika id (e.g. btc-bitcoin), search for it
|
|
40
|
+
if (!id.includes('-')) {
|
|
41
|
+
const searchRes = await this.search(id);
|
|
42
|
+
const match = searchRes.find(s => s.symbol.toLowerCase() === id || s.id === id);
|
|
43
|
+
if (match && match.id) {
|
|
44
|
+
id = match.id;
|
|
45
|
+
} else if (searchRes.length > 0 && searchRes[0].id) {
|
|
46
|
+
id = searchRes[0].id;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const res = await fetch(`${this.baseUrl}/tickers/${id}`);
|
|
51
|
+
if (!res.ok) return null;
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: data.name,
|
|
56
|
+
symbol: data.symbol.toUpperCase(),
|
|
57
|
+
description: data.description,
|
|
58
|
+
priceUsd: data.quotes?.USD?.price || 0,
|
|
59
|
+
marketCap: data.quotes?.USD?.market_cap,
|
|
60
|
+
marketCapRank: data.rank,
|
|
61
|
+
circulatingSupply: data.circulating_supply,
|
|
62
|
+
totalSupply: data.total_supply,
|
|
63
|
+
maxSupply: data.max_supply,
|
|
64
|
+
volume24h: data.quotes?.USD?.volume_24h,
|
|
65
|
+
priceChangePercentage24h: data.quotes?.USD?.percent_change_24h,
|
|
66
|
+
priceChangePercentage7d: data.quotes?.USD?.percent_change_7d,
|
|
67
|
+
ath: data.quotes?.USD?.ath_price,
|
|
68
|
+
athDate: data.quotes?.USD?.ath_date,
|
|
69
|
+
links: {
|
|
70
|
+
website: data.links?.website?.[0],
|
|
71
|
+
twitter: data.links?.twitter?.[0],
|
|
72
|
+
discord: data.links?.discord?.[0],
|
|
73
|
+
github: data.links?.source_code?.[0],
|
|
74
|
+
},
|
|
75
|
+
source: this.name,
|
|
76
|
+
lastUpdated: Date.now(),
|
|
77
|
+
};
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|