@spectratools/defillama-cli 0.2.0 → 0.4.2
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 +101 -0
- package/dist/cli.js +575 -60
- package/dist/index.js +581 -60
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { readFileSync, realpathSync } from "fs";
|
|
5
5
|
import { dirname, resolve } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { Cli as
|
|
7
|
+
import { Cli as Cli5 } from "incur";
|
|
8
8
|
|
|
9
|
-
// src/commands/
|
|
9
|
+
// src/commands/fees.ts
|
|
10
10
|
import { Cli, z as z2 } from "incur";
|
|
11
11
|
|
|
12
12
|
// src/api.ts
|
|
@@ -118,48 +118,403 @@ var protocolDetailSchema = z.object({
|
|
|
118
118
|
var volumeEntrySchema = z.object({
|
|
119
119
|
name: z.string(),
|
|
120
120
|
displayName: z.string().optional(),
|
|
121
|
+
slug: z.string().optional(),
|
|
122
|
+
category: z.string().nullable().optional(),
|
|
123
|
+
chains: z.array(z.string()).optional(),
|
|
121
124
|
total24h: z.number().nullable().optional(),
|
|
122
125
|
total7d: z.number().nullable().optional(),
|
|
123
126
|
total30d: z.number().nullable().optional(),
|
|
127
|
+
totalAllTime: z.number().nullable().optional(),
|
|
124
128
|
change_1d: z.number().nullable().optional(),
|
|
125
129
|
change_7d: z.number().nullable().optional(),
|
|
126
130
|
change_1m: z.number().nullable().optional()
|
|
127
131
|
});
|
|
132
|
+
var volumeOverviewResponseSchema = z.object({
|
|
133
|
+
protocols: z.array(volumeEntrySchema),
|
|
134
|
+
allChains: z.array(z.string()).optional(),
|
|
135
|
+
chain: z.string().nullable().optional(),
|
|
136
|
+
total24h: z.number().nullable().optional(),
|
|
137
|
+
total7d: z.number().nullable().optional(),
|
|
138
|
+
total30d: z.number().nullable().optional(),
|
|
139
|
+
change_1d: z.number().nullable().optional()
|
|
140
|
+
});
|
|
128
141
|
var feeEntrySchema = z.object({
|
|
129
142
|
name: z.string(),
|
|
130
143
|
displayName: z.string().optional(),
|
|
144
|
+
slug: z.string().optional(),
|
|
145
|
+
category: z.string().nullable().optional(),
|
|
146
|
+
chains: z.array(z.string()).optional(),
|
|
131
147
|
total24h: z.number().nullable().optional(),
|
|
132
148
|
total7d: z.number().nullable().optional(),
|
|
133
149
|
total30d: z.number().nullable().optional(),
|
|
134
150
|
totalAllTime: z.number().nullable().optional(),
|
|
151
|
+
change_1d: z.number().nullable().optional(),
|
|
152
|
+
change_7d: z.number().nullable().optional(),
|
|
153
|
+
change_1m: z.number().nullable().optional()
|
|
154
|
+
});
|
|
155
|
+
var feeOverviewResponseSchema = z.object({
|
|
156
|
+
protocols: z.array(feeEntrySchema),
|
|
157
|
+
allChains: z.array(z.string()).optional(),
|
|
158
|
+
chain: z.string().nullable().optional(),
|
|
159
|
+
total24h: z.number().nullable().optional(),
|
|
160
|
+
total7d: z.number().nullable().optional(),
|
|
161
|
+
total30d: z.number().nullable().optional(),
|
|
135
162
|
change_1d: z.number().nullable().optional()
|
|
136
163
|
});
|
|
164
|
+
var summaryDetailSchema = z.object({
|
|
165
|
+
name: z.string(),
|
|
166
|
+
displayName: z.string().optional(),
|
|
167
|
+
slug: z.string().optional(),
|
|
168
|
+
category: z.string().nullable().optional(),
|
|
169
|
+
chains: z.array(z.string()).optional(),
|
|
170
|
+
total24h: z.number().nullable().optional(),
|
|
171
|
+
total48hto24h: z.number().nullable().optional(),
|
|
172
|
+
total7d: z.number().nullable().optional(),
|
|
173
|
+
total30d: z.number().nullable().optional(),
|
|
174
|
+
totalAllTime: z.number().nullable().optional(),
|
|
175
|
+
change_1d: z.number().nullable().optional(),
|
|
176
|
+
change_7d: z.number().nullable().optional(),
|
|
177
|
+
change_1m: z.number().nullable().optional()
|
|
178
|
+
});
|
|
179
|
+
var coinPriceSchema = z.object({
|
|
180
|
+
price: z.number(),
|
|
181
|
+
decimals: z.number().optional(),
|
|
182
|
+
symbol: z.string(),
|
|
183
|
+
timestamp: z.number(),
|
|
184
|
+
confidence: z.number().optional()
|
|
185
|
+
});
|
|
186
|
+
var pricesResponseSchema = z.object({
|
|
187
|
+
coins: z.record(z.string(), coinPriceSchema)
|
|
188
|
+
});
|
|
189
|
+
var coinChartSchema = z.object({
|
|
190
|
+
decimals: z.number().optional(),
|
|
191
|
+
symbol: z.string(),
|
|
192
|
+
prices: z.array(z.object({ timestamp: z.number(), price: z.number() })),
|
|
193
|
+
confidence: z.number().optional()
|
|
194
|
+
});
|
|
195
|
+
var chartResponseSchema = z.object({
|
|
196
|
+
coins: z.record(z.string(), coinChartSchema)
|
|
197
|
+
});
|
|
137
198
|
|
|
138
|
-
// src/commands/
|
|
139
|
-
var
|
|
140
|
-
description: "
|
|
199
|
+
// src/commands/fees.ts
|
|
200
|
+
var feesCli = Cli.create("fees", {
|
|
201
|
+
description: "Protocol fees and revenue queries."
|
|
141
202
|
});
|
|
142
|
-
var
|
|
143
|
-
|
|
144
|
-
description: "List protocols ranked by
|
|
203
|
+
var feesSortFields = ["total24h", "total7d", "change_1d"];
|
|
204
|
+
feesCli.command("overview", {
|
|
205
|
+
description: "List protocols ranked by fees and revenue.",
|
|
145
206
|
options: z2.object({
|
|
146
|
-
chain: z2.string().optional().describe("Filter
|
|
207
|
+
chain: z2.string().optional().describe("Filter by chain name"),
|
|
147
208
|
limit: z2.coerce.number().default(20).describe("Max protocols to display"),
|
|
148
|
-
sort: z2.enum(
|
|
209
|
+
sort: z2.enum(feesSortFields).default("total24h").describe("Sort field: total24h, total7d, or change_1d"),
|
|
210
|
+
category: z2.string().optional().describe("Filter by category (e.g. Dexs, Lending, Bridge)")
|
|
149
211
|
}),
|
|
150
212
|
output: z2.object({
|
|
151
213
|
protocols: z2.array(
|
|
152
214
|
z2.object({
|
|
153
215
|
name: z2.string(),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
category: z2.string()
|
|
216
|
+
fees_24h: z2.string(),
|
|
217
|
+
fees_7d: z2.string(),
|
|
218
|
+
change_1d: z2.string()
|
|
158
219
|
})
|
|
159
220
|
),
|
|
160
221
|
chain: z2.string().optional(),
|
|
222
|
+
category: z2.string().optional(),
|
|
161
223
|
total: z2.number()
|
|
162
224
|
}),
|
|
225
|
+
examples: [
|
|
226
|
+
{ options: { limit: 10 }, description: "Top 10 protocols by 24h fees" },
|
|
227
|
+
{ options: { chain: "abstract", limit: 10 }, description: "Top protocols on Abstract by fees" },
|
|
228
|
+
{ options: { sort: "change_1d", limit: 5 }, description: "Top 5 by 1-day fee change" },
|
|
229
|
+
{
|
|
230
|
+
options: { category: "Dexs", limit: 10 },
|
|
231
|
+
description: "Top 10 DEXes by fees"
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
async run(c) {
|
|
235
|
+
const client = createDefiLlamaClient();
|
|
236
|
+
const path = c.options.chain ? `/overview/fees/${c.options.chain}?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true` : "/overview/fees?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true";
|
|
237
|
+
const raw = await client.get("api", path);
|
|
238
|
+
const data = feeOverviewResponseSchema.parse(raw);
|
|
239
|
+
let protocols = data.protocols;
|
|
240
|
+
if (c.options.category) {
|
|
241
|
+
const catLower = c.options.category.toLowerCase();
|
|
242
|
+
protocols = protocols.filter(
|
|
243
|
+
(p) => p.category != null && p.category.toLowerCase() === catLower
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
protocols = protocols.filter((p) => p.total24h != null && p.total24h > 0);
|
|
247
|
+
const sortKey = c.options.sort;
|
|
248
|
+
protocols.sort((a, b) => {
|
|
249
|
+
const aVal = a[sortKey] ?? 0;
|
|
250
|
+
const bVal = b[sortKey] ?? 0;
|
|
251
|
+
return bVal - aVal;
|
|
252
|
+
});
|
|
253
|
+
const limited = protocols.slice(0, c.options.limit);
|
|
254
|
+
const rows = limited.map((p) => ({
|
|
255
|
+
name: p.displayName ?? p.name,
|
|
256
|
+
fees_24h: formatUsd(p.total24h ?? 0),
|
|
257
|
+
fees_7d: formatUsd(p.total7d ?? 0),
|
|
258
|
+
change_1d: formatPct(p.change_1d)
|
|
259
|
+
}));
|
|
260
|
+
return c.ok({
|
|
261
|
+
protocols: rows,
|
|
262
|
+
chain: c.options.chain,
|
|
263
|
+
category: c.options.category,
|
|
264
|
+
total: protocols.length
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
feesCli.command("protocol", {
|
|
269
|
+
description: "Get detailed fee and revenue data for a specific protocol.",
|
|
270
|
+
args: z2.object({
|
|
271
|
+
slug: z2.string().describe("Protocol slug (e.g. aave, lido)")
|
|
272
|
+
}),
|
|
273
|
+
output: z2.object({
|
|
274
|
+
name: z2.string(),
|
|
275
|
+
fees_24h: z2.string(),
|
|
276
|
+
fees_7d: z2.string(),
|
|
277
|
+
fees_30d: z2.string(),
|
|
278
|
+
fees_all_time: z2.string(),
|
|
279
|
+
change_1d: z2.string(),
|
|
280
|
+
change_7d: z2.string(),
|
|
281
|
+
change_1m: z2.string(),
|
|
282
|
+
chains: z2.array(z2.string())
|
|
283
|
+
}),
|
|
284
|
+
examples: [{ args: { slug: "aave" }, description: "Aave fee details" }],
|
|
285
|
+
async run(c) {
|
|
286
|
+
const client = createDefiLlamaClient();
|
|
287
|
+
const raw = await client.get("api", `/summary/fees/${c.args.slug}`);
|
|
288
|
+
const detail = summaryDetailSchema.parse(raw);
|
|
289
|
+
return c.ok({
|
|
290
|
+
name: detail.displayName ?? detail.name,
|
|
291
|
+
fees_24h: formatUsd(detail.total24h ?? 0),
|
|
292
|
+
fees_7d: formatUsd(detail.total7d ?? 0),
|
|
293
|
+
fees_30d: formatUsd(detail.total30d ?? 0),
|
|
294
|
+
fees_all_time: formatUsd(detail.totalAllTime ?? 0),
|
|
295
|
+
change_1d: formatPct(detail.change_1d),
|
|
296
|
+
change_7d: formatPct(detail.change_7d),
|
|
297
|
+
change_1m: formatPct(detail.change_1m),
|
|
298
|
+
chains: detail.chains ?? []
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// src/commands/prices.ts
|
|
304
|
+
import { Cli as Cli2, z as z3 } from "incur";
|
|
305
|
+
var pricesCli = Cli2.create("prices", {
|
|
306
|
+
description: "Token price queries via coins.llama.fi."
|
|
307
|
+
});
|
|
308
|
+
var COIN_ID_RE = /^[a-zA-Z0-9_-]+:0x[a-fA-F0-9]{1,}$/;
|
|
309
|
+
function normalizeCoinArgs(raw) {
|
|
310
|
+
return Array.isArray(raw) ? raw : [raw];
|
|
311
|
+
}
|
|
312
|
+
function parseCoins(raw) {
|
|
313
|
+
const coins = [];
|
|
314
|
+
for (const arg of normalizeCoinArgs(raw)) {
|
|
315
|
+
for (const part of arg.split(",")) {
|
|
316
|
+
const trimmed = part.trim();
|
|
317
|
+
if (trimmed.length === 0) continue;
|
|
318
|
+
if (!COIN_ID_RE.test(trimmed)) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Invalid coin identifier "${trimmed}". Expected format: chainName:0xAddress (e.g. ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7)`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
coins.push(trimmed);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (coins.length === 0) {
|
|
327
|
+
throw new Error("At least one coin identifier is required.");
|
|
328
|
+
}
|
|
329
|
+
return [...new Set(coins)].join(",");
|
|
330
|
+
}
|
|
331
|
+
function parseTimestamp(value) {
|
|
332
|
+
if (/^\d+$/.test(value)) {
|
|
333
|
+
return Number(value);
|
|
334
|
+
}
|
|
335
|
+
const ms = Date.parse(value);
|
|
336
|
+
if (Number.isNaN(ms)) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Invalid timestamp "${value}". Provide a Unix timestamp in seconds or an ISO date string (e.g. 2025-01-01 or 2025-01-01T00:00:00Z).`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
return Math.floor(ms / 1e3);
|
|
342
|
+
}
|
|
343
|
+
var coinsArgsSchema = z3.string().describe("Coin identifiers (chainName:0xAddress)");
|
|
344
|
+
pricesCli.command("current", {
|
|
345
|
+
description: "Get current prices for one or more tokens.",
|
|
346
|
+
args: z3.object({
|
|
347
|
+
coins: coinsArgsSchema
|
|
348
|
+
}),
|
|
349
|
+
options: z3.object({
|
|
350
|
+
"search-width": z3.string().default("4h").describe("Timestamp search width (e.g. 4h, 6h)")
|
|
351
|
+
}),
|
|
352
|
+
output: z3.object({
|
|
353
|
+
prices: z3.array(
|
|
354
|
+
z3.object({
|
|
355
|
+
coin: z3.string(),
|
|
356
|
+
symbol: z3.string(),
|
|
357
|
+
price: z3.string(),
|
|
358
|
+
confidence: z3.number().optional(),
|
|
359
|
+
timestamp: z3.string()
|
|
360
|
+
})
|
|
361
|
+
)
|
|
362
|
+
}),
|
|
363
|
+
examples: [
|
|
364
|
+
{
|
|
365
|
+
args: { coins: "ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7" },
|
|
366
|
+
description: "Current price of USDT on Ethereum"
|
|
367
|
+
}
|
|
368
|
+
],
|
|
369
|
+
async run(c) {
|
|
370
|
+
const coinsPath = parseCoins(c.args.coins);
|
|
371
|
+
const client = createDefiLlamaClient();
|
|
372
|
+
const searchWidth = c.options["search-width"];
|
|
373
|
+
const path = `/prices/current/${coinsPath}?searchWidth=${searchWidth}`;
|
|
374
|
+
const raw = await client.get("coins", path);
|
|
375
|
+
const data = pricesResponseSchema.parse(raw);
|
|
376
|
+
const prices = Object.entries(data.coins).map(([coin, info]) => ({
|
|
377
|
+
coin,
|
|
378
|
+
symbol: info.symbol,
|
|
379
|
+
price: formatUsd(info.price),
|
|
380
|
+
confidence: info.confidence,
|
|
381
|
+
timestamp: new Date(info.timestamp * 1e3).toISOString()
|
|
382
|
+
}));
|
|
383
|
+
return c.ok({ prices });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
pricesCli.command("historical", {
|
|
387
|
+
description: "Get token prices at a specific point in time.",
|
|
388
|
+
args: z3.object({
|
|
389
|
+
coins: coinsArgsSchema
|
|
390
|
+
}),
|
|
391
|
+
options: z3.object({
|
|
392
|
+
timestamp: z3.string().optional().describe("Unix timestamp in seconds"),
|
|
393
|
+
date: z3.string().optional().describe("ISO date string (e.g. 2025-01-01)"),
|
|
394
|
+
"search-width": z3.string().default("4h").describe("Timestamp search width (e.g. 4h, 6h)")
|
|
395
|
+
}),
|
|
396
|
+
output: z3.object({
|
|
397
|
+
prices: z3.array(
|
|
398
|
+
z3.object({
|
|
399
|
+
coin: z3.string(),
|
|
400
|
+
symbol: z3.string(),
|
|
401
|
+
price: z3.string(),
|
|
402
|
+
timestamp: z3.string()
|
|
403
|
+
})
|
|
404
|
+
)
|
|
405
|
+
}),
|
|
406
|
+
examples: [
|
|
407
|
+
{
|
|
408
|
+
args: { coins: "ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7" },
|
|
409
|
+
options: { date: "2025-01-01" },
|
|
410
|
+
description: "USDT price on 2025-01-01"
|
|
411
|
+
}
|
|
412
|
+
],
|
|
413
|
+
async run(c) {
|
|
414
|
+
const tsRaw = c.options.timestamp ?? c.options.date;
|
|
415
|
+
if (!tsRaw) {
|
|
416
|
+
throw new Error("Either --timestamp or --date is required for historical prices.");
|
|
417
|
+
}
|
|
418
|
+
const ts = parseTimestamp(tsRaw);
|
|
419
|
+
const coinsPath = parseCoins(c.args.coins);
|
|
420
|
+
const client = createDefiLlamaClient();
|
|
421
|
+
const searchWidth = c.options["search-width"];
|
|
422
|
+
const path = `/prices/historical/${ts}/${coinsPath}?searchWidth=${searchWidth}`;
|
|
423
|
+
const raw = await client.get("coins", path);
|
|
424
|
+
const data = pricesResponseSchema.parse(raw);
|
|
425
|
+
const prices = Object.entries(data.coins).map(([coin, info]) => ({
|
|
426
|
+
coin,
|
|
427
|
+
symbol: info.symbol,
|
|
428
|
+
price: formatUsd(info.price),
|
|
429
|
+
timestamp: new Date(info.timestamp * 1e3).toISOString()
|
|
430
|
+
}));
|
|
431
|
+
return c.ok({ prices });
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
pricesCli.command("chart", {
|
|
435
|
+
description: "Get a price chart over a time range.",
|
|
436
|
+
args: z3.object({
|
|
437
|
+
coins: coinsArgsSchema
|
|
438
|
+
}),
|
|
439
|
+
options: z3.object({
|
|
440
|
+
start: z3.string().optional().describe("Start timestamp or ISO date"),
|
|
441
|
+
end: z3.string().optional().describe("End timestamp or ISO date (default: now)"),
|
|
442
|
+
span: z3.coerce.number().optional().describe("Number of data points"),
|
|
443
|
+
period: z3.string().optional().describe("Data point period (e.g. 1d, 1h, 4h)"),
|
|
444
|
+
"search-width": z3.string().default("4h").describe("Timestamp search width (e.g. 4h, 600)")
|
|
445
|
+
}),
|
|
446
|
+
output: z3.object({
|
|
447
|
+
charts: z3.array(
|
|
448
|
+
z3.object({
|
|
449
|
+
coin: z3.string(),
|
|
450
|
+
symbol: z3.string(),
|
|
451
|
+
prices: z3.array(
|
|
452
|
+
z3.object({
|
|
453
|
+
date: z3.string(),
|
|
454
|
+
price: z3.string()
|
|
455
|
+
})
|
|
456
|
+
)
|
|
457
|
+
})
|
|
458
|
+
)
|
|
459
|
+
}),
|
|
460
|
+
examples: [
|
|
461
|
+
{
|
|
462
|
+
args: { coins: "ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7" },
|
|
463
|
+
options: { start: "2025-01-01", period: "1d" },
|
|
464
|
+
description: "USDT daily price chart since 2025-01-01"
|
|
465
|
+
}
|
|
466
|
+
],
|
|
467
|
+
async run(c) {
|
|
468
|
+
const coinsPath = parseCoins(c.args.coins);
|
|
469
|
+
const client = createDefiLlamaClient();
|
|
470
|
+
const params = new URLSearchParams();
|
|
471
|
+
if (c.options.start) params.set("start", String(parseTimestamp(c.options.start)));
|
|
472
|
+
if (c.options.end) params.set("end", String(parseTimestamp(c.options.end)));
|
|
473
|
+
if (c.options.span) params.set("span", String(c.options.span));
|
|
474
|
+
if (c.options.period) params.set("period", c.options.period);
|
|
475
|
+
params.set("searchWidth", c.options["search-width"]);
|
|
476
|
+
const qs = params.toString();
|
|
477
|
+
const path = `/chart/${coinsPath}${qs ? `?${qs}` : ""}`;
|
|
478
|
+
const raw = await client.get("coins", path);
|
|
479
|
+
const data = chartResponseSchema.parse(raw);
|
|
480
|
+
const charts = Object.entries(data.coins).map(([coin, info]) => ({
|
|
481
|
+
coin,
|
|
482
|
+
symbol: info.symbol,
|
|
483
|
+
prices: info.prices.map((p) => ({
|
|
484
|
+
date: new Date(p.timestamp * 1e3).toISOString(),
|
|
485
|
+
price: formatUsd(p.price)
|
|
486
|
+
}))
|
|
487
|
+
}));
|
|
488
|
+
return c.ok({ charts });
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// src/commands/tvl.ts
|
|
493
|
+
import { Cli as Cli3, z as z4 } from "incur";
|
|
494
|
+
var tvlCli = Cli3.create("tvl", {
|
|
495
|
+
description: "Total value locked queries."
|
|
496
|
+
});
|
|
497
|
+
var protocolSortFields = ["tvl", "change_1d", "change_7d"];
|
|
498
|
+
tvlCli.command("protocols", {
|
|
499
|
+
description: "List protocols ranked by TVL.",
|
|
500
|
+
options: z4.object({
|
|
501
|
+
chain: z4.string().optional().describe("Filter protocols by chain name"),
|
|
502
|
+
limit: z4.coerce.number().default(20).describe("Max protocols to display"),
|
|
503
|
+
sort: z4.enum(protocolSortFields).default("tvl").describe("Sort field: tvl, change_1d, or change_7d")
|
|
504
|
+
}),
|
|
505
|
+
output: z4.object({
|
|
506
|
+
protocols: z4.array(
|
|
507
|
+
z4.object({
|
|
508
|
+
name: z4.string(),
|
|
509
|
+
tvl: z4.string(),
|
|
510
|
+
change_1d: z4.string(),
|
|
511
|
+
change_7d: z4.string(),
|
|
512
|
+
category: z4.string()
|
|
513
|
+
})
|
|
514
|
+
),
|
|
515
|
+
chain: z4.string().optional(),
|
|
516
|
+
total: z4.number()
|
|
517
|
+
}),
|
|
163
518
|
examples: [
|
|
164
519
|
{ options: { limit: 10 }, description: "Top 10 protocols by TVL" },
|
|
165
520
|
{ options: { chain: "abstract", limit: 10 }, description: "Top protocols on Abstract" },
|
|
@@ -200,17 +555,17 @@ tvlCli.command("protocols", {
|
|
|
200
555
|
});
|
|
201
556
|
tvlCli.command("chains", {
|
|
202
557
|
description: "List chains ranked by TVL.",
|
|
203
|
-
options:
|
|
204
|
-
limit:
|
|
558
|
+
options: z4.object({
|
|
559
|
+
limit: z4.coerce.number().default(20).describe("Max chains to display")
|
|
205
560
|
}),
|
|
206
|
-
output:
|
|
207
|
-
chains:
|
|
208
|
-
|
|
209
|
-
name:
|
|
210
|
-
tvl:
|
|
561
|
+
output: z4.object({
|
|
562
|
+
chains: z4.array(
|
|
563
|
+
z4.object({
|
|
564
|
+
name: z4.string(),
|
|
565
|
+
tvl: z4.string()
|
|
211
566
|
})
|
|
212
567
|
),
|
|
213
|
-
total:
|
|
568
|
+
total: z4.number()
|
|
214
569
|
}),
|
|
215
570
|
examples: [{ options: { limit: 10 }, description: "Top 10 chains by TVL" }],
|
|
216
571
|
async run(c) {
|
|
@@ -231,20 +586,20 @@ tvlCli.command("chains", {
|
|
|
231
586
|
});
|
|
232
587
|
tvlCli.command("protocol", {
|
|
233
588
|
description: "Get detailed protocol info with TVL breakdown by chain.",
|
|
234
|
-
args:
|
|
235
|
-
slug:
|
|
589
|
+
args: z4.object({
|
|
590
|
+
slug: z4.string().describe("Protocol slug (e.g. aave, uniswap)")
|
|
236
591
|
}),
|
|
237
|
-
output:
|
|
238
|
-
name:
|
|
239
|
-
description:
|
|
240
|
-
category:
|
|
241
|
-
url:
|
|
242
|
-
symbol:
|
|
243
|
-
tvl:
|
|
244
|
-
chains:
|
|
245
|
-
|
|
246
|
-
chain:
|
|
247
|
-
tvl:
|
|
592
|
+
output: z4.object({
|
|
593
|
+
name: z4.string(),
|
|
594
|
+
description: z4.string(),
|
|
595
|
+
category: z4.string(),
|
|
596
|
+
url: z4.string(),
|
|
597
|
+
symbol: z4.string(),
|
|
598
|
+
tvl: z4.string(),
|
|
599
|
+
chains: z4.array(
|
|
600
|
+
z4.object({
|
|
601
|
+
chain: z4.string(),
|
|
602
|
+
tvl: z4.string()
|
|
248
603
|
})
|
|
249
604
|
)
|
|
250
605
|
}),
|
|
@@ -287,21 +642,21 @@ tvlCli.command("protocol", {
|
|
|
287
642
|
});
|
|
288
643
|
tvlCli.command("history", {
|
|
289
644
|
description: "Show historical TVL for a protocol.",
|
|
290
|
-
args:
|
|
291
|
-
slug:
|
|
645
|
+
args: z4.object({
|
|
646
|
+
slug: z4.string().describe("Protocol slug (e.g. aave, uniswap)")
|
|
292
647
|
}),
|
|
293
|
-
options:
|
|
294
|
-
days:
|
|
295
|
-
chain:
|
|
648
|
+
options: z4.object({
|
|
649
|
+
days: z4.coerce.number().default(30).describe("Number of days of history to display"),
|
|
650
|
+
chain: z4.string().optional().describe("Filter to a specific chain")
|
|
296
651
|
}),
|
|
297
|
-
output:
|
|
298
|
-
protocol:
|
|
299
|
-
chain:
|
|
300
|
-
days:
|
|
301
|
-
history:
|
|
302
|
-
|
|
303
|
-
date:
|
|
304
|
-
tvl:
|
|
652
|
+
output: z4.object({
|
|
653
|
+
protocol: z4.string(),
|
|
654
|
+
chain: z4.string().optional(),
|
|
655
|
+
days: z4.number(),
|
|
656
|
+
history: z4.array(
|
|
657
|
+
z4.object({
|
|
658
|
+
date: z4.string(),
|
|
659
|
+
tvl: z4.string()
|
|
305
660
|
})
|
|
306
661
|
)
|
|
307
662
|
}),
|
|
@@ -347,26 +702,185 @@ tvlCli.command("history", {
|
|
|
347
702
|
}
|
|
348
703
|
});
|
|
349
704
|
|
|
705
|
+
// src/commands/volume.ts
|
|
706
|
+
import { Cli as Cli4, z as z5 } from "incur";
|
|
707
|
+
var volumeCli = Cli4.create("volume", {
|
|
708
|
+
description: "DEX volume queries."
|
|
709
|
+
});
|
|
710
|
+
var dexsSortFields = ["total24h", "total7d", "change_1d"];
|
|
711
|
+
volumeCli.command("dexs", {
|
|
712
|
+
description: "List DEXes ranked by trading volume.",
|
|
713
|
+
options: z5.object({
|
|
714
|
+
chain: z5.string().optional().describe("Filter by chain name"),
|
|
715
|
+
limit: z5.coerce.number().default(20).describe("Max protocols to display"),
|
|
716
|
+
sort: z5.enum(dexsSortFields).default("total24h").describe("Sort field: total24h, total7d, or change_1d"),
|
|
717
|
+
category: z5.string().optional().describe("Filter by category (e.g. Dexs, Prediction)")
|
|
718
|
+
}),
|
|
719
|
+
output: z5.object({
|
|
720
|
+
protocols: z5.array(
|
|
721
|
+
z5.object({
|
|
722
|
+
name: z5.string(),
|
|
723
|
+
volume_24h: z5.string(),
|
|
724
|
+
volume_7d: z5.string(),
|
|
725
|
+
change_1d: z5.string()
|
|
726
|
+
})
|
|
727
|
+
),
|
|
728
|
+
chain: z5.string().optional(),
|
|
729
|
+
category: z5.string().optional(),
|
|
730
|
+
total: z5.number()
|
|
731
|
+
}),
|
|
732
|
+
examples: [
|
|
733
|
+
{ options: { limit: 10 }, description: "Top 10 DEXes by 24h volume" },
|
|
734
|
+
{ options: { chain: "abstract", limit: 10 }, description: "Top DEXes on Abstract" },
|
|
735
|
+
{ options: { sort: "change_1d", limit: 5 }, description: "Top 5 by 1-day volume change" },
|
|
736
|
+
{
|
|
737
|
+
options: { category: "Dexs", limit: 10 },
|
|
738
|
+
description: "Top 10 DEXes excluding prediction markets"
|
|
739
|
+
}
|
|
740
|
+
],
|
|
741
|
+
async run(c) {
|
|
742
|
+
const client = createDefiLlamaClient();
|
|
743
|
+
const path = c.options.chain ? `/overview/dexs/${c.options.chain}?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true` : "/overview/dexs?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true";
|
|
744
|
+
const raw = await client.get("api", path);
|
|
745
|
+
const data = volumeOverviewResponseSchema.parse(raw);
|
|
746
|
+
let protocols = data.protocols;
|
|
747
|
+
if (c.options.category) {
|
|
748
|
+
const catLower = c.options.category.toLowerCase();
|
|
749
|
+
protocols = protocols.filter(
|
|
750
|
+
(p) => p.category != null && p.category.toLowerCase() === catLower
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
protocols = protocols.filter((p) => p.total24h != null && p.total24h > 0);
|
|
754
|
+
const sortKey = c.options.sort;
|
|
755
|
+
protocols.sort((a, b) => {
|
|
756
|
+
const aVal = a[sortKey] ?? 0;
|
|
757
|
+
const bVal = b[sortKey] ?? 0;
|
|
758
|
+
return bVal - aVal;
|
|
759
|
+
});
|
|
760
|
+
const limited = protocols.slice(0, c.options.limit);
|
|
761
|
+
const rows = limited.map((p) => ({
|
|
762
|
+
name: p.displayName ?? p.name,
|
|
763
|
+
volume_24h: formatUsd(p.total24h ?? 0),
|
|
764
|
+
volume_7d: formatUsd(p.total7d ?? 0),
|
|
765
|
+
change_1d: formatPct(p.change_1d)
|
|
766
|
+
}));
|
|
767
|
+
return c.ok({
|
|
768
|
+
protocols: rows,
|
|
769
|
+
chain: c.options.chain,
|
|
770
|
+
category: c.options.category,
|
|
771
|
+
total: protocols.length
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
volumeCli.command("protocol", {
|
|
776
|
+
description: "Get detailed volume data for a specific protocol.",
|
|
777
|
+
args: z5.object({
|
|
778
|
+
slug: z5.string().describe("Protocol slug (e.g. uniswap, curve-dex)")
|
|
779
|
+
}),
|
|
780
|
+
output: z5.object({
|
|
781
|
+
name: z5.string(),
|
|
782
|
+
volume_24h: z5.string(),
|
|
783
|
+
volume_7d: z5.string(),
|
|
784
|
+
volume_30d: z5.string(),
|
|
785
|
+
volume_all_time: z5.string(),
|
|
786
|
+
change_1d: z5.string(),
|
|
787
|
+
change_7d: z5.string(),
|
|
788
|
+
change_1m: z5.string(),
|
|
789
|
+
chains: z5.array(z5.string())
|
|
790
|
+
}),
|
|
791
|
+
examples: [{ args: { slug: "uniswap" }, description: "Uniswap volume details" }],
|
|
792
|
+
async run(c) {
|
|
793
|
+
const client = createDefiLlamaClient();
|
|
794
|
+
const raw = await client.get("api", `/summary/dexs/${c.args.slug}`);
|
|
795
|
+
const detail = summaryDetailSchema.parse(raw);
|
|
796
|
+
return c.ok({
|
|
797
|
+
name: detail.displayName ?? detail.name,
|
|
798
|
+
volume_24h: formatUsd(detail.total24h ?? 0),
|
|
799
|
+
volume_7d: formatUsd(detail.total7d ?? 0),
|
|
800
|
+
volume_30d: formatUsd(detail.total30d ?? 0),
|
|
801
|
+
volume_all_time: formatUsd(detail.totalAllTime ?? 0),
|
|
802
|
+
change_1d: formatPct(detail.change_1d),
|
|
803
|
+
change_7d: formatPct(detail.change_7d),
|
|
804
|
+
change_1m: formatPct(detail.change_1m),
|
|
805
|
+
chains: detail.chains ?? []
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
volumeCli.command("aggregators", {
|
|
810
|
+
description: "List DEX aggregators ranked by trading volume.",
|
|
811
|
+
options: z5.object({
|
|
812
|
+
chain: z5.string().optional().describe("Filter by chain name"),
|
|
813
|
+
limit: z5.coerce.number().default(20).describe("Max aggregators to display")
|
|
814
|
+
}),
|
|
815
|
+
output: z5.object({
|
|
816
|
+
protocols: z5.array(
|
|
817
|
+
z5.object({
|
|
818
|
+
name: z5.string(),
|
|
819
|
+
volume_24h: z5.string(),
|
|
820
|
+
volume_7d: z5.string(),
|
|
821
|
+
change_1d: z5.string()
|
|
822
|
+
})
|
|
823
|
+
),
|
|
824
|
+
chain: z5.string().optional(),
|
|
825
|
+
total: z5.number()
|
|
826
|
+
}),
|
|
827
|
+
examples: [
|
|
828
|
+
{ options: { limit: 10 }, description: "Top 10 DEX aggregators by volume" },
|
|
829
|
+
{
|
|
830
|
+
options: { chain: "ethereum", limit: 5 },
|
|
831
|
+
description: "Top 5 aggregators on Ethereum"
|
|
832
|
+
}
|
|
833
|
+
],
|
|
834
|
+
async run(c) {
|
|
835
|
+
const client = createDefiLlamaClient();
|
|
836
|
+
const path = c.options.chain ? `/overview/aggregators/${c.options.chain}?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true` : "/overview/aggregators?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true";
|
|
837
|
+
const raw = await client.get("api", path);
|
|
838
|
+
const data = volumeOverviewResponseSchema.parse(raw);
|
|
839
|
+
let protocols = data.protocols;
|
|
840
|
+
protocols = protocols.filter((p) => p.total24h != null && p.total24h > 0);
|
|
841
|
+
protocols.sort((a, b) => (b.total24h ?? 0) - (a.total24h ?? 0));
|
|
842
|
+
const limited = protocols.slice(0, c.options.limit);
|
|
843
|
+
const rows = limited.map((p) => ({
|
|
844
|
+
name: p.displayName ?? p.name,
|
|
845
|
+
volume_24h: formatUsd(p.total24h ?? 0),
|
|
846
|
+
volume_7d: formatUsd(p.total7d ?? 0),
|
|
847
|
+
change_1d: formatPct(p.change_1d)
|
|
848
|
+
}));
|
|
849
|
+
return c.ok({
|
|
850
|
+
protocols: rows,
|
|
851
|
+
chain: c.options.chain,
|
|
852
|
+
total: protocols.length
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
350
857
|
// src/cli.ts
|
|
351
858
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
352
859
|
var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
|
|
353
|
-
var cli =
|
|
860
|
+
var cli = Cli5.create("defillama", {
|
|
354
861
|
version: pkg.version,
|
|
355
862
|
description: "Query DefiLlama API data from the command line."
|
|
356
863
|
});
|
|
357
864
|
cli.command(tvlCli);
|
|
358
|
-
var volumeCli = Cli2.create("volume", {
|
|
359
|
-
description: "DEX volume queries."
|
|
360
|
-
});
|
|
361
865
|
cli.command(volumeCli);
|
|
362
|
-
var feesCli = Cli2.create("fees", {
|
|
363
|
-
description: "Protocol fees and revenue queries."
|
|
364
|
-
});
|
|
365
866
|
cli.command(feesCli);
|
|
366
|
-
var pricesCli = Cli2.create("prices", {
|
|
367
|
-
description: "Token price queries."
|
|
368
|
-
});
|
|
369
867
|
cli.command(pricesCli);
|
|
868
|
+
var PRICE_SUBCOMMANDS = /* @__PURE__ */ new Set(["current", "historical", "chart"]);
|
|
869
|
+
function normalizePricesArgv(argv) {
|
|
870
|
+
if (argv[0] !== "prices") return argv;
|
|
871
|
+
const subcommand = argv[1];
|
|
872
|
+
if (!subcommand || !PRICE_SUBCOMMANDS.has(subcommand)) return argv;
|
|
873
|
+
let i = 2;
|
|
874
|
+
const coins = [];
|
|
875
|
+
while (i < argv.length) {
|
|
876
|
+
const token = argv[i];
|
|
877
|
+
if (!token || token.startsWith("-")) break;
|
|
878
|
+
coins.push(token);
|
|
879
|
+
i += 1;
|
|
880
|
+
}
|
|
881
|
+
if (coins.length <= 1) return argv;
|
|
882
|
+
return [...argv.slice(0, 2), coins.join(","), ...argv.slice(i)];
|
|
883
|
+
}
|
|
370
884
|
var isMain = (() => {
|
|
371
885
|
const entrypoint = process.argv[1];
|
|
372
886
|
if (!entrypoint) {
|
|
@@ -379,8 +893,9 @@ var isMain = (() => {
|
|
|
379
893
|
}
|
|
380
894
|
})();
|
|
381
895
|
if (isMain) {
|
|
382
|
-
cli.serve();
|
|
896
|
+
cli.serve(normalizePricesArgv(process.argv.slice(2)));
|
|
383
897
|
}
|
|
384
898
|
export {
|
|
385
|
-
cli
|
|
899
|
+
cli,
|
|
900
|
+
normalizePricesArgv
|
|
386
901
|
};
|