@zoralabs/cli 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +17 -16
  2. package/dist/index.js +1239 -502
  3. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -52,6 +52,12 @@ function apiErrorMessage(err) {
52
52
  return "Zora is temporarily unavailable. Try again later.";
53
53
  return formatError(err);
54
54
  }
55
+ function bannedCoinMessage(address) {
56
+ return `The coin at ${address} is unavailable because it violates the Zora terms of service.`;
57
+ }
58
+ function bannedCoinBuyMessage(address) {
59
+ return `Unable to buy ${address} because it violates the Zora terms of service. Already own this coin? Run zora sell ${address} --all to exit your position.`;
60
+ }
55
61
  function fsErrorMessage(err, path) {
56
62
  if (!(err instanceof Error)) return String(err);
57
63
  const code = err.code;
@@ -279,6 +285,10 @@ var passwordOrFail = async (json, opts, nonInteractive) => {
279
285
  }
280
286
  return password(opts);
281
287
  };
288
+ var passwordOrSkip = async (opts, nonInteractive) => {
289
+ if (nonInteractive) return "";
290
+ return password(opts);
291
+ };
282
292
 
283
293
  // src/lib/analytics.ts
284
294
  import { PostHog } from "posthog-node";
@@ -431,7 +441,7 @@ var getClient = () => {
431
441
  return client;
432
442
  };
433
443
  var commonProperties = () => ({
434
- cli_version: true ? "0.3.0" : "development",
444
+ cli_version: true ? "1.0.0" : "development",
435
445
  os: process.platform,
436
446
  arch: process.arch,
437
447
  node_version: process.version
@@ -494,7 +504,9 @@ var shutdownAnalytics = async () => {
494
504
  // src/commands/auth.ts
495
505
  var authCommand = new Command("auth").description(
496
506
  "Manage API key authentication.\nAPI key is optional \u2014 without one, requests are rate-limited.\nGet a key at https://zora.co/settings/developer"
497
- );
507
+ ).action(function() {
508
+ this.outputHelp();
509
+ });
498
510
  authCommand.command("configure").description("Set your Zora API key").option("--yes", "Skip interactive prompt and execute directly").action(async function() {
499
511
  const json = getJson(this);
500
512
  const nonInteractive = getYes(this);
@@ -671,7 +683,7 @@ var Table = ({
671
683
 
672
684
  // src/lib/format.ts
673
685
  import { format, formatDistanceStrict } from "date-fns";
674
- import { formatEther } from "viem";
686
+ import { formatUnits } from "viem";
675
687
  function formatCompactUsd(value) {
676
688
  if (!value || Number(value) === 0) return "$0";
677
689
  return new Intl.NumberFormat("en-US", {
@@ -720,17 +732,35 @@ function formatCreatedAt(isoDate, now) {
720
732
  if (isNaN(date.getTime())) return "-";
721
733
  return `${formatRelativeTime(date, now)} (${formatAbsoluteTime(date)})`;
722
734
  }
723
- var formatEthDisplay = (wei) => {
724
- const eth = formatEther(wei);
725
- const parts = eth.split(".");
726
- if (!parts[1]) return eth;
727
- const trimmed = parts[1].replace(/0+$/, "") || "0";
728
- return `${parts[0]}.${trimmed}`;
735
+ var formatAmountDisplay = (amount, decimals) => {
736
+ const formatted = formatUnits(amount, decimals);
737
+ const parts = formatted.split(".");
738
+ if (!parts[1]) {
739
+ return new Intl.NumberFormat("en-US", {
740
+ maximumFractionDigits: 2
741
+ }).format(Number(formatted));
742
+ }
743
+ const twoDecimal = `${parts[0]}.${parts[1].slice(0, 2)}`;
744
+ if (Number(twoDecimal) === 0 && amount > 0n) {
745
+ const sigIndex = parts[1].search(/[1-9]/);
746
+ const maxDecimals = sigIndex === -1 ? 6 : Math.min(sigIndex + 4, parts[1].length);
747
+ const truncated = `${parts[0]}.${parts[1].slice(0, maxDecimals)}`;
748
+ return new Intl.NumberFormat("en-US", {
749
+ maximumFractionDigits: maxDecimals
750
+ }).format(Number(truncated));
751
+ }
752
+ return new Intl.NumberFormat("en-US", {
753
+ maximumFractionDigits: 2
754
+ }).format(Number(formatted));
729
755
  };
756
+ function computeMarketCapChange24h(marketCap, marketCapDelta24h) {
757
+ if (marketCap === null || marketCapDelta24h === null || marketCap - marketCapDelta24h === 0)
758
+ return null;
759
+ return Number(
760
+ (marketCapDelta24h / (marketCap - marketCapDelta24h) * 100).toFixed(4)
761
+ );
762
+ }
730
763
  var truncateAddress = (address) => `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
731
- var formatCoinsDisplay = (coinsOut) => new Intl.NumberFormat("en-US", {
732
- maximumFractionDigits: 2
733
- }).format(Number(coinsOut));
734
764
 
735
765
  // src/lib/balance-format.ts
736
766
  var COIN_DECIMALS = 18;
@@ -961,8 +991,7 @@ var BalanceView = ({
961
991
  const hints = [];
962
992
  if (paginated && cursorHistory.length > 0) hints.push("\u2190 prev");
963
993
  if (paginated && data?.pageInfo?.hasNextPage) hints.push("\u2192 next");
964
- hints.push("r refresh");
965
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
994
+ hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
966
995
  hints.push("q quit");
967
996
  const footer = hints.join(" \xB7 ");
968
997
  const showWallet = mode === "full" || mode === "wallet";
@@ -1017,7 +1046,7 @@ import { getTokenInfo } from "@zoralabs/coins-sdk";
1017
1046
  import {
1018
1047
  createPublicClient as createPublicClient2,
1019
1048
  erc20Abi,
1020
- formatUnits,
1049
+ formatUnits as formatUnits2,
1021
1050
  http
1022
1051
  } from "viem";
1023
1052
  import { base as base2 } from "viem/chains";
@@ -1102,7 +1131,7 @@ var fetchWalletBalances = async (walletAddress) => {
1102
1131
  });
1103
1132
  const visible = resolved.filter((r) => r.balance > 0n || r.token.isNative);
1104
1133
  const intermediate = visible.map(({ token, balance, priceUsd }) => {
1105
- const human = formatUnits(balance, token.decimals);
1134
+ const human = formatUnits2(balance, token.decimals);
1106
1135
  const usdValue = priceUsd !== null ? Number(human) * priceUsd : null;
1107
1136
  return { token, human, priceUsd, usdValue };
1108
1137
  });
@@ -1150,11 +1179,10 @@ var formatBalanceJson = (balance, rank) => {
1150
1179
  const totalVolume = balance.coin?.totalVolume ? Number(balance.coin.totalVolume) : null;
1151
1180
  const priceUsdValue = priceUsd ? Number(priceUsd) : null;
1152
1181
  const usdValue = priceUsdValue !== null ? Number((parseRawBalance(balance.balance) * priceUsdValue).toFixed(6)) : null;
1153
- const marketCapChange24h = marketCap !== null && marketCapDelta24h !== null && marketCap - marketCapDelta24h !== 0 ? Number(
1154
- (marketCapDelta24h / (marketCap - marketCapDelta24h) * 100).toFixed(
1155
- 4
1156
- )
1157
- ) : null;
1182
+ const marketCapChange24h = computeMarketCapChange24h(
1183
+ marketCap,
1184
+ marketCapDelta24h
1185
+ );
1158
1186
  return {
1159
1187
  rank,
1160
1188
  name: balance.coin?.name ?? null,
@@ -1177,13 +1205,9 @@ var formatBalanceJson = (balance, rank) => {
1177
1205
  function resolveContext(json) {
1178
1206
  const account = resolveAccount(json);
1179
1207
  const apiKey = getApiKey();
1180
- if (!apiKey) {
1181
- outputErrorAndExit(
1182
- json,
1183
- "Not authenticated. Run 'zora auth configure' to set your API key."
1184
- );
1208
+ if (apiKey) {
1209
+ setApiKey(apiKey);
1185
1210
  }
1186
- setApiKey(apiKey);
1187
1211
  return account;
1188
1212
  }
1189
1213
  function renderWallet(json, walletResult) {
@@ -1527,10 +1551,10 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
1527
1551
  // src/commands/buy.ts
1528
1552
  import { Command as Command3 } from "commander";
1529
1553
  import confirm2 from "@inquirer/confirm";
1530
- import { parseUnits, formatUnits as formatUnits3, isAddress } from "viem";
1554
+ import { parseUnits, formatUnits as formatUnits4, isAddress } from "viem";
1531
1555
  import {
1532
1556
  setApiKey as setApiKey2,
1533
- getCoin,
1557
+ getCoin as getCoin2,
1534
1558
  tradeCoin,
1535
1559
  createTradeCall
1536
1560
  } from "@zoralabs/coins-sdk";
@@ -1538,7 +1562,7 @@ import {
1538
1562
  // src/lib/trade-helpers.ts
1539
1563
  import {
1540
1564
  parseEther,
1541
- formatUnits as formatUnits2,
1565
+ formatUnits as formatUnits3,
1542
1566
  isAddressEqual,
1543
1567
  parseEventLogs,
1544
1568
  erc20Abi as erc20Abi2
@@ -1571,25 +1595,6 @@ var parsePercentageLikeValue = (value) => {
1571
1595
  const parsed = Number(value);
1572
1596
  return Number.isFinite(parsed) ? parsed : void 0;
1573
1597
  };
1574
- var formatAmountDisplay = (amount, decimals) => {
1575
- const formatted = formatUnits2(amount, decimals);
1576
- const parts = formatted.split(".");
1577
- if (!parts[1]) {
1578
- return new Intl.NumberFormat("en-US", {
1579
- maximumFractionDigits: 2
1580
- }).format(Number(formatted));
1581
- }
1582
- const twoDecimal = `${parts[0]}.${parts[1].slice(0, 2)}`;
1583
- let maxDecimals = 2;
1584
- if (Number(twoDecimal) === 0 && amount > 0n) {
1585
- const sigIndex = parts[1].search(/[1-9]/);
1586
- maxDecimals = sigIndex === -1 ? 6 : Math.min(sigIndex + 4, parts[1].length);
1587
- }
1588
- const truncated = `${parts[0]}.${parts[1].slice(0, maxDecimals)}`;
1589
- return new Intl.NumberFormat("en-US", {
1590
- maximumFractionDigits: maxDecimals
1591
- }).format(Number(truncated));
1592
- };
1593
1598
  var getReceivedAmountFromReceipt = ({
1594
1599
  receipt,
1595
1600
  tokenAddress,
@@ -1655,12 +1660,12 @@ var printQuote = (json, info) => {
1655
1660
  coin: info.coinSymbol,
1656
1661
  address: info.address,
1657
1662
  spend: {
1658
- amount: formatUnits2(info.amountIn, info.inputTokenDecimals),
1663
+ amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
1659
1664
  raw: info.amountIn.toString(),
1660
1665
  symbol: info.inputTokenSymbol
1661
1666
  },
1662
1667
  estimated: {
1663
- amount: formatUnits2(BigInt(info.amountOut), 18),
1668
+ amount: formatUnits3(BigInt(info.amountOut), 18),
1664
1669
  raw: info.amountOut,
1665
1670
  symbol: info.coinSymbol
1666
1671
  },
@@ -1668,29 +1673,35 @@ var printQuote = (json, info) => {
1668
1673
  });
1669
1674
  return;
1670
1675
  }
1676
+ const spendFormatted = formatAmountDisplay(
1677
+ info.amountIn,
1678
+ info.inputTokenDecimals
1679
+ );
1680
+ const coinsFormatted = formatAmountDisplay(BigInt(info.amountOut), 18);
1671
1681
  console.log(`
1672
- Buy ${info.coinName} (${info.coinSymbol})
1682
+ Buy \x1B[1m${info.coinName}\x1B[0m`);
1683
+ console.log(` ${info.coinType} \xB7 ${info.address}
1673
1684
  `);
1674
- console.log(` Amount ${info.spendAmount}`);
1675
- console.log(` You get ~${info.coinsFormatted} ${info.coinSymbol}`);
1685
+ console.log(
1686
+ ` Amount ${spendFormatted} ${info.inputTokenSymbol}${info.amountUsd ? ` (${info.amountUsd})` : ""}`
1687
+ );
1688
+ console.log(` You get ~${coinsFormatted} ${info.coinSymbol}`);
1676
1689
  console.log(` Slippage ${info.slippagePct}%
1677
1690
  `);
1678
1691
  };
1679
1692
  var printTradeResult = (json, info) => {
1680
- const receivedAmount = formatUnits2(info.receivedAmountOut, 18);
1681
- const receivedFormatted = formatCoinsDisplay(receivedAmount);
1682
1693
  if (json) {
1683
1694
  outputJson({
1684
1695
  action: "buy",
1685
1696
  coin: info.coinSymbol,
1686
1697
  address: info.address,
1687
1698
  spent: {
1688
- amount: formatUnits2(info.amountIn, info.inputTokenDecimals),
1699
+ amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
1689
1700
  raw: info.amountIn.toString(),
1690
1701
  symbol: info.inputTokenSymbol
1691
1702
  },
1692
1703
  received: {
1693
- amount: receivedAmount,
1704
+ amount: formatUnits3(info.receivedAmountOut, 18),
1694
1705
  raw: info.receivedAmountOut.toString(),
1695
1706
  symbol: info.coinSymbol
1696
1707
  },
@@ -1698,21 +1709,235 @@ var printTradeResult = (json, info) => {
1698
1709
  });
1699
1710
  return;
1700
1711
  }
1712
+ const spentFormatted = formatAmountDisplay(
1713
+ info.amountIn,
1714
+ info.inputTokenDecimals
1715
+ );
1716
+ const receivedFormatted = formatAmountDisplay(info.receivedAmountOut, 18);
1701
1717
  console.log(`
1702
- Bought ${info.coinName}
1718
+ Bought \x1B[1m${info.coinName}\x1B[0m`);
1719
+ console.log(` ${info.coinType} \xB7 ${info.address}
1703
1720
  `);
1704
- console.log(` Spent ${info.spendAmount} ${info.inputTokenSymbol}`);
1721
+ console.log(
1722
+ ` Spent ${spentFormatted} ${info.inputTokenSymbol}${info.amountUsd ? ` (${info.amountUsd})` : ""}`
1723
+ );
1705
1724
  console.log(` Received ${receivedFormatted} ${info.coinSymbol}`);
1706
1725
  console.log(` Tx ${info.txHash}
1707
1726
  `);
1708
1727
  };
1709
1728
 
1729
+ // src/lib/coin-ref.ts
1730
+ import { getCoin, getProfile, getTrend } from "@zoralabs/coins-sdk";
1731
+ var TYPE_KEYWORDS = /* @__PURE__ */ new Set(["creator-coin", "trend"]);
1732
+ var CoinArgError = class extends Error {
1733
+ suggestion;
1734
+ constructor(message, suggestion) {
1735
+ super(message);
1736
+ this.suggestion = suggestion;
1737
+ }
1738
+ };
1739
+ function parsePositionalCoinArgs(firstArg, secondArg) {
1740
+ if (TYPE_KEYWORDS.has(firstArg)) {
1741
+ if (!secondArg) {
1742
+ throw new CoinArgError(
1743
+ `Missing identifier after "${firstArg}".`,
1744
+ `Usage: zora <command> ${firstArg} <name>`
1745
+ );
1746
+ }
1747
+ return { kind: "typed", type: firstArg, identifier: secondArg };
1748
+ }
1749
+ if (firstArg.startsWith("0x")) {
1750
+ return { kind: "address", address: firstArg };
1751
+ }
1752
+ return { kind: "ambiguous-name", name: firstArg };
1753
+ }
1754
+ function coinArgsToRef(parsed) {
1755
+ switch (parsed.kind) {
1756
+ case "typed":
1757
+ return { kind: "prefixed", type: parsed.type, name: parsed.identifier };
1758
+ case "address":
1759
+ return { kind: "address", address: parsed.address };
1760
+ case "ambiguous-name":
1761
+ return { kind: "ambiguous", name: parsed.name };
1762
+ }
1763
+ }
1764
+ async function resolveAmbiguousName(name) {
1765
+ const [creatorResult, trendResult] = await Promise.all([
1766
+ resolveByCreatorName(name),
1767
+ resolveByTrendTicker(name)
1768
+ ]);
1769
+ const creatorFound = creatorResult.kind === "found" ? creatorResult.coin : null;
1770
+ const trendFound = trendResult.kind === "found" ? trendResult.coin : null;
1771
+ if (creatorFound && trendFound) {
1772
+ return { kind: "ambiguous", creator: creatorFound, trend: trendFound };
1773
+ }
1774
+ if (creatorFound) {
1775
+ return { kind: "found", coin: creatorFound };
1776
+ }
1777
+ if (trendFound) {
1778
+ return { kind: "found", coin: trendFound };
1779
+ }
1780
+ return {
1781
+ kind: "not-found",
1782
+ message: `No coin found matching "${name}".`
1783
+ };
1784
+ }
1785
+ function formatAmbiguousError(name, creator, trend, command) {
1786
+ const creatorMcap = formatCompactUsd(creator.marketCap);
1787
+ const trendMcap = formatCompactUsd(trend.marketCap);
1788
+ return {
1789
+ message: [
1790
+ `Multiple coins match "${name}":`,
1791
+ ` creator-coin ${creator.name} ${creatorMcap} mcap`,
1792
+ ` trend ${trend.name} ${trendMcap} mcap`
1793
+ ].join("\n"),
1794
+ suggestion: `Use: zora ${command} creator-coin ${name} or zora ${command} trend ${name}`
1795
+ };
1796
+ }
1797
+ var COIN_TYPE_MAP = {
1798
+ CONTENT: "post",
1799
+ CREATOR: "creator-coin",
1800
+ TREND: "trend"
1801
+ };
1802
+ function mapCoinType(raw) {
1803
+ if (!raw) return "unknown";
1804
+ return COIN_TYPE_MAP[raw] ?? "unknown";
1805
+ }
1806
+ function coinFromToken(token) {
1807
+ return {
1808
+ name: token.name ?? "Unknown",
1809
+ address: token.address ?? "",
1810
+ coinType: mapCoinType(token.coinType),
1811
+ marketCap: token.marketCap ?? "0",
1812
+ marketCapDelta24h: token.marketCapDelta24h ?? "0",
1813
+ volume24h: token.volume24h ?? "0",
1814
+ uniqueHolders: token.uniqueHolders ?? 0,
1815
+ createdAt: token.createdAt,
1816
+ creatorAddress: token.creatorAddress,
1817
+ creatorHandle: token.creatorProfile?.handle,
1818
+ platformBlocked: token.platformBlocked ?? false
1819
+ };
1820
+ }
1821
+ async function resolveByAddress(address) {
1822
+ const response = await getCoin({ address });
1823
+ if (response.error || !response.data?.zora20Token) {
1824
+ return {
1825
+ kind: "not-found",
1826
+ message: `No coin found at address ${address}`
1827
+ };
1828
+ }
1829
+ return { kind: "found", coin: coinFromToken(response.data.zora20Token) };
1830
+ }
1831
+ async function resolveByTrendTicker(ticker) {
1832
+ const response = await getTrend({ ticker });
1833
+ if (response.error || !response.data?.trendCoin) {
1834
+ return {
1835
+ kind: "not-found",
1836
+ message: `No trend coin found with ticker "${ticker}"`
1837
+ };
1838
+ }
1839
+ return { kind: "found", coin: coinFromToken(response.data.trendCoin) };
1840
+ }
1841
+ async function resolveByCreatorName(name) {
1842
+ const response = await getProfile({ identifier: name });
1843
+ if (response.error || !response.data?.profile) {
1844
+ return {
1845
+ kind: "not-found",
1846
+ message: `No creator found with name "${name}"`
1847
+ };
1848
+ }
1849
+ const profile = response.data.profile;
1850
+ if (!profile.creatorCoin) {
1851
+ return {
1852
+ kind: "not-found",
1853
+ message: `"${name}" does not have a creator coin`
1854
+ };
1855
+ }
1856
+ return resolveByAddress(profile.creatorCoin.address);
1857
+ }
1858
+ async function resolveCoin(ref) {
1859
+ switch (ref.kind) {
1860
+ case "address":
1861
+ return resolveByAddress(ref.address);
1862
+ case "prefixed":
1863
+ if (ref.type === "trend") {
1864
+ return resolveByTrendTicker(ref.name);
1865
+ }
1866
+ return resolveByCreatorName(ref.name);
1867
+ case "ambiguous":
1868
+ return resolveByCreatorName(ref.name);
1869
+ }
1870
+ }
1871
+
1710
1872
  // src/commands/buy.ts
1711
- 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) {
1873
+ var buyCommand = new Command3("buy").description("Buy a coin").argument(
1874
+ "[typeOrId]",
1875
+ "Type prefix (creator-coin, trend) or coin address/name"
1876
+ ).argument("[identifier]", "Coin name (when type prefix is given)").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(typeOrId, identifier, opts) {
1712
1877
  const json = getJson(this);
1713
1878
  const debug = opts.debug === true;
1714
- if (!isAddress(coinAddress)) {
1715
- outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
1879
+ let parsed;
1880
+ try {
1881
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
1882
+ } catch (err) {
1883
+ if (err instanceof CoinArgError) {
1884
+ outputErrorAndExit(json, err.message, err.suggestion);
1885
+ }
1886
+ throw err;
1887
+ }
1888
+ const apiKey = getApiKey();
1889
+ if (apiKey) {
1890
+ setApiKey2(apiKey);
1891
+ }
1892
+ let coinAddress;
1893
+ if (parsed.kind === "address") {
1894
+ if (!isAddress(parsed.address)) {
1895
+ outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
1896
+ return;
1897
+ }
1898
+ coinAddress = parsed.address;
1899
+ } else if (parsed.kind === "ambiguous-name") {
1900
+ let ambResult;
1901
+ try {
1902
+ ambResult = await resolveAmbiguousName(parsed.name);
1903
+ } catch (err) {
1904
+ outputErrorAndExit(
1905
+ json,
1906
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1907
+ );
1908
+ return;
1909
+ }
1910
+ if (ambResult.kind === "not-found") {
1911
+ outputErrorAndExit(json, ambResult.message);
1912
+ return;
1913
+ }
1914
+ if (ambResult.kind === "ambiguous") {
1915
+ const { message, suggestion } = formatAmbiguousError(
1916
+ parsed.name,
1917
+ ambResult.creator,
1918
+ ambResult.trend,
1919
+ "buy"
1920
+ );
1921
+ outputErrorAndExit(json, message, suggestion);
1922
+ return;
1923
+ }
1924
+ coinAddress = ambResult.coin.address;
1925
+ } else {
1926
+ const ref = coinArgsToRef(parsed);
1927
+ try {
1928
+ const result = await resolveCoin(ref);
1929
+ if (result.kind === "not-found") {
1930
+ outputErrorAndExit(json, result.message, result.suggestion);
1931
+ return;
1932
+ }
1933
+ coinAddress = result.coin.address;
1934
+ } catch (err) {
1935
+ outputErrorAndExit(
1936
+ json,
1937
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1938
+ );
1939
+ return;
1940
+ }
1716
1941
  }
1717
1942
  const tokenKey = opts.token.toLowerCase();
1718
1943
  if (!(tokenKey in BASE_TRADE_TOKENS)) {
@@ -1736,15 +1961,11 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1736
1961
  );
1737
1962
  }
1738
1963
  const slippage = slippagePct / 100;
1739
- const apiKey = getApiKey();
1740
- if (apiKey) {
1741
- setApiKey2(apiKey);
1742
- }
1743
1964
  const account = resolveAccount(json);
1744
1965
  const { publicClient, walletClient } = createClients(account);
1745
1966
  let token;
1746
1967
  try {
1747
- const response = await getCoin({ address: coinAddress });
1968
+ const response = await getCoin2({ address: coinAddress });
1748
1969
  token = response.data?.zora20Token;
1749
1970
  } catch (err) {
1750
1971
  outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
@@ -1752,8 +1973,12 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1752
1973
  if (!token) {
1753
1974
  outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
1754
1975
  }
1976
+ if (token.platformBlocked) {
1977
+ outputErrorAndExit(json, bannedCoinBuyMessage(coinAddress));
1978
+ }
1755
1979
  const coinName = token.name;
1756
1980
  const coinSymbol = token.symbol;
1981
+ const coinType = mapCoinType(token.coinType);
1757
1982
  let amountIn;
1758
1983
  if (amountMode === "usd") {
1759
1984
  const usdVal = parsePercentageLikeValue(opts.usd);
@@ -1788,7 +2013,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1788
2013
  }
1789
2014
  if (debug) {
1790
2015
  console.error(
1791
- `[debug] $${usdVal} USD = ${formatUnits3(amountIn, inputToken.decimals)} ${inputToken.symbol} (price: $${priceUsd})`
2016
+ `[debug] $${usdVal} USD = ${formatUnits4(amountIn, inputToken.decimals)} ${inputToken.symbol} (price: $${priceUsd})`
1792
2017
  );
1793
2018
  }
1794
2019
  } else if (amountMode === "eth") {
@@ -1841,7 +2066,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1841
2066
  if (isEth && balance <= gasReserve) {
1842
2067
  outputErrorAndExit(
1843
2068
  json,
1844
- `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
2069
+ `Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
1845
2070
  );
1846
2071
  }
1847
2072
  const spendableBalance = balance - gasReserve;
@@ -1871,7 +2096,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1871
2096
  const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
1872
2097
  if (priceUsd != null) {
1873
2098
  swapAmountUsd = Number(
1874
- (Number(formatUnits3(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
2099
+ (Number(formatUnits4(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
1875
2100
  );
1876
2101
  }
1877
2102
  }
@@ -1926,25 +2151,20 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1926
2151
  "Check the coin address is valid and try again. Use --debug for full error details."
1927
2152
  );
1928
2153
  }
1929
- const spendAmount = formatUnits3(amountIn, inputToken.decimals);
1930
- const spendFormatted = new Intl.NumberFormat("en-US", {
1931
- maximumFractionDigits: 6
1932
- }).format(Number(spendAmount));
1933
- const coinsOut = formatUnits3(BigInt(amountOut), 18);
1934
- const coinsFormatted = formatCoinsDisplay(coinsOut);
2154
+ const quoteInfo = {
2155
+ coinName,
2156
+ coinSymbol,
2157
+ coinType,
2158
+ address: coinAddress,
2159
+ amountIn,
2160
+ inputTokenSymbol: inputToken.symbol,
2161
+ inputTokenDecimals: inputToken.decimals,
2162
+ amountOut,
2163
+ slippagePct
2164
+ };
2165
+ const amountUsd = swapAmountUsd != null && inputToken.fixedPriceUsd == null ? `${amountMode === "usd" ? "" : "~"}${formatUsd(swapAmountUsd)}` : void 0;
1935
2166
  if (opts.quote) {
1936
- printQuote(json, {
1937
- coinName,
1938
- coinSymbol,
1939
- address: coinAddress,
1940
- spendAmount: `${spendFormatted} ${inputToken.symbol}`,
1941
- amountIn,
1942
- inputTokenSymbol: inputToken.symbol,
1943
- inputTokenDecimals: inputToken.decimals,
1944
- coinsFormatted,
1945
- amountOut,
1946
- slippagePct
1947
- });
2167
+ printQuote(json, { ...quoteInfo, amountUsd });
1948
2168
  track("cli_buy", {
1949
2169
  action: "quote",
1950
2170
  coin_address: coinAddress,
@@ -1960,18 +2180,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1960
2180
  return;
1961
2181
  }
1962
2182
  if (!opts.yes) {
1963
- printQuote(false, {
1964
- coinName,
1965
- coinSymbol,
1966
- address: coinAddress,
1967
- spendAmount: `${spendFormatted} ${inputToken.symbol}`,
1968
- amountIn,
1969
- inputTokenSymbol: inputToken.symbol,
1970
- inputTokenDecimals: inputToken.decimals,
1971
- coinsFormatted,
1972
- amountOut,
1973
- slippagePct
1974
- });
2183
+ printQuote(false, { ...quoteInfo, amountUsd });
1975
2184
  const ok = await confirm2({
1976
2185
  message: "Confirm?",
1977
2186
  default: false
@@ -2028,8 +2237,9 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2028
2237
  printTradeResult(json, {
2029
2238
  coinName,
2030
2239
  coinSymbol,
2240
+ coinType,
2031
2241
  address: coinAddress,
2032
- spendAmount,
2242
+ amountUsd,
2033
2243
  amountIn,
2034
2244
  inputTokenSymbol: inputToken.symbol,
2035
2245
  inputTokenDecimals: inputToken.decimals,
@@ -2063,9 +2273,6 @@ import {
2063
2273
  getCoinsTopVolume24h,
2064
2274
  getCoinsMostValuable,
2065
2275
  getCoinsNew,
2066
- getCoinsTopGainers,
2067
- getCoinsLastTraded,
2068
- getCoinsLastTradedUnique,
2069
2276
  getExploreTopVolumeAll24h,
2070
2277
  getExploreTopVolumeCreators24h,
2071
2278
  getExploreNewAll,
@@ -2088,9 +2295,6 @@ var SORT_LABELS2 = {
2088
2295
  mcap: "Top by Market Cap",
2089
2296
  volume: "Top by 24h Volume",
2090
2297
  new: "New",
2091
- gainers: "Top Gainers (24h)",
2092
- "last-traded": "Last Traded",
2093
- "last-traded-unique": "Last Traded (Unique)",
2094
2298
  trending: "Trending",
2095
2299
  featured: "Featured"
2096
2300
  };
@@ -2340,8 +2544,7 @@ var ExploreView = ({
2340
2544
  hints.push("c copy address");
2341
2545
  if (cursorHistory.length > 0) hints.push("\u2190 prev");
2342
2546
  if (pageInfo?.hasNextPage) hints.push("\u2192 next");
2343
- hints.push("r refresh");
2344
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
2547
+ hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
2345
2548
  hints.push("q quit");
2346
2549
  const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
2347
2550
  return /* @__PURE__ */ jsx4(
@@ -2359,6 +2562,47 @@ var ExploreView = ({
2359
2562
 
2360
2563
  // src/commands/explore.tsx
2361
2564
  import { jsx as jsx5 } from "react/jsx-runtime";
2565
+ var formatExploreCoinJson = (node) => {
2566
+ const marketCap = node.marketCap ? Number(node.marketCap) : null;
2567
+ const marketCapDelta24h = node.marketCapDelta24h ? Number(node.marketCapDelta24h) : null;
2568
+ const marketCapChange24h = computeMarketCapChange24h(
2569
+ marketCap,
2570
+ marketCapDelta24h
2571
+ );
2572
+ const priceUsd = node.tokenPrice?.priceInUsdc ? Number(node.tokenPrice.priceInUsdc) : null;
2573
+ const coinType = node.coinType ? COIN_TYPE_DISPLAY[node.coinType] ?? node.coinType : null;
2574
+ const socials = node.creatorProfile?.socialAccounts;
2575
+ const socialAccounts = socials ? {
2576
+ instagram: socials.instagram ?? null,
2577
+ tiktok: socials.tiktok ?? null,
2578
+ twitter: socials.twitter ?? null,
2579
+ farcaster: socials.farcaster ?? null
2580
+ } : null;
2581
+ return {
2582
+ name: node.name ?? null,
2583
+ description: node.description ?? null,
2584
+ symbol: node.symbol ?? null,
2585
+ coinType,
2586
+ chainId: node.chainId ?? null,
2587
+ address: node.address ?? null,
2588
+ platformBlocked: node.platformBlocked ?? false,
2589
+ totalSupply: node.totalSupply ?? null,
2590
+ creatorAddress: node.creatorAddress ?? null,
2591
+ creatorHandle: node.creatorProfile?.handle ?? null,
2592
+ socialAccounts,
2593
+ mediaContentMimeType: node.mediaContent?.mimeType ?? null,
2594
+ mediaContentOriginalUri: node.mediaContent?.originalUri ?? null,
2595
+ previewImage: node.mediaContent?.previewImage?.medium ?? null,
2596
+ priceUsd,
2597
+ marketCap,
2598
+ marketCapDelta24h,
2599
+ marketCapChange24h,
2600
+ volume24h: node.volume24h ? Number(node.volume24h) : null,
2601
+ totalVolume: node.totalVolume ? Number(node.totalVolume) : null,
2602
+ uniqueHolders: node.uniqueHolders ?? null,
2603
+ createdAt: node.createdAt ?? null
2604
+ };
2605
+ };
2362
2606
  var QUERY_MAP = {
2363
2607
  mcap: {
2364
2608
  all: getMostValuableAll,
@@ -2378,15 +2622,6 @@ var QUERY_MAP = {
2378
2622
  "creator-coin": getCreatorCoins,
2379
2623
  post: getCoinsNew
2380
2624
  },
2381
- gainers: {
2382
- post: getCoinsTopGainers
2383
- },
2384
- "last-traded": {
2385
- post: getCoinsLastTraded
2386
- },
2387
- "last-traded-unique": {
2388
- post: getCoinsLastTradedUnique
2389
- },
2390
2625
  trending: {
2391
2626
  all: getTrendingAll,
2392
2627
  trend: getTrendingTrends,
@@ -2428,7 +2663,7 @@ var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
2428
2663
  var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
2429
2664
  "--type <type>",
2430
2665
  "Filter by type: all, trend, creator-coin, post (availability varies by sort)",
2431
- "post"
2666
+ "creator-coin"
2432
2667
  ).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
2433
2668
  "--refresh <seconds>",
2434
2669
  "Auto-refresh interval in seconds, requires --live (min 5)",
@@ -2479,7 +2714,10 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
2479
2714
  outputErrorAndExit(json, `API error: ${msg}`);
2480
2715
  }
2481
2716
  const edges = response.data?.exploreList?.edges ?? [];
2482
- const coins = edges.map((e) => e.node);
2717
+ const rawNodes = edges.map((e) => e.node);
2718
+ const coins = rawNodes.map(
2719
+ (node, i) => formatExploreCoinJson(node)
2720
+ );
2483
2721
  const pageInfo = response.data?.exploreList?.pageInfo;
2484
2722
  outputJson({ coins, pageInfo: pageInfo ?? null });
2485
2723
  track("cli_explore", {
@@ -2560,106 +2798,18 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
2560
2798
  import { Command as Command5 } from "commander";
2561
2799
  import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
2562
2800
 
2563
- // src/lib/coin-ref.ts
2564
- import { getCoin as getCoin2, getProfile, getTrend } from "@zoralabs/coins-sdk";
2565
- var COIN_TYPE_MAP = {
2566
- CONTENT: "post",
2567
- CREATOR: "creator-coin",
2568
- TREND: "trend"
2569
- };
2570
- function mapCoinType(raw) {
2571
- if (!raw) return "unknown";
2572
- return COIN_TYPE_MAP[raw] ?? "unknown";
2573
- }
2574
- function coinFromToken(token) {
2575
- return {
2576
- name: token.name ?? "Unknown",
2577
- address: token.address ?? "",
2578
- coinType: mapCoinType(token.coinType),
2579
- marketCap: token.marketCap ?? "0",
2580
- marketCapDelta24h: token.marketCapDelta24h ?? "0",
2581
- volume24h: token.volume24h ?? "0",
2582
- uniqueHolders: token.uniqueHolders ?? 0,
2583
- createdAt: token.createdAt,
2584
- creatorAddress: token.creatorAddress,
2585
- creatorHandle: token.creatorProfile?.handle
2586
- };
2587
- }
2588
- function parseCoinRef(identifier, type) {
2589
- if (identifier.startsWith("0x")) {
2590
- return { kind: "address", address: identifier };
2591
- }
2592
- if (type === "creator-coin") {
2593
- return { kind: "prefixed", type: "creator-coin", name: identifier };
2594
- }
2595
- if (type === "trend") {
2596
- return { kind: "prefixed", type: "trend", name: identifier };
2597
- }
2598
- return { kind: "ambiguous", name: identifier };
2599
- }
2600
- async function resolveByAddress(address) {
2601
- const response = await getCoin2({ address });
2602
- if (response.error || !response.data?.zora20Token) {
2603
- return {
2604
- kind: "not-found",
2605
- message: `No coin found at address ${address}`
2606
- };
2607
- }
2608
- return { kind: "found", coin: coinFromToken(response.data.zora20Token) };
2609
- }
2610
- async function resolveByTrendTicker(ticker) {
2611
- const response = await getTrend({ ticker });
2612
- if (response.error || !response.data?.trendCoin) {
2613
- return {
2614
- kind: "not-found",
2615
- message: `No trend coin found with ticker "${ticker}"`
2616
- };
2617
- }
2618
- return { kind: "found", coin: coinFromToken(response.data.trendCoin) };
2619
- }
2620
- async function resolveByCreatorName(name) {
2621
- const response = await getProfile({ identifier: name });
2622
- if (response.error || !response.data?.profile) {
2623
- return {
2624
- kind: "not-found",
2625
- message: `No creator found with name "${name}"`
2626
- };
2627
- }
2628
- const profile = response.data.profile;
2629
- if (!profile.creatorCoin) {
2630
- return {
2631
- kind: "not-found",
2632
- message: `"${name}" does not have a creator coin`
2633
- };
2634
- }
2635
- return resolveByAddress(profile.creatorCoin.address);
2636
- }
2637
- async function resolveCoin(ref) {
2638
- switch (ref.kind) {
2639
- case "address":
2640
- return resolveByAddress(ref.address);
2641
- case "prefixed":
2642
- if (ref.type === "trend") {
2643
- return resolveByTrendTicker(ref.name);
2644
- }
2645
- return resolveByCreatorName(ref.name);
2646
- case "ambiguous":
2647
- return resolveByCreatorName(ref.name);
2648
- }
2649
- }
2650
-
2651
- // src/components/CoinDetail.tsx
2652
- import { Box as Box5, Text as Text5 } from "ink";
2653
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2654
- var LABEL_WIDTH = 18;
2655
- function Row({
2656
- label,
2657
- children
2658
- }) {
2659
- return /* @__PURE__ */ jsxs5(Box5, { children: [
2660
- /* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
2661
- /* @__PURE__ */ jsx6(Text5, { children })
2662
- ] });
2801
+ // src/components/CoinDetail.tsx
2802
+ import { Box as Box5, Text as Text5 } from "ink";
2803
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2804
+ var LABEL_WIDTH = 18;
2805
+ function Row({
2806
+ label,
2807
+ children
2808
+ }) {
2809
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
2810
+ /* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
2811
+ /* @__PURE__ */ jsx6(Text5, { children })
2812
+ ] });
2663
2813
  }
2664
2814
  function CoinDetail({ coin }) {
2665
2815
  const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
@@ -2702,29 +2852,87 @@ function formatCoinJson(coin) {
2702
2852
  creatorHandle: coin.creatorHandle ?? null
2703
2853
  };
2704
2854
  }
2705
- var VALID_TYPES = ["creator-coin", "post", "trend"];
2706
- 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) {
2855
+ function outputCoin(json, coin) {
2856
+ outputData(json, {
2857
+ json: formatCoinJson(coin),
2858
+ render: () => {
2859
+ renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin }));
2860
+ }
2861
+ });
2862
+ }
2863
+ var getCommand = new Command5("get").description("Look up a coin by address or name").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
2864
+ "[identifier]",
2865
+ "Coin address (0x...) or name (when type prefix is given)"
2866
+ ).action(async function(typeOrId, identifier) {
2707
2867
  const json = getJson(this);
2708
- if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
2709
- outputErrorAndExit(
2710
- json,
2711
- `Invalid --type value: ${opts.type}.`,
2712
- `Supported: ${VALID_TYPES.join(", ")}`
2713
- );
2714
- }
2715
- const type = opts.type;
2716
- if (type === "post" && !identifier.startsWith("0x")) {
2717
- outputErrorAndExit(
2718
- json,
2719
- "Posts can only be looked up by address.",
2720
- "Use: zora get 0x..."
2721
- );
2868
+ let parsed;
2869
+ try {
2870
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
2871
+ } catch (err) {
2872
+ if (err instanceof CoinArgError) {
2873
+ outputErrorAndExit(json, err.message, err.suggestion);
2874
+ }
2875
+ throw err;
2722
2876
  }
2723
- const ref = parseCoinRef(identifier, opts.type);
2724
2877
  const apiKey = getApiKey();
2725
2878
  if (apiKey) {
2726
2879
  setApiKey4(apiKey);
2727
2880
  }
2881
+ if (parsed.kind === "ambiguous-name") {
2882
+ let ambResult;
2883
+ try {
2884
+ ambResult = await resolveAmbiguousName(parsed.name);
2885
+ } catch (err) {
2886
+ outputErrorAndExit(
2887
+ json,
2888
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
2889
+ );
2890
+ return;
2891
+ }
2892
+ if (ambResult.kind === "not-found") {
2893
+ outputErrorAndExit(json, ambResult.message);
2894
+ return;
2895
+ }
2896
+ if (ambResult.kind === "ambiguous") {
2897
+ if (json) {
2898
+ outputData(json, {
2899
+ json: {
2900
+ matches: [
2901
+ { type: "creator-coin", ...formatCoinJson(ambResult.creator) },
2902
+ { type: "trend", ...formatCoinJson(ambResult.trend) }
2903
+ ],
2904
+ hint: `Use: zora get creator-coin ${parsed.name} or zora get trend ${parsed.name}`
2905
+ },
2906
+ render: () => {
2907
+ }
2908
+ });
2909
+ } else {
2910
+ outputCoin(false, ambResult.creator);
2911
+ console.log("");
2912
+ outputCoin(false, ambResult.trend);
2913
+ console.log(
2914
+ `
2915
+ \x1B[2mUse \`zora get creator-coin ${parsed.name}\` or \`zora get trend ${parsed.name}\` for a specific type.\x1B[0m`
2916
+ );
2917
+ }
2918
+ track("cli_get", {
2919
+ lookup_type: "name",
2920
+ found: true,
2921
+ ambiguous: true,
2922
+ output_format: json ? "json" : "text"
2923
+ });
2924
+ return;
2925
+ }
2926
+ outputCoin(json, ambResult.coin);
2927
+ track("cli_get", {
2928
+ lookup_type: "name",
2929
+ found: true,
2930
+ coin_type: ambResult.coin.coinType,
2931
+ output_format: json ? "json" : "text"
2932
+ });
2933
+ return;
2934
+ }
2935
+ const ref = coinArgsToRef(parsed);
2728
2936
  let result;
2729
2937
  try {
2730
2938
  result = await resolveCoin(ref);
@@ -2732,29 +2940,20 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
2732
2940
  outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
2733
2941
  return;
2734
2942
  }
2735
- if (type && result.kind === "found" && result.coin.coinType !== type) {
2736
- outputErrorAndExit(
2737
- json,
2738
- `Coin at ${result.coin.address} is a ${result.coin.coinType}, not a ${type}.`,
2739
- `Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
2740
- );
2741
- return;
2742
- }
2743
2943
  if (result.kind === "not-found") {
2744
2944
  outputErrorAndExit(json, result.message);
2745
2945
  return;
2746
2946
  }
2747
- outputData(json, {
2748
- json: formatCoinJson(result.coin),
2749
- render: () => {
2750
- renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin: result.coin }));
2751
- }
2752
- });
2947
+ if (result.coin.platformBlocked) {
2948
+ outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
2949
+ return;
2950
+ }
2951
+ outputCoin(json, result.coin);
2753
2952
  track("cli_get", {
2754
- lookup_type: identifier.startsWith("0x") ? "address" : "name",
2755
- coin_type_filter: type ?? null,
2756
- found: result.kind === "found",
2757
- coin_type: result.kind === "found" ? result.coin.coinType : null,
2953
+ lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
2954
+ coin_type_filter: parsed.kind === "typed" ? parsed.type : null,
2955
+ found: true,
2956
+ coin_type: result.coin.coinType,
2758
2957
  output_format: json ? "json" : "text"
2759
2958
  });
2760
2959
  });
@@ -2827,7 +3026,6 @@ var downsample = (values, maxWidth) => {
2827
3026
 
2828
3027
  // src/commands/price-history.tsx
2829
3028
  import { jsx as jsx9 } from "react/jsx-runtime";
2830
- var VALID_TYPES2 = ["creator-coin", "post", "trend"];
2831
3029
  var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
2832
3030
  var INTERVAL_TO_API_FIELD = {
2833
3031
  "1h": "oneHour",
@@ -2868,54 +3066,7 @@ var fetchPriceHistory = async (address, interval) => {
2868
3066
  price: Number(p.closePrice)
2869
3067
  }));
2870
3068
  };
2871
- 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(
2872
- "--interval <interval>",
2873
- `Time range: ${VALID_INTERVALS.join(", ")}`,
2874
- "1w"
2875
- ).action(async function(identifier, opts) {
2876
- const json = getJson(this);
2877
- const interval = opts.interval ?? "1w";
2878
- if (!VALID_INTERVALS.includes(interval)) {
2879
- outputErrorAndExit(
2880
- json,
2881
- `Invalid --interval value: ${interval}.`,
2882
- `Supported: ${VALID_INTERVALS.join(", ")}`
2883
- );
2884
- }
2885
- if (opts.type !== void 0 && !VALID_TYPES2.includes(opts.type)) {
2886
- outputErrorAndExit(
2887
- json,
2888
- `Invalid --type value: ${opts.type}.`,
2889
- `Supported: ${VALID_TYPES2.join(", ")}`
2890
- );
2891
- }
2892
- if (opts.type === "post" && !identifier.startsWith("0x")) {
2893
- outputErrorAndExit(
2894
- json,
2895
- "Posts can only be looked up by address.",
2896
- "Use: zora price-history 0x..."
2897
- );
2898
- }
2899
- const ref = parseCoinRef(identifier, opts.type);
2900
- const apiKey = getApiKey();
2901
- if (apiKey) {
2902
- setApiKey5(apiKey);
2903
- }
2904
- let result;
2905
- try {
2906
- result = await resolveCoin(ref);
2907
- } catch (err) {
2908
- outputErrorAndExit(
2909
- json,
2910
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
2911
- );
2912
- return;
2913
- }
2914
- if (result.kind === "not-found") {
2915
- outputErrorAndExit(json, result.message, result.suggestion);
2916
- return;
2917
- }
2918
- const { coin } = result;
3069
+ async function showPriceHistory(json, coin, interval) {
2919
3070
  let prices;
2920
3071
  try {
2921
3072
  prices = await fetchPriceHistory(coin.address, interval);
@@ -2941,9 +3092,7 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
2941
3092
  priceValues[0],
2942
3093
  priceValues[priceValues.length - 1]
2943
3094
  );
2944
- const sparklineText = sparkline(
2945
- downsample(priceValues, MAX_SPARKLINE_WIDTH)
2946
- );
3095
+ const sparklineText = sparkline(downsample(priceValues, MAX_SPARKLINE_WIDTH));
2947
3096
  outputData(json, {
2948
3097
  json: {
2949
3098
  coin: coin.name,
@@ -2974,11 +3123,139 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
2974
3123
  );
2975
3124
  }
2976
3125
  });
3126
+ return prices.length;
3127
+ }
3128
+ var priceHistoryCommand = new Command6("price-history").description("Display price history for a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
3129
+ "[identifier]",
3130
+ "Coin address (0x...) or name (when type prefix is given)"
3131
+ ).option(
3132
+ "--interval <interval>",
3133
+ `Time range: ${VALID_INTERVALS.join(", ")}`,
3134
+ "1w"
3135
+ ).action(async function(typeOrId, identifier, opts) {
3136
+ const json = getJson(this);
3137
+ const interval = opts.interval ?? "1w";
3138
+ if (!VALID_INTERVALS.includes(interval)) {
3139
+ outputErrorAndExit(
3140
+ json,
3141
+ `Invalid --interval value: ${interval}.`,
3142
+ `Supported: ${VALID_INTERVALS.join(", ")}`
3143
+ );
3144
+ }
3145
+ let parsed;
3146
+ try {
3147
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
3148
+ } catch (err) {
3149
+ if (err instanceof CoinArgError) {
3150
+ outputErrorAndExit(json, err.message, err.suggestion);
3151
+ }
3152
+ throw err;
3153
+ }
3154
+ const apiKey = getApiKey();
3155
+ if (apiKey) {
3156
+ setApiKey5(apiKey);
3157
+ }
3158
+ if (parsed.kind === "ambiguous-name") {
3159
+ let ambResult;
3160
+ try {
3161
+ ambResult = await resolveAmbiguousName(parsed.name);
3162
+ } catch (err) {
3163
+ outputErrorAndExit(
3164
+ json,
3165
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3166
+ );
3167
+ return;
3168
+ }
3169
+ if (ambResult.kind === "not-found") {
3170
+ outputErrorAndExit(json, ambResult.message);
3171
+ return;
3172
+ }
3173
+ if (ambResult.kind === "ambiguous") {
3174
+ if (json) {
3175
+ const [creatorPrices, trendPrices] = await Promise.all([
3176
+ fetchPriceHistory(ambResult.creator.address, interval),
3177
+ fetchPriceHistory(ambResult.trend.address, interval)
3178
+ ]);
3179
+ outputData(json, {
3180
+ json: {
3181
+ matches: [
3182
+ {
3183
+ type: "creator-coin",
3184
+ coin: ambResult.creator.name,
3185
+ prices: creatorPrices
3186
+ },
3187
+ {
3188
+ type: "trend",
3189
+ coin: ambResult.trend.name,
3190
+ prices: trendPrices
3191
+ }
3192
+ ],
3193
+ hint: `Use: zora price-history creator-coin ${parsed.name} or zora price-history trend ${parsed.name}`
3194
+ },
3195
+ render: () => {
3196
+ }
3197
+ });
3198
+ } else {
3199
+ await showPriceHistory(
3200
+ false,
3201
+ ambResult.creator,
3202
+ interval
3203
+ );
3204
+ console.log("");
3205
+ await showPriceHistory(false, ambResult.trend, interval);
3206
+ console.log(
3207
+ `
3208
+ \x1B[2mUse \`zora price-history creator-coin ${parsed.name}\` or \`zora price-history trend ${parsed.name}\` for a specific type.\x1B[0m`
3209
+ );
3210
+ }
3211
+ track("cli_price_history", {
3212
+ lookup_type: "name",
3213
+ ambiguous: true,
3214
+ interval,
3215
+ output_format: json ? "json" : "text"
3216
+ });
3217
+ return;
3218
+ }
3219
+ const dataPoints2 = await showPriceHistory(
3220
+ json,
3221
+ ambResult.coin,
3222
+ interval
3223
+ );
3224
+ track("cli_price_history", {
3225
+ lookup_type: "name",
3226
+ coin_type: ambResult.coin.coinType,
3227
+ interval,
3228
+ data_points: dataPoints2,
3229
+ output_format: json ? "json" : "text"
3230
+ });
3231
+ return;
3232
+ }
3233
+ const ref = coinArgsToRef(parsed);
3234
+ let result;
3235
+ try {
3236
+ result = await resolveCoin(ref);
3237
+ } catch (err) {
3238
+ outputErrorAndExit(
3239
+ json,
3240
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3241
+ );
3242
+ return;
3243
+ }
3244
+ if (result.kind === "not-found") {
3245
+ outputErrorAndExit(json, result.message, result.suggestion);
3246
+ return;
3247
+ }
3248
+ if (result.coin.platformBlocked) {
3249
+ outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
3250
+ return;
3251
+ }
3252
+ const { coin } = result;
3253
+ const dataPoints = await showPriceHistory(json, coin, interval);
2977
3254
  track("cli_price_history", {
2978
- lookup_type: identifier.startsWith("0x") ? "address" : "name",
3255
+ lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
2979
3256
  coin_type: coin.coinType,
2980
3257
  interval,
2981
- data_points: prices.length,
3258
+ data_points: dataPoints,
2982
3259
  output_format: json ? "json" : "text"
2983
3260
  });
2984
3261
  });
@@ -2988,7 +3265,7 @@ import { Command as Command7 } from "commander";
2988
3265
  import confirm3 from "@inquirer/confirm";
2989
3266
  import {
2990
3267
  erc20Abi as erc20Abi3,
2991
- formatUnits as formatUnits4,
3268
+ formatUnits as formatUnits5,
2992
3269
  isAddress as isAddress2,
2993
3270
  parseUnits as parseUnits2
2994
3271
  } from "viem";
@@ -3005,12 +3282,12 @@ function printSellQuote(output, info) {
3005
3282
  coin: info.coinSymbol,
3006
3283
  address: info.address,
3007
3284
  sell: {
3008
- amount: formatUnits4(info.amountIn, info.coinDecimals),
3285
+ amount: formatUnits5(info.amountIn, info.coinDecimals),
3009
3286
  raw: info.amountIn.toString(),
3010
3287
  symbol: info.coinSymbol
3011
3288
  },
3012
3289
  estimated: {
3013
- amount: formatUnits4(BigInt(info.quoteAmountOut), info.outputDecimals),
3290
+ amount: formatUnits5(BigInt(info.quoteAmountOut), info.outputDecimals),
3014
3291
  raw: info.quoteAmountOut,
3015
3292
  symbol: info.outputSymbol
3016
3293
  },
@@ -3019,17 +3296,18 @@ function printSellQuote(output, info) {
3019
3296
  return;
3020
3297
  }
3021
3298
  console.log(`
3022
- Sell ${info.coinName} (${info.coinSymbol})
3299
+ Sell \x1B[1m${info.coinName}\x1B[0m`);
3300
+ console.log(` ${info.coinType} \xB7 ${info.address}
3023
3301
  `);
3024
3302
  console.log(` Amount ${info.soldFormatted} ${info.coinSymbol}`);
3025
3303
  console.log(
3026
- ` You get ~${info.receivedFormatted} ${info.outputSymbol}`
3304
+ ` You get ~${info.receivedFormatted} ${info.outputSymbol}${info.receivedUsd ? ` (${info.receivedUsd})` : ""}`
3027
3305
  );
3028
3306
  console.log(` Slippage ${info.slippagePct}%
3029
3307
  `);
3030
3308
  }
3031
3309
  function printSellResult(output, info) {
3032
- const receivedAmount = formatUnits4(
3310
+ const receivedAmount = formatUnits5(
3033
3311
  info.receivedAmountOut,
3034
3312
  info.outputDecimals
3035
3313
  );
@@ -3043,7 +3321,7 @@ function printSellResult(output, info) {
3043
3321
  coin: info.coinSymbol,
3044
3322
  address: info.address,
3045
3323
  sold: {
3046
- amount: formatUnits4(info.amountIn, info.coinDecimals),
3324
+ amount: formatUnits5(info.amountIn, info.coinDecimals),
3047
3325
  raw: info.amountIn.toString(),
3048
3326
  symbol: info.coinSymbol
3049
3327
  },
@@ -3058,11 +3336,12 @@ function printSellResult(output, info) {
3058
3336
  return;
3059
3337
  }
3060
3338
  console.log(`
3061
- Sold ${info.coinName}
3339
+ Sold \x1B[1m${info.coinName}\x1B[0m`);
3340
+ console.log(` ${info.coinType} \xB7 ${info.address}
3062
3341
  `);
3063
3342
  console.log(` Sold ${info.soldFormatted} ${info.coinSymbol}`);
3064
3343
  console.log(
3065
- ` Received ${info.receivedSource === "quote" ? "~" : ""}${receivedFormatted} ${info.outputSymbol}`
3344
+ ` Received ${info.receivedSource === "quote" ? "~" : ""}${receivedFormatted} ${info.outputSymbol}${info.receivedUsd ? ` (${info.receivedUsd})` : ""}`
3066
3345
  );
3067
3346
  if (info.receivedSource === "quote") {
3068
3347
  console.log(" Note based on quote");
@@ -3070,11 +3349,74 @@ function printSellResult(output, info) {
3070
3349
  console.log(` Tx ${info.txHash}
3071
3350
  `);
3072
3351
  }
3073
- 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) {
3352
+ var sellCommand = new Command7("sell").description("Sell a coin").argument(
3353
+ "[typeOrId]",
3354
+ "Type prefix (creator-coin, trend) or coin address/name"
3355
+ ).argument("[identifier]", "Coin name (when type prefix is given)").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(typeOrId, identifier, opts) {
3074
3356
  const json = getJson(this);
3075
3357
  const debug = opts.debug === true;
3076
- if (!isAddress2(coinAddress)) {
3077
- outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
3358
+ let parsed;
3359
+ try {
3360
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
3361
+ } catch (err) {
3362
+ if (err instanceof CoinArgError) {
3363
+ outputErrorAndExit(json, err.message, err.suggestion);
3364
+ }
3365
+ throw err;
3366
+ }
3367
+ const apiKey = getApiKey();
3368
+ if (apiKey) {
3369
+ setApiKey6(apiKey);
3370
+ }
3371
+ let coinAddress;
3372
+ if (parsed.kind === "address") {
3373
+ if (!isAddress2(parsed.address)) {
3374
+ outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
3375
+ return;
3376
+ }
3377
+ coinAddress = parsed.address;
3378
+ } else if (parsed.kind === "ambiguous-name") {
3379
+ let ambResult;
3380
+ try {
3381
+ ambResult = await resolveAmbiguousName(parsed.name);
3382
+ } catch (err) {
3383
+ outputErrorAndExit(
3384
+ json,
3385
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3386
+ );
3387
+ return;
3388
+ }
3389
+ if (ambResult.kind === "not-found") {
3390
+ outputErrorAndExit(json, ambResult.message);
3391
+ return;
3392
+ }
3393
+ if (ambResult.kind === "ambiguous") {
3394
+ const { message, suggestion } = formatAmbiguousError(
3395
+ parsed.name,
3396
+ ambResult.creator,
3397
+ ambResult.trend,
3398
+ "sell"
3399
+ );
3400
+ outputErrorAndExit(json, message, suggestion);
3401
+ return;
3402
+ }
3403
+ coinAddress = ambResult.coin.address;
3404
+ } else {
3405
+ const ref = coinArgsToRef(parsed);
3406
+ try {
3407
+ const result = await resolveCoin(ref);
3408
+ if (result.kind === "not-found") {
3409
+ outputErrorAndExit(json, result.message, result.suggestion);
3410
+ return;
3411
+ }
3412
+ coinAddress = result.coin.address;
3413
+ } catch (err) {
3414
+ outputErrorAndExit(
3415
+ json,
3416
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3417
+ );
3418
+ return;
3419
+ }
3078
3420
  }
3079
3421
  const output = json ? "json" : "static";
3080
3422
  const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
@@ -3099,10 +3441,6 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3099
3441
  );
3100
3442
  }
3101
3443
  const slippage = slippagePct / 100;
3102
- const apiKey = getApiKey();
3103
- if (apiKey) {
3104
- setApiKey6(apiKey);
3105
- }
3106
3444
  const account = resolveAccount(json);
3107
3445
  const { publicClient, walletClient } = createClients(account);
3108
3446
  let token;
@@ -3117,6 +3455,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3117
3455
  }
3118
3456
  const coinName = token.name;
3119
3457
  const coinSymbol = token.symbol;
3458
+ const coinType = mapCoinType(token.coinType);
3120
3459
  const coinDecimals = Number(token.decimals ?? 18);
3121
3460
  let amountIn;
3122
3461
  if (amountMode === "usd") {
@@ -3128,22 +3467,22 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3128
3467
  );
3129
3468
  return;
3130
3469
  }
3131
- const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
3132
- if (coinPriceUsd === null || coinPriceUsd <= 0) {
3470
+ const coinPriceUsd2 = await fetchTokenPriceUsd(coinAddress);
3471
+ if (coinPriceUsd2 === null || coinPriceUsd2 <= 0) {
3133
3472
  outputErrorAndExit(
3134
3473
  json,
3135
3474
  `Failed to fetch ${coinSymbol} price for USD conversion.`
3136
3475
  );
3137
3476
  return;
3138
3477
  }
3139
- const coinAmount = usdVal / coinPriceUsd;
3478
+ const coinAmount = usdVal / coinPriceUsd2;
3140
3479
  amountIn = parseUnits2(coinAmount.toFixed(coinDecimals), coinDecimals);
3141
3480
  if (amountIn === 0n) {
3142
3481
  outputErrorAndExit(json, "Calculated amount is zero. USD too small.");
3143
3482
  }
3144
3483
  if (debug) {
3145
3484
  console.error(
3146
- `[debug] $${usdVal} USD = ${formatUnits4(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd})`
3485
+ `[debug] $${usdVal} USD = ${formatUnits5(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd2})`
3147
3486
  );
3148
3487
  }
3149
3488
  } else if (amountMode === "amount") {
@@ -3191,18 +3530,19 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3191
3530
  }
3192
3531
  }
3193
3532
  }
3533
+ const needsCoinPrice = amountMode !== "usd";
3534
+ const needsOutputPrice = outputToken.fixedPriceUsd == null;
3535
+ const [coinPriceUsd, outputPriceUsd] = await Promise.all([
3536
+ needsCoinPrice ? fetchTokenPriceUsd(coinAddress) : Promise.resolve(null),
3537
+ needsOutputPrice ? fetchTokenPriceUsd(outputToken.priceAddress) : Promise.resolve(null)
3538
+ ]);
3194
3539
  let swapAmountUsd;
3195
3540
  if (amountMode === "usd") {
3196
3541
  swapAmountUsd = parsePercentageLikeValue(opts.usd);
3197
- } else {
3198
- const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
3199
- if (coinPriceUsd !== null && coinPriceUsd > 0) {
3200
- swapAmountUsd = Number(
3201
- (Number(formatUnits4(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
3202
- 2
3203
- )
3204
- );
3205
- }
3542
+ } else if (coinPriceUsd !== null && coinPriceUsd > 0) {
3543
+ swapAmountUsd = Number(
3544
+ (Number(formatUnits5(amountIn, coinDecimals)) * coinPriceUsd).toFixed(2)
3545
+ );
3206
3546
  }
3207
3547
  const tradeParameters = {
3208
3548
  sell: { type: "erc20", address: coinAddress },
@@ -3260,10 +3600,18 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3260
3600
  BigInt(quoteAmountOut),
3261
3601
  outputToken.decimals
3262
3602
  );
3603
+ let receivedUsd;
3604
+ if (outputPriceUsd != null) {
3605
+ const outAmount = Number(
3606
+ formatUnits5(BigInt(quoteAmountOut), outputToken.decimals)
3607
+ );
3608
+ receivedUsd = `~${formatUsd(outAmount * outputPriceUsd)}`;
3609
+ }
3263
3610
  if (opts.quote) {
3264
3611
  printSellQuote(output, {
3265
3612
  coinName,
3266
3613
  coinSymbol,
3614
+ coinType,
3267
3615
  address: coinAddress,
3268
3616
  soldFormatted,
3269
3617
  amountIn,
@@ -3272,7 +3620,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3272
3620
  quoteAmountOut,
3273
3621
  outputSymbol: outputToken.symbol,
3274
3622
  outputDecimals: outputToken.decimals,
3275
- slippagePct
3623
+ slippagePct,
3624
+ receivedUsd
3276
3625
  });
3277
3626
  track("cli_sell", {
3278
3627
  action: "quote",
@@ -3293,6 +3642,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3293
3642
  printSellQuote("static", {
3294
3643
  coinName,
3295
3644
  coinSymbol,
3645
+ coinType,
3296
3646
  address: coinAddress,
3297
3647
  soldFormatted,
3298
3648
  amountIn,
@@ -3301,7 +3651,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3301
3651
  quoteAmountOut,
3302
3652
  outputSymbol: outputToken.symbol,
3303
3653
  outputDecimals: outputToken.decimals,
3304
- slippagePct
3654
+ slippagePct,
3655
+ receivedUsd
3305
3656
  });
3306
3657
  const ok = await confirm3({
3307
3658
  message: "Confirm?",
@@ -3358,9 +3709,16 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3358
3709
  } catch {
3359
3710
  }
3360
3711
  }
3712
+ if (outputPriceUsd != null) {
3713
+ const actualAmount = Number(
3714
+ formatUnits5(receivedAmountOut, outputToken.decimals)
3715
+ );
3716
+ receivedUsd = `~${formatUsd(actualAmount * outputPriceUsd)}`;
3717
+ }
3361
3718
  printSellResult(output, {
3362
3719
  coinName,
3363
3720
  coinSymbol,
3721
+ coinType,
3364
3722
  address: coinAddress,
3365
3723
  amountIn,
3366
3724
  coinDecimals,
@@ -3369,7 +3727,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3369
3727
  outputSymbol: outputToken.symbol,
3370
3728
  outputDecimals: outputToken.decimals,
3371
3729
  receivedSource,
3372
- txHash
3730
+ txHash,
3731
+ receivedUsd
3373
3732
  });
3374
3733
  track("cli_sell", {
3375
3734
  action: "trade",
@@ -3517,8 +3876,10 @@ var ProfileView = ({
3517
3876
  ] }) });
3518
3877
  }
3519
3878
  if (!data) return null;
3520
- const hints = ["\u2190 \u2192 switch tab", "r refresh"];
3521
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
3879
+ const hints = [
3880
+ "\u2190 \u2192 switch tab",
3881
+ autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh"
3882
+ ];
3522
3883
  hints.push("q quit");
3523
3884
  const footer = hints.join(" \xB7 ");
3524
3885
  const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
@@ -3581,15 +3942,11 @@ var extractErrorMessage2 = (error) => {
3581
3942
  }
3582
3943
  return JSON.stringify(error);
3583
3944
  };
3584
- var resolveApiKey = (json) => {
3945
+ var resolveApiKey = () => {
3585
3946
  const apiKey = getApiKey();
3586
- if (!apiKey) {
3587
- outputErrorAndExit(
3588
- json,
3589
- "Not authenticated. Run 'zora auth configure' to set your API key."
3590
- );
3947
+ if (apiKey) {
3948
+ setApiKey7(apiKey);
3591
3949
  }
3592
- setApiKey7(apiKey);
3593
3950
  };
3594
3951
  var formatPostJson = (post, rank) => ({
3595
3952
  rank,
@@ -3665,7 +4022,7 @@ var profileCommand = new Command8("profile").description("View profile activity
3665
4022
  ).action(async function(identifierArg) {
3666
4023
  const output = getOutputMode(this, "live");
3667
4024
  const json = output === "json";
3668
- resolveApiKey(json);
4025
+ resolveApiKey();
3669
4026
  const { live, intervalSeconds } = getLiveConfig(this, output);
3670
4027
  let identifier = identifierArg;
3671
4028
  if (!identifier) {
@@ -3789,7 +4146,7 @@ import { Command as Command9 } from "commander";
3789
4146
  import confirm4 from "@inquirer/confirm";
3790
4147
  import {
3791
4148
  erc20Abi as erc20Abi4,
3792
- formatUnits as formatUnits5,
4149
+ formatUnits as formatUnits6,
3793
4150
  isAddress as isAddress3,
3794
4151
  parseUnits as parseUnits3
3795
4152
  } from "viem";
@@ -3799,7 +4156,7 @@ var SEND_AMOUNT_CHECKS = {
3799
4156
  percent: (opts) => opts.percent !== void 0,
3800
4157
  all: (opts) => opts.all === true
3801
4158
  };
3802
- var VALID_TYPES3 = ["creator-coin", "post", "trend"];
4159
+ var KNOWN_TOKEN_NAMES = /* @__PURE__ */ new Set(["eth", "usdc", "zora"]);
3803
4160
  function printSendPreview(info) {
3804
4161
  const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
3805
4162
  console.log(`
@@ -3820,7 +4177,7 @@ function printSendResult(json, info) {
3820
4177
  coin: info.symbol,
3821
4178
  address: info.address,
3822
4179
  sent: {
3823
- amount: formatUnits5(info.amount, info.decimals),
4180
+ amount: formatUnits6(info.amount, info.decimals),
3824
4181
  raw: info.amount.toString(),
3825
4182
  symbol: info.symbol,
3826
4183
  amountUsd: info.amountUsd
@@ -3841,7 +4198,10 @@ function printSendResult(json, info) {
3841
4198
  console.log(` Tx ${info.txHash}
3842
4199
  `);
3843
4200
  }
3844
- var sendCommand = new Command9("send").description("Send coins or ETH to an address").argument("[identifier]", "Coin address, name, or token (eth, usdc, zora)").option("--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) {
4201
+ var sendCommand = new Command9("send").description("Send coins or ETH to an address").argument(
4202
+ "[typeOrId]",
4203
+ "Token (eth, usdc, zora), type prefix (creator-coin, trend), or coin address/name"
4204
+ ).argument("[identifier]", "Coin name (when type prefix is given)").option("--to <address>", "Recipient address (0x...)").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(firstArg, secondArg, opts) {
3845
4205
  const json = getJson(this);
3846
4206
  if (!opts.to) {
3847
4207
  outputErrorAndExit(
@@ -3858,24 +4218,15 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3858
4218
  );
3859
4219
  }
3860
4220
  const recipient = opts.to;
3861
- if (opts.type !== void 0 && !VALID_TYPES3.includes(opts.type)) {
3862
- outputErrorAndExit(
3863
- json,
3864
- `Invalid --type value: ${opts.type}.`,
3865
- `Supported: ${VALID_TYPES3.join(", ")}`
3866
- );
3867
- }
3868
4221
  const amountMode = getAmountMode(
3869
4222
  json,
3870
4223
  opts,
3871
4224
  SEND_AMOUNT_CHECKS,
3872
4225
  "--amount, --percent, or --all"
3873
4226
  );
3874
- const isEth = identifier.toLowerCase() === "eth";
4227
+ const isKnownToken = KNOWN_TOKEN_NAMES.has(firstArg.toLowerCase());
4228
+ const isEth = firstArg.toLowerCase() === "eth";
3875
4229
  if (isEth) {
3876
- if (opts.type) {
3877
- outputErrorAndExit(json, "--type is not valid when sending ETH.");
3878
- }
3879
4230
  const account = resolveAccount(json);
3880
4231
  const { publicClient, walletClient } = createClients(account);
3881
4232
  const balance = await publicClient.getBalance({
@@ -3913,14 +4264,14 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3913
4264
  if (amount + GAS_RESERVE > balance) {
3914
4265
  outputErrorAndExit(
3915
4266
  json,
3916
- `Insufficient balance. Have ${formatEthDisplay(balance)} ETH (need to reserve ~${formatEthDisplay(GAS_RESERVE)} ETH for gas).`
4267
+ `Insufficient balance. Have ${formatAmountDisplay(balance, 18)} ETH (need to reserve ~${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas).`
3917
4268
  );
3918
4269
  }
3919
4270
  } else {
3920
4271
  if (balance <= GAS_RESERVE) {
3921
4272
  outputErrorAndExit(
3922
4273
  json,
3923
- `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
4274
+ `Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
3924
4275
  );
3925
4276
  }
3926
4277
  const spendable = balance - GAS_RESERVE;
@@ -3943,12 +4294,12 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3943
4294
  }
3944
4295
  }
3945
4296
  }
3946
- const amountFormatted = formatEthDisplay(amount);
4297
+ const amountFormatted = formatAmountDisplay(amount, 18);
3947
4298
  let amountUsd = null;
3948
4299
  const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
3949
4300
  if (ethPriceUsd != null) {
3950
4301
  amountUsd = Number(
3951
- (Number(formatUnits5(amount, 18)) * ethPriceUsd).toFixed(2)
4302
+ (Number(formatUnits6(amount, 18)) * ethPriceUsd).toFixed(2)
3952
4303
  );
3953
4304
  }
3954
4305
  if (!opts.yes) {
@@ -4008,14 +4359,8 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4008
4359
  tx_hash: txHash
4009
4360
  });
4010
4361
  } else {
4011
- const knownTokenKey = identifier.toLowerCase();
4012
- const knownToken = knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
4013
- if (knownToken && opts.type) {
4014
- outputErrorAndExit(
4015
- json,
4016
- `--type is not valid when sending ${knownToken.symbol}.`
4017
- );
4018
- }
4362
+ const knownTokenKey = firstArg.toLowerCase();
4363
+ const knownToken = isKnownToken && knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
4019
4364
  let tokenAddress;
4020
4365
  let tokenName;
4021
4366
  if (knownToken) {
@@ -4027,21 +4372,60 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4027
4372
  if (apiKey) {
4028
4373
  setApiKey8(apiKey);
4029
4374
  }
4030
- const ref = parseCoinRef(identifier, opts.type);
4031
- let result;
4375
+ let parsed;
4032
4376
  try {
4033
- result = await resolveCoin(ref);
4377
+ parsed = parsePositionalCoinArgs(firstArg, secondArg);
4034
4378
  } catch (err) {
4035
- outputErrorAndExit(
4036
- json,
4037
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
4038
- );
4379
+ if (err instanceof CoinArgError) {
4380
+ outputErrorAndExit(json, err.message, err.suggestion);
4381
+ }
4382
+ throw err;
4039
4383
  }
4040
- if (result.kind === "not-found") {
4041
- outputErrorAndExit(json, result.message, result.suggestion);
4384
+ if (parsed.kind === "ambiguous-name") {
4385
+ let ambResult;
4386
+ try {
4387
+ ambResult = await resolveAmbiguousName(parsed.name);
4388
+ } catch (err) {
4389
+ outputErrorAndExit(
4390
+ json,
4391
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
4392
+ );
4393
+ return;
4394
+ }
4395
+ if (ambResult.kind === "not-found") {
4396
+ outputErrorAndExit(json, ambResult.message);
4397
+ return;
4398
+ }
4399
+ if (ambResult.kind === "ambiguous") {
4400
+ const { message, suggestion } = formatAmbiguousError(
4401
+ parsed.name,
4402
+ ambResult.creator,
4403
+ ambResult.trend,
4404
+ "send"
4405
+ );
4406
+ outputErrorAndExit(json, message, suggestion);
4407
+ return;
4408
+ }
4409
+ tokenAddress = ambResult.coin.address;
4410
+ tokenName = ambResult.coin.name;
4411
+ } else {
4412
+ const ref = coinArgsToRef(parsed);
4413
+ try {
4414
+ const result = await resolveCoin(ref);
4415
+ if (result.kind === "not-found") {
4416
+ outputErrorAndExit(json, result.message, result.suggestion);
4417
+ return;
4418
+ }
4419
+ tokenAddress = result.coin.address;
4420
+ tokenName = result.coin.name;
4421
+ } catch (err) {
4422
+ outputErrorAndExit(
4423
+ json,
4424
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
4425
+ );
4426
+ return;
4427
+ }
4042
4428
  }
4043
- tokenAddress = result.coin.address;
4044
- tokenName = result.coin.name;
4045
4429
  }
4046
4430
  const account = resolveAccount(json);
4047
4431
  const { publicClient, walletClient } = createClients(account);
@@ -4139,7 +4523,7 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4139
4523
  const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
4140
4524
  if (priceUsd != null) {
4141
4525
  amountUsd = Number(
4142
- (Number(formatUnits5(amount, decimals)) * priceUsd).toFixed(2)
4526
+ (Number(formatUnits6(amount, decimals)) * priceUsd).toFixed(2)
4143
4527
  );
4144
4528
  }
4145
4529
  if (!opts.yes) {
@@ -4207,32 +4591,33 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4207
4591
  }
4208
4592
  });
4209
4593
 
4210
- // src/commands/setup.ts
4594
+ // src/commands/setup.tsx
4211
4595
  import { Command as Command10 } from "commander";
4212
- import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
4596
+ import { Text as Text9, Box as Box9 } from "ink";
4213
4597
 
4214
4598
  // src/lib/strings.ts
4215
- var DEPOSIT_INSTRUCTIONS = "Deposit ETH or USDC to this address on Base to start trading.\n\n You can do this from:\n - Coinbase \u2014 withdraw directly to Base\n - Another wallet (MetaMask, Rainbow, etc.) \u2014 send on Base network\n - Bridge from other chains \u2014 use https://superbridge.app/base";
4599
+ var DEPOSIT_SOURCES = "Deposit from:\n- Coinbase \u2014 withdraw directly to Base\n- Another wallet (MetaMask, Rainbow, etc.) \u2014 send on Base network\n- Bridge from other chains \u2014 use https://superbridge.app/base";
4216
4600
  var NO_WALLET_CONFIGURED = "No wallet configured.";
4217
4601
  var NO_WALLET_SUGGESTION = "Run 'zora setup' to create or import one.";
4218
4602
  var SAVE_ERROR_HINT = "Check that the directory exists and is writable.";
4219
- var BACKUP_WARNING = "Back up this file \u2014 it's the only copy of your key.";
4603
+ var BACKUP_WARNING = "Back up this file \u2014 it's the only copy of your key. Zora is not responsible for any loss of funds.";
4220
4604
 
4221
- // src/commands/setup.ts
4605
+ // src/lib/wallet-setup.ts
4606
+ import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
4222
4607
  var isValidPrivateKey = (key) => /^(0x)?[0-9a-fA-F]{64}$/.test(key);
4223
4608
  var toAccount = (json, key, errorPrefix) => {
4224
4609
  try {
4225
4610
  return privateKeyToAccount4(normalizeKey(key));
4226
4611
  } catch {
4227
- outputErrorAndExit(
4612
+ return outputErrorAndExit(
4228
4613
  json,
4229
4614
  `\u2717 ${errorPrefix} isn't a valid private key.`
4230
4615
  );
4231
4616
  }
4232
4617
  };
4233
- var setupCommand = new Command10("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) {
4234
- const json = getJson(this);
4235
- const nonInteractive = getYes(this);
4618
+ var walletExistsWarning = (truncated) => `Wallet already configured: ${truncated}. Make sure your wallet is backed up \u2014 Zora is not responsible for any loss of funds.`;
4619
+ async function configureWallet(opts) {
4620
+ const { json, nonInteractive, promptOverwrite } = opts;
4236
4621
  const envKey = process.env.ZORA_PRIVATE_KEY;
4237
4622
  if (envKey !== void 0) {
4238
4623
  if (!isValidPrivateKey(envKey)) {
@@ -4242,25 +4627,11 @@ var setupCommand = new Command10("setup").description("Set up your Zora wallet")
4242
4627
  "Fix it and run zora setup again."
4243
4628
  );
4244
4629
  }
4245
- const account = toAccount(json, envKey, "ZORA_PRIVATE_KEY");
4246
- outputData(json, {
4247
- json: { source: "env", address: account.address },
4248
- render: () => {
4249
- console.log(" Using wallet from ZORA_PRIVATE_KEY.\n");
4250
- console.log(` Address: ${account.address}
4251
- `);
4252
- console.log(` ${DEPOSIT_INSTRUCTIONS}`);
4253
- }
4254
- });
4255
- track("cli_setup", {
4256
- action: "env_detected",
4257
- source: "env",
4258
- output_format: json ? "json" : "text"
4259
- });
4260
- return;
4630
+ const account2 = toAccount(json, envKey, "ZORA_PRIVATE_KEY");
4631
+ return { action: "env_detected", address: account2.address };
4261
4632
  }
4262
4633
  let existing;
4263
- if (!options.force) {
4634
+ if (!opts.force) {
4264
4635
  try {
4265
4636
  existing = getPrivateKey();
4266
4637
  } catch (err) {
@@ -4272,20 +4643,33 @@ var setupCommand = new Command10("setup").description("Set up your Zora wallet")
4272
4643
  }
4273
4644
  }
4274
4645
  if (existing) {
4275
- const account = toAccount(json, existing, "Stored private key");
4276
- const truncated = truncateAddress(account.address);
4277
- console.log(` Wallet already configured: ${truncated}
4278
- `);
4279
- if (!options.force) {
4280
- outputErrorAndExit(
4281
- json,
4282
- "Wallet already exists.",
4283
- "Use --force to overwrite."
4646
+ const account2 = toAccount(json, existing, "Stored private key");
4647
+ const truncated = `${account2.address.slice(0, 6)}\u2026${account2.address.slice(-4)}`;
4648
+ const warning = walletExistsWarning(truncated);
4649
+ if (promptOverwrite) {
4650
+ if (nonInteractive) {
4651
+ return { action: "skipped", address: account2.address, warning };
4652
+ }
4653
+ const overwrite = await confirmOrDefault(
4654
+ { message: "Overwrite wallet configuration?", default: false },
4655
+ false
4284
4656
  );
4657
+ if (!overwrite) {
4658
+ return { action: "skipped", address: account2.address, warning };
4659
+ }
4660
+ } else {
4661
+ if (!opts.force) {
4662
+ outputErrorAndExit(
4663
+ json,
4664
+ `${warning}
4665
+ Wallet already exists.`,
4666
+ "Use --force to overwrite."
4667
+ );
4668
+ }
4285
4669
  }
4286
4670
  }
4287
4671
  let choice;
4288
- if (options.create) {
4672
+ if (opts.create) {
4289
4673
  choice = "create";
4290
4674
  } else {
4291
4675
  choice = await selectOrDefault(
@@ -4319,7 +4703,7 @@ var setupCommand = new Command10("setup").description("Set up your Zora wallet")
4319
4703
  );
4320
4704
  }
4321
4705
  }
4322
- const account = toAccount(json, importedKey, "Imported key");
4706
+ const account2 = toAccount(json, importedKey, "Imported key");
4323
4707
  try {
4324
4708
  savePrivateKey(importedKey);
4325
4709
  } catch {
@@ -4329,64 +4713,201 @@ var setupCommand = new Command10("setup").description("Set up your Zora wallet")
4329
4713
  SAVE_ERROR_HINT
4330
4714
  );
4331
4715
  }
4332
- outputData(json, {
4333
- json: {
4334
- action: "imported",
4335
- address: account.address,
4336
- path: getWalletPath()
4337
- },
4338
- render: () => {
4339
- console.log("\n\u2713 Wallet imported\n");
4340
- console.log(` Address: ${account.address}`);
4341
- console.log(` Private key: saved to ${getWalletPath()}
4716
+ return {
4717
+ action: "imported",
4718
+ address: account2.address,
4719
+ path: getWalletPath()
4720
+ };
4721
+ }
4722
+ const privateKey = generatePrivateKey();
4723
+ const account = toAccount(json, privateKey, "Generated key");
4724
+ try {
4725
+ savePrivateKey(privateKey);
4726
+ } catch {
4727
+ outputErrorAndExit(
4728
+ json,
4729
+ `\u2717 Couldn't save to ${getWalletPath()}.`,
4730
+ SAVE_ERROR_HINT
4731
+ );
4732
+ }
4733
+ return {
4734
+ action: "created",
4735
+ address: account.address,
4736
+ path: getWalletPath()
4737
+ };
4738
+ }
4739
+
4740
+ // src/lib/ansi.ts
4741
+ var DIM = "\x1B[2m";
4742
+ var BOLD = "\x1B[1m";
4743
+ var YELLOW = "\x1B[33m";
4744
+ var RESET = "\x1B[0m";
4745
+ var useAnsi = () => process.stdout.isTTY && !process.env.NO_COLOR;
4746
+
4747
+ // src/lib/warning-box.ts
4748
+ function warningBox(text) {
4749
+ if (!useAnsi()) {
4750
+ console.log(`\u26A0 ${text}
4342
4751
  `);
4343
- console.log(` ${BACKUP_WARNING}
4752
+ return;
4753
+ }
4754
+ console.log(`${YELLOW}\u26A0 ${text}${RESET}
4755
+ `);
4756
+ }
4757
+
4758
+ // src/commands/setup.tsx
4759
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
4760
+ function stepLine(step, total, title) {
4761
+ const cols = (process.stdout.columns || 80) - 4;
4762
+ if (!useAnsi()) {
4763
+ console.log(`
4764
+ [${step}/${total}] ${title}`);
4765
+ console.log(`${"\u2500".repeat(Math.max(cols, 20))}
4344
4766
  `);
4345
- console.log(` ${DEPOSIT_INSTRUCTIONS}`);
4346
- }
4347
- });
4348
- track("cli_setup", {
4349
- action: "imported",
4350
- source: "file",
4351
- output_format: json ? "json" : "text"
4352
- });
4353
4767
  return;
4354
4768
  }
4355
- if (choice === "create") {
4356
- const privateKey = generatePrivateKey();
4357
- const account = toAccount(json, privateKey, "Generated key");
4358
- try {
4359
- savePrivateKey(privateKey);
4360
- } catch {
4361
- outputErrorAndExit(
4362
- json,
4363
- `\u2717 Couldn't save to ${getWalletPath()}.`,
4364
- SAVE_ERROR_HINT
4365
- );
4366
- }
4367
- outputData(json, {
4368
- json: {
4369
- action: "created",
4370
- address: account.address,
4371
- path: getWalletPath()
4372
- },
4373
- render: () => {
4374
- console.log("\n\u2713 Wallet created\n");
4375
- console.log(` Address: ${account.address}`);
4376
- console.log(` Private key: saved to ${getWalletPath()}
4769
+ console.log(
4770
+ `
4771
+ ${BOLD}${DIM}[${step}/${total}]${RESET} ${BOLD}${title}${RESET}`
4772
+ );
4773
+ console.log(`${DIM}${"\u2500".repeat(Math.max(cols, 20))}${RESET}
4377
4774
  `);
4378
- console.log(` ${BACKUP_WARNING}
4775
+ }
4776
+ var setupCommand = new Command10("setup").description("Guided first-time setup").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) {
4777
+ const json = getJson(this);
4778
+ const nonInteractive = getYes(this);
4779
+ if (!json) stepLine(1, 3, "Set up wallet");
4780
+ const walletResult = await configureWallet({
4781
+ json,
4782
+ nonInteractive,
4783
+ create: options.create,
4784
+ force: options.force,
4785
+ promptOverwrite: true
4786
+ });
4787
+ if (!json) {
4788
+ if (walletResult.action === "env_detected") {
4789
+ console.log("Using wallet from ZORA_PRIVATE_KEY.");
4790
+ console.log(`Address: ${walletResult.address}
4379
4791
  `);
4380
- console.log(` ${DEPOSIT_INSTRUCTIONS}`);
4792
+ } else if (walletResult.action === "skipped") {
4793
+ warningBox(walletResult.warning);
4794
+ console.log(`${DIM}Keeping existing wallet.${RESET}
4795
+ `);
4796
+ } else {
4797
+ const verb = walletResult.action === "created" ? "created" : "imported";
4798
+ console.log(`\u2713 Wallet ${verb}`);
4799
+ console.log(`Address: ${walletResult.address}`);
4800
+ console.log(`Private key: saved to ${walletResult.path}
4801
+ `);
4802
+ warningBox(BACKUP_WARNING);
4803
+ }
4804
+ }
4805
+ if (!json) stepLine(2, 3, "Set up API key (optional)");
4806
+ let apiKeyStatus;
4807
+ const envApiKey = getEnvApiKey();
4808
+ if (envApiKey) {
4809
+ apiKeyStatus = "env_override";
4810
+ if (!json) {
4811
+ console.log("API key is set via ZORA_API_KEY environment variable.\n");
4812
+ }
4813
+ } else {
4814
+ const existingKey = getApiKey();
4815
+ if (existingKey && !options.force) {
4816
+ if (nonInteractive) {
4817
+ apiKeyStatus = "already_set";
4818
+ if (!json) {
4819
+ console.log(
4820
+ `API key already configured: ${maskKey(existingKey)}
4821
+ `
4822
+ );
4823
+ }
4824
+ } else {
4825
+ if (!json) console.log(`Current key: ${maskKey(existingKey)}`);
4826
+ const overwrite = await confirmOrDefault(
4827
+ { message: "Overwrite API key?", default: false },
4828
+ false
4829
+ );
4830
+ if (!overwrite) {
4831
+ apiKeyStatus = "already_set";
4832
+ if (!json) console.log("");
4833
+ } else {
4834
+ apiKeyStatus = await promptAndSaveApiKey(json);
4835
+ }
4381
4836
  }
4382
- });
4383
- track("cli_setup", {
4384
- action: "created",
4385
- source: "file",
4386
- output_format: json ? "json" : "text"
4387
- });
4837
+ } else {
4838
+ apiKeyStatus = await promptAndSaveApiKey(json, nonInteractive);
4839
+ }
4840
+ }
4841
+ if (!json) stepLine(3, 3, "Deposit");
4842
+ if (!json) {
4843
+ renderOnce(
4844
+ /* @__PURE__ */ jsxs9(
4845
+ Box9,
4846
+ {
4847
+ flexDirection: "column",
4848
+ borderStyle: "single",
4849
+ borderDimColor: true,
4850
+ paddingX: 1,
4851
+ paddingY: 1,
4852
+ children: [
4853
+ /* @__PURE__ */ jsxs9(Text9, { children: [
4854
+ "Your address: ",
4855
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: walletResult.address })
4856
+ ] }),
4857
+ /* @__PURE__ */ jsxs9(Text9, { children: [
4858
+ "Deposit",
4859
+ " ",
4860
+ /* @__PURE__ */ jsx12(Text9, { bold: true, color: "blue", children: "ETH or USDC on Base" }),
4861
+ " ",
4862
+ "to start trading."
4863
+ ] })
4864
+ ]
4865
+ }
4866
+ )
4867
+ );
4868
+ console.log(`
4869
+ ${DEPOSIT_SOURCES}
4870
+ `);
4388
4871
  }
4872
+ outputData(json, {
4873
+ json: { wallet: walletResult, apiKey: apiKeyStatus },
4874
+ render: () => {
4875
+ }
4876
+ });
4877
+ track("cli_setup", {
4878
+ wallet_action: walletResult.action,
4879
+ api_key_status: apiKeyStatus,
4880
+ output_format: json ? "json" : "text"
4881
+ });
4389
4882
  });
4883
+ async function promptAndSaveApiKey(json, nonInteractive = false) {
4884
+ if (!json && !nonInteractive) {
4885
+ console.log(
4886
+ "Optional. An API key unlocks higher rate limits for frequent trading."
4887
+ );
4888
+ console.log("Get your API key from: https://zora.co/settings/developer\n");
4889
+ }
4890
+ const apiKey = await passwordOrSkip(
4891
+ { message: "Paste your API key (Enter to skip):" },
4892
+ nonInteractive
4893
+ );
4894
+ const trimmed = apiKey.trim();
4895
+ if (!trimmed) {
4896
+ if (!json) console.log("Skipped API key configuration.\n");
4897
+ return "skipped";
4898
+ }
4899
+ try {
4900
+ saveApiKey(trimmed);
4901
+ if (!json) console.log(`API key saved to ${getConfigPath()}
4902
+ `);
4903
+ return "saved";
4904
+ } catch (err) {
4905
+ outputErrorAndExit(
4906
+ json,
4907
+ `Failed to save API key: ${fsErrorMessage(err, getConfigPath())}`
4908
+ );
4909
+ }
4910
+ }
4390
4911
 
4391
4912
  // src/commands/wallet.ts
4392
4913
  import { Command as Command11 } from "commander";
@@ -4402,9 +4923,9 @@ var resolvePrivateKey = () => {
4402
4923
  }
4403
4924
  return void 0;
4404
4925
  };
4405
- var walletCommand = new Command11("wallet").description(
4406
- "Manage your Zora wallet"
4407
- );
4926
+ var walletCommand = new Command11("wallet").description("Manage your Zora wallet").action(function() {
4927
+ this.outputHelp();
4928
+ });
4408
4929
  walletCommand.command("info").description("Show wallet address and storage location").action(function() {
4409
4930
  const json = getJson(this);
4410
4931
  const resolved = resolvePrivateKey();
@@ -4460,9 +4981,156 @@ walletCommand.command("export").description("Print the raw private key to stdout
4460
4981
  output_format: json ? "json" : "text"
4461
4982
  });
4462
4983
  });
4984
+ walletCommand.command("configure").description("Create or import a 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) {
4985
+ const json = getJson(this);
4986
+ const nonInteractive = getYes(this);
4987
+ const result = await configureWallet({
4988
+ json,
4989
+ nonInteractive,
4990
+ create: options.create,
4991
+ force: options.force,
4992
+ promptOverwrite: false
4993
+ });
4994
+ outputData(json, {
4995
+ json: result,
4996
+ render: () => {
4997
+ if (result.action === "env_detected") {
4998
+ console.log(" Using wallet from ZORA_PRIVATE_KEY.\n");
4999
+ console.log(` Address: ${result.address}
5000
+ `);
5001
+ console.log(
5002
+ ` Deposit ETH or USDC on Base to start trading.
5003
+
5004
+ ${DEPOSIT_SOURCES}`
5005
+ );
5006
+ } else if (result.action === "created" || result.action === "imported") {
5007
+ const verb = result.action === "created" ? "created" : "imported";
5008
+ console.log(`
5009
+ \u2713 Wallet ${verb}
5010
+ `);
5011
+ console.log(` Address: ${result.address}`);
5012
+ console.log(` Private key: saved to ${result.path}
5013
+ `);
5014
+ console.log(` ${BACKUP_WARNING}
5015
+ `);
5016
+ console.log(
5017
+ ` Deposit ETH or USDC on Base to start trading.
5018
+
5019
+ ${DEPOSIT_SOURCES}`
5020
+ );
5021
+ } else if (result.action === "skipped") {
5022
+ console.log(` Wallet already configured: ${result.address}
5023
+ `);
5024
+ }
5025
+ }
5026
+ });
5027
+ track("cli_wallet_config", {
5028
+ action: result.action,
5029
+ output_format: json ? "json" : "text"
5030
+ });
5031
+ });
5032
+
5033
+ // src/components/StyledHelp.tsx
5034
+ import { Text as Text11, Box as Box11 } from "ink";
5035
+
5036
+ // src/components/KeyValueTable.tsx
5037
+ import { Text as Text10, Box as Box10 } from "ink";
5038
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
5039
+ function KeyValueTable({
5040
+ rows,
5041
+ labelWidth
5042
+ }) {
5043
+ const pad = labelWidth ?? Math.max(0, ...rows.map((r) => r.label.length)) + 2;
5044
+ return /* @__PURE__ */ jsx13(Box10, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs10(Text10, { children: [
5045
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: row.label.padEnd(pad) }),
5046
+ /* @__PURE__ */ jsx13(Text10, { dimColor: true, children: row.value })
5047
+ ] }, i)) });
5048
+ }
5049
+
5050
+ // src/lib/parse-help.ts
5051
+ var DEFAULT_DESC_COLUMN = 38;
5052
+ var TWO_COLUMN_REGEX = /^(.+\S)([ \t]{2,})(\S.*)/m;
5053
+ function getDescriptionColumnOffset(sections) {
5054
+ for (const section of sections) {
5055
+ const match = section.content.match(TWO_COLUMN_REGEX);
5056
+ if (match) {
5057
+ return match[1].length + match[2].length;
5058
+ }
5059
+ }
5060
+ return DEFAULT_DESC_COLUMN;
5061
+ }
5062
+ function parseHelpSections(text) {
5063
+ const sections = [];
5064
+ let currentTitle = "";
5065
+ let currentLines = [];
5066
+ for (const line of text.split("\n")) {
5067
+ const match = line.match(/^([A-Z]\w+):(.*)/);
5068
+ if (match) {
5069
+ if (currentTitle) {
5070
+ const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
5071
+ sections.push({ title: currentTitle, content });
5072
+ }
5073
+ currentTitle = match[1];
5074
+ currentLines = match[2].trim() ? [match[2].trim()] : [];
5075
+ } else if (currentTitle) {
5076
+ currentLines.push(line.startsWith(" ") ? line.slice(2) : line);
5077
+ }
5078
+ }
5079
+ if (currentTitle) {
5080
+ const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
5081
+ sections.push({ title: currentTitle, content });
5082
+ }
5083
+ return sections.filter((s) => s.content);
5084
+ }
5085
+
5086
+ // src/components/StyledHelp.tsx
5087
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
5088
+ function StyledHelp({
5089
+ sections,
5090
+ header
5091
+ }) {
5092
+ const descriptionColumnOffset = getDescriptionColumnOffset(sections);
5093
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
5094
+ header,
5095
+ /* @__PURE__ */ jsxs11(Box11, { paddingX: 1, children: [
5096
+ /* @__PURE__ */ jsx14(Text11, { color: "yellow", children: "\u26A0 Beta:" }),
5097
+ /* @__PURE__ */ jsx14(Text11, { children: " This CLI is in beta and should be used with caution." })
5098
+ ] }),
5099
+ sections.map((section, i) => {
5100
+ const hasTwoColumns = TWO_COLUMN_REGEX.test(section.content);
5101
+ const rows = hasTwoColumns ? section.content.split("\n").map((line) => {
5102
+ const m = line.match(TWO_COLUMN_REGEX);
5103
+ if (m)
5104
+ return {
5105
+ label: m[1],
5106
+ value: m[3][0].toUpperCase() + m[3].slice(1)
5107
+ };
5108
+ return { label: "", value: line.trimStart() };
5109
+ }) : null;
5110
+ return /* @__PURE__ */ jsxs11(
5111
+ Box11,
5112
+ {
5113
+ flexDirection: "column",
5114
+ borderStyle: "single",
5115
+ borderDimColor: true,
5116
+ paddingX: 1,
5117
+ paddingY: 1,
5118
+ children: [
5119
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: section.title }),
5120
+ rows ? /* @__PURE__ */ jsx14(KeyValueTable, { rows, labelWidth: descriptionColumnOffset }) : /* @__PURE__ */ jsx14(Text11, { children: section.content })
5121
+ ]
5122
+ },
5123
+ i
5124
+ );
5125
+ })
5126
+ ] });
5127
+ }
5128
+
5129
+ // src/components/StyledHelpHeader.tsx
5130
+ import { Text as Text13, Box as Box13 } from "ink";
4463
5131
 
4464
5132
  // src/components/Zorb.tsx
4465
- import { Text as Text9, Box as Box9 } from "ink";
5133
+ import { Text as Text12, Box as Box12 } from "ink";
4466
5134
 
4467
5135
  // src/lib/zorb-pixels.ts
4468
5136
  function supportsTruecolor() {
@@ -4607,7 +5275,7 @@ function generateZorbPixels(size) {
4607
5275
  }
4608
5276
 
4609
5277
  // src/components/Zorb.tsx
4610
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
5278
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
4611
5279
  var LOWER_HALF_BLOCK = "\u2584";
4612
5280
  var UPPER_HALF_BLOCK = "\u2580";
4613
5281
  function rgbString([r, g, b]) {
@@ -4630,19 +5298,19 @@ function Zorb({ size = 20 }) {
4630
5298
  const topIsBlack = isBlack(top);
4631
5299
  const bottomIsBlack = isBlack(bottom);
4632
5300
  if (topIsBlack && bottomIsBlack) {
4633
- cells.push(/* @__PURE__ */ jsx12(Text9, { children: " " }, x));
5301
+ cells.push(/* @__PURE__ */ jsx15(Text12, { children: " " }, x));
4634
5302
  } else if (topIsBlack) {
4635
5303
  cells.push(
4636
- /* @__PURE__ */ jsx12(Text9, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
5304
+ /* @__PURE__ */ jsx15(Text12, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
4637
5305
  );
4638
5306
  } else if (bottomIsBlack) {
4639
5307
  cells.push(
4640
- /* @__PURE__ */ jsx12(Text9, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
5308
+ /* @__PURE__ */ jsx15(Text12, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
4641
5309
  );
4642
5310
  } else {
4643
5311
  cells.push(
4644
- /* @__PURE__ */ jsx12(
4645
- Text9,
5312
+ /* @__PURE__ */ jsx15(
5313
+ Text12,
4646
5314
  {
4647
5315
  backgroundColor: rgbString(top),
4648
5316
  color: rgbString(bottom),
@@ -4653,25 +5321,92 @@ function Zorb({ size = 20 }) {
4653
5321
  );
4654
5322
  }
4655
5323
  }
4656
- rows.push(/* @__PURE__ */ jsx12(Text9, { children: cells }, y));
5324
+ rows.push(/* @__PURE__ */ jsx15(Text12, { children: cells }, y));
4657
5325
  }
4658
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4659
- /* @__PURE__ */ jsx12(Text9, { children: " " }),
5326
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
5327
+ /* @__PURE__ */ jsx15(Text12, { children: " " }),
4660
5328
  rows,
4661
- /* @__PURE__ */ jsx12(Text9, { children: " " })
5329
+ /* @__PURE__ */ jsx15(Text12, { children: " " })
4662
5330
  ] });
4663
5331
  }
4664
5332
 
5333
+ // src/components/StyledHelpHeader.tsx
5334
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
5335
+ function StyledHelpHeader({
5336
+ sections
5337
+ }) {
5338
+ const descriptionColumnOffset = getDescriptionColumnOffset(sections);
5339
+ return /* @__PURE__ */ jsxs13(
5340
+ Box13,
5341
+ {
5342
+ flexDirection: "row",
5343
+ borderStyle: "single",
5344
+ borderDimColor: true,
5345
+ paddingX: 1,
5346
+ paddingY: 1,
5347
+ children: [
5348
+ /* @__PURE__ */ jsx16(Box13, { flexShrink: 0, width: descriptionColumnOffset, children: /* @__PURE__ */ jsx16(Zorb, { size: 20 }) }),
5349
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: [
5350
+ /* @__PURE__ */ jsx16(Text13, { bold: true, children: /* @__PURE__ */ jsxs13(Text13, { backgroundColor: "#3fff00", color: "black", children: [
5351
+ " ",
5352
+ "Zora CLI",
5353
+ " "
5354
+ ] }) }),
5355
+ /* @__PURE__ */ jsxs13(Text13, { children: [
5356
+ /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "Trade what's trending. Run" }),
5357
+ " ",
5358
+ /* @__PURE__ */ jsxs13(Text13, { backgroundColor: "#3fff00", color: "black", children: [
5359
+ " ",
5360
+ "zora setup",
5361
+ " "
5362
+ ] }),
5363
+ " ",
5364
+ /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "to get started." })
5365
+ ] })
5366
+ ] })
5367
+ ]
5368
+ }
5369
+ );
5370
+ }
5371
+
4665
5372
  // src/index.tsx
4666
- import { jsx as jsx13 } from "react/jsx-runtime";
5373
+ import { jsx as jsx17 } from "react/jsx-runtime";
4667
5374
  if (process.env.ZORA_API_TARGET) {
4668
5375
  setApiBaseUrl(process.env.ZORA_API_TARGET);
4669
5376
  }
4670
- var version = true ? "0.3.0" : JSON.parse(
5377
+ var version = true ? "1.0.0" : JSON.parse(
4671
5378
  readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
4672
5379
  ).version;
5380
+ function styledHelpWriteOut(showHeader) {
5381
+ return (str) => {
5382
+ if (supportsTruecolor()) {
5383
+ const sections = parseHelpSections(str);
5384
+ if (sections.length > 0) {
5385
+ const header = showHeader ? /* @__PURE__ */ jsx17(StyledHelpHeader, { sections }) : void 0;
5386
+ renderOnce(/* @__PURE__ */ jsx17(StyledHelp, { sections, header }));
5387
+ return;
5388
+ }
5389
+ }
5390
+ process.stdout.write(str);
5391
+ process.stdout.write(
5392
+ "\n\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution.\n"
5393
+ );
5394
+ };
5395
+ }
4673
5396
  var buildProgram = () => {
4674
- const program2 = new Command12().name("zora").description("Zora CLI").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
5397
+ const program2 = new Command12().name("zora").description("Trade what's trending. Run `zora setup` to get started.").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
5398
+ const helpWidth = (process.stdout.columns || 80) - 4;
5399
+ program2.configureHelp({
5400
+ helpWidth,
5401
+ commandDescription: (cmd) => {
5402
+ if (!cmd.parent && supportsTruecolor()) return "";
5403
+ return cmd.description();
5404
+ }
5405
+ });
5406
+ program2.configureOutput({ writeOut: styledHelpWriteOut(true) });
5407
+ program2.action(() => {
5408
+ program2.outputHelp();
5409
+ });
4675
5410
  program2.addCommand(authCommand);
4676
5411
  program2.addCommand(balanceCommand);
4677
5412
  program2.addCommand(buyCommand);
@@ -4683,9 +5418,18 @@ var buildProgram = () => {
4683
5418
  program2.addCommand(walletCommand);
4684
5419
  program2.addCommand(sellCommand);
4685
5420
  program2.addCommand(sendCommand);
5421
+ const applyToSubcommands = (parent) => {
5422
+ for (const cmd of parent.commands) {
5423
+ cmd.configureHelp({ helpWidth });
5424
+ cmd.configureOutput({ writeOut: styledHelpWriteOut(false) });
5425
+ applyToSubcommands(cmd);
5426
+ }
5427
+ };
5428
+ applyToSubcommands(program2);
5429
+ const argOptionalCommands = /* @__PURE__ */ new Set(["profile"]);
4686
5430
  program2.hook("preAction", (_thisCommand, actionCommand) => {
4687
5431
  const expected = actionCommand.registeredArguments.length;
4688
- if (expected > 0 && actionCommand.args.length < expected) {
5432
+ if (expected > 0 && actionCommand.args.length === 0 && !argOptionalCommands.has(actionCommand.name())) {
4689
5433
  actionCommand.outputHelp();
4690
5434
  process.exit(1);
4691
5435
  }
@@ -4694,13 +5438,6 @@ var buildProgram = () => {
4694
5438
  };
4695
5439
  var program = buildProgram();
4696
5440
  if (!process.env.VITEST) {
4697
- const showingHelp = process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h");
4698
- if (showingHelp && !process.argv.includes("--json") && supportsTruecolor()) {
4699
- renderOnce(/* @__PURE__ */ jsx13(Zorb, { size: 20 }));
4700
- }
4701
- console.warn(
4702
- "\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
4703
- );
4704
5441
  identify();
4705
5442
  try {
4706
5443
  await program.parseAsync();