@zoralabs/cli 0.2.3 → 0.2.4

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 (2) hide show
  1. package/dist/index.js +1729 -559
  2. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.tsx
4
- import { Command as Command9 } from "commander";
4
+ import { Command as Command11 } from "commander";
5
5
  import { ExitPromptError } from "@inquirer/core";
6
6
  import "fs";
7
7
  import { setApiBaseUrl } from "@zoralabs/coins-sdk";
@@ -155,7 +155,18 @@ function maskKey(key) {
155
155
  }
156
156
 
157
157
  // src/lib/output.ts
158
- var getJson = (cmd) => cmd.optsWithGlobals().json;
158
+ var VALID_OUTPUT_MODES = ["table", "json", "live"];
159
+ var getOutputMode = (cmd, defaultMode) => {
160
+ const raw = cmd.optsWithGlobals().output;
161
+ if (!raw) return defaultMode;
162
+ if (VALID_OUTPUT_MODES.includes(raw)) return raw;
163
+ return outputErrorAndExit(
164
+ false,
165
+ `Invalid --output value: ${raw}.`,
166
+ `Supported: ${VALID_OUTPUT_MODES.join(", ")}`
167
+ );
168
+ };
169
+ var getJson = (cmd) => getOutputMode(cmd, "table") === "json";
159
170
  var getYes = (cmd) => cmd.optsWithGlobals().yes ?? false;
160
171
  var outputJson = (data) => {
161
172
  console.log(JSON.stringify(data, null, 2));
@@ -180,6 +191,13 @@ var outputData = (json, opts) => {
180
191
  opts.table();
181
192
  }
182
193
  };
194
+ var getLiveConfig = (cmd, defaultMode) => {
195
+ const mode = getOutputMode(cmd, defaultMode);
196
+ const live = mode === "live";
197
+ const intervalRaw = parseInt(cmd.optsWithGlobals().interval, 10);
198
+ const intervalSeconds = isNaN(intervalRaw) || intervalRaw < 5 ? 30 : intervalRaw;
199
+ return { live, intervalSeconds };
200
+ };
183
201
 
184
202
  // src/lib/prompt.ts
185
203
  import confirm from "@inquirer/confirm";
@@ -356,7 +374,7 @@ var getClient = () => {
356
374
  return client;
357
375
  };
358
376
  var commonProperties = () => ({
359
- cli_version: true ? "0.2.3" : "development",
377
+ cli_version: true ? "0.2.4" : "development",
360
378
  os: process.platform,
361
379
  arch: process.arch,
362
380
  node_version: process.version
@@ -506,86 +524,92 @@ authCommand.command("status").description("Check authentication status").action(
506
524
  });
507
525
  });
508
526
 
509
- // src/commands/balance.ts
510
- import { Command as Command3 } from "commander";
511
- import { getProfileBalances, setApiKey as setApiKey2 } from "@zoralabs/coins-sdk";
527
+ // src/commands/balance.tsx
528
+ import { Command as Command2 } from "commander";
529
+ import { Box as Box3, Text as Text3 } from "ink";
530
+ import { getProfileBalances, setApiKey } from "@zoralabs/coins-sdk";
512
531
 
513
532
  // src/lib/render.tsx
514
- import { renderToString } from "ink";
533
+ import { render, renderToString } from "ink";
515
534
  var renderOnce = (element) => {
516
- const output = renderToString(element);
535
+ const columns = process.stdout.columns || 80;
536
+ const output = renderToString(element, { columns });
517
537
  console.log(output);
518
538
  };
539
+ var renderLive = async (element) => {
540
+ const instance = render(element);
541
+ await instance.waitUntilExit();
542
+ };
543
+
544
+ // src/components/BalanceView.tsx
545
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef } from "react";
546
+ import { Box as Box2, Text as Text2, useInput, useApp } from "ink";
547
+ import Spinner from "ink-spinner";
519
548
 
520
549
  // src/components/table.tsx
521
550
  import { Box, Text } from "ink";
522
551
  import { jsx, jsxs } from "react/jsx-runtime";
552
+ var PADDING_LEFT = 1;
523
553
  var truncate = (str, max) => {
524
554
  if (str.length <= max) return str;
525
555
  return str.slice(0, max - 1) + "\u2026";
526
556
  };
527
- var TableComponent = ({
557
+ var computeColumnWidths = (columns, fullWidth) => {
558
+ const baseWidths = columns.map((col) => col.width);
559
+ const totalBase = baseWidths.reduce((sum, w) => sum + w, 0);
560
+ if (!fullWidth) return baseWidths;
561
+ const width = process.stdout.columns ?? 80;
562
+ const available = width - PADDING_LEFT;
563
+ if (available <= totalBase) return baseWidths;
564
+ const computed = baseWidths.map(
565
+ (w) => Math.max(1, Math.floor(w / totalBase * available))
566
+ );
567
+ let remainder = available - computed.reduce((sum, w) => sum + w, 0);
568
+ for (let i = 0; i < computed.length && remainder > 0; i++) {
569
+ computed[i]++;
570
+ remainder--;
571
+ }
572
+ return computed;
573
+ };
574
+ var Table = ({
528
575
  columns,
529
576
  data,
530
577
  title,
531
- subtitle
532
- }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
533
- title && /* @__PURE__ */ jsxs(Box, { paddingLeft: 1, marginBottom: 1, children: [
534
- /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
535
- subtitle && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
536
- " ",
537
- subtitle
538
- ] })
539
- ] }),
540
- /* @__PURE__ */ jsx(Box, { paddingLeft: 1, children: columns.map((col) => /* @__PURE__ */ jsx(Box, { width: col.width, children: /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: col.header }) }, col.header)) }),
541
- data.map((row, i) => /* @__PURE__ */ jsx(Box, { paddingLeft: 1, children: columns.map((col) => {
542
- const value = col.noTruncate ? col.accessor(row) : truncate(col.accessor(row), col.width - 2);
543
- const colorName = col.color?.(row);
544
- return /* @__PURE__ */ jsx(Box, { width: col.width, children: /* @__PURE__ */ jsx(Text, { color: colorName, children: value }) }, col.header);
545
- }) }, i))
546
- ] });
547
-
548
- // src/commands/explore.tsx
549
- import { Command as Command2 } from "commander";
550
- import {
551
- setApiKey,
552
- getCoinsTopVolume24h,
553
- getCoinsMostValuable,
554
- getCoinsNew,
555
- getCoinsTopGainers,
556
- getCoinsLastTraded,
557
- getCoinsLastTradedUnique,
558
- getExploreTopVolumeAll24h,
559
- getExploreTopVolumeCreators24h,
560
- getExploreNewAll,
561
- getExploreFeaturedCreators,
562
- getExploreFeaturedVideos,
563
- getCreatorCoins,
564
- getMostValuableCreatorCoins,
565
- getMostValuableAll,
566
- getMostValuableTrends,
567
- getNewTrends,
568
- getTopVolumeTrends24h,
569
- getTrendingAll,
570
- getTrendingCreators,
571
- getTrendingPosts,
572
- getTrendingTrends
573
- } from "@zoralabs/coins-sdk";
578
+ subtitle,
579
+ fullWidth = true,
580
+ footer
581
+ }) => {
582
+ const widths = computeColumnWidths(columns, fullWidth);
583
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
584
+ title && /* @__PURE__ */ jsxs(Box, { paddingLeft: PADDING_LEFT, marginBottom: 1, children: [
585
+ /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
586
+ subtitle && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
587
+ " ",
588
+ subtitle
589
+ ] })
590
+ ] }),
591
+ /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, i) => /* @__PURE__ */ jsx(Box, { width: widths[i], children: /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, wrap: "truncate", children: col.header }) }, col.header)) }),
592
+ data.map((row, i) => /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, colIdx) => {
593
+ const colWidth = widths[colIdx];
594
+ const value = col.noTruncate ? col.accessor(row) : truncate(col.accessor(row), colWidth - 2);
595
+ const colorName = col.color?.(row);
596
+ return /* @__PURE__ */ jsx(Box, { width: colWidth, children: /* @__PURE__ */ jsx(
597
+ Text,
598
+ {
599
+ color: colorName,
600
+ wrap: col.noTruncate ? "wrap" : "truncate",
601
+ children: value
602
+ }
603
+ ) }, col.header);
604
+ }) }, i)),
605
+ footer && /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: footer }) })
606
+ ] });
607
+ };
574
608
 
575
609
  // src/lib/format.ts
576
610
  import { format, formatDistanceStrict } from "date-fns";
577
611
  import { formatEther } from "viem";
578
- var ANSI_CODES = {
579
- dim: ["\x1B[2m", "\x1B[22m"],
580
- bold: ["\x1B[1m", "\x1B[22m"]
581
- };
582
- function styledText(text, style) {
583
- const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
584
- if (!useColor) return text;
585
- const [open, close] = ANSI_CODES[style];
586
- return `${open}${text}${close}`;
587
- }
588
- function formatCurrency(value) {
612
+ function formatCompactUsd(value) {
589
613
  if (!value || Number(value) === 0) return "$0";
590
614
  return new Intl.NumberFormat("en-US", {
591
615
  style: "currency",
@@ -644,243 +668,9 @@ var formatCoinsDisplay = (coinsOut) => new Intl.NumberFormat("en-US", {
644
668
  maximumFractionDigits: 2
645
669
  }).format(Number(coinsOut));
646
670
 
647
- // src/lib/types.ts
648
- var SORT_LABELS = {
649
- mcap: "Top by Market Cap",
650
- volume: "Top by 24h Volume",
651
- new: "New",
652
- gainers: "Top Gainers (24h)",
653
- "last-traded": "Last Traded",
654
- "last-traded-unique": "Last Traded (Unique)",
655
- trending: "Trending",
656
- featured: "Featured"
657
- };
658
- var TYPE_LABELS = {
659
- all: "all",
660
- trend: "trends",
661
- "creator-coin": "creator coins",
662
- post: "posts"
663
- };
664
- var COIN_TYPE_DISPLAY = {
665
- CONTENT: "post",
666
- CREATOR: "creator-coin",
667
- TREND: "trend"
668
- };
669
-
670
- // src/commands/explore.tsx
671
- import { Box as Box2, Text as Text2 } from "ink";
672
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
673
- var QUERY_MAP = {
674
- mcap: {
675
- all: getMostValuableAll,
676
- trend: getMostValuableTrends,
677
- "creator-coin": getMostValuableCreatorCoins,
678
- post: getCoinsMostValuable
679
- },
680
- volume: {
681
- all: getExploreTopVolumeAll24h,
682
- trend: getTopVolumeTrends24h,
683
- "creator-coin": getExploreTopVolumeCreators24h,
684
- post: getCoinsTopVolume24h
685
- },
686
- new: {
687
- all: getExploreNewAll,
688
- trend: getNewTrends,
689
- "creator-coin": getCreatorCoins,
690
- post: getCoinsNew
691
- },
692
- gainers: {
693
- post: getCoinsTopGainers
694
- },
695
- "last-traded": {
696
- post: getCoinsLastTraded
697
- },
698
- "last-traded-unique": {
699
- post: getCoinsLastTradedUnique
700
- },
701
- trending: {
702
- all: getTrendingAll,
703
- trend: getTrendingTrends,
704
- "creator-coin": getTrendingCreators,
705
- post: getTrendingPosts
706
- },
707
- featured: {
708
- "creator-coin": getExploreFeaturedCreators,
709
- post: getExploreFeaturedVideos
710
- }
711
- };
712
- var formatCompactCurrency = (value) => {
713
- if (!value) return "$0";
714
- return new Intl.NumberFormat("en-US", {
715
- style: "currency",
716
- currency: "USD",
717
- notation: "compact",
718
- maximumFractionDigits: 1
719
- }).format(Number(value));
720
- };
721
- var formatChange = (marketCap, delta) => {
722
- if (!delta || !marketCap) return "-";
723
- const cap = Number(marketCap);
724
- const d = Number(delta);
725
- if (cap === 0) return "-";
726
- const prevCap = cap - d;
727
- if (prevCap === 0) return "-";
728
- const pct = d / prevCap * 100;
729
- const sign = pct >= 0 ? "+" : "";
730
- return `${sign}${pct.toFixed(1)}%`;
731
- };
732
- var changeColor = (row) => {
733
- if (!row.marketCapDelta24h || !row.marketCap) return void 0;
734
- const cap = Number(row.marketCap);
735
- const d = Number(row.marketCapDelta24h);
736
- if (cap === 0 || cap - d === 0) return void 0;
737
- const pct = d / (cap - d) * 100;
738
- if (pct > 0) return "green";
739
- if (pct < 0) return "red";
740
- return void 0;
741
- };
742
- var SORT_OPTIONS = Object.keys(SORT_LABELS).join(", ");
743
- var rankColumn = {
744
- header: "#",
745
- width: 5,
746
- accessor: (r) => String(r.rank)
747
- };
748
- var exploreColumns = [
749
- { header: "Name", width: 27, accessor: (r) => r.name ?? "Unknown" },
750
- { header: "Address", width: 44, accessor: (r) => r.address ?? "" },
751
- {
752
- header: "Type",
753
- width: 16,
754
- accessor: (r) => COIN_TYPE_DISPLAY[r.coinType ?? ""] ?? r.coinType ?? ""
755
- },
756
- {
757
- header: "Market Cap",
758
- width: 14,
759
- accessor: (r) => formatCompactCurrency(r.marketCap)
760
- },
761
- {
762
- header: "24h Vol",
763
- width: 14,
764
- accessor: (r) => formatCompactCurrency(r.volume24h)
765
- },
766
- {
767
- header: "24h Change",
768
- width: 12,
769
- accessor: (r) => formatChange(r.marketCap, r.marketCapDelta24h),
770
- color: changeColor
771
- }
772
- ];
773
- var exploreCommand = new Command2("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "mcap").option(
774
- "--type <type>",
775
- "Filter by type: all, trend, creator-coin, post (availability varies by sort)",
776
- "post"
777
- ).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
778
- const json = getJson(this);
779
- const sort = opts.sort;
780
- const type = opts.type;
781
- const limit = parseInt(opts.limit, 10);
782
- const after = opts.after;
783
- if (isNaN(limit) || limit <= 0 || limit > 20) {
784
- outputErrorAndExit(
785
- json,
786
- `Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
787
- "Usage: zora explore --limit 10"
788
- );
789
- }
790
- if (!QUERY_MAP[sort]) {
791
- outputErrorAndExit(
792
- json,
793
- `Invalid --sort value: ${sort}.`,
794
- `Supported: ${SORT_OPTIONS}`
795
- );
796
- }
797
- if (!QUERY_MAP[sort][type]) {
798
- const supported = Object.keys(QUERY_MAP[sort]);
799
- outputErrorAndExit(
800
- json,
801
- `Invalid --type for --sort ${sort}.`,
802
- `Supported: ${supported.join(", ")}`
803
- );
804
- }
805
- const apiKey = getApiKey();
806
- if (apiKey) {
807
- setApiKey(apiKey);
808
- }
809
- const queryFn = QUERY_MAP[sort][type];
810
- let response;
811
- try {
812
- response = await queryFn({ count: limit, after });
813
- } catch (err) {
814
- outputErrorAndExit(
815
- json,
816
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
817
- );
818
- }
819
- if (response.error) {
820
- const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
821
- outputErrorAndExit(json, `API error: ${msg}`);
822
- }
823
- const edges = response.data?.exploreList?.edges ?? [];
824
- const coins = edges.map((e) => e.node);
825
- const pageInfo = response.data?.exploreList?.pageInfo;
826
- if (coins.length === 0) {
827
- outputData(json, {
828
- json: { coins: [], pageInfo: pageInfo ?? null },
829
- table: () => {
830
- renderOnce(
831
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginTop: 1, children: [
832
- /* @__PURE__ */ jsx2(Text2, { children: "No coins found." }),
833
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
834
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Try a different sort or type (defaults to posts):" }),
835
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " zora explore --sort volume --type all" }),
836
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " zora explore --sort new --type all" })
837
- ] })
838
- ] })
839
- );
840
- }
841
- });
842
- return;
843
- }
844
- const rankedCoins = coins.map((c, i) => ({ ...c, rank: i + 1 }));
845
- const columns = after ? exploreColumns : [rankColumn, ...exploreColumns];
846
- const title = type !== "all" ? `${SORT_LABELS[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS[sort];
847
- const subtitle = `${coins.length} result${coins.length !== 1 ? "s" : ""}`;
848
- outputData(json, {
849
- json: { coins, pageInfo: pageInfo ?? null },
850
- table: () => {
851
- renderOnce(
852
- /* @__PURE__ */ jsx2(
853
- TableComponent,
854
- {
855
- columns,
856
- data: rankedCoins,
857
- title,
858
- subtitle
859
- }
860
- )
861
- );
862
- if (pageInfo?.hasNextPage && pageInfo.endCursor) {
863
- console.log(
864
- `
865
- ${styledText(`Next page: zora explore --sort ${sort} --type ${type} --limit ${limit} --after ${pageInfo.endCursor}`, "dim")}`
866
- );
867
- }
868
- }
869
- });
870
- track("cli_explore", {
871
- sort,
872
- type,
873
- limit,
874
- paginated: after !== void 0,
875
- result_count: coins.length,
876
- has_next_page: pageInfo?.hasNextPage ?? false,
877
- output_format: json ? "json" : "text"
878
- });
879
- });
880
-
881
671
  // src/lib/balance-format.ts
882
672
  var COIN_DECIMALS = 18;
883
- var toHumanBalance = (rawBalance) => Number(normalizeTokenAmount(rawBalance));
673
+ var parseRawBalance = (rawBalance) => Number(normalizeTokenAmount(rawBalance));
884
674
  var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
885
675
  try {
886
676
  const value = BigInt(rawBalance);
@@ -895,20 +685,20 @@ var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
895
685
  return rawBalance;
896
686
  }
897
687
  };
898
- var formatUsdValue = (balance, priceInUsdc) => {
688
+ var formatBalanceAsUsd = (balance, priceInUsdc) => {
899
689
  if (!priceInUsdc) return "-";
900
- const value = toHumanBalance(balance) * Number(priceInUsdc);
690
+ const value = parseRawBalance(balance) * Number(priceInUsdc);
901
691
  if (value < 0.01) return "<$0.01";
902
692
  return formatUsd(value);
903
693
  };
904
694
  var formatBalance = (balance) => {
905
- const n = toHumanBalance(balance);
695
+ const n = parseRawBalance(balance);
906
696
  if (n === 0) return "0";
907
697
  if (n < 1e-3) return "<0.001";
908
698
  if (n < 1) return n.toFixed(4);
909
699
  return new Intl.NumberFormat("en-US", {
910
700
  notation: "compact",
911
- compactDisplay: "long",
701
+ compactDisplay: "short",
912
702
  maximumFractionDigits: 1
913
703
  }).format(n);
914
704
  };
@@ -918,37 +708,246 @@ var trimTrailingZeros = (value) => {
918
708
  return trimmed || "0";
919
709
  };
920
710
 
921
- // src/lib/wallet-balances.ts
922
- import { getTokenInfo } from "@zoralabs/coins-sdk";
923
- import {
924
- createPublicClient as createPublicClient2,
925
- erc20Abi,
926
- formatUnits,
927
- http
928
- } from "viem";
929
- import { base as base2 } from "viem/chains";
930
- var TRACKED_TOKENS = [
711
+ // src/lib/balance-columns.tsx
712
+ var SORT_LABELS = {
713
+ "usd-value": "USD Value",
714
+ balance: "Balance",
715
+ "market-cap": "Market Cap",
716
+ "price-change": "Price Change"
717
+ };
718
+ var walletColumns = [
719
+ { header: "Name", width: 14, accessor: (row) => row.name },
931
720
  {
932
- name: "Ether",
933
- symbol: "ETH",
934
- address: WETH_ADDRESS,
935
- decimals: 18,
936
- priceAddress: WETH_ADDRESS,
937
- isNative: true
721
+ header: "Symbol",
722
+ width: 10,
723
+ noTruncate: true,
724
+ accessor: (row) => row.symbol
938
725
  },
726
+ { header: "Balance", width: 20, accessor: (row) => row.balance },
727
+ { header: "USD Value", width: 16, accessor: (row) => row.usdValue }
728
+ ];
729
+ var balanceColumns = [
730
+ { header: "#", width: 5, accessor: (row) => String(row.rank) },
939
731
  {
940
- name: "USD Coin",
941
- symbol: "USDC",
942
- address: USDC_ADDRESS,
943
- decimals: USDC_DECIMALS,
944
- priceAddress: USDC_ADDRESS,
945
- fixedPriceUsd: 1
732
+ header: "Name",
733
+ width: 24,
734
+ accessor: (row) => row.coin?.name ?? "Unknown"
946
735
  },
947
736
  {
948
- name: "ZORA",
949
- symbol: "ZORA",
950
- address: ZORA_ADDRESS,
951
- decimals: 18,
737
+ header: "Symbol",
738
+ width: 12,
739
+ noTruncate: true,
740
+ accessor: (row) => row.coin?.symbol ?? ""
741
+ },
742
+ {
743
+ header: "Balance",
744
+ width: 14,
745
+ accessor: (row) => formatBalance(row.balance)
746
+ },
747
+ {
748
+ header: "USD Value",
749
+ width: 14,
750
+ accessor: (row) => formatBalanceAsUsd(row.balance, row.coin?.tokenPrice?.priceInUsdc)
751
+ },
752
+ {
753
+ header: "Market Cap",
754
+ width: 14,
755
+ accessor: (row) => formatCompactUsd(row.coin?.marketCap)
756
+ },
757
+ {
758
+ header: "24h Change",
759
+ width: 12,
760
+ accessor: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).text,
761
+ color: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).color
762
+ }
763
+ ];
764
+
765
+ // src/hooks/use-auto-refresh.ts
766
+ import { useState, useEffect, useCallback } from "react";
767
+ var useAutoRefresh = (intervalSeconds, enabled) => {
768
+ const [refreshCount, setRefreshCount] = useState(0);
769
+ const [secondsUntilRefresh, setSecondsUntilRefresh] = useState(intervalSeconds);
770
+ const [resetCount, setResetCount] = useState(0);
771
+ const triggerManualRefresh = useCallback(() => {
772
+ if (!enabled) return;
773
+ setRefreshCount((c) => c + 1);
774
+ setSecondsUntilRefresh(intervalSeconds);
775
+ setResetCount((c) => c + 1);
776
+ }, [enabled, intervalSeconds]);
777
+ useEffect(() => {
778
+ if (!enabled) return;
779
+ setSecondsUntilRefresh(intervalSeconds);
780
+ const ticker = setInterval(() => {
781
+ setSecondsUntilRefresh((prev) => {
782
+ if (prev <= 1) {
783
+ setRefreshCount((c) => c + 1);
784
+ return intervalSeconds;
785
+ }
786
+ return prev - 1;
787
+ });
788
+ }, 1e3);
789
+ return () => clearInterval(ticker);
790
+ }, [enabled, intervalSeconds, resetCount]);
791
+ if (!enabled) {
792
+ return { refreshCount: 0, secondsUntilRefresh: 0, triggerManualRefresh };
793
+ }
794
+ return { refreshCount, secondsUntilRefresh, triggerManualRefresh };
795
+ };
796
+
797
+ // src/components/BalanceView.tsx
798
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
799
+ var BalanceView = ({
800
+ fetchData,
801
+ sort,
802
+ mode = "full",
803
+ autoRefresh = false,
804
+ intervalSeconds = 30
805
+ }) => {
806
+ const { exit } = useApp();
807
+ const [loading, setLoading] = useState2(true);
808
+ const [isRefreshing, setIsRefreshing] = useState2(false);
809
+ const [error, setError] = useState2(null);
810
+ const [data, setData] = useState2(null);
811
+ const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
812
+ const [manualRefreshCount, setManualRefreshCount] = useState2(0);
813
+ const hasLoadedOnce = useRef(false);
814
+ const load = useCallback2(async () => {
815
+ if (hasLoadedOnce.current) {
816
+ setIsRefreshing(true);
817
+ } else {
818
+ setLoading(true);
819
+ }
820
+ setError(null);
821
+ try {
822
+ const result = await fetchData();
823
+ setData(result);
824
+ hasLoadedOnce.current = true;
825
+ } catch (err) {
826
+ setError(err instanceof Error ? err.message : String(err));
827
+ }
828
+ setLoading(false);
829
+ setIsRefreshing(false);
830
+ }, [fetchData]);
831
+ useEffect2(() => {
832
+ load();
833
+ }, [refreshCount, manualRefreshCount]);
834
+ useInput((input, key) => {
835
+ if (input === "q" || key.escape) {
836
+ exit();
837
+ return;
838
+ }
839
+ if (input === "r" && !loading) {
840
+ triggerManualRefresh();
841
+ setManualRefreshCount((c) => c + 1);
842
+ }
843
+ });
844
+ if (error && !data) {
845
+ return /* @__PURE__ */ jsxs2(
846
+ Box2,
847
+ {
848
+ flexDirection: "column",
849
+ paddingLeft: 1,
850
+ paddingTop: 1,
851
+ paddingBottom: 1,
852
+ children: [
853
+ /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
854
+ "Error: ",
855
+ error
856
+ ] }),
857
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Press q to exit" }) })
858
+ ]
859
+ }
860
+ );
861
+ }
862
+ if (loading && !data) {
863
+ return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
864
+ /* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
865
+ " Loading\u2026"
866
+ ] }) });
867
+ }
868
+ if (!data) return null;
869
+ const hints = ["r refresh"];
870
+ if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
871
+ hints.push("q quit");
872
+ const footer = hints.join(" \xB7 ");
873
+ const showWallet = mode === "full" || mode === "wallet";
874
+ const showCoins = mode === "full" || mode === "coins";
875
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
876
+ isRefreshing && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
877
+ /* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
878
+ " Refreshing\u2026"
879
+ ] }) }),
880
+ showWallet && /* @__PURE__ */ jsx2(
881
+ Table,
882
+ {
883
+ columns: walletColumns,
884
+ data: data.walletBalances,
885
+ title: "Wallet"
886
+ }
887
+ ),
888
+ showCoins && data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs2(
889
+ Box2,
890
+ {
891
+ flexDirection: "column",
892
+ paddingLeft: 1,
893
+ paddingTop: 1,
894
+ paddingBottom: 1,
895
+ children: [
896
+ /* @__PURE__ */ jsx2(Text2, { children: "No coin balances found." }),
897
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
898
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Buy coins to see them here:" }),
899
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
900
+ " zora buy ",
901
+ "<address>",
902
+ " --eth 0.001"
903
+ ] })
904
+ ] })
905
+ ]
906
+ }
907
+ ) : showCoins ? /* @__PURE__ */ jsx2(
908
+ Table,
909
+ {
910
+ columns: balanceColumns,
911
+ data: data.rankedBalances,
912
+ title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
913
+ subtitle: `${data.rankedBalances.length} of ${data.total}`
914
+ }
915
+ ) : null,
916
+ /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: footer }) })
917
+ ] });
918
+ };
919
+
920
+ // src/lib/wallet-balances.ts
921
+ import { getTokenInfo } from "@zoralabs/coins-sdk";
922
+ import {
923
+ createPublicClient as createPublicClient2,
924
+ erc20Abi,
925
+ formatUnits,
926
+ http
927
+ } from "viem";
928
+ import { base as base2 } from "viem/chains";
929
+ var TRACKED_TOKENS = [
930
+ {
931
+ name: "Ether",
932
+ symbol: "ETH",
933
+ address: WETH_ADDRESS,
934
+ decimals: 18,
935
+ priceAddress: WETH_ADDRESS,
936
+ isNative: true
937
+ },
938
+ {
939
+ name: "USD Coin",
940
+ symbol: "USDC",
941
+ address: USDC_ADDRESS,
942
+ decimals: USDC_DECIMALS,
943
+ priceAddress: USDC_ADDRESS,
944
+ fixedPriceUsd: 1
945
+ },
946
+ {
947
+ name: "ZORA",
948
+ symbol: "ZORA",
949
+ address: ZORA_ADDRESS,
950
+ decimals: 18,
952
951
  priceAddress: ZORA_ADDRESS
953
952
  }
954
953
  ];
@@ -1033,78 +1032,21 @@ var fetchWalletBalances = async (walletAddress) => {
1033
1032
  return { walletBalances, walletBalancesJson };
1034
1033
  };
1035
1034
 
1036
- // src/commands/balance.ts
1035
+ // src/commands/balance.tsx
1036
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1037
1037
  var SORT_MAP = {
1038
1038
  "usd-value": "USD_VALUE",
1039
1039
  balance: "BALANCE",
1040
1040
  "market-cap": "MARKET_CAP",
1041
1041
  "price-change": "PRICE_CHANGE"
1042
1042
  };
1043
- var SORT_LABELS2 = {
1044
- "usd-value": "USD Value",
1045
- balance: "Balance",
1046
- "market-cap": "Market Cap",
1047
- "price-change": "Price Change"
1048
- };
1049
- var SORT_OPTIONS2 = Object.keys(SORT_MAP).join(", ");
1043
+ var SORT_OPTIONS = Object.keys(SORT_LABELS).join(", ");
1050
1044
  var extractErrorMessage = (error) => {
1051
1045
  if (typeof error === "object" && error !== null && "error" in error) {
1052
1046
  return String(error.error);
1053
1047
  }
1054
1048
  return JSON.stringify(error);
1055
1049
  };
1056
- var changeColor2 = (row) => {
1057
- if (!row.coin?.marketCap || !row.coin.marketCapDelta24h) return void 0;
1058
- const cap = Number(row.coin.marketCap);
1059
- const d = Number(row.coin.marketCapDelta24h);
1060
- if (cap === 0 || cap - d === 0) return void 0;
1061
- const pct = d / (cap - d) * 100;
1062
- if (pct > 0) return "green";
1063
- if (pct < 0) return "red";
1064
- return void 0;
1065
- };
1066
- var walletColumns = [
1067
- { header: "Name", width: 14, accessor: (row) => row.name },
1068
- {
1069
- header: "Symbol",
1070
- width: 10,
1071
- noTruncate: true,
1072
- accessor: (row) => row.symbol
1073
- },
1074
- { header: "Balance", width: 20, accessor: (row) => row.balance },
1075
- { header: "USD Value", width: 16, accessor: (row) => row.usdValue }
1076
- ];
1077
- var balanceColumns = [
1078
- { header: "#", width: 5, accessor: (row) => String(row.rank) },
1079
- { header: "Name", width: 24, accessor: (row) => row.coin?.name ?? "Unknown" },
1080
- {
1081
- header: "Symbol",
1082
- width: 12,
1083
- noTruncate: true,
1084
- accessor: (row) => row.coin?.symbol ?? ""
1085
- },
1086
- {
1087
- header: "Balance",
1088
- width: 14,
1089
- accessor: (row) => formatBalance(row.balance)
1090
- },
1091
- {
1092
- header: "USD Value",
1093
- width: 14,
1094
- accessor: (row) => formatUsdValue(row.balance, row.coin?.tokenPrice?.priceInUsdc)
1095
- },
1096
- {
1097
- header: "Market Cap",
1098
- width: 14,
1099
- accessor: (row) => formatCompactCurrency(row.coin?.marketCap)
1100
- },
1101
- {
1102
- header: "24h Change",
1103
- width: 12,
1104
- accessor: (row) => formatChange(row.coin?.marketCap, row.coin?.marketCapDelta24h),
1105
- color: changeColor2
1106
- }
1107
- ];
1108
1050
  var formatBalanceJson = (balance, rank) => {
1109
1051
  const priceUsd = balance.coin?.tokenPrice?.priceInUsdc;
1110
1052
  const marketCap = balance.coin?.marketCap ? Number(balance.coin.marketCap) : null;
@@ -1112,7 +1054,7 @@ var formatBalanceJson = (balance, rank) => {
1112
1054
  const volume24h = balance.coin?.volume24h ? Number(balance.coin.volume24h) : null;
1113
1055
  const totalVolume = balance.coin?.totalVolume ? Number(balance.coin.totalVolume) : null;
1114
1056
  const priceUsdValue = priceUsd ? Number(priceUsd) : null;
1115
- const usdValue = priceUsdValue !== null ? Number((toHumanBalance(balance.balance) * priceUsdValue).toFixed(6)) : null;
1057
+ const usdValue = priceUsdValue !== null ? Number((parseRawBalance(balance.balance) * priceUsdValue).toFixed(6)) : null;
1116
1058
  const marketCapChange24h = marketCap !== null && marketCapDelta24h !== null && marketCap - marketCapDelta24h !== 0 ? Number(
1117
1059
  (marketCapDelta24h / (marketCap - marketCapDelta24h) * 100).toFixed(
1118
1060
  4
@@ -1146,7 +1088,7 @@ function resolveContext(json) {
1146
1088
  "Not authenticated. Run 'zora auth configure' to set your API key."
1147
1089
  );
1148
1090
  }
1149
- setApiKey2(apiKey);
1091
+ setApiKey(apiKey);
1150
1092
  return account;
1151
1093
  }
1152
1094
  function renderWallet(json, walletResult) {
@@ -1154,11 +1096,14 @@ function renderWallet(json, walletResult) {
1154
1096
  json: { wallet: walletResult.walletBalancesJson },
1155
1097
  table: () => {
1156
1098
  renderOnce(
1157
- TableComponent({
1158
- columns: walletColumns,
1159
- data: walletResult.walletBalances,
1160
- title: "Wallet"
1161
- })
1099
+ /* @__PURE__ */ jsx3(
1100
+ Table,
1101
+ {
1102
+ columns: walletColumns,
1103
+ data: walletResult.walletBalances,
1104
+ title: "Wallet"
1105
+ }
1106
+ )
1162
1107
  );
1163
1108
  }
1164
1109
  });
@@ -1181,12 +1126,15 @@ function renderCoins(json, balances, total, sort) {
1181
1126
  console.log(" zora buy <address> --eth 0.001\n");
1182
1127
  } else {
1183
1128
  renderOnce(
1184
- TableComponent({
1185
- columns: balanceColumns,
1186
- data: rankedBalances,
1187
- title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
1188
- subtitle: `${balances.length} of ${total}`
1189
- })
1129
+ /* @__PURE__ */ jsx3(
1130
+ Table,
1131
+ {
1132
+ columns: balanceColumns,
1133
+ data: rankedBalances,
1134
+ title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
1135
+ subtitle: `${balances.length} of ${total}`
1136
+ }
1137
+ )
1190
1138
  );
1191
1139
  }
1192
1140
  }
@@ -1224,7 +1172,7 @@ function validateCoinOpts(json, sort, limitStr) {
1224
1172
  outputErrorAndExit(
1225
1173
  json,
1226
1174
  `Invalid --sort value: ${sort}.`,
1227
- `Supported: ${SORT_OPTIONS2}`
1175
+ `Supported: ${SORT_OPTIONS}`
1228
1176
  );
1229
1177
  }
1230
1178
  const limit = parseInt(limitStr, 10);
@@ -1236,100 +1184,243 @@ function validateCoinOpts(json, sort, limitStr) {
1236
1184
  }
1237
1185
  return { sort, limit };
1238
1186
  }
1239
- var balanceCommand = new Command3("balance").description("Show balances in your wallet").action(async function() {
1240
- const json = getJson(this);
1187
+ var balanceCommand = new Command2("balance").description("Show balances in your wallet").action(async function() {
1188
+ const output = getOutputMode(this, "live");
1189
+ const json = output === "json";
1241
1190
  const account = resolveContext(json);
1191
+ const { live, intervalSeconds } = getLiveConfig(this, "live");
1242
1192
  const sort = "usd-value";
1243
1193
  const limit = 10;
1244
- let walletResult;
1245
- let coinsResult;
1246
- try {
1247
- [walletResult, coinsResult] = await Promise.all([
1194
+ const fetchBalanceData = async () => {
1195
+ const [walletResult, coinsResult] = await Promise.allSettled([
1248
1196
  fetchWalletBalances(account.address),
1249
1197
  fetchCoins(json, account.address, sort, limit)
1250
1198
  ]);
1251
- } catch (err) {
1252
- outputErrorAndExit(
1253
- json,
1254
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
1199
+ if (walletResult.status === "rejected" || coinsResult.status === "rejected") {
1200
+ const err = walletResult.status === "rejected" ? walletResult.reason : coinsResult.reason;
1201
+ throw new Error(err instanceof Error ? err.message : String(err));
1202
+ }
1203
+ const rankedBalances = coinsResult.value.balances.map(
1204
+ (balance, index) => ({
1205
+ ...balance,
1206
+ rank: index + 1
1207
+ })
1255
1208
  );
1256
- }
1257
- const rankedBalances = coinsResult.balances.map((balance, index) => ({
1258
- ...balance,
1259
- rank: index + 1
1260
- }));
1261
- outputData(json, {
1262
- json: {
1263
- wallet: walletResult.walletBalancesJson,
1264
- coins: rankedBalances.map(
1265
- (balance) => formatBalanceJson(balance, balance.rank)
1209
+ return {
1210
+ walletBalances: walletResult.value.walletBalances,
1211
+ walletBalancesJson: walletResult.value.walletBalancesJson,
1212
+ rankedBalances,
1213
+ total: coinsResult.value.total
1214
+ };
1215
+ };
1216
+ if (json) {
1217
+ const data = await fetchBalanceData().catch(
1218
+ (err) => outputErrorAndExit(
1219
+ json,
1220
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1266
1221
  )
1267
- },
1268
- table: () => {
1269
- renderOnce(
1270
- TableComponent({
1271
- columns: walletColumns,
1272
- data: walletResult.walletBalances,
1273
- title: "Wallet"
1274
- })
1275
- );
1276
- if (coinsResult.balances.length === 0) {
1277
- console.log("\n No coin balances found.\n");
1278
- console.log(" Buy coins to see them here:");
1279
- console.log(" zora buy <address> --eth 0.001\n");
1280
- } else {
1281
- renderOnce(
1282
- TableComponent({
1283
- columns: balanceColumns,
1284
- data: rankedBalances,
1285
- title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
1286
- subtitle: `${coinsResult.balances.length} of ${coinsResult.total}`
1287
- })
1288
- );
1222
+ );
1223
+ outputData(json, {
1224
+ json: {
1225
+ wallet: data.walletBalancesJson,
1226
+ coins: data.rankedBalances.map(
1227
+ (balance) => formatBalanceJson(balance, balance.rank)
1228
+ )
1229
+ },
1230
+ table: () => {
1289
1231
  }
1290
- }
1291
- });
1292
- track("cli_balances", {
1293
- sort,
1294
- limit,
1295
- result_count: coinsResult.balances.length,
1296
- total_count: coinsResult.total,
1297
- output_format: json ? "json" : "text"
1298
- });
1232
+ });
1233
+ track("cli_balances", {
1234
+ sort,
1235
+ limit,
1236
+ live: false,
1237
+ result_count: data.rankedBalances.length,
1238
+ total_count: data.total,
1239
+ output_format: "json"
1240
+ });
1241
+ } else if (live) {
1242
+ await renderLive(
1243
+ /* @__PURE__ */ jsx3(
1244
+ BalanceView,
1245
+ {
1246
+ fetchData: fetchBalanceData,
1247
+ sort,
1248
+ mode: "full",
1249
+ autoRefresh: live,
1250
+ intervalSeconds
1251
+ }
1252
+ )
1253
+ );
1254
+ track("cli_balances", {
1255
+ sort,
1256
+ limit,
1257
+ live,
1258
+ interval: intervalSeconds,
1259
+ output_format: "live"
1260
+ });
1261
+ } else {
1262
+ const data = await fetchBalanceData().catch(
1263
+ (err) => outputErrorAndExit(
1264
+ json,
1265
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1266
+ )
1267
+ );
1268
+ renderOnce(
1269
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1270
+ /* @__PURE__ */ jsx3(
1271
+ Table,
1272
+ {
1273
+ columns: walletColumns,
1274
+ data: data.walletBalances,
1275
+ title: "Wallet"
1276
+ }
1277
+ ),
1278
+ data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs3(
1279
+ Box3,
1280
+ {
1281
+ flexDirection: "column",
1282
+ paddingLeft: 1,
1283
+ paddingTop: 1,
1284
+ paddingBottom: 1,
1285
+ children: [
1286
+ /* @__PURE__ */ jsx3(Text3, { children: "No coin balances found." }),
1287
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1288
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Buy coins to see them here:" }),
1289
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1290
+ " zora buy ",
1291
+ "<address>",
1292
+ " --eth 0.001"
1293
+ ] })
1294
+ ] })
1295
+ ]
1296
+ }
1297
+ ) : /* @__PURE__ */ jsx3(
1298
+ Table,
1299
+ {
1300
+ columns: balanceColumns,
1301
+ data: data.rankedBalances,
1302
+ title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
1303
+ subtitle: `${data.rankedBalances.length} of ${data.total}`
1304
+ }
1305
+ )
1306
+ ] })
1307
+ );
1308
+ track("cli_balances", {
1309
+ sort,
1310
+ limit,
1311
+ live: false,
1312
+ result_count: data.rankedBalances.length,
1313
+ total_count: data.total,
1314
+ output_format: "text"
1315
+ });
1316
+ }
1299
1317
  });
1300
1318
  balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").action(async function() {
1301
- const json = getJson(this);
1319
+ const output = getOutputMode(this, "live");
1320
+ const json = output === "json";
1302
1321
  const account = resolveContext(json);
1303
- let walletResult;
1304
- try {
1305
- walletResult = await fetchWalletBalances(account.address);
1306
- } catch (err) {
1307
- outputErrorAndExit(
1308
- json,
1309
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
1322
+ const { live, intervalSeconds } = getLiveConfig(this, "live");
1323
+ const fetchSpendableData = async () => {
1324
+ const walletResult = await fetchWalletBalances(account.address);
1325
+ return {
1326
+ walletBalances: walletResult.walletBalances,
1327
+ walletBalancesJson: walletResult.walletBalancesJson,
1328
+ rankedBalances: [],
1329
+ total: 0
1330
+ };
1331
+ };
1332
+ if (json) {
1333
+ const data = await fetchSpendableData().catch(
1334
+ (err) => outputErrorAndExit(
1335
+ json,
1336
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1337
+ )
1310
1338
  );
1339
+ outputData(json, {
1340
+ json: { wallet: data.walletBalancesJson },
1341
+ table: () => {
1342
+ }
1343
+ });
1344
+ } else if (live) {
1345
+ await renderLive(
1346
+ /* @__PURE__ */ jsx3(
1347
+ BalanceView,
1348
+ {
1349
+ fetchData: fetchSpendableData,
1350
+ sort: "usd-value",
1351
+ mode: "wallet",
1352
+ autoRefresh: live,
1353
+ intervalSeconds
1354
+ }
1355
+ )
1356
+ );
1357
+ } else {
1358
+ const walletResult = await fetchWalletBalances(account.address).catch(
1359
+ (err) => outputErrorAndExit(
1360
+ json,
1361
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1362
+ )
1363
+ );
1364
+ renderWallet(json, walletResult);
1311
1365
  }
1312
- renderWallet(json, walletResult);
1313
1366
  });
1314
- balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "usd-value").option("--limit <n>", "Number of results (max 20)", "10").action(async function(opts) {
1315
- const json = getJson(this);
1367
+ balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "usd-value").option("--limit <n>", "Number of results (max 20)", "10").action(async function(opts) {
1368
+ const output = getOutputMode(this, "live");
1369
+ const json = output === "json";
1316
1370
  const { sort, limit } = validateCoinOpts(json, opts.sort, opts.limit);
1317
1371
  const account = resolveContext(json);
1318
- const { balances, total } = await fetchCoins(
1319
- json,
1320
- account.address,
1321
- sort,
1322
- limit
1323
- );
1324
- renderCoins(json, balances, total, sort);
1372
+ const { live, intervalSeconds } = getLiveConfig(this, "live");
1373
+ const fetchCoinsData = async () => {
1374
+ const { balances, total } = await fetchCoins(
1375
+ json,
1376
+ account.address,
1377
+ sort,
1378
+ limit
1379
+ );
1380
+ const rankedBalances = balances.map((balance, index) => ({
1381
+ ...balance,
1382
+ rank: index + 1
1383
+ }));
1384
+ return {
1385
+ walletBalances: [],
1386
+ walletBalancesJson: [],
1387
+ rankedBalances,
1388
+ total
1389
+ };
1390
+ };
1391
+ if (json) {
1392
+ const data = await fetchCoinsData();
1393
+ renderCoins(json, data.rankedBalances, data.total, sort);
1394
+ } else if (live) {
1395
+ await renderLive(
1396
+ /* @__PURE__ */ jsx3(
1397
+ BalanceView,
1398
+ {
1399
+ fetchData: fetchCoinsData,
1400
+ sort,
1401
+ mode: "coins",
1402
+ autoRefresh: live,
1403
+ intervalSeconds
1404
+ }
1405
+ )
1406
+ );
1407
+ } else {
1408
+ const { balances, total } = await fetchCoins(
1409
+ json,
1410
+ account.address,
1411
+ sort,
1412
+ limit
1413
+ );
1414
+ renderCoins(json, balances, total, sort);
1415
+ }
1325
1416
  });
1326
1417
 
1327
1418
  // src/commands/buy.ts
1328
- import { Command as Command4 } from "commander";
1419
+ import { Command as Command3 } from "commander";
1329
1420
  import confirm2 from "@inquirer/confirm";
1330
1421
  import { parseUnits, formatUnits as formatUnits3, isAddress } from "viem";
1331
1422
  import {
1332
- setApiKey as setApiKey3,
1423
+ setApiKey as setApiKey2,
1333
1424
  getCoin,
1334
1425
  tradeCoin,
1335
1426
  createTradeCall
@@ -1343,7 +1434,7 @@ import {
1343
1434
  parseEventLogs,
1344
1435
  erc20Abi as erc20Abi2
1345
1436
  } from "viem";
1346
- var GAS_RESERVE = parseEther("0.001");
1437
+ var GAS_RESERVE = parseEther("0.00001");
1347
1438
  var BUY_AMOUNT_CHECKS = {
1348
1439
  eth: (opts) => opts.eth !== void 0,
1349
1440
  usd: (opts) => opts.usd !== void 0,
@@ -1409,13 +1500,16 @@ var getReceivedAmountFromReceipt = ({
1409
1500
  if (matchingTransfers.length === 0) {
1410
1501
  throw new Error("No matching Transfer event found in receipt.");
1411
1502
  }
1412
- return matchingTransfers.reduce((total, transfer) => {
1503
+ const amount = matchingTransfers.reduce((total, transfer) => {
1413
1504
  const value = transfer.args?.value;
1414
1505
  if (value === void 0) {
1415
1506
  throw new Error("Transfer event missing amount.");
1416
1507
  }
1417
1508
  return total + value;
1418
1509
  }, 0n);
1510
+ const lastTransfer = matchingTransfers[matchingTransfers.length - 1];
1511
+ const logIndex = lastTransfer?.logIndex ?? null;
1512
+ return { amount, logIndex };
1419
1513
  };
1420
1514
  var printDebugRequest = (label, tradeParameters) => {
1421
1515
  if (process.env.ZORA_API_TARGET) {
@@ -1505,19 +1599,12 @@ var printTradeResult = (json, info) => {
1505
1599
  };
1506
1600
 
1507
1601
  // src/commands/buy.ts
1508
- var buyCommand = new Command4("buy").description("Buy a coin").argument("<address>", "Coin contract address (0x\u2026)").option("--eth <value>", "Buy with ETH amount").option("--usd <value>", "Buy with USD equivalent (use with --token)").option("--token <asset>", "Token to spend: eth, usdc, zora", "eth").option("--percent <value>", "Buy with percentage of ETH balance").option("--all", "Swap all ETH for coin").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").option("-o, --output <format>", "Output format: table, json", "table").action(async (coinAddress, opts) => {
1509
- const json = opts.output === "json";
1602
+ var buyCommand = new Command3("buy").description("Buy a coin").argument("<address>", "Coin contract address (0x\u2026)").option("--eth <value>", "Buy with ETH amount").option("--usd <value>", "Buy with USD equivalent (use with --token)").option("--token <asset>", "Token to spend: eth, usdc, zora", "eth").option("--percent <value>", "Buy with percentage of ETH balance").option("--all", "Swap all ETH for coin").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(coinAddress, opts) {
1603
+ const json = getJson(this);
1510
1604
  const debug = opts.debug === true;
1511
1605
  if (!isAddress(coinAddress)) {
1512
1606
  outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
1513
1607
  }
1514
- const output = opts.output;
1515
- if (output !== "table" && output !== "json") {
1516
- outputErrorAndExit(
1517
- false,
1518
- `Invalid --output value: ${output}. Use: table, json`
1519
- );
1520
- }
1521
1608
  const tokenKey = opts.token.toLowerCase();
1522
1609
  if (!(tokenKey in BASE_TRADE_TOKENS)) {
1523
1610
  outputErrorAndExit(
@@ -1542,7 +1629,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
1542
1629
  const slippage = slippagePct / 100;
1543
1630
  const apiKey = getApiKey();
1544
1631
  if (apiKey) {
1545
- setApiKey3(apiKey);
1632
+ setApiKey2(apiKey);
1546
1633
  }
1547
1634
  const account = resolveAccount(json);
1548
1635
  const { publicClient, walletClient } = createClients(account);
@@ -1648,7 +1735,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
1648
1735
  if (isEth && balance <= gasReserve) {
1649
1736
  outputErrorAndExit(
1650
1737
  json,
1651
- `Balance too low (${formatEthDisplay(balance)} ETH). Need >0.001 ETH for gas.`
1738
+ `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
1652
1739
  );
1653
1740
  }
1654
1741
  const spendableBalance = balance - gasReserve;
@@ -1662,7 +1749,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
1662
1749
  "Invalid --percent value. Must be between 0 and 100."
1663
1750
  );
1664
1751
  }
1665
- amountIn = pct === 100 ? spendableBalance : balance * BigInt(Math.round(pct * 100)) / 10000n;
1752
+ amountIn = pct === 100 ? spendableBalance : spendableBalance * BigInt(Math.round(pct * 100)) / 10000n;
1666
1753
  if (amountIn === 0n) {
1667
1754
  outputErrorAndExit(
1668
1755
  json,
@@ -1671,6 +1758,17 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
1671
1758
  }
1672
1759
  }
1673
1760
  }
1761
+ let swapAmountUsd;
1762
+ if (amountMode === "usd") {
1763
+ swapAmountUsd = parsePercentageLikeValue(opts.usd);
1764
+ } else {
1765
+ const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
1766
+ if (priceUsd != null) {
1767
+ swapAmountUsd = Number(
1768
+ (Number(formatUnits3(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
1769
+ );
1770
+ }
1771
+ }
1674
1772
  const tradeParameters = {
1675
1773
  sell: inputToken.trade,
1676
1774
  buy: { type: "erc20", address: coinAddress },
@@ -1747,8 +1845,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1747
1845
  coin_name: coinName,
1748
1846
  coin_symbol: coinSymbol,
1749
1847
  amount_mode: amountMode,
1848
+ swap_amount_usd: swapAmountUsd,
1849
+ valueUsd: swapAmountUsd,
1850
+ swapCoinType: token.coinType ?? null,
1750
1851
  slippage: slippagePct,
1751
- output_format: opts.output
1852
+ output_format: json ? "json" : "table"
1752
1853
  });
1753
1854
  return;
1754
1855
  }
@@ -1776,6 +1877,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1776
1877
  let receipt;
1777
1878
  let txHash;
1778
1879
  let receivedAmountOut = BigInt(amountOut);
1880
+ let swapLogIndex = null;
1881
+ const swapCoinType = token.coinType ?? null;
1779
1882
  try {
1780
1883
  receipt = await tradeCoin({
1781
1884
  tradeParameters,
@@ -1790,8 +1893,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1790
1893
  coin_name: coinName,
1791
1894
  coin_symbol: coinSymbol,
1792
1895
  amount_mode: amountMode,
1896
+ swap_amount_usd: swapAmountUsd,
1897
+ valueUsd: swapAmountUsd,
1898
+ swapCoinType,
1793
1899
  slippage: slippagePct,
1794
- output_format: opts.output,
1900
+ output_format: json ? "json" : "table",
1795
1901
  success: false,
1796
1902
  error_type: err instanceof Error ? err.constructor.name : "unknown"
1797
1903
  });
@@ -1803,11 +1909,13 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1803
1909
  }
1804
1910
  txHash = receipt.transactionHash;
1805
1911
  try {
1806
- receivedAmountOut = getReceivedAmountFromReceipt({
1912
+ const result = getReceivedAmountFromReceipt({
1807
1913
  receipt,
1808
1914
  tokenAddress: coinAddress,
1809
1915
  recipient: account.address
1810
1916
  });
1917
+ receivedAmountOut = result.amount;
1918
+ swapLogIndex = result.logIndex;
1811
1919
  } catch (err) {
1812
1920
  console.warn(
1813
1921
  `Warning: transaction succeeded but could not determine received amount: ${err instanceof Error ? err.message : String(err)}`
@@ -1833,13 +1941,401 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1833
1941
  amount_mode: amountMode,
1834
1942
  input_amount: amountIn.toString(),
1835
1943
  input_token_symbol: inputToken.symbol,
1944
+ swap_amount_usd: swapAmountUsd,
1945
+ valueUsd: swapAmountUsd,
1946
+ swapCoinType,
1947
+ transactionHash: txHash,
1948
+ logIndex: swapLogIndex,
1836
1949
  slippage: slippagePct,
1837
- output_format: opts.output,
1950
+ output_format: json ? "json" : "table",
1838
1951
  success: true,
1839
1952
  tx_hash: txHash
1840
1953
  });
1841
1954
  });
1842
1955
 
1956
+ // src/commands/explore.tsx
1957
+ import { Command as Command4 } from "commander";
1958
+ import {
1959
+ setApiKey as setApiKey3,
1960
+ getCoinsTopVolume24h,
1961
+ getCoinsMostValuable,
1962
+ getCoinsNew,
1963
+ getCoinsTopGainers,
1964
+ getCoinsLastTraded,
1965
+ getCoinsLastTradedUnique,
1966
+ getExploreTopVolumeAll24h,
1967
+ getExploreTopVolumeCreators24h,
1968
+ getExploreNewAll,
1969
+ getExploreFeaturedCreators,
1970
+ getExploreFeaturedVideos,
1971
+ getCreatorCoins,
1972
+ getMostValuableCreatorCoins,
1973
+ getMostValuableAll,
1974
+ getMostValuableTrends,
1975
+ getNewTrends,
1976
+ getTopVolumeTrends24h,
1977
+ getTrendingAll,
1978
+ getTrendingCreators,
1979
+ getTrendingPosts,
1980
+ getTrendingTrends
1981
+ } from "@zoralabs/coins-sdk";
1982
+
1983
+ // src/lib/types.ts
1984
+ var SORT_LABELS2 = {
1985
+ mcap: "Top by Market Cap",
1986
+ volume: "Top by 24h Volume",
1987
+ new: "New",
1988
+ gainers: "Top Gainers (24h)",
1989
+ "last-traded": "Last Traded",
1990
+ "last-traded-unique": "Last Traded (Unique)",
1991
+ trending: "Trending",
1992
+ featured: "Featured"
1993
+ };
1994
+ var TYPE_LABELS = {
1995
+ all: "all",
1996
+ trend: "trends",
1997
+ "creator-coin": "creator coins",
1998
+ post: "posts"
1999
+ };
2000
+ var COIN_TYPE_DISPLAY = {
2001
+ CONTENT: "post",
2002
+ CREATOR: "creator-coin",
2003
+ TREND: "trend"
2004
+ };
2005
+
2006
+ // src/components/ExploreView.tsx
2007
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2 } from "react";
2008
+ import { Box as Box4, Text as Text4, useInput as useInput2, useApp as useApp2 } from "ink";
2009
+ import Spinner2 from "ink-spinner";
2010
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2011
+ var COLUMNS = [
2012
+ { header: "#", width: 4, accessor: (c) => String(c.rank) },
2013
+ { header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
2014
+ { header: "Address", width: 44, accessor: (c) => c.address ?? "" },
2015
+ {
2016
+ header: "Type",
2017
+ width: 14,
2018
+ accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
2019
+ },
2020
+ {
2021
+ header: "Market Cap",
2022
+ width: 12,
2023
+ accessor: (c) => formatCompactUsd(c.marketCap)
2024
+ },
2025
+ {
2026
+ header: "24h Vol",
2027
+ width: 12,
2028
+ accessor: (c) => formatCompactUsd(c.volume24h)
2029
+ },
2030
+ {
2031
+ header: "24h Change",
2032
+ width: 11,
2033
+ accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
2034
+ color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
2035
+ }
2036
+ ];
2037
+ var CACHE_TTL_MS = 6e4;
2038
+ var CACHE_KEY_FIRST = "__first__";
2039
+ var ExploreView = ({
2040
+ fetchPage,
2041
+ sort,
2042
+ type,
2043
+ limit,
2044
+ initialCursor,
2045
+ cacheTtlMs = CACHE_TTL_MS,
2046
+ autoRefresh = false,
2047
+ intervalSeconds = 30
2048
+ }) => {
2049
+ const { exit } = useApp2();
2050
+ const [loading, setLoading] = useState3(true);
2051
+ const [error, setError] = useState3(null);
2052
+ const [coins, setCoins] = useState3([]);
2053
+ const [pageInfo, setPageInfo] = useState3(null);
2054
+ const [page, setPage] = useState3(1);
2055
+ const [cursorHistory, setCursorHistory] = useState3(
2056
+ []
2057
+ );
2058
+ const [currentCursor, setCurrentCursor] = useState3(
2059
+ initialCursor
2060
+ );
2061
+ const cache = useRef2(/* @__PURE__ */ new Map());
2062
+ const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
2063
+ const [manualRefreshCount, setManualRefreshCount] = useState3(0);
2064
+ const loadPage = useCallback3(
2065
+ async (cursor) => {
2066
+ const cacheKey = cursor ?? CACHE_KEY_FIRST;
2067
+ const cached = cache.current.get(cacheKey);
2068
+ const isFresh = cached && Date.now() - cached.fetchedAt < cacheTtlMs;
2069
+ if (isFresh) {
2070
+ setCoins(cached.result.coins);
2071
+ setPageInfo(cached.result.pageInfo ?? null);
2072
+ setError(null);
2073
+ setLoading(false);
2074
+ return;
2075
+ }
2076
+ setLoading(true);
2077
+ setError(null);
2078
+ try {
2079
+ const result = await fetchPage(cursor);
2080
+ cache.current.set(cacheKey, { result, fetchedAt: Date.now() });
2081
+ setCoins(result.coins);
2082
+ setPageInfo(result.pageInfo ?? null);
2083
+ } catch (err) {
2084
+ setError(err instanceof Error ? err.message : String(err));
2085
+ }
2086
+ setLoading(false);
2087
+ },
2088
+ [fetchPage, cacheTtlMs]
2089
+ );
2090
+ useEffect3(() => {
2091
+ if (refreshCount === 0) return;
2092
+ const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
2093
+ cache.current.delete(cacheKey);
2094
+ }, [refreshCount, currentCursor]);
2095
+ useEffect3(() => {
2096
+ loadPage(currentCursor);
2097
+ }, [currentCursor, loadPage, refreshCount, manualRefreshCount]);
2098
+ useInput2((input, key) => {
2099
+ if (input === "q" || key.escape) {
2100
+ exit();
2101
+ return;
2102
+ }
2103
+ if (loading) return;
2104
+ const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
2105
+ const canGoPrev = cursorHistory.length > 0;
2106
+ if ((input === "n" || key.rightArrow) && canGoNext) {
2107
+ setCursorHistory((prev) => [...prev, currentCursor]);
2108
+ setCurrentCursor(pageInfo.endCursor);
2109
+ setPage((p) => p + 1);
2110
+ }
2111
+ if ((input === "p" || key.leftArrow) && canGoPrev) {
2112
+ const prev = cursorHistory[cursorHistory.length - 1];
2113
+ setCursorHistory((h) => h.slice(0, -1));
2114
+ setCurrentCursor(prev);
2115
+ setPage((p) => p - 1);
2116
+ }
2117
+ if (input === "r") {
2118
+ const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
2119
+ cache.current.delete(cacheKey);
2120
+ triggerManualRefresh();
2121
+ setManualRefreshCount((c) => c + 1);
2122
+ }
2123
+ });
2124
+ if (error) {
2125
+ return /* @__PURE__ */ jsxs4(
2126
+ Box4,
2127
+ {
2128
+ flexDirection: "column",
2129
+ paddingLeft: 1,
2130
+ paddingTop: 1,
2131
+ paddingBottom: 1,
2132
+ children: [
2133
+ /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
2134
+ "Error: ",
2135
+ error
2136
+ ] }),
2137
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
2138
+ ]
2139
+ }
2140
+ );
2141
+ }
2142
+ if (loading) {
2143
+ return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
2144
+ /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
2145
+ " Loading\u2026"
2146
+ ] }) });
2147
+ }
2148
+ if (coins.length === 0) {
2149
+ return /* @__PURE__ */ jsxs4(
2150
+ Box4,
2151
+ {
2152
+ flexDirection: "column",
2153
+ paddingLeft: 1,
2154
+ paddingTop: 1,
2155
+ paddingBottom: 1,
2156
+ children: [
2157
+ /* @__PURE__ */ jsx4(Text4, { children: "No coins found." }),
2158
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2159
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Try a different sort or type:" }),
2160
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort volume --type all" }),
2161
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort new --type all" })
2162
+ ] }),
2163
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
2164
+ ]
2165
+ }
2166
+ );
2167
+ }
2168
+ const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
2169
+ const subtitle = `Page ${page} \xB7 ${coins.length} result${coins.length !== 1 ? "s" : ""}`;
2170
+ const rankedCoins = coins.map((c, i) => ({
2171
+ ...c,
2172
+ rank: (page - 1) * limit + i + 1
2173
+ }));
2174
+ const hints = [];
2175
+ if (cursorHistory.length > 0) hints.push("\u2190 prev");
2176
+ if (pageInfo?.hasNextPage) hints.push("\u2192 next");
2177
+ hints.push("r refresh");
2178
+ if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
2179
+ hints.push("q quit");
2180
+ const footer = hints.join(" \xB7 ");
2181
+ return /* @__PURE__ */ jsx4(
2182
+ Table,
2183
+ {
2184
+ data: rankedCoins,
2185
+ columns: COLUMNS,
2186
+ title,
2187
+ subtitle,
2188
+ footer
2189
+ }
2190
+ );
2191
+ };
2192
+
2193
+ // src/commands/explore.tsx
2194
+ import { jsx as jsx5 } from "react/jsx-runtime";
2195
+ var QUERY_MAP = {
2196
+ mcap: {
2197
+ all: getMostValuableAll,
2198
+ trend: getMostValuableTrends,
2199
+ "creator-coin": getMostValuableCreatorCoins,
2200
+ post: getCoinsMostValuable
2201
+ },
2202
+ volume: {
2203
+ all: getExploreTopVolumeAll24h,
2204
+ trend: getTopVolumeTrends24h,
2205
+ "creator-coin": getExploreTopVolumeCreators24h,
2206
+ post: getCoinsTopVolume24h
2207
+ },
2208
+ new: {
2209
+ all: getExploreNewAll,
2210
+ trend: getNewTrends,
2211
+ "creator-coin": getCreatorCoins,
2212
+ post: getCoinsNew
2213
+ },
2214
+ gainers: {
2215
+ post: getCoinsTopGainers
2216
+ },
2217
+ "last-traded": {
2218
+ post: getCoinsLastTraded
2219
+ },
2220
+ "last-traded-unique": {
2221
+ post: getCoinsLastTradedUnique
2222
+ },
2223
+ trending: {
2224
+ all: getTrendingAll,
2225
+ trend: getTrendingTrends,
2226
+ "creator-coin": getTrendingCreators,
2227
+ post: getTrendingPosts
2228
+ },
2229
+ featured: {
2230
+ "creator-coin": getExploreFeaturedCreators,
2231
+ post: getExploreFeaturedVideos
2232
+ }
2233
+ };
2234
+ var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
2235
+ var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
2236
+ "--type <type>",
2237
+ "Filter by type: all, trend, creator-coin, post (availability varies by sort)",
2238
+ "post"
2239
+ ).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
2240
+ const output = getOutputMode(this, "live");
2241
+ const json = output === "json";
2242
+ const sort = opts.sort;
2243
+ const type = opts.type;
2244
+ const limit = parseInt(opts.limit, 10);
2245
+ const after = opts.after;
2246
+ if (isNaN(limit) || limit <= 0 || limit > 20) {
2247
+ outputErrorAndExit(
2248
+ json,
2249
+ `Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
2250
+ "Usage: zora explore --limit 10"
2251
+ );
2252
+ }
2253
+ if (!QUERY_MAP[sort]) {
2254
+ outputErrorAndExit(
2255
+ json,
2256
+ `Invalid --sort value: ${sort}.`,
2257
+ `Supported: ${SORT_OPTIONS2}`
2258
+ );
2259
+ }
2260
+ if (!QUERY_MAP[sort][type]) {
2261
+ const supported = Object.keys(QUERY_MAP[sort]);
2262
+ outputErrorAndExit(
2263
+ json,
2264
+ `Invalid --type for --sort ${sort}.`,
2265
+ `Supported: ${supported.join(", ")}`
2266
+ );
2267
+ }
2268
+ const apiKey = getApiKey();
2269
+ if (apiKey) {
2270
+ setApiKey3(apiKey);
2271
+ }
2272
+ const queryFn = QUERY_MAP[sort][type];
2273
+ if (json) {
2274
+ let response;
2275
+ try {
2276
+ response = await queryFn({ count: limit, after });
2277
+ } catch (err) {
2278
+ outputErrorAndExit(
2279
+ json,
2280
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
2281
+ );
2282
+ }
2283
+ if (response.error) {
2284
+ const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
2285
+ outputErrorAndExit(json, `API error: ${msg}`);
2286
+ }
2287
+ const edges = response.data?.exploreList?.edges ?? [];
2288
+ const coins = edges.map((e) => e.node);
2289
+ const pageInfo = response.data?.exploreList?.pageInfo;
2290
+ outputJson({ coins, pageInfo: pageInfo ?? null });
2291
+ track("cli_explore", {
2292
+ sort,
2293
+ type,
2294
+ limit,
2295
+ paginated: after !== void 0,
2296
+ result_count: coins.length,
2297
+ has_next_page: pageInfo?.hasNextPage ?? false,
2298
+ output_format: "json"
2299
+ });
2300
+ } else {
2301
+ const { live, intervalSeconds } = getLiveConfig(this, "live");
2302
+ const fetchPage = async (cursor) => {
2303
+ const response = await queryFn({ count: limit, after: cursor });
2304
+ if (response.error) {
2305
+ const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
2306
+ throw new Error(msg);
2307
+ }
2308
+ const edges = response.data?.exploreList?.edges ?? [];
2309
+ const coins = edges.map((e) => e.node);
2310
+ const pageInfo = response.data?.exploreList?.pageInfo;
2311
+ return { coins, pageInfo };
2312
+ };
2313
+ await renderLive(
2314
+ /* @__PURE__ */ jsx5(
2315
+ ExploreView,
2316
+ {
2317
+ fetchPage,
2318
+ sort,
2319
+ type,
2320
+ limit,
2321
+ initialCursor: after,
2322
+ autoRefresh: live,
2323
+ intervalSeconds
2324
+ }
2325
+ )
2326
+ );
2327
+ track("cli_explore", {
2328
+ sort,
2329
+ type,
2330
+ limit,
2331
+ live,
2332
+ interval: intervalSeconds,
2333
+ paginated: after !== void 0,
2334
+ output_format: "text"
2335
+ });
2336
+ }
2337
+ });
2338
+
1843
2339
  // src/commands/get.tsx
1844
2340
  import { Command as Command5 } from "commander";
1845
2341
  import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
@@ -1933,24 +2429,24 @@ async function resolveCoin(ref) {
1933
2429
  }
1934
2430
 
1935
2431
  // src/components/CoinDetail.tsx
1936
- import { Box as Box3, Text as Text3 } from "ink";
1937
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2432
+ import { Box as Box5, Text as Text5 } from "ink";
2433
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1938
2434
  var LABEL_WIDTH = 18;
1939
2435
  function Row({
1940
2436
  label,
1941
2437
  children
1942
2438
  }) {
1943
- return /* @__PURE__ */ jsxs3(Box3, { children: [
1944
- /* @__PURE__ */ jsx3(Box3, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: label }) }),
1945
- /* @__PURE__ */ jsx3(Text3, { children })
2439
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
2440
+ /* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
2441
+ /* @__PURE__ */ jsx6(Text5, { children })
1946
2442
  ] });
1947
2443
  }
1948
2444
  function CoinDetail({ coin }) {
1949
2445
  const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
1950
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingLeft: 1, children: [
1951
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1952
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: coin.name }),
1953
- /* @__PURE__ */ jsxs3(Text3, { children: [
2446
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, children: [
2447
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2448
+ /* @__PURE__ */ jsx6(Text5, { bold: true, children: coin.name }),
2449
+ /* @__PURE__ */ jsxs5(Text5, { children: [
1954
2450
  coin.coinType,
1955
2451
  " ",
1956
2452
  "\xB7",
@@ -1958,20 +2454,20 @@ function CoinDetail({ coin }) {
1958
2454
  coin.address
1959
2455
  ] })
1960
2456
  ] }),
1961
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1962
- /* @__PURE__ */ jsx3(Row, { label: "Market Cap", children: formatCurrency(coin.marketCap) }),
1963
- /* @__PURE__ */ jsx3(Row, { label: "24h Volume", children: formatCurrency(coin.volume24h) }),
1964
- /* @__PURE__ */ jsx3(Row, { label: "24h Change", children: /* @__PURE__ */ jsx3(Text3, { color: change.color, children: change.text }) }),
1965
- /* @__PURE__ */ jsx3(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
1966
- coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx3(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
1967
- /* @__PURE__ */ jsx3(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
2457
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2458
+ /* @__PURE__ */ jsx6(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
2459
+ /* @__PURE__ */ jsx6(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
2460
+ /* @__PURE__ */ jsx6(Row, { label: "24h Change", children: /* @__PURE__ */ jsx6(Text5, { color: change.color, children: change.text }) }),
2461
+ /* @__PURE__ */ jsx6(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
2462
+ coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx6(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
2463
+ /* @__PURE__ */ jsx6(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
1968
2464
  ] }),
1969
- /* @__PURE__ */ jsx3(Box3, { marginBottom: 1 })
2465
+ /* @__PURE__ */ jsx6(Box5, { marginBottom: 1 })
1970
2466
  ] });
1971
2467
  }
1972
2468
 
1973
2469
  // src/commands/get.tsx
1974
- import { jsx as jsx4 } from "react/jsx-runtime";
2470
+ import { jsx as jsx7 } from "react/jsx-runtime";
1975
2471
  function formatCoinJson(coin) {
1976
2472
  return {
1977
2473
  name: coin.name,
@@ -1989,25 +2485,204 @@ function formatCoinJson(coin) {
1989
2485
  var VALID_TYPES = ["creator-coin", "post", "trend"];
1990
2486
  var getCommand = new Command5("get").description("Look up a coin by address or name").argument("<identifier>", "Coin address (0x...) or creator name").option("--type <type>", "Coin type: creator-coin, post, trend").action(async function(identifier, opts) {
1991
2487
  const json = getJson(this);
1992
- if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
2488
+ if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
2489
+ outputErrorAndExit(
2490
+ json,
2491
+ `Invalid --type value: ${opts.type}.`,
2492
+ `Supported: ${VALID_TYPES.join(", ")}`
2493
+ );
2494
+ }
2495
+ const type = opts.type;
2496
+ if (type === "post" && !identifier.startsWith("0x")) {
2497
+ outputErrorAndExit(
2498
+ json,
2499
+ "Posts can only be looked up by address.",
2500
+ "Use: zora get 0x..."
2501
+ );
2502
+ }
2503
+ const ref = parseCoinRef(identifier, opts.type);
2504
+ const apiKey = getApiKey();
2505
+ if (apiKey) {
2506
+ setApiKey4(apiKey);
2507
+ }
2508
+ let result;
2509
+ try {
2510
+ result = await resolveCoin(ref);
2511
+ } catch (err) {
2512
+ outputErrorAndExit(
2513
+ json,
2514
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
2515
+ );
2516
+ return;
2517
+ }
2518
+ if (type && result.kind === "found" && result.coin.coinType !== type) {
2519
+ outputErrorAndExit(
2520
+ json,
2521
+ `Coin at ${result.coin.address} is a ${result.coin.coinType}, not a ${type}.`,
2522
+ `Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
2523
+ );
2524
+ return;
2525
+ }
2526
+ if (result.kind === "not-found") {
2527
+ outputErrorAndExit(json, result.message);
2528
+ return;
2529
+ }
2530
+ outputData(json, {
2531
+ json: formatCoinJson(result.coin),
2532
+ table: () => {
2533
+ renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin: result.coin }));
2534
+ }
2535
+ });
2536
+ track("cli_get", {
2537
+ lookup_type: identifier.startsWith("0x") ? "address" : "name",
2538
+ coin_type_filter: type ?? null,
2539
+ found: result.kind === "found",
2540
+ coin_type: result.kind === "found" ? result.coin.coinType : null,
2541
+ output_format: json ? "json" : "text"
2542
+ });
2543
+ });
2544
+
2545
+ // src/commands/price-history.tsx
2546
+ import { Command as Command6 } from "commander";
2547
+ import { setApiKey as setApiKey5, apiGet } from "@zoralabs/coins-sdk";
2548
+
2549
+ // src/components/PriceHistory.tsx
2550
+ import { Box as Box6, Text as Text6 } from "ink";
2551
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2552
+ var LABEL_WIDTH2 = 18;
2553
+ var Row2 = ({
2554
+ label,
2555
+ children
2556
+ }) => /* @__PURE__ */ jsxs6(Box6, { children: [
2557
+ /* @__PURE__ */ jsx8(Box6, { width: LABEL_WIDTH2, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: label }) }),
2558
+ /* @__PURE__ */ jsx8(Text6, { children })
2559
+ ] });
2560
+ var PriceHistory = ({
2561
+ coin,
2562
+ coinType,
2563
+ interval,
2564
+ high,
2565
+ low,
2566
+ change,
2567
+ sparklineText
2568
+ }) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 1, children: [
2569
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2570
+ /* @__PURE__ */ jsx8(Row2, { label: "Coin", children: coin }),
2571
+ /* @__PURE__ */ jsx8(Row2, { label: "Type", children: coinType }),
2572
+ /* @__PURE__ */ jsx8(Row2, { label: "Interval", children: interval }),
2573
+ /* @__PURE__ */ jsx8(Row2, { label: "High", children: high }),
2574
+ /* @__PURE__ */ jsx8(Row2, { label: "Low", children: low }),
2575
+ /* @__PURE__ */ jsx8(Row2, { label: "Change", children: /* @__PURE__ */ jsx8(Text6, { color: change.color, children: change.text }) })
2576
+ ] }),
2577
+ sparklineText.length > 0 && /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text6, { children: sparklineText }) }),
2578
+ /* @__PURE__ */ jsx8(Box6, { marginBottom: 1 })
2579
+ ] });
2580
+
2581
+ // src/lib/sparkline.ts
2582
+ var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
2583
+ var sparkline = (values) => {
2584
+ if (values.length <= 1) return values.length === 1 ? BLOCKS[4] : "";
2585
+ const min = Math.min(...values);
2586
+ const max = Math.max(...values);
2587
+ const range = max - min;
2588
+ if (range === 0) return BLOCKS[4].repeat(values.length);
2589
+ return values.map((v) => {
2590
+ const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
2591
+ return BLOCKS[idx];
2592
+ }).join("");
2593
+ };
2594
+ var MAX_SPARKLINE_WIDTH = 50;
2595
+ var downsample = (values, maxWidth) => {
2596
+ if (values.length <= maxWidth) return values;
2597
+ const bucketSize = values.length / maxWidth;
2598
+ const result = [];
2599
+ for (let i = 0; i < maxWidth; i++) {
2600
+ const start = Math.floor(i * bucketSize);
2601
+ const end = Math.floor((i + 1) * bucketSize);
2602
+ let sum = 0;
2603
+ for (let j = start; j < end; j++) {
2604
+ sum += values[j];
2605
+ }
2606
+ result.push(sum / (end - start));
2607
+ }
2608
+ return result;
2609
+ };
2610
+
2611
+ // src/commands/price-history.tsx
2612
+ import { jsx as jsx9 } from "react/jsx-runtime";
2613
+ var VALID_TYPES2 = ["creator-coin", "post", "trend"];
2614
+ var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
2615
+ var INTERVAL_TO_API_FIELD = {
2616
+ "1h": "oneHour",
2617
+ "24h": "oneDay",
2618
+ "1w": "oneWeek",
2619
+ "1m": "oneMonth",
2620
+ ALL: "all"
2621
+ };
2622
+ var formatPrice = (price) => {
2623
+ if (price >= 1) {
2624
+ return `$${price.toFixed(2)}`;
2625
+ }
2626
+ if (price >= 0.01) {
2627
+ return `$${price.toFixed(4)}`;
2628
+ }
2629
+ return `$${price.toPrecision(4)}`;
2630
+ };
2631
+ var formatChange = (first, last) => {
2632
+ if (first === 0) return { text: "-", color: void 0 };
2633
+ const pct = (last - first) / first * 100;
2634
+ const prefix = pct >= 0 ? "+" : "";
2635
+ const text = `${prefix}${pct.toFixed(1)}%`;
2636
+ const color = pct > 0 ? "green" : pct < 0 ? "red" : void 0;
2637
+ return { text, color };
2638
+ };
2639
+ var fetchPriceHistory = async (address, interval) => {
2640
+ const response = await apiGet("/coinPriceHistory", {
2641
+ address
2642
+ });
2643
+ const data = response.data;
2644
+ const token = data?.zora20Token;
2645
+ if (!token) return [];
2646
+ const field = INTERVAL_TO_API_FIELD[interval];
2647
+ const points = token[field];
2648
+ if (!points || points.length === 0) return [];
2649
+ return points.map((p) => ({
2650
+ timestamp: p.timestamp,
2651
+ price: Number(p.closePrice)
2652
+ }));
2653
+ };
2654
+ var priceHistoryCommand = new Command6("price-history").description("Display price history for a coin").argument("<identifier>", "Coin address (0x...) or name").option("--type <type>", "Coin type: creator-coin, post, trend").option(
2655
+ "--interval <interval>",
2656
+ `Time range: ${VALID_INTERVALS.join(", ")}`,
2657
+ "1w"
2658
+ ).action(async function(identifier, opts) {
2659
+ const json = getJson(this);
2660
+ const interval = opts.interval ?? "1w";
2661
+ if (!VALID_INTERVALS.includes(interval)) {
2662
+ outputErrorAndExit(
2663
+ json,
2664
+ `Invalid --interval value: ${interval}.`,
2665
+ `Supported: ${VALID_INTERVALS.join(", ")}`
2666
+ );
2667
+ }
2668
+ if (opts.type !== void 0 && !VALID_TYPES2.includes(opts.type)) {
1993
2669
  outputErrorAndExit(
1994
2670
  json,
1995
2671
  `Invalid --type value: ${opts.type}.`,
1996
- `Supported: ${VALID_TYPES.join(", ")}`
2672
+ `Supported: ${VALID_TYPES2.join(", ")}`
1997
2673
  );
1998
2674
  }
1999
- const type = opts.type;
2000
- if (type === "post" && !identifier.startsWith("0x")) {
2675
+ if (opts.type === "post" && !identifier.startsWith("0x")) {
2001
2676
  outputErrorAndExit(
2002
2677
  json,
2003
2678
  "Posts can only be looked up by address.",
2004
- "Use: zora get 0x..."
2679
+ "Use: zora price-history 0x..."
2005
2680
  );
2006
2681
  }
2007
2682
  const ref = parseCoinRef(identifier, opts.type);
2008
2683
  const apiKey = getApiKey();
2009
2684
  if (apiKey) {
2010
- setApiKey4(apiKey);
2685
+ setApiKey5(apiKey);
2011
2686
  }
2012
2687
  let result;
2013
2688
  try {
@@ -2019,35 +2694,80 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
2019
2694
  );
2020
2695
  return;
2021
2696
  }
2022
- if (type && result.kind === "found" && result.coin.coinType !== type) {
2697
+ if (result.kind === "not-found") {
2698
+ outputErrorAndExit(json, result.message, result.suggestion);
2699
+ return;
2700
+ }
2701
+ const { coin } = result;
2702
+ let prices;
2703
+ try {
2704
+ prices = await fetchPriceHistory(coin.address, interval);
2705
+ } catch (err) {
2023
2706
  outputErrorAndExit(
2024
2707
  json,
2025
- `Coin at ${result.coin.address} is a ${result.coin.coinType}, not a ${type}.`,
2026
- `Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
2708
+ `Failed to fetch price data: ${err instanceof Error ? err.message : String(err)}`
2027
2709
  );
2028
2710
  return;
2029
2711
  }
2030
- if (result.kind === "not-found") {
2031
- outputErrorAndExit(json, result.message);
2712
+ if (prices.length === 0) {
2713
+ outputErrorAndExit(
2714
+ json,
2715
+ `No price data found for ${coin.name} in the last ${interval}.`,
2716
+ "Try a longer interval with --interval"
2717
+ );
2032
2718
  return;
2033
2719
  }
2720
+ const priceValues = prices.map((p) => p.price);
2721
+ const high = Math.max(...priceValues);
2722
+ const low = Math.min(...priceValues);
2723
+ const change = formatChange(
2724
+ priceValues[0],
2725
+ priceValues[priceValues.length - 1]
2726
+ );
2727
+ const sparklineText = sparkline(
2728
+ downsample(priceValues, MAX_SPARKLINE_WIDTH)
2729
+ );
2034
2730
  outputData(json, {
2035
- json: formatCoinJson(result.coin),
2731
+ json: {
2732
+ coin: coin.name,
2733
+ type: coin.coinType,
2734
+ interval,
2735
+ high,
2736
+ low,
2737
+ change: priceValues[0] === 0 ? null : (priceValues[priceValues.length - 1] - priceValues[0]) / priceValues[0],
2738
+ prices: prices.map((p) => ({
2739
+ timestamp: p.timestamp,
2740
+ price: p.price
2741
+ }))
2742
+ },
2036
2743
  table: () => {
2037
- renderOnce(/* @__PURE__ */ jsx4(CoinDetail, { coin: result.coin }));
2744
+ renderOnce(
2745
+ /* @__PURE__ */ jsx9(
2746
+ PriceHistory,
2747
+ {
2748
+ coin: coin.name,
2749
+ coinType: coin.coinType,
2750
+ interval,
2751
+ high: formatPrice(high),
2752
+ low: formatPrice(low),
2753
+ change,
2754
+ sparklineText
2755
+ }
2756
+ )
2757
+ );
2038
2758
  }
2039
2759
  });
2040
- track("cli_get", {
2760
+ track("cli_price_history", {
2041
2761
  lookup_type: identifier.startsWith("0x") ? "address" : "name",
2042
- coin_type_filter: type ?? null,
2043
- found: result.kind === "found",
2044
- coin_type: result.kind === "found" ? result.coin.coinType : null,
2762
+ coin_type: coin.coinType,
2763
+ interval,
2764
+ data_points: prices.length,
2045
2765
  output_format: json ? "json" : "text"
2046
2766
  });
2047
2767
  });
2048
2768
 
2049
2769
  // src/commands/sell.ts
2050
- import { Command as Command6 } from "commander";
2770
+ import { Command as Command7 } from "commander";
2051
2771
  import confirm3 from "@inquirer/confirm";
2052
2772
  import {
2053
2773
  erc20Abi as erc20Abi3,
@@ -2058,7 +2778,7 @@ import {
2058
2778
  import {
2059
2779
  createTradeCall as createTradeCall2,
2060
2780
  getCoin as getCoin3,
2061
- setApiKey as setApiKey5,
2781
+ setApiKey as setApiKey6,
2062
2782
  tradeCoin as tradeCoin2
2063
2783
  } from "@zoralabs/coins-sdk";
2064
2784
  function printSellQuote(output, info) {
@@ -2133,19 +2853,13 @@ function printSellResult(output, info) {
2133
2853
  console.log(` Tx ${info.txHash}
2134
2854
  `);
2135
2855
  }
2136
- var sellCommand = new Command6("sell").description("Sell a coin").argument("<address>", "Coin contract address (0x\u2026)").option("--amount <value>", "Sell specific number of coins").option("--usd <value>", "Sell USD equivalent worth of coins").option("--percent <value>", "Sell percentage of coin balance").option("--all", "Sell entire coin balance").option("--to <asset>", "Receive asset: eth, usdc, zora", "eth").option("--token <asset>", "Receive asset: eth, usdc, zora (alias for --to)").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").option("-o, --output <format>", "Output format: table, json", "table").action(async (coinAddress, opts) => {
2137
- const json = opts.output === "json";
2856
+ var sellCommand = new Command7("sell").description("Sell a coin").argument("<address>", "Coin contract address (0x\u2026)").option("--amount <value>", "Sell specific number of coins").option("--usd <value>", "Sell USD equivalent worth of coins").option("--percent <value>", "Sell percentage of coin balance").option("--all", "Sell entire coin balance").option("--to <asset>", "Receive asset: eth, usdc, zora", "eth").option("--token <asset>", "Receive asset: eth, usdc, zora (alias for --to)").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(coinAddress, opts) {
2857
+ const json = getJson(this);
2138
2858
  const debug = opts.debug === true;
2139
2859
  if (!isAddress2(coinAddress)) {
2140
2860
  outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
2141
2861
  }
2142
- const output = opts.output;
2143
- if (output !== "table" && output !== "json") {
2144
- outputErrorAndExit(
2145
- false,
2146
- `Invalid --output value: ${output}. Use: table, json`
2147
- );
2148
- }
2862
+ const output = json ? "json" : "table";
2149
2863
  const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
2150
2864
  if (!(outputAsset in BASE_TRADE_TOKENS)) {
2151
2865
  outputErrorAndExit(
@@ -2170,7 +2884,7 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
2170
2884
  const slippage = slippagePct / 100;
2171
2885
  const apiKey = getApiKey();
2172
2886
  if (apiKey) {
2173
- setApiKey5(apiKey);
2887
+ setApiKey6(apiKey);
2174
2888
  }
2175
2889
  const account = resolveAccount(json);
2176
2890
  const { publicClient, walletClient } = createClients(account);
@@ -2263,6 +2977,19 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
2263
2977
  }
2264
2978
  }
2265
2979
  }
2980
+ let swapAmountUsd;
2981
+ if (amountMode === "usd") {
2982
+ swapAmountUsd = parsePercentageLikeValue(opts.usd);
2983
+ } else {
2984
+ const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
2985
+ if (coinPriceUsd !== null && coinPriceUsd > 0) {
2986
+ swapAmountUsd = Number(
2987
+ (Number(formatUnits4(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
2988
+ 2
2989
+ )
2990
+ );
2991
+ }
2992
+ }
2266
2993
  const tradeParameters = {
2267
2994
  sell: { type: "erc20", address: coinAddress },
2268
2995
  buy: outputToken.trade,
@@ -2339,6 +3066,9 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2339
3066
  coin_name: coinName,
2340
3067
  coin_symbol: coinSymbol,
2341
3068
  amount_mode: amountMode,
3069
+ swap_amount_usd: swapAmountUsd,
3070
+ valueUsd: swapAmountUsd,
3071
+ swapCoinType: token.coinType ?? null,
2342
3072
  output_asset: outputAsset,
2343
3073
  slippage: slippagePct,
2344
3074
  output_format: output
@@ -2372,6 +3102,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2372
3102
  let txHash;
2373
3103
  let receivedAmountOut = BigInt(quoteAmountOut);
2374
3104
  let receivedSource = "quote";
3105
+ let swapLogIndex = null;
3106
+ const swapCoinType = token.coinType ?? null;
2375
3107
  try {
2376
3108
  receipt = await tradeCoin2({
2377
3109
  tradeParameters,
@@ -2386,6 +3118,9 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2386
3118
  coin_name: coinName,
2387
3119
  coin_symbol: coinSymbol,
2388
3120
  amount_mode: amountMode,
3121
+ swap_amount_usd: swapAmountUsd,
3122
+ valueUsd: swapAmountUsd,
3123
+ swapCoinType,
2389
3124
  output_asset: outputAsset,
2390
3125
  slippage: slippagePct,
2391
3126
  output_format: output,
@@ -2401,11 +3136,13 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2401
3136
  txHash = receipt.transactionHash;
2402
3137
  if (outputToken.trade.type === "erc20") {
2403
3138
  try {
2404
- receivedAmountOut = getReceivedAmountFromReceipt({
3139
+ const result = getReceivedAmountFromReceipt({
2405
3140
  receipt,
2406
3141
  tokenAddress: outputToken.trade.address,
2407
3142
  recipient: account.address
2408
3143
  });
3144
+ receivedAmountOut = result.amount;
3145
+ swapLogIndex = result.logIndex;
2409
3146
  receivedSource = "receipt";
2410
3147
  } catch {
2411
3148
  }
@@ -2429,6 +3166,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2429
3166
  coin_name: coinName,
2430
3167
  coin_symbol: coinSymbol,
2431
3168
  amount_mode: amountMode,
3169
+ swap_amount_usd: swapAmountUsd,
3170
+ valueUsd: swapAmountUsd,
3171
+ swapCoinType,
3172
+ transactionHash: txHash,
3173
+ logIndex: swapLogIndex,
2432
3174
  output_asset: outputAsset,
2433
3175
  slippage: slippagePct,
2434
3176
  output_format: output,
@@ -2437,8 +3179,424 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2437
3179
  });
2438
3180
  });
2439
3181
 
3182
+ // src/commands/send.ts
3183
+ import { Command as Command8 } from "commander";
3184
+ import confirm4 from "@inquirer/confirm";
3185
+ import {
3186
+ erc20Abi as erc20Abi4,
3187
+ formatUnits as formatUnits5,
3188
+ isAddress as isAddress3,
3189
+ parseUnits as parseUnits3
3190
+ } from "viem";
3191
+ import { setApiKey as setApiKey7 } from "@zoralabs/coins-sdk";
3192
+ var SEND_AMOUNT_CHECKS = {
3193
+ amount: (opts) => opts.amount !== void 0,
3194
+ percent: (opts) => opts.percent !== void 0,
3195
+ all: (opts) => opts.all === true
3196
+ };
3197
+ var VALID_TYPES3 = ["creator-coin", "post", "trend"];
3198
+ function printSendPreview(info) {
3199
+ const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
3200
+ console.log(`
3201
+ Send ${info.name} (${info.symbol})
3202
+ `);
3203
+ console.log(
3204
+ ` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
3205
+ );
3206
+ console.log(` To ${info.to}`);
3207
+ console.log(`
3208
+ Ensure receiving wallet can receive on Base.`);
3209
+ console.log("");
3210
+ }
3211
+ function printSendResult(json, info) {
3212
+ if (json) {
3213
+ outputJson({
3214
+ action: "send",
3215
+ coin: info.symbol,
3216
+ address: info.address,
3217
+ sent: {
3218
+ amount: formatUnits5(info.amount, info.decimals),
3219
+ raw: info.amount.toString(),
3220
+ symbol: info.symbol,
3221
+ amountUsd: info.amountUsd
3222
+ },
3223
+ to: info.to,
3224
+ tx: info.txHash
3225
+ });
3226
+ return;
3227
+ }
3228
+ const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
3229
+ console.log(`
3230
+ Sent ${info.name}
3231
+ `);
3232
+ console.log(
3233
+ ` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
3234
+ );
3235
+ console.log(` To ${info.to}`);
3236
+ console.log(` Tx ${info.txHash}
3237
+ `);
3238
+ }
3239
+ var sendCommand = new Command8("send").description("Send coins or ETH to an address").argument("<identifier>", "Coin address, name, or token (eth, usdc, zora)").requiredOption("--to <address>", "Recipient address (0x...)").option("--type <type>", "Coin type: creator-coin, post, trend").option("--amount <value>", "Send specific amount").option("--percent <value>", "Send percentage of balance (1-100)").option("--all", "Send entire balance").option("--yes", "Skip confirmation").action(async function(identifier, opts) {
3240
+ const json = getJson(this);
3241
+ if (!isAddress3(opts.to)) {
3242
+ outputErrorAndExit(
3243
+ json,
3244
+ `Invalid recipient address: ${opts.to}`,
3245
+ "Must be a valid 0x address."
3246
+ );
3247
+ }
3248
+ const recipient = opts.to;
3249
+ if (opts.type !== void 0 && !VALID_TYPES3.includes(opts.type)) {
3250
+ outputErrorAndExit(
3251
+ json,
3252
+ `Invalid --type value: ${opts.type}.`,
3253
+ `Supported: ${VALID_TYPES3.join(", ")}`
3254
+ );
3255
+ }
3256
+ const amountMode = getAmountMode(
3257
+ json,
3258
+ opts,
3259
+ SEND_AMOUNT_CHECKS,
3260
+ "--amount, --percent, or --all"
3261
+ );
3262
+ const isEth = identifier.toLowerCase() === "eth";
3263
+ if (isEth) {
3264
+ if (opts.type) {
3265
+ outputErrorAndExit(json, "--type is not valid when sending ETH.");
3266
+ }
3267
+ const account = resolveAccount(json);
3268
+ const { publicClient, walletClient } = createClients(account);
3269
+ const balance = await publicClient.getBalance({
3270
+ address: account.address
3271
+ });
3272
+ if (balance === 0n) {
3273
+ outputErrorAndExit(
3274
+ json,
3275
+ `No ETH balance. Deposit ETH to ${account.address} on Base.`
3276
+ );
3277
+ }
3278
+ let amount;
3279
+ if (amountMode === "amount") {
3280
+ const val = parsePercentageLikeValue(opts.amount);
3281
+ if (val === void 0 || val <= 0) {
3282
+ outputErrorAndExit(
3283
+ json,
3284
+ "Invalid --amount value. Must be a positive number."
3285
+ );
3286
+ }
3287
+ try {
3288
+ amount = parseUnits3(opts.amount, 18);
3289
+ } catch {
3290
+ outputErrorAndExit(
3291
+ json,
3292
+ "Invalid --amount value. Must be a valid ETH amount."
3293
+ );
3294
+ }
3295
+ if (amount === 0n) {
3296
+ outputErrorAndExit(
3297
+ json,
3298
+ "Amount too small \u2014 rounds to zero at 18 decimal places."
3299
+ );
3300
+ }
3301
+ if (amount + GAS_RESERVE > balance) {
3302
+ outputErrorAndExit(
3303
+ json,
3304
+ `Insufficient balance. Have ${formatEthDisplay(balance)} ETH (need to reserve ~${formatEthDisplay(GAS_RESERVE)} ETH for gas).`
3305
+ );
3306
+ }
3307
+ } else {
3308
+ if (balance <= GAS_RESERVE) {
3309
+ outputErrorAndExit(
3310
+ json,
3311
+ `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
3312
+ );
3313
+ }
3314
+ const spendable = balance - GAS_RESERVE;
3315
+ if (amountMode === "all") {
3316
+ amount = spendable;
3317
+ } else {
3318
+ const pct = parsePercentageLikeValue(opts.percent);
3319
+ if (pct === void 0 || pct <= 0 || pct > 100) {
3320
+ outputErrorAndExit(
3321
+ json,
3322
+ "Invalid --percent value. Must be between 0 and 100."
3323
+ );
3324
+ }
3325
+ amount = pct === 100 ? spendable : spendable * BigInt(Math.round(pct * 100)) / 10000n;
3326
+ if (amount === 0n) {
3327
+ outputErrorAndExit(
3328
+ json,
3329
+ "Calculated amount is zero. Balance too low."
3330
+ );
3331
+ }
3332
+ }
3333
+ }
3334
+ const amountFormatted = formatEthDisplay(amount);
3335
+ let amountUsd = null;
3336
+ const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
3337
+ if (ethPriceUsd != null) {
3338
+ amountUsd = Number(
3339
+ (Number(formatUnits5(amount, 18)) * ethPriceUsd).toFixed(2)
3340
+ );
3341
+ }
3342
+ if (!opts.yes) {
3343
+ printSendPreview({
3344
+ name: "ETH",
3345
+ symbol: "ETH",
3346
+ amountFormatted,
3347
+ amountUsd,
3348
+ to: recipient
3349
+ });
3350
+ const ok = await confirm4({ message: "Confirm?", default: false });
3351
+ if (!ok) {
3352
+ console.error("Aborted.");
3353
+ process.exit(0);
3354
+ }
3355
+ }
3356
+ let txHash;
3357
+ try {
3358
+ txHash = await walletClient.sendTransaction({
3359
+ to: recipient,
3360
+ value: amount
3361
+ });
3362
+ } catch (err) {
3363
+ track("cli_send", {
3364
+ asset: "eth",
3365
+ output_format: json ? "json" : "table",
3366
+ success: false,
3367
+ error_type: err instanceof Error ? err.constructor.name : "unknown"
3368
+ });
3369
+ await shutdownAnalytics();
3370
+ outputErrorAndExit(
3371
+ json,
3372
+ `Transaction failed: ${err instanceof Error ? err.message : String(err)}`
3373
+ );
3374
+ }
3375
+ await publicClient.waitForTransactionReceipt({
3376
+ hash: txHash
3377
+ });
3378
+ printSendResult(json, {
3379
+ name: "ETH",
3380
+ symbol: "ETH",
3381
+ address: null,
3382
+ amount,
3383
+ decimals: 18,
3384
+ amountFormatted,
3385
+ amountUsd,
3386
+ to: recipient,
3387
+ txHash
3388
+ });
3389
+ track("cli_send", {
3390
+ asset: "eth",
3391
+ amount_mode: amountMode,
3392
+ amount_usd: amountUsd,
3393
+ transactionHash: txHash,
3394
+ output_format: json ? "json" : "table",
3395
+ success: true,
3396
+ tx_hash: txHash
3397
+ });
3398
+ } else {
3399
+ const knownTokenKey = identifier.toLowerCase();
3400
+ const knownToken = knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
3401
+ if (knownToken && opts.type) {
3402
+ outputErrorAndExit(
3403
+ json,
3404
+ `--type is not valid when sending ${knownToken.symbol}.`
3405
+ );
3406
+ }
3407
+ let tokenAddress;
3408
+ let tokenName;
3409
+ if (knownToken) {
3410
+ const trade = knownToken.trade;
3411
+ tokenAddress = trade.address;
3412
+ tokenName = knownToken.symbol;
3413
+ } else {
3414
+ const apiKey = getApiKey();
3415
+ if (apiKey) {
3416
+ setApiKey7(apiKey);
3417
+ }
3418
+ const ref = parseCoinRef(identifier, opts.type);
3419
+ let result;
3420
+ try {
3421
+ result = await resolveCoin(ref);
3422
+ } catch (err) {
3423
+ outputErrorAndExit(
3424
+ json,
3425
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3426
+ );
3427
+ }
3428
+ if (result.kind === "not-found") {
3429
+ outputErrorAndExit(json, result.message, result.suggestion);
3430
+ }
3431
+ tokenAddress = result.coin.address;
3432
+ tokenName = result.coin.name;
3433
+ }
3434
+ const account = resolveAccount(json);
3435
+ const { publicClient, walletClient } = createClients(account);
3436
+ let balance;
3437
+ let decimals;
3438
+ let symbol;
3439
+ if (knownToken) {
3440
+ balance = await publicClient.readContract({
3441
+ abi: erc20Abi4,
3442
+ address: tokenAddress,
3443
+ functionName: "balanceOf",
3444
+ args: [account.address]
3445
+ });
3446
+ decimals = knownToken.decimals;
3447
+ symbol = knownToken.symbol;
3448
+ } else {
3449
+ const results = await Promise.all([
3450
+ publicClient.readContract({
3451
+ abi: erc20Abi4,
3452
+ address: tokenAddress,
3453
+ functionName: "balanceOf",
3454
+ args: [account.address]
3455
+ }),
3456
+ publicClient.readContract({
3457
+ abi: erc20Abi4,
3458
+ address: tokenAddress,
3459
+ functionName: "decimals"
3460
+ }),
3461
+ publicClient.readContract({
3462
+ abi: erc20Abi4,
3463
+ address: tokenAddress,
3464
+ functionName: "symbol"
3465
+ })
3466
+ ]);
3467
+ balance = results[0];
3468
+ decimals = results[1];
3469
+ symbol = results[2];
3470
+ }
3471
+ if (balance === 0n) {
3472
+ outputErrorAndExit(
3473
+ json,
3474
+ `No ${symbol} balance. Buy some first or pick a different wallet.`
3475
+ );
3476
+ }
3477
+ let amount;
3478
+ if (amountMode === "amount") {
3479
+ const val = parsePercentageLikeValue(opts.amount);
3480
+ if (val === void 0 || val <= 0) {
3481
+ outputErrorAndExit(
3482
+ json,
3483
+ "Invalid --amount value. Must be a positive number."
3484
+ );
3485
+ }
3486
+ try {
3487
+ amount = parseUnits3(opts.amount, decimals);
3488
+ } catch {
3489
+ outputErrorAndExit(
3490
+ json,
3491
+ "Invalid --amount value for token decimals."
3492
+ );
3493
+ }
3494
+ if (amount === 0n) {
3495
+ outputErrorAndExit(
3496
+ json,
3497
+ `Amount too small \u2014 rounds to zero at ${decimals} decimal places.`
3498
+ );
3499
+ }
3500
+ if (amount > balance) {
3501
+ outputErrorAndExit(
3502
+ json,
3503
+ `Insufficient balance. Have ${formatAmountDisplay(balance, decimals)} ${symbol}.`
3504
+ );
3505
+ }
3506
+ } else if (amountMode === "all") {
3507
+ amount = balance;
3508
+ } else {
3509
+ const pct = parsePercentageLikeValue(opts.percent);
3510
+ if (pct === void 0 || pct <= 0 || pct > 100) {
3511
+ outputErrorAndExit(
3512
+ json,
3513
+ "Invalid --percent value. Must be between 0 and 100."
3514
+ );
3515
+ }
3516
+ amount = pct === 100 ? balance : balance * BigInt(Math.round(pct * 100)) / 10000n;
3517
+ if (amount === 0n) {
3518
+ outputErrorAndExit(
3519
+ json,
3520
+ "Calculated amount is zero. Balance too low."
3521
+ );
3522
+ }
3523
+ }
3524
+ const amountFormatted = formatAmountDisplay(amount, decimals);
3525
+ let amountUsd = null;
3526
+ const priceAddress = knownToken ? knownToken.priceAddress : tokenAddress;
3527
+ const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
3528
+ if (priceUsd != null) {
3529
+ amountUsd = Number(
3530
+ (Number(formatUnits5(amount, decimals)) * priceUsd).toFixed(2)
3531
+ );
3532
+ }
3533
+ if (!opts.yes) {
3534
+ printSendPreview({
3535
+ name: tokenName,
3536
+ symbol,
3537
+ amountFormatted,
3538
+ amountUsd,
3539
+ to: recipient
3540
+ });
3541
+ const ok = await confirm4({ message: "Confirm?", default: false });
3542
+ if (!ok) {
3543
+ console.error("Aborted.");
3544
+ process.exit(0);
3545
+ }
3546
+ }
3547
+ let txHash;
3548
+ try {
3549
+ txHash = await walletClient.writeContract({
3550
+ abi: erc20Abi4,
3551
+ address: tokenAddress,
3552
+ functionName: "transfer",
3553
+ args: [recipient, amount]
3554
+ });
3555
+ } catch (err) {
3556
+ track("cli_send", {
3557
+ asset: knownToken ? knownTokenKey : "coin",
3558
+ coin_address: tokenAddress,
3559
+ coin_name: tokenName,
3560
+ coin_symbol: symbol,
3561
+ output_format: json ? "json" : "table",
3562
+ success: false,
3563
+ error_type: err instanceof Error ? err.constructor.name : "unknown"
3564
+ });
3565
+ await shutdownAnalytics();
3566
+ outputErrorAndExit(
3567
+ json,
3568
+ `Transaction failed: ${err instanceof Error ? err.message : String(err)}`
3569
+ );
3570
+ }
3571
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
3572
+ printSendResult(json, {
3573
+ name: tokenName,
3574
+ symbol,
3575
+ address: tokenAddress,
3576
+ amount,
3577
+ decimals,
3578
+ amountFormatted,
3579
+ amountUsd,
3580
+ to: recipient,
3581
+ txHash
3582
+ });
3583
+ track("cli_send", {
3584
+ asset: knownToken ? knownTokenKey : "coin",
3585
+ coin_address: tokenAddress,
3586
+ coin_name: tokenName,
3587
+ coin_symbol: symbol,
3588
+ amount_mode: amountMode,
3589
+ amount_usd: amountUsd,
3590
+ transactionHash: txHash,
3591
+ output_format: json ? "json" : "table",
3592
+ success: true,
3593
+ tx_hash: txHash
3594
+ });
3595
+ }
3596
+ });
3597
+
2440
3598
  // src/commands/setup.ts
2441
- import { Command as Command7 } from "commander";
3599
+ import { Command as Command9 } from "commander";
2442
3600
  import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
2443
3601
 
2444
3602
  // src/lib/strings.ts
@@ -2460,7 +3618,7 @@ var toAccount = (json, key, errorPrefix) => {
2460
3618
  );
2461
3619
  }
2462
3620
  };
2463
- var setupCommand = new Command7("setup").description("Set up your Zora wallet").option("--create", "Create a new wallet without prompting").option("--force", "Overwrite existing wallet without prompting").option("--yes", "Skip interactive prompt and execute directly").action(async function(options) {
3621
+ var setupCommand = new Command9("setup").description("Set up your Zora wallet").option("--create", "Create a new wallet without prompting").option("--force", "Overwrite existing wallet without prompting").option("--yes", "Skip interactive prompt and execute directly").action(async function(options) {
2464
3622
  const json = getJson(this);
2465
3623
  const nonInteractive = getYes(this);
2466
3624
  const envKey = process.env.ZORA_PRIVATE_KEY;
@@ -2619,7 +3777,7 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
2619
3777
  });
2620
3778
 
2621
3779
  // src/commands/wallet.ts
2622
- import { Command as Command8 } from "commander";
3780
+ import { Command as Command10 } from "commander";
2623
3781
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
2624
3782
  var resolvePrivateKey = () => {
2625
3783
  const envKey = process.env.ZORA_PRIVATE_KEY;
@@ -2632,7 +3790,7 @@ var resolvePrivateKey = () => {
2632
3790
  }
2633
3791
  return void 0;
2634
3792
  };
2635
- var walletCommand = new Command8("wallet").description(
3793
+ var walletCommand = new Command10("wallet").description(
2636
3794
  "Manage your Zora wallet"
2637
3795
  );
2638
3796
  walletCommand.command("info").description("Show wallet address and storage location").action(function() {
@@ -2692,7 +3850,7 @@ walletCommand.command("export").description("Print the raw private key to stdout
2692
3850
  });
2693
3851
 
2694
3852
  // src/components/Zorb.tsx
2695
- import { Text as Text4, Box as Box4 } from "ink";
3853
+ import { Text as Text7, Box as Box7 } from "ink";
2696
3854
 
2697
3855
  // src/lib/zorb-pixels.ts
2698
3856
  function supportsTruecolor() {
@@ -2837,7 +3995,7 @@ function generateZorbPixels(size) {
2837
3995
  }
2838
3996
 
2839
3997
  // src/components/Zorb.tsx
2840
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3998
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2841
3999
  var LOWER_HALF_BLOCK = "\u2584";
2842
4000
  var UPPER_HALF_BLOCK = "\u2580";
2843
4001
  function rgbString([r, g, b]) {
@@ -2860,19 +4018,19 @@ function Zorb({ size = 20 }) {
2860
4018
  const topIsBlack = isBlack(top);
2861
4019
  const bottomIsBlack = isBlack(bottom);
2862
4020
  if (topIsBlack && bottomIsBlack) {
2863
- cells.push(/* @__PURE__ */ jsx5(Text4, { children: " " }, x));
4021
+ cells.push(/* @__PURE__ */ jsx10(Text7, { children: " " }, x));
2864
4022
  } else if (topIsBlack) {
2865
4023
  cells.push(
2866
- /* @__PURE__ */ jsx5(Text4, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
4024
+ /* @__PURE__ */ jsx10(Text7, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
2867
4025
  );
2868
4026
  } else if (bottomIsBlack) {
2869
4027
  cells.push(
2870
- /* @__PURE__ */ jsx5(Text4, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
4028
+ /* @__PURE__ */ jsx10(Text7, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
2871
4029
  );
2872
4030
  } else {
2873
4031
  cells.push(
2874
- /* @__PURE__ */ jsx5(
2875
- Text4,
4032
+ /* @__PURE__ */ jsx10(
4033
+ Text7,
2876
4034
  {
2877
4035
  backgroundColor: rgbString(top),
2878
4036
  color: rgbString(bottom),
@@ -2883,41 +4041,53 @@ function Zorb({ size = 20 }) {
2883
4041
  );
2884
4042
  }
2885
4043
  }
2886
- rows.push(/* @__PURE__ */ jsx5(Text4, { children: cells }, y));
4044
+ rows.push(/* @__PURE__ */ jsx10(Text7, { children: cells }, y));
2887
4045
  }
2888
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2889
- /* @__PURE__ */ jsx5(Text4, { children: " " }),
4046
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
4047
+ /* @__PURE__ */ jsx10(Text7, { children: " " }),
2890
4048
  rows,
2891
- /* @__PURE__ */ jsx5(Text4, { children: " " })
4049
+ /* @__PURE__ */ jsx10(Text7, { children: " " })
2892
4050
  ] });
2893
4051
  }
2894
4052
 
2895
4053
  // src/index.tsx
2896
- import { jsx as jsx6 } from "react/jsx-runtime";
4054
+ import { jsx as jsx11 } from "react/jsx-runtime";
2897
4055
  if (process.env.ZORA_API_TARGET) {
2898
4056
  setApiBaseUrl(process.env.ZORA_API_TARGET);
2899
4057
  }
2900
- var version = true ? "0.2.3" : JSON.parse(
4058
+ var version = true ? "0.2.4" : JSON.parse(
2901
4059
  readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
2902
4060
  ).version;
2903
4061
  var buildProgram = () => {
2904
- const program2 = new Command9().name("zora").description("Zora CLI").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
4062
+ const program2 = new Command11().name("zora").description("Zora CLI").version(version).option(
4063
+ "--output <format>",
4064
+ "Output format: table, json, live (default varies by command)"
4065
+ ).option(
4066
+ "--interval <seconds>",
4067
+ "Auto-refresh interval in seconds (min 5)",
4068
+ "30"
4069
+ );
2905
4070
  program2.addCommand(authCommand);
2906
4071
  program2.addCommand(balanceCommand);
2907
4072
  program2.addCommand(buyCommand);
2908
4073
  program2.addCommand(exploreCommand);
2909
4074
  program2.addCommand(getCommand);
4075
+ program2.addCommand(priceHistoryCommand);
2910
4076
  program2.addCommand(setupCommand);
2911
4077
  program2.addCommand(walletCommand);
2912
4078
  program2.addCommand(sellCommand);
4079
+ program2.addCommand(sendCommand);
2913
4080
  return program2;
2914
4081
  };
2915
4082
  var program = buildProgram();
2916
4083
  if (!process.env.VITEST) {
2917
4084
  const showingHelp = process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h");
2918
- if (showingHelp && !process.argv.includes("--json") && supportsTruecolor()) {
2919
- renderOnce(/* @__PURE__ */ jsx6(Zorb, { size: 20 }));
4085
+ if (showingHelp && !process.argv.includes("--output") && supportsTruecolor()) {
4086
+ renderOnce(/* @__PURE__ */ jsx11(Zorb, { size: 20 }));
2920
4087
  }
4088
+ console.warn(
4089
+ "\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
4090
+ );
2921
4091
  identify();
2922
4092
  try {
2923
4093
  await program.parseAsync();