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