@zoralabs/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +849 -396
  2. 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;
@@ -431,7 +437,7 @@ var getClient = () => {
431
437
  return client;
432
438
  };
433
439
  var commonProperties = () => ({
434
- cli_version: true ? "0.3.0" : "development",
440
+ cli_version: true ? "0.3.1" : "development",
435
441
  os: process.platform,
436
442
  arch: process.arch,
437
443
  node_version: process.version
@@ -671,7 +677,7 @@ var Table = ({
671
677
 
672
678
  // src/lib/format.ts
673
679
  import { format, formatDistanceStrict } from "date-fns";
674
- import { formatEther } from "viem";
680
+ import { formatUnits } from "viem";
675
681
  function formatCompactUsd(value) {
676
682
  if (!value || Number(value) === 0) return "$0";
677
683
  return new Intl.NumberFormat("en-US", {
@@ -720,17 +726,28 @@ function formatCreatedAt(isoDate, now) {
720
726
  if (isNaN(date.getTime())) return "-";
721
727
  return `${formatRelativeTime(date, now)} (${formatAbsoluteTime(date)})`;
722
728
  }
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}`;
729
+ var formatAmountDisplay = (amount, decimals) => {
730
+ const formatted = formatUnits(amount, decimals);
731
+ const parts = formatted.split(".");
732
+ if (!parts[1]) {
733
+ return new Intl.NumberFormat("en-US", {
734
+ maximumFractionDigits: 2
735
+ }).format(Number(formatted));
736
+ }
737
+ const twoDecimal = `${parts[0]}.${parts[1].slice(0, 2)}`;
738
+ if (Number(twoDecimal) === 0 && amount > 0n) {
739
+ const sigIndex = parts[1].search(/[1-9]/);
740
+ const maxDecimals = sigIndex === -1 ? 6 : Math.min(sigIndex + 4, parts[1].length);
741
+ const truncated = `${parts[0]}.${parts[1].slice(0, maxDecimals)}`;
742
+ return new Intl.NumberFormat("en-US", {
743
+ maximumFractionDigits: maxDecimals
744
+ }).format(Number(truncated));
745
+ }
746
+ return new Intl.NumberFormat("en-US", {
747
+ maximumFractionDigits: 2
748
+ }).format(Number(formatted));
729
749
  };
730
750
  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
751
 
735
752
  // src/lib/balance-format.ts
736
753
  var COIN_DECIMALS = 18;
@@ -961,8 +978,7 @@ var BalanceView = ({
961
978
  const hints = [];
962
979
  if (paginated && cursorHistory.length > 0) hints.push("\u2190 prev");
963
980
  if (paginated && data?.pageInfo?.hasNextPage) hints.push("\u2192 next");
964
- hints.push("r refresh");
965
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
981
+ hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
966
982
  hints.push("q quit");
967
983
  const footer = hints.join(" \xB7 ");
968
984
  const showWallet = mode === "full" || mode === "wallet";
@@ -1017,7 +1033,7 @@ import { getTokenInfo } from "@zoralabs/coins-sdk";
1017
1033
  import {
1018
1034
  createPublicClient as createPublicClient2,
1019
1035
  erc20Abi,
1020
- formatUnits,
1036
+ formatUnits as formatUnits2,
1021
1037
  http
1022
1038
  } from "viem";
1023
1039
  import { base as base2 } from "viem/chains";
@@ -1102,7 +1118,7 @@ var fetchWalletBalances = async (walletAddress) => {
1102
1118
  });
1103
1119
  const visible = resolved.filter((r) => r.balance > 0n || r.token.isNative);
1104
1120
  const intermediate = visible.map(({ token, balance, priceUsd }) => {
1105
- const human = formatUnits(balance, token.decimals);
1121
+ const human = formatUnits2(balance, token.decimals);
1106
1122
  const usdValue = priceUsd !== null ? Number(human) * priceUsd : null;
1107
1123
  return { token, human, priceUsd, usdValue };
1108
1124
  });
@@ -1527,10 +1543,10 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
1527
1543
  // src/commands/buy.ts
1528
1544
  import { Command as Command3 } from "commander";
1529
1545
  import confirm2 from "@inquirer/confirm";
1530
- import { parseUnits, formatUnits as formatUnits3, isAddress } from "viem";
1546
+ import { parseUnits, formatUnits as formatUnits4, isAddress } from "viem";
1531
1547
  import {
1532
1548
  setApiKey as setApiKey2,
1533
- getCoin,
1549
+ getCoin as getCoin2,
1534
1550
  tradeCoin,
1535
1551
  createTradeCall
1536
1552
  } from "@zoralabs/coins-sdk";
@@ -1538,7 +1554,7 @@ import {
1538
1554
  // src/lib/trade-helpers.ts
1539
1555
  import {
1540
1556
  parseEther,
1541
- formatUnits as formatUnits2,
1557
+ formatUnits as formatUnits3,
1542
1558
  isAddressEqual,
1543
1559
  parseEventLogs,
1544
1560
  erc20Abi as erc20Abi2
@@ -1571,25 +1587,6 @@ var parsePercentageLikeValue = (value) => {
1571
1587
  const parsed = Number(value);
1572
1588
  return Number.isFinite(parsed) ? parsed : void 0;
1573
1589
  };
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
1590
  var getReceivedAmountFromReceipt = ({
1594
1591
  receipt,
1595
1592
  tokenAddress,
@@ -1655,12 +1652,12 @@ var printQuote = (json, info) => {
1655
1652
  coin: info.coinSymbol,
1656
1653
  address: info.address,
1657
1654
  spend: {
1658
- amount: formatUnits2(info.amountIn, info.inputTokenDecimals),
1655
+ amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
1659
1656
  raw: info.amountIn.toString(),
1660
1657
  symbol: info.inputTokenSymbol
1661
1658
  },
1662
1659
  estimated: {
1663
- amount: formatUnits2(BigInt(info.amountOut), 18),
1660
+ amount: formatUnits3(BigInt(info.amountOut), 18),
1664
1661
  raw: info.amountOut,
1665
1662
  symbol: info.coinSymbol
1666
1663
  },
@@ -1668,29 +1665,33 @@ var printQuote = (json, info) => {
1668
1665
  });
1669
1666
  return;
1670
1667
  }
1668
+ const spendFormatted = formatAmountDisplay(
1669
+ info.amountIn,
1670
+ info.inputTokenDecimals
1671
+ );
1672
+ const coinsFormatted = formatAmountDisplay(BigInt(info.amountOut), 18);
1671
1673
  console.log(`
1672
- Buy ${info.coinName} (${info.coinSymbol})
1674
+ Buy \x1B[1m${info.coinName}\x1B[0m`);
1675
+ console.log(` ${info.coinType} \xB7 ${info.address}
1673
1676
  `);
1674
- console.log(` Amount ${info.spendAmount}`);
1675
- console.log(` You get ~${info.coinsFormatted} ${info.coinSymbol}`);
1677
+ console.log(` Amount ${spendFormatted} ${info.inputTokenSymbol}`);
1678
+ console.log(` You get ~${coinsFormatted} ${info.coinSymbol}`);
1676
1679
  console.log(` Slippage ${info.slippagePct}%
1677
1680
  `);
1678
1681
  };
1679
1682
  var printTradeResult = (json, info) => {
1680
- const receivedAmount = formatUnits2(info.receivedAmountOut, 18);
1681
- const receivedFormatted = formatCoinsDisplay(receivedAmount);
1682
1683
  if (json) {
1683
1684
  outputJson({
1684
1685
  action: "buy",
1685
1686
  coin: info.coinSymbol,
1686
1687
  address: info.address,
1687
1688
  spent: {
1688
- amount: formatUnits2(info.amountIn, info.inputTokenDecimals),
1689
+ amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
1689
1690
  raw: info.amountIn.toString(),
1690
1691
  symbol: info.inputTokenSymbol
1691
1692
  },
1692
1693
  received: {
1693
- amount: receivedAmount,
1694
+ amount: formatUnits3(info.receivedAmountOut, 18),
1694
1695
  raw: info.receivedAmountOut.toString(),
1695
1696
  symbol: info.coinSymbol
1696
1697
  },
@@ -1698,21 +1699,233 @@ var printTradeResult = (json, info) => {
1698
1699
  });
1699
1700
  return;
1700
1701
  }
1702
+ const spentFormatted = formatAmountDisplay(
1703
+ info.amountIn,
1704
+ info.inputTokenDecimals
1705
+ );
1706
+ const receivedFormatted = formatAmountDisplay(info.receivedAmountOut, 18);
1701
1707
  console.log(`
1702
- Bought ${info.coinName}
1708
+ Bought \x1B[1m${info.coinName}\x1B[0m`);
1709
+ console.log(` ${info.coinType} \xB7 ${info.address}
1703
1710
  `);
1704
- console.log(` Spent ${info.spendAmount} ${info.inputTokenSymbol}`);
1711
+ console.log(` Spent ${spentFormatted} ${info.inputTokenSymbol}`);
1705
1712
  console.log(` Received ${receivedFormatted} ${info.coinSymbol}`);
1706
1713
  console.log(` Tx ${info.txHash}
1707
1714
  `);
1708
1715
  };
1709
1716
 
1717
+ // src/lib/coin-ref.ts
1718
+ import { getCoin, getProfile, getTrend } from "@zoralabs/coins-sdk";
1719
+ var TYPE_KEYWORDS = /* @__PURE__ */ new Set(["creator-coin", "trend"]);
1720
+ var CoinArgError = class extends Error {
1721
+ suggestion;
1722
+ constructor(message, suggestion) {
1723
+ super(message);
1724
+ this.suggestion = suggestion;
1725
+ }
1726
+ };
1727
+ function parsePositionalCoinArgs(firstArg, secondArg) {
1728
+ if (TYPE_KEYWORDS.has(firstArg)) {
1729
+ if (!secondArg) {
1730
+ throw new CoinArgError(
1731
+ `Missing identifier after "${firstArg}".`,
1732
+ `Usage: zora <command> ${firstArg} <name>`
1733
+ );
1734
+ }
1735
+ return { kind: "typed", type: firstArg, identifier: secondArg };
1736
+ }
1737
+ if (firstArg.startsWith("0x")) {
1738
+ return { kind: "address", address: firstArg };
1739
+ }
1740
+ return { kind: "ambiguous-name", name: firstArg };
1741
+ }
1742
+ function coinArgsToRef(parsed) {
1743
+ switch (parsed.kind) {
1744
+ case "typed":
1745
+ return { kind: "prefixed", type: parsed.type, name: parsed.identifier };
1746
+ case "address":
1747
+ return { kind: "address", address: parsed.address };
1748
+ case "ambiguous-name":
1749
+ return { kind: "ambiguous", name: parsed.name };
1750
+ }
1751
+ }
1752
+ async function resolveAmbiguousName(name) {
1753
+ const [creatorResult, trendResult] = await Promise.all([
1754
+ resolveByCreatorName(name),
1755
+ resolveByTrendTicker(name)
1756
+ ]);
1757
+ const creatorFound = creatorResult.kind === "found" ? creatorResult.coin : null;
1758
+ const trendFound = trendResult.kind === "found" ? trendResult.coin : null;
1759
+ if (creatorFound && trendFound) {
1760
+ return { kind: "ambiguous", creator: creatorFound, trend: trendFound };
1761
+ }
1762
+ if (creatorFound) {
1763
+ return { kind: "found", coin: creatorFound };
1764
+ }
1765
+ if (trendFound) {
1766
+ return { kind: "found", coin: trendFound };
1767
+ }
1768
+ return {
1769
+ kind: "not-found",
1770
+ message: `No coin found matching "${name}".`
1771
+ };
1772
+ }
1773
+ function formatAmbiguousError(name, creator, trend, command) {
1774
+ const creatorMcap = formatCompactUsd(creator.marketCap);
1775
+ const trendMcap = formatCompactUsd(trend.marketCap);
1776
+ return {
1777
+ message: [
1778
+ `Multiple coins match "${name}":`,
1779
+ ` creator-coin ${creator.name} ${creatorMcap} mcap`,
1780
+ ` trend ${trend.name} ${trendMcap} mcap`
1781
+ ].join("\n"),
1782
+ suggestion: `Use: zora ${command} creator-coin ${name} or zora ${command} trend ${name}`
1783
+ };
1784
+ }
1785
+ var COIN_TYPE_MAP = {
1786
+ CONTENT: "post",
1787
+ CREATOR: "creator-coin",
1788
+ TREND: "trend"
1789
+ };
1790
+ function mapCoinType(raw) {
1791
+ if (!raw) return "unknown";
1792
+ return COIN_TYPE_MAP[raw] ?? "unknown";
1793
+ }
1794
+ function coinFromToken(token) {
1795
+ return {
1796
+ name: token.name ?? "Unknown",
1797
+ address: token.address ?? "",
1798
+ coinType: mapCoinType(token.coinType),
1799
+ marketCap: token.marketCap ?? "0",
1800
+ marketCapDelta24h: token.marketCapDelta24h ?? "0",
1801
+ volume24h: token.volume24h ?? "0",
1802
+ uniqueHolders: token.uniqueHolders ?? 0,
1803
+ createdAt: token.createdAt,
1804
+ creatorAddress: token.creatorAddress,
1805
+ creatorHandle: token.creatorProfile?.handle,
1806
+ platformBlocked: token.platformBlocked ?? false
1807
+ };
1808
+ }
1809
+ async function resolveByAddress(address) {
1810
+ const response = await getCoin({ address });
1811
+ if (response.error || !response.data?.zora20Token) {
1812
+ return {
1813
+ kind: "not-found",
1814
+ message: `No coin found at address ${address}`
1815
+ };
1816
+ }
1817
+ return { kind: "found", coin: coinFromToken(response.data.zora20Token) };
1818
+ }
1819
+ async function resolveByTrendTicker(ticker) {
1820
+ const response = await getTrend({ ticker });
1821
+ if (response.error || !response.data?.trendCoin) {
1822
+ return {
1823
+ kind: "not-found",
1824
+ message: `No trend coin found with ticker "${ticker}"`
1825
+ };
1826
+ }
1827
+ return { kind: "found", coin: coinFromToken(response.data.trendCoin) };
1828
+ }
1829
+ async function resolveByCreatorName(name) {
1830
+ const response = await getProfile({ identifier: name });
1831
+ if (response.error || !response.data?.profile) {
1832
+ return {
1833
+ kind: "not-found",
1834
+ message: `No creator found with name "${name}"`
1835
+ };
1836
+ }
1837
+ const profile = response.data.profile;
1838
+ if (!profile.creatorCoin) {
1839
+ return {
1840
+ kind: "not-found",
1841
+ message: `"${name}" does not have a creator coin`
1842
+ };
1843
+ }
1844
+ return resolveByAddress(profile.creatorCoin.address);
1845
+ }
1846
+ async function resolveCoin(ref) {
1847
+ switch (ref.kind) {
1848
+ case "address":
1849
+ return resolveByAddress(ref.address);
1850
+ case "prefixed":
1851
+ if (ref.type === "trend") {
1852
+ return resolveByTrendTicker(ref.name);
1853
+ }
1854
+ return resolveByCreatorName(ref.name);
1855
+ case "ambiguous":
1856
+ return resolveByCreatorName(ref.name);
1857
+ }
1858
+ }
1859
+
1710
1860
  // 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) {
1861
+ var buyCommand = new Command3("buy").description("Buy a coin").argument(
1862
+ "[typeOrId]",
1863
+ "Type prefix (creator-coin, trend) or coin address/name"
1864
+ ).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
1865
  const json = getJson(this);
1713
1866
  const debug = opts.debug === true;
1714
- if (!isAddress(coinAddress)) {
1715
- outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
1867
+ let parsed;
1868
+ try {
1869
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
1870
+ } catch (err) {
1871
+ if (err instanceof CoinArgError) {
1872
+ outputErrorAndExit(json, err.message, err.suggestion);
1873
+ }
1874
+ throw err;
1875
+ }
1876
+ const apiKey = getApiKey();
1877
+ if (apiKey) {
1878
+ setApiKey2(apiKey);
1879
+ }
1880
+ let coinAddress;
1881
+ if (parsed.kind === "address") {
1882
+ if (!isAddress(parsed.address)) {
1883
+ outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
1884
+ return;
1885
+ }
1886
+ coinAddress = parsed.address;
1887
+ } else if (parsed.kind === "ambiguous-name") {
1888
+ let ambResult;
1889
+ try {
1890
+ ambResult = await resolveAmbiguousName(parsed.name);
1891
+ } catch (err) {
1892
+ outputErrorAndExit(
1893
+ json,
1894
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1895
+ );
1896
+ return;
1897
+ }
1898
+ if (ambResult.kind === "not-found") {
1899
+ outputErrorAndExit(json, ambResult.message);
1900
+ return;
1901
+ }
1902
+ if (ambResult.kind === "ambiguous") {
1903
+ const { message, suggestion } = formatAmbiguousError(
1904
+ parsed.name,
1905
+ ambResult.creator,
1906
+ ambResult.trend,
1907
+ "buy"
1908
+ );
1909
+ outputErrorAndExit(json, message, suggestion);
1910
+ return;
1911
+ }
1912
+ coinAddress = ambResult.coin.address;
1913
+ } else {
1914
+ const ref = coinArgsToRef(parsed);
1915
+ try {
1916
+ const result = await resolveCoin(ref);
1917
+ if (result.kind === "not-found") {
1918
+ outputErrorAndExit(json, result.message, result.suggestion);
1919
+ return;
1920
+ }
1921
+ coinAddress = result.coin.address;
1922
+ } catch (err) {
1923
+ outputErrorAndExit(
1924
+ json,
1925
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
1926
+ );
1927
+ return;
1928
+ }
1716
1929
  }
1717
1930
  const tokenKey = opts.token.toLowerCase();
1718
1931
  if (!(tokenKey in BASE_TRADE_TOKENS)) {
@@ -1736,15 +1949,11 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1736
1949
  );
1737
1950
  }
1738
1951
  const slippage = slippagePct / 100;
1739
- const apiKey = getApiKey();
1740
- if (apiKey) {
1741
- setApiKey2(apiKey);
1742
- }
1743
1952
  const account = resolveAccount(json);
1744
1953
  const { publicClient, walletClient } = createClients(account);
1745
1954
  let token;
1746
1955
  try {
1747
- const response = await getCoin({ address: coinAddress });
1956
+ const response = await getCoin2({ address: coinAddress });
1748
1957
  token = response.data?.zora20Token;
1749
1958
  } catch (err) {
1750
1959
  outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
@@ -1752,8 +1961,12 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1752
1961
  if (!token) {
1753
1962
  outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
1754
1963
  }
1964
+ if (token.platformBlocked) {
1965
+ outputErrorAndExit(json, bannedCoinBuyMessage(coinAddress));
1966
+ }
1755
1967
  const coinName = token.name;
1756
1968
  const coinSymbol = token.symbol;
1969
+ const coinType = mapCoinType(token.coinType);
1757
1970
  let amountIn;
1758
1971
  if (amountMode === "usd") {
1759
1972
  const usdVal = parsePercentageLikeValue(opts.usd);
@@ -1788,7 +2001,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1788
2001
  }
1789
2002
  if (debug) {
1790
2003
  console.error(
1791
- `[debug] $${usdVal} USD = ${formatUnits3(amountIn, inputToken.decimals)} ${inputToken.symbol} (price: $${priceUsd})`
2004
+ `[debug] $${usdVal} USD = ${formatUnits4(amountIn, inputToken.decimals)} ${inputToken.symbol} (price: $${priceUsd})`
1792
2005
  );
1793
2006
  }
1794
2007
  } else if (amountMode === "eth") {
@@ -1841,7 +2054,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1841
2054
  if (isEth && balance <= gasReserve) {
1842
2055
  outputErrorAndExit(
1843
2056
  json,
1844
- `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
2057
+ `Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
1845
2058
  );
1846
2059
  }
1847
2060
  const spendableBalance = balance - gasReserve;
@@ -1871,7 +2084,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("[addres
1871
2084
  const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
1872
2085
  if (priceUsd != null) {
1873
2086
  swapAmountUsd = Number(
1874
- (Number(formatUnits3(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
2087
+ (Number(formatUnits4(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
1875
2088
  );
1876
2089
  }
1877
2090
  }
@@ -1926,25 +2139,19 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1926
2139
  "Check the coin address is valid and try again. Use --debug for full error details."
1927
2140
  );
1928
2141
  }
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);
2142
+ const quoteInfo = {
2143
+ coinName,
2144
+ coinSymbol,
2145
+ coinType,
2146
+ address: coinAddress,
2147
+ amountIn,
2148
+ inputTokenSymbol: inputToken.symbol,
2149
+ inputTokenDecimals: inputToken.decimals,
2150
+ amountOut,
2151
+ slippagePct
2152
+ };
1935
2153
  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
- });
2154
+ printQuote(json, quoteInfo);
1948
2155
  track("cli_buy", {
1949
2156
  action: "quote",
1950
2157
  coin_address: coinAddress,
@@ -1960,18 +2167,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
1960
2167
  return;
1961
2168
  }
1962
2169
  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
- });
2170
+ printQuote(false, quoteInfo);
1975
2171
  const ok = await confirm2({
1976
2172
  message: "Confirm?",
1977
2173
  default: false
@@ -2028,8 +2224,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
2028
2224
  printTradeResult(json, {
2029
2225
  coinName,
2030
2226
  coinSymbol,
2227
+ coinType,
2031
2228
  address: coinAddress,
2032
- spendAmount,
2033
2229
  amountIn,
2034
2230
  inputTokenSymbol: inputToken.symbol,
2035
2231
  inputTokenDecimals: inputToken.decimals,
@@ -2063,9 +2259,6 @@ import {
2063
2259
  getCoinsTopVolume24h,
2064
2260
  getCoinsMostValuable,
2065
2261
  getCoinsNew,
2066
- getCoinsTopGainers,
2067
- getCoinsLastTraded,
2068
- getCoinsLastTradedUnique,
2069
2262
  getExploreTopVolumeAll24h,
2070
2263
  getExploreTopVolumeCreators24h,
2071
2264
  getExploreNewAll,
@@ -2088,9 +2281,6 @@ var SORT_LABELS2 = {
2088
2281
  mcap: "Top by Market Cap",
2089
2282
  volume: "Top by 24h Volume",
2090
2283
  new: "New",
2091
- gainers: "Top Gainers (24h)",
2092
- "last-traded": "Last Traded",
2093
- "last-traded-unique": "Last Traded (Unique)",
2094
2284
  trending: "Trending",
2095
2285
  featured: "Featured"
2096
2286
  };
@@ -2340,8 +2530,7 @@ var ExploreView = ({
2340
2530
  hints.push("c copy address");
2341
2531
  if (cursorHistory.length > 0) hints.push("\u2190 prev");
2342
2532
  if (pageInfo?.hasNextPage) hints.push("\u2192 next");
2343
- hints.push("r refresh");
2344
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
2533
+ hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
2345
2534
  hints.push("q quit");
2346
2535
  const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
2347
2536
  return /* @__PURE__ */ jsx4(
@@ -2378,15 +2567,6 @@ var QUERY_MAP = {
2378
2567
  "creator-coin": getCreatorCoins,
2379
2568
  post: getCoinsNew
2380
2569
  },
2381
- gainers: {
2382
- post: getCoinsTopGainers
2383
- },
2384
- "last-traded": {
2385
- post: getCoinsLastTraded
2386
- },
2387
- "last-traded-unique": {
2388
- post: getCoinsLastTradedUnique
2389
- },
2390
2570
  trending: {
2391
2571
  all: getTrendingAll,
2392
2572
  trend: getTrendingTrends,
@@ -2428,7 +2608,7 @@ var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
2428
2608
  var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
2429
2609
  "--type <type>",
2430
2610
  "Filter by type: all, trend, creator-coin, post (availability varies by sort)",
2431
- "post"
2611
+ "creator-coin"
2432
2612
  ).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
2613
  "--refresh <seconds>",
2434
2614
  "Auto-refresh interval in seconds, requires --live (min 5)",
@@ -2560,130 +2740,42 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
2560
2740
  import { Command as Command5 } from "commander";
2561
2741
  import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
2562
2742
 
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
- };
2743
+ // src/components/CoinDetail.tsx
2744
+ import { Box as Box5, Text as Text5 } from "ink";
2745
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2746
+ var LABEL_WIDTH = 18;
2747
+ function Row({
2748
+ label,
2749
+ children
2750
+ }) {
2751
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
2752
+ /* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
2753
+ /* @__PURE__ */ jsx6(Text5, { children })
2754
+ ] });
2587
2755
  }
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
- ] });
2663
- }
2664
- function CoinDetail({ coin }) {
2665
- const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
2666
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, children: [
2667
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2668
- /* @__PURE__ */ jsx6(Text5, { bold: true, children: coin.name }),
2669
- /* @__PURE__ */ jsxs5(Text5, { children: [
2670
- coin.coinType,
2671
- " ",
2672
- "\xB7",
2673
- " ",
2674
- coin.address
2675
- ] })
2676
- ] }),
2677
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2678
- /* @__PURE__ */ jsx6(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
2679
- /* @__PURE__ */ jsx6(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
2680
- /* @__PURE__ */ jsx6(Row, { label: "24h Change", children: /* @__PURE__ */ jsx6(Text5, { color: change.color, children: change.text }) }),
2681
- /* @__PURE__ */ jsx6(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
2682
- coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx6(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
2683
- /* @__PURE__ */ jsx6(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
2684
- ] }),
2685
- /* @__PURE__ */ jsx6(Box5, { marginBottom: 1 })
2686
- ] });
2756
+ function CoinDetail({ coin }) {
2757
+ const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
2758
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, children: [
2759
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2760
+ /* @__PURE__ */ jsx6(Text5, { bold: true, children: coin.name }),
2761
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2762
+ coin.coinType,
2763
+ " ",
2764
+ "\xB7",
2765
+ " ",
2766
+ coin.address
2767
+ ] })
2768
+ ] }),
2769
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2770
+ /* @__PURE__ */ jsx6(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
2771
+ /* @__PURE__ */ jsx6(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
2772
+ /* @__PURE__ */ jsx6(Row, { label: "24h Change", children: /* @__PURE__ */ jsx6(Text5, { color: change.color, children: change.text }) }),
2773
+ /* @__PURE__ */ jsx6(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
2774
+ coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx6(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
2775
+ /* @__PURE__ */ jsx6(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
2776
+ ] }),
2777
+ /* @__PURE__ */ jsx6(Box5, { marginBottom: 1 })
2778
+ ] });
2687
2779
  }
2688
2780
 
2689
2781
  // src/commands/get.tsx
@@ -2702,29 +2794,87 @@ function formatCoinJson(coin) {
2702
2794
  creatorHandle: coin.creatorHandle ?? null
2703
2795
  };
2704
2796
  }
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) {
2797
+ function outputCoin(json, coin) {
2798
+ outputData(json, {
2799
+ json: formatCoinJson(coin),
2800
+ render: () => {
2801
+ renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin }));
2802
+ }
2803
+ });
2804
+ }
2805
+ var getCommand = new Command5("get").description("Look up a coin by address or name").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
2806
+ "[identifier]",
2807
+ "Coin address (0x...) or name (when type prefix is given)"
2808
+ ).action(async function(typeOrId, identifier) {
2707
2809
  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
- );
2810
+ let parsed;
2811
+ try {
2812
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
2813
+ } catch (err) {
2814
+ if (err instanceof CoinArgError) {
2815
+ outputErrorAndExit(json, err.message, err.suggestion);
2816
+ }
2817
+ throw err;
2722
2818
  }
2723
- const ref = parseCoinRef(identifier, opts.type);
2724
2819
  const apiKey = getApiKey();
2725
2820
  if (apiKey) {
2726
2821
  setApiKey4(apiKey);
2727
2822
  }
2823
+ if (parsed.kind === "ambiguous-name") {
2824
+ let ambResult;
2825
+ try {
2826
+ ambResult = await resolveAmbiguousName(parsed.name);
2827
+ } catch (err) {
2828
+ outputErrorAndExit(
2829
+ json,
2830
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
2831
+ );
2832
+ return;
2833
+ }
2834
+ if (ambResult.kind === "not-found") {
2835
+ outputErrorAndExit(json, ambResult.message);
2836
+ return;
2837
+ }
2838
+ if (ambResult.kind === "ambiguous") {
2839
+ if (json) {
2840
+ outputData(json, {
2841
+ json: {
2842
+ matches: [
2843
+ { type: "creator-coin", ...formatCoinJson(ambResult.creator) },
2844
+ { type: "trend", ...formatCoinJson(ambResult.trend) }
2845
+ ],
2846
+ hint: `Use: zora get creator-coin ${parsed.name} or zora get trend ${parsed.name}`
2847
+ },
2848
+ render: () => {
2849
+ }
2850
+ });
2851
+ } else {
2852
+ outputCoin(false, ambResult.creator);
2853
+ console.log("");
2854
+ outputCoin(false, ambResult.trend);
2855
+ console.log(
2856
+ `
2857
+ \x1B[2mUse \`zora get creator-coin ${parsed.name}\` or \`zora get trend ${parsed.name}\` for a specific type.\x1B[0m`
2858
+ );
2859
+ }
2860
+ track("cli_get", {
2861
+ lookup_type: "name",
2862
+ found: true,
2863
+ ambiguous: true,
2864
+ output_format: json ? "json" : "text"
2865
+ });
2866
+ return;
2867
+ }
2868
+ outputCoin(json, ambResult.coin);
2869
+ track("cli_get", {
2870
+ lookup_type: "name",
2871
+ found: true,
2872
+ coin_type: ambResult.coin.coinType,
2873
+ output_format: json ? "json" : "text"
2874
+ });
2875
+ return;
2876
+ }
2877
+ const ref = coinArgsToRef(parsed);
2728
2878
  let result;
2729
2879
  try {
2730
2880
  result = await resolveCoin(ref);
@@ -2732,29 +2882,20 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
2732
2882
  outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
2733
2883
  return;
2734
2884
  }
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
2885
  if (result.kind === "not-found") {
2744
2886
  outputErrorAndExit(json, result.message);
2745
2887
  return;
2746
2888
  }
2747
- outputData(json, {
2748
- json: formatCoinJson(result.coin),
2749
- render: () => {
2750
- renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin: result.coin }));
2751
- }
2752
- });
2889
+ if (result.coin.platformBlocked) {
2890
+ outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
2891
+ return;
2892
+ }
2893
+ outputCoin(json, result.coin);
2753
2894
  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,
2895
+ lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
2896
+ coin_type_filter: parsed.kind === "typed" ? parsed.type : null,
2897
+ found: true,
2898
+ coin_type: result.coin.coinType,
2758
2899
  output_format: json ? "json" : "text"
2759
2900
  });
2760
2901
  });
@@ -2827,7 +2968,6 @@ var downsample = (values, maxWidth) => {
2827
2968
 
2828
2969
  // src/commands/price-history.tsx
2829
2970
  import { jsx as jsx9 } from "react/jsx-runtime";
2830
- var VALID_TYPES2 = ["creator-coin", "post", "trend"];
2831
2971
  var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
2832
2972
  var INTERVAL_TO_API_FIELD = {
2833
2973
  "1h": "oneHour",
@@ -2868,54 +3008,7 @@ var fetchPriceHistory = async (address, interval) => {
2868
3008
  price: Number(p.closePrice)
2869
3009
  }));
2870
3010
  };
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;
3011
+ async function showPriceHistory(json, coin, interval) {
2919
3012
  let prices;
2920
3013
  try {
2921
3014
  prices = await fetchPriceHistory(coin.address, interval);
@@ -2941,9 +3034,7 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
2941
3034
  priceValues[0],
2942
3035
  priceValues[priceValues.length - 1]
2943
3036
  );
2944
- const sparklineText = sparkline(
2945
- downsample(priceValues, MAX_SPARKLINE_WIDTH)
2946
- );
3037
+ const sparklineText = sparkline(downsample(priceValues, MAX_SPARKLINE_WIDTH));
2947
3038
  outputData(json, {
2948
3039
  json: {
2949
3040
  coin: coin.name,
@@ -2974,11 +3065,139 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
2974
3065
  );
2975
3066
  }
2976
3067
  });
3068
+ return prices.length;
3069
+ }
3070
+ var priceHistoryCommand = new Command6("price-history").description("Display price history for a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
3071
+ "[identifier]",
3072
+ "Coin address (0x...) or name (when type prefix is given)"
3073
+ ).option(
3074
+ "--interval <interval>",
3075
+ `Time range: ${VALID_INTERVALS.join(", ")}`,
3076
+ "1w"
3077
+ ).action(async function(typeOrId, identifier, opts) {
3078
+ const json = getJson(this);
3079
+ const interval = opts.interval ?? "1w";
3080
+ if (!VALID_INTERVALS.includes(interval)) {
3081
+ outputErrorAndExit(
3082
+ json,
3083
+ `Invalid --interval value: ${interval}.`,
3084
+ `Supported: ${VALID_INTERVALS.join(", ")}`
3085
+ );
3086
+ }
3087
+ let parsed;
3088
+ try {
3089
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
3090
+ } catch (err) {
3091
+ if (err instanceof CoinArgError) {
3092
+ outputErrorAndExit(json, err.message, err.suggestion);
3093
+ }
3094
+ throw err;
3095
+ }
3096
+ const apiKey = getApiKey();
3097
+ if (apiKey) {
3098
+ setApiKey5(apiKey);
3099
+ }
3100
+ if (parsed.kind === "ambiguous-name") {
3101
+ let ambResult;
3102
+ try {
3103
+ ambResult = await resolveAmbiguousName(parsed.name);
3104
+ } catch (err) {
3105
+ outputErrorAndExit(
3106
+ json,
3107
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3108
+ );
3109
+ return;
3110
+ }
3111
+ if (ambResult.kind === "not-found") {
3112
+ outputErrorAndExit(json, ambResult.message);
3113
+ return;
3114
+ }
3115
+ if (ambResult.kind === "ambiguous") {
3116
+ if (json) {
3117
+ const [creatorPrices, trendPrices] = await Promise.all([
3118
+ fetchPriceHistory(ambResult.creator.address, interval),
3119
+ fetchPriceHistory(ambResult.trend.address, interval)
3120
+ ]);
3121
+ outputData(json, {
3122
+ json: {
3123
+ matches: [
3124
+ {
3125
+ type: "creator-coin",
3126
+ coin: ambResult.creator.name,
3127
+ prices: creatorPrices
3128
+ },
3129
+ {
3130
+ type: "trend",
3131
+ coin: ambResult.trend.name,
3132
+ prices: trendPrices
3133
+ }
3134
+ ],
3135
+ hint: `Use: zora price-history creator-coin ${parsed.name} or zora price-history trend ${parsed.name}`
3136
+ },
3137
+ render: () => {
3138
+ }
3139
+ });
3140
+ } else {
3141
+ await showPriceHistory(
3142
+ false,
3143
+ ambResult.creator,
3144
+ interval
3145
+ );
3146
+ console.log("");
3147
+ await showPriceHistory(false, ambResult.trend, interval);
3148
+ console.log(
3149
+ `
3150
+ \x1B[2mUse \`zora price-history creator-coin ${parsed.name}\` or \`zora price-history trend ${parsed.name}\` for a specific type.\x1B[0m`
3151
+ );
3152
+ }
3153
+ track("cli_price_history", {
3154
+ lookup_type: "name",
3155
+ ambiguous: true,
3156
+ interval,
3157
+ output_format: json ? "json" : "text"
3158
+ });
3159
+ return;
3160
+ }
3161
+ const dataPoints2 = await showPriceHistory(
3162
+ json,
3163
+ ambResult.coin,
3164
+ interval
3165
+ );
3166
+ track("cli_price_history", {
3167
+ lookup_type: "name",
3168
+ coin_type: ambResult.coin.coinType,
3169
+ interval,
3170
+ data_points: dataPoints2,
3171
+ output_format: json ? "json" : "text"
3172
+ });
3173
+ return;
3174
+ }
3175
+ const ref = coinArgsToRef(parsed);
3176
+ let result;
3177
+ try {
3178
+ result = await resolveCoin(ref);
3179
+ } catch (err) {
3180
+ outputErrorAndExit(
3181
+ json,
3182
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3183
+ );
3184
+ return;
3185
+ }
3186
+ if (result.kind === "not-found") {
3187
+ outputErrorAndExit(json, result.message, result.suggestion);
3188
+ return;
3189
+ }
3190
+ if (result.coin.platformBlocked) {
3191
+ outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
3192
+ return;
3193
+ }
3194
+ const { coin } = result;
3195
+ const dataPoints = await showPriceHistory(json, coin, interval);
2977
3196
  track("cli_price_history", {
2978
- lookup_type: identifier.startsWith("0x") ? "address" : "name",
3197
+ lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
2979
3198
  coin_type: coin.coinType,
2980
3199
  interval,
2981
- data_points: prices.length,
3200
+ data_points: dataPoints,
2982
3201
  output_format: json ? "json" : "text"
2983
3202
  });
2984
3203
  });
@@ -2988,7 +3207,7 @@ import { Command as Command7 } from "commander";
2988
3207
  import confirm3 from "@inquirer/confirm";
2989
3208
  import {
2990
3209
  erc20Abi as erc20Abi3,
2991
- formatUnits as formatUnits4,
3210
+ formatUnits as formatUnits5,
2992
3211
  isAddress as isAddress2,
2993
3212
  parseUnits as parseUnits2
2994
3213
  } from "viem";
@@ -3005,12 +3224,12 @@ function printSellQuote(output, info) {
3005
3224
  coin: info.coinSymbol,
3006
3225
  address: info.address,
3007
3226
  sell: {
3008
- amount: formatUnits4(info.amountIn, info.coinDecimals),
3227
+ amount: formatUnits5(info.amountIn, info.coinDecimals),
3009
3228
  raw: info.amountIn.toString(),
3010
3229
  symbol: info.coinSymbol
3011
3230
  },
3012
3231
  estimated: {
3013
- amount: formatUnits4(BigInt(info.quoteAmountOut), info.outputDecimals),
3232
+ amount: formatUnits5(BigInt(info.quoteAmountOut), info.outputDecimals),
3014
3233
  raw: info.quoteAmountOut,
3015
3234
  symbol: info.outputSymbol
3016
3235
  },
@@ -3019,7 +3238,8 @@ function printSellQuote(output, info) {
3019
3238
  return;
3020
3239
  }
3021
3240
  console.log(`
3022
- Sell ${info.coinName} (${info.coinSymbol})
3241
+ Sell \x1B[1m${info.coinName}\x1B[0m`);
3242
+ console.log(` ${info.coinType} \xB7 ${info.address}
3023
3243
  `);
3024
3244
  console.log(` Amount ${info.soldFormatted} ${info.coinSymbol}`);
3025
3245
  console.log(
@@ -3029,7 +3249,7 @@ function printSellQuote(output, info) {
3029
3249
  `);
3030
3250
  }
3031
3251
  function printSellResult(output, info) {
3032
- const receivedAmount = formatUnits4(
3252
+ const receivedAmount = formatUnits5(
3033
3253
  info.receivedAmountOut,
3034
3254
  info.outputDecimals
3035
3255
  );
@@ -3043,7 +3263,7 @@ function printSellResult(output, info) {
3043
3263
  coin: info.coinSymbol,
3044
3264
  address: info.address,
3045
3265
  sold: {
3046
- amount: formatUnits4(info.amountIn, info.coinDecimals),
3266
+ amount: formatUnits5(info.amountIn, info.coinDecimals),
3047
3267
  raw: info.amountIn.toString(),
3048
3268
  symbol: info.coinSymbol
3049
3269
  },
@@ -3058,7 +3278,8 @@ function printSellResult(output, info) {
3058
3278
  return;
3059
3279
  }
3060
3280
  console.log(`
3061
- Sold ${info.coinName}
3281
+ Sold \x1B[1m${info.coinName}\x1B[0m`);
3282
+ console.log(` ${info.coinType} \xB7 ${info.address}
3062
3283
  `);
3063
3284
  console.log(` Sold ${info.soldFormatted} ${info.coinSymbol}`);
3064
3285
  console.log(
@@ -3070,11 +3291,74 @@ function printSellResult(output, info) {
3070
3291
  console.log(` Tx ${info.txHash}
3071
3292
  `);
3072
3293
  }
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) {
3294
+ var sellCommand = new Command7("sell").description("Sell a coin").argument(
3295
+ "[typeOrId]",
3296
+ "Type prefix (creator-coin, trend) or coin address/name"
3297
+ ).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
3298
  const json = getJson(this);
3075
3299
  const debug = opts.debug === true;
3076
- if (!isAddress2(coinAddress)) {
3077
- outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
3300
+ let parsed;
3301
+ try {
3302
+ parsed = parsePositionalCoinArgs(typeOrId, identifier);
3303
+ } catch (err) {
3304
+ if (err instanceof CoinArgError) {
3305
+ outputErrorAndExit(json, err.message, err.suggestion);
3306
+ }
3307
+ throw err;
3308
+ }
3309
+ const apiKey = getApiKey();
3310
+ if (apiKey) {
3311
+ setApiKey6(apiKey);
3312
+ }
3313
+ let coinAddress;
3314
+ if (parsed.kind === "address") {
3315
+ if (!isAddress2(parsed.address)) {
3316
+ outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
3317
+ return;
3318
+ }
3319
+ coinAddress = parsed.address;
3320
+ } else if (parsed.kind === "ambiguous-name") {
3321
+ let ambResult;
3322
+ try {
3323
+ ambResult = await resolveAmbiguousName(parsed.name);
3324
+ } catch (err) {
3325
+ outputErrorAndExit(
3326
+ json,
3327
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3328
+ );
3329
+ return;
3330
+ }
3331
+ if (ambResult.kind === "not-found") {
3332
+ outputErrorAndExit(json, ambResult.message);
3333
+ return;
3334
+ }
3335
+ if (ambResult.kind === "ambiguous") {
3336
+ const { message, suggestion } = formatAmbiguousError(
3337
+ parsed.name,
3338
+ ambResult.creator,
3339
+ ambResult.trend,
3340
+ "sell"
3341
+ );
3342
+ outputErrorAndExit(json, message, suggestion);
3343
+ return;
3344
+ }
3345
+ coinAddress = ambResult.coin.address;
3346
+ } else {
3347
+ const ref = coinArgsToRef(parsed);
3348
+ try {
3349
+ const result = await resolveCoin(ref);
3350
+ if (result.kind === "not-found") {
3351
+ outputErrorAndExit(json, result.message, result.suggestion);
3352
+ return;
3353
+ }
3354
+ coinAddress = result.coin.address;
3355
+ } catch (err) {
3356
+ outputErrorAndExit(
3357
+ json,
3358
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
3359
+ );
3360
+ return;
3361
+ }
3078
3362
  }
3079
3363
  const output = json ? "json" : "static";
3080
3364
  const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
@@ -3099,10 +3383,6 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3099
3383
  );
3100
3384
  }
3101
3385
  const slippage = slippagePct / 100;
3102
- const apiKey = getApiKey();
3103
- if (apiKey) {
3104
- setApiKey6(apiKey);
3105
- }
3106
3386
  const account = resolveAccount(json);
3107
3387
  const { publicClient, walletClient } = createClients(account);
3108
3388
  let token;
@@ -3117,6 +3397,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3117
3397
  }
3118
3398
  const coinName = token.name;
3119
3399
  const coinSymbol = token.symbol;
3400
+ const coinType = mapCoinType(token.coinType);
3120
3401
  const coinDecimals = Number(token.decimals ?? 18);
3121
3402
  let amountIn;
3122
3403
  if (amountMode === "usd") {
@@ -3143,7 +3424,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3143
3424
  }
3144
3425
  if (debug) {
3145
3426
  console.error(
3146
- `[debug] $${usdVal} USD = ${formatUnits4(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd})`
3427
+ `[debug] $${usdVal} USD = ${formatUnits5(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd})`
3147
3428
  );
3148
3429
  }
3149
3430
  } else if (amountMode === "amount") {
@@ -3198,7 +3479,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("[add
3198
3479
  const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
3199
3480
  if (coinPriceUsd !== null && coinPriceUsd > 0) {
3200
3481
  swapAmountUsd = Number(
3201
- (Number(formatUnits4(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
3482
+ (Number(formatUnits5(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
3202
3483
  2
3203
3484
  )
3204
3485
  );
@@ -3264,6 +3545,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3264
3545
  printSellQuote(output, {
3265
3546
  coinName,
3266
3547
  coinSymbol,
3548
+ coinType,
3267
3549
  address: coinAddress,
3268
3550
  soldFormatted,
3269
3551
  amountIn,
@@ -3293,6 +3575,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3293
3575
  printSellQuote("static", {
3294
3576
  coinName,
3295
3577
  coinSymbol,
3578
+ coinType,
3296
3579
  address: coinAddress,
3297
3580
  soldFormatted,
3298
3581
  amountIn,
@@ -3361,6 +3644,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
3361
3644
  printSellResult(output, {
3362
3645
  coinName,
3363
3646
  coinSymbol,
3647
+ coinType,
3364
3648
  address: coinAddress,
3365
3649
  amountIn,
3366
3650
  coinDecimals,
@@ -3517,8 +3801,10 @@ var ProfileView = ({
3517
3801
  ] }) });
3518
3802
  }
3519
3803
  if (!data) return null;
3520
- const hints = ["\u2190 \u2192 switch tab", "r refresh"];
3521
- if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
3804
+ const hints = [
3805
+ "\u2190 \u2192 switch tab",
3806
+ autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh"
3807
+ ];
3522
3808
  hints.push("q quit");
3523
3809
  const footer = hints.join(" \xB7 ");
3524
3810
  const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
@@ -3789,7 +4075,7 @@ import { Command as Command9 } from "commander";
3789
4075
  import confirm4 from "@inquirer/confirm";
3790
4076
  import {
3791
4077
  erc20Abi as erc20Abi4,
3792
- formatUnits as formatUnits5,
4078
+ formatUnits as formatUnits6,
3793
4079
  isAddress as isAddress3,
3794
4080
  parseUnits as parseUnits3
3795
4081
  } from "viem";
@@ -3799,7 +4085,7 @@ var SEND_AMOUNT_CHECKS = {
3799
4085
  percent: (opts) => opts.percent !== void 0,
3800
4086
  all: (opts) => opts.all === true
3801
4087
  };
3802
- var VALID_TYPES3 = ["creator-coin", "post", "trend"];
4088
+ var KNOWN_TOKEN_NAMES = /* @__PURE__ */ new Set(["eth", "usdc", "zora"]);
3803
4089
  function printSendPreview(info) {
3804
4090
  const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
3805
4091
  console.log(`
@@ -3820,7 +4106,7 @@ function printSendResult(json, info) {
3820
4106
  coin: info.symbol,
3821
4107
  address: info.address,
3822
4108
  sent: {
3823
- amount: formatUnits5(info.amount, info.decimals),
4109
+ amount: formatUnits6(info.amount, info.decimals),
3824
4110
  raw: info.amount.toString(),
3825
4111
  symbol: info.symbol,
3826
4112
  amountUsd: info.amountUsd
@@ -3841,7 +4127,10 @@ function printSendResult(json, info) {
3841
4127
  console.log(` Tx ${info.txHash}
3842
4128
  `);
3843
4129
  }
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) {
4130
+ var sendCommand = new Command9("send").description("Send coins or ETH to an address").argument(
4131
+ "[typeOrId]",
4132
+ "Token (eth, usdc, zora), type prefix (creator-coin, trend), or coin address/name"
4133
+ ).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
4134
  const json = getJson(this);
3846
4135
  if (!opts.to) {
3847
4136
  outputErrorAndExit(
@@ -3858,24 +4147,15 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3858
4147
  );
3859
4148
  }
3860
4149
  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
4150
  const amountMode = getAmountMode(
3869
4151
  json,
3870
4152
  opts,
3871
4153
  SEND_AMOUNT_CHECKS,
3872
4154
  "--amount, --percent, or --all"
3873
4155
  );
3874
- const isEth = identifier.toLowerCase() === "eth";
4156
+ const isKnownToken = KNOWN_TOKEN_NAMES.has(firstArg.toLowerCase());
4157
+ const isEth = firstArg.toLowerCase() === "eth";
3875
4158
  if (isEth) {
3876
- if (opts.type) {
3877
- outputErrorAndExit(json, "--type is not valid when sending ETH.");
3878
- }
3879
4159
  const account = resolveAccount(json);
3880
4160
  const { publicClient, walletClient } = createClients(account);
3881
4161
  const balance = await publicClient.getBalance({
@@ -3913,14 +4193,14 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3913
4193
  if (amount + GAS_RESERVE > balance) {
3914
4194
  outputErrorAndExit(
3915
4195
  json,
3916
- `Insufficient balance. Have ${formatEthDisplay(balance)} ETH (need to reserve ~${formatEthDisplay(GAS_RESERVE)} ETH for gas).`
4196
+ `Insufficient balance. Have ${formatAmountDisplay(balance, 18)} ETH (need to reserve ~${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas).`
3917
4197
  );
3918
4198
  }
3919
4199
  } else {
3920
4200
  if (balance <= GAS_RESERVE) {
3921
4201
  outputErrorAndExit(
3922
4202
  json,
3923
- `Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
4203
+ `Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
3924
4204
  );
3925
4205
  }
3926
4206
  const spendable = balance - GAS_RESERVE;
@@ -3943,12 +4223,12 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
3943
4223
  }
3944
4224
  }
3945
4225
  }
3946
- const amountFormatted = formatEthDisplay(amount);
4226
+ const amountFormatted = formatAmountDisplay(amount, 18);
3947
4227
  let amountUsd = null;
3948
4228
  const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
3949
4229
  if (ethPriceUsd != null) {
3950
4230
  amountUsd = Number(
3951
- (Number(formatUnits5(amount, 18)) * ethPriceUsd).toFixed(2)
4231
+ (Number(formatUnits6(amount, 18)) * ethPriceUsd).toFixed(2)
3952
4232
  );
3953
4233
  }
3954
4234
  if (!opts.yes) {
@@ -4008,14 +4288,8 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4008
4288
  tx_hash: txHash
4009
4289
  });
4010
4290
  } 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
- }
4291
+ const knownTokenKey = firstArg.toLowerCase();
4292
+ const knownToken = isKnownToken && knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
4019
4293
  let tokenAddress;
4020
4294
  let tokenName;
4021
4295
  if (knownToken) {
@@ -4027,21 +4301,60 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4027
4301
  if (apiKey) {
4028
4302
  setApiKey8(apiKey);
4029
4303
  }
4030
- const ref = parseCoinRef(identifier, opts.type);
4031
- let result;
4304
+ let parsed;
4032
4305
  try {
4033
- result = await resolveCoin(ref);
4306
+ parsed = parsePositionalCoinArgs(firstArg, secondArg);
4034
4307
  } catch (err) {
4035
- outputErrorAndExit(
4036
- json,
4037
- `Request failed: ${err instanceof Error ? err.message : String(err)}`
4038
- );
4308
+ if (err instanceof CoinArgError) {
4309
+ outputErrorAndExit(json, err.message, err.suggestion);
4310
+ }
4311
+ throw err;
4039
4312
  }
4040
- if (result.kind === "not-found") {
4041
- outputErrorAndExit(json, result.message, result.suggestion);
4313
+ if (parsed.kind === "ambiguous-name") {
4314
+ let ambResult;
4315
+ try {
4316
+ ambResult = await resolveAmbiguousName(parsed.name);
4317
+ } catch (err) {
4318
+ outputErrorAndExit(
4319
+ json,
4320
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
4321
+ );
4322
+ return;
4323
+ }
4324
+ if (ambResult.kind === "not-found") {
4325
+ outputErrorAndExit(json, ambResult.message);
4326
+ return;
4327
+ }
4328
+ if (ambResult.kind === "ambiguous") {
4329
+ const { message, suggestion } = formatAmbiguousError(
4330
+ parsed.name,
4331
+ ambResult.creator,
4332
+ ambResult.trend,
4333
+ "send"
4334
+ );
4335
+ outputErrorAndExit(json, message, suggestion);
4336
+ return;
4337
+ }
4338
+ tokenAddress = ambResult.coin.address;
4339
+ tokenName = ambResult.coin.name;
4340
+ } else {
4341
+ const ref = coinArgsToRef(parsed);
4342
+ try {
4343
+ const result = await resolveCoin(ref);
4344
+ if (result.kind === "not-found") {
4345
+ outputErrorAndExit(json, result.message, result.suggestion);
4346
+ return;
4347
+ }
4348
+ tokenAddress = result.coin.address;
4349
+ tokenName = result.coin.name;
4350
+ } catch (err) {
4351
+ outputErrorAndExit(
4352
+ json,
4353
+ `Request failed: ${err instanceof Error ? err.message : String(err)}`
4354
+ );
4355
+ return;
4356
+ }
4042
4357
  }
4043
- tokenAddress = result.coin.address;
4044
- tokenName = result.coin.name;
4045
4358
  }
4046
4359
  const account = resolveAccount(json);
4047
4360
  const { publicClient, walletClient } = createClients(account);
@@ -4139,7 +4452,7 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
4139
4452
  const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
4140
4453
  if (priceUsd != null) {
4141
4454
  amountUsd = Number(
4142
- (Number(formatUnits5(amount, decimals)) * priceUsd).toFixed(2)
4455
+ (Number(formatUnits6(amount, decimals)) * priceUsd).toFixed(2)
4143
4456
  );
4144
4457
  }
4145
4458
  if (!opts.yes) {
@@ -4461,8 +4774,103 @@ walletCommand.command("export").description("Print the raw private key to stdout
4461
4774
  });
4462
4775
  });
4463
4776
 
4464
- // src/components/Zorb.tsx
4777
+ // src/components/StyledHelp.tsx
4778
+ import { Text as Text10, Box as Box10 } from "ink";
4779
+
4780
+ // src/components/KeyValueTable.tsx
4465
4781
  import { Text as Text9, Box as Box9 } from "ink";
4782
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
4783
+ function KeyValueTable({
4784
+ rows,
4785
+ labelWidth
4786
+ }) {
4787
+ const pad = labelWidth ?? Math.max(0, ...rows.map((r) => r.label.length)) + 2;
4788
+ return /* @__PURE__ */ jsx12(Box9, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
4789
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: row.label.padEnd(pad) }),
4790
+ /* @__PURE__ */ jsx12(Text9, { dimColor: true, children: row.value })
4791
+ ] }, i)) });
4792
+ }
4793
+
4794
+ // src/lib/parse-help.ts
4795
+ var DEFAULT_DESC_COLUMN = 38;
4796
+ var TWO_COLUMN_REGEX = /^(.+\S)([ \t]{2,})(\S.*)/m;
4797
+ function getDescriptionColumnOffset(sections) {
4798
+ for (const section of sections) {
4799
+ const match = section.content.match(TWO_COLUMN_REGEX);
4800
+ if (match) {
4801
+ return match[1].length + match[2].length;
4802
+ }
4803
+ }
4804
+ return DEFAULT_DESC_COLUMN;
4805
+ }
4806
+ function parseHelpSections(text) {
4807
+ const sections = [];
4808
+ let currentTitle = "";
4809
+ let currentLines = [];
4810
+ for (const line of text.split("\n")) {
4811
+ const match = line.match(/^([A-Z]\w+):(.*)/);
4812
+ if (match) {
4813
+ if (currentTitle) {
4814
+ const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
4815
+ sections.push({ title: currentTitle, content });
4816
+ }
4817
+ currentTitle = match[1];
4818
+ currentLines = match[2].trim() ? [match[2].trim()] : [];
4819
+ } else if (currentTitle) {
4820
+ currentLines.push(line.startsWith(" ") ? line.slice(2) : line);
4821
+ }
4822
+ }
4823
+ if (currentTitle) {
4824
+ const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
4825
+ sections.push({ title: currentTitle, content });
4826
+ }
4827
+ return sections.filter((s) => s.content);
4828
+ }
4829
+
4830
+ // src/components/StyledHelp.tsx
4831
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
4832
+ function StyledHelp({
4833
+ sections,
4834
+ header
4835
+ }) {
4836
+ const descriptionColumnOffset = getDescriptionColumnOffset(sections);
4837
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
4838
+ header,
4839
+ sections.map((section, i) => {
4840
+ const hasTwoColumns = TWO_COLUMN_REGEX.test(section.content);
4841
+ const rows = hasTwoColumns ? section.content.split("\n").map((line) => {
4842
+ const m = line.match(TWO_COLUMN_REGEX);
4843
+ if (m)
4844
+ return {
4845
+ label: m[1],
4846
+ value: m[3][0].toUpperCase() + m[3].slice(1)
4847
+ };
4848
+ return { label: "", value: line.trimStart() };
4849
+ }) : null;
4850
+ return /* @__PURE__ */ jsxs10(
4851
+ Box10,
4852
+ {
4853
+ flexDirection: "column",
4854
+ borderStyle: "single",
4855
+ borderDimColor: true,
4856
+ paddingX: 1,
4857
+ paddingY: 1,
4858
+ children: [
4859
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: section.title }),
4860
+ rows ? /* @__PURE__ */ jsx13(KeyValueTable, { rows, labelWidth: descriptionColumnOffset }) : /* @__PURE__ */ jsx13(Text10, { children: section.content })
4861
+ ]
4862
+ },
4863
+ i
4864
+ );
4865
+ })
4866
+ ] });
4867
+ }
4868
+
4869
+ // src/components/StyledHelpHeader.tsx
4870
+ import { Text as Text12, Box as Box12 } from "ink";
4871
+
4872
+ // src/components/Zorb.tsx
4873
+ import { Text as Text11, Box as Box11 } from "ink";
4466
4874
 
4467
4875
  // src/lib/zorb-pixels.ts
4468
4876
  function supportsTruecolor() {
@@ -4607,7 +5015,7 @@ function generateZorbPixels(size) {
4607
5015
  }
4608
5016
 
4609
5017
  // src/components/Zorb.tsx
4610
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
5018
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
4611
5019
  var LOWER_HALF_BLOCK = "\u2584";
4612
5020
  var UPPER_HALF_BLOCK = "\u2580";
4613
5021
  function rgbString([r, g, b]) {
@@ -4630,19 +5038,19 @@ function Zorb({ size = 20 }) {
4630
5038
  const topIsBlack = isBlack(top);
4631
5039
  const bottomIsBlack = isBlack(bottom);
4632
5040
  if (topIsBlack && bottomIsBlack) {
4633
- cells.push(/* @__PURE__ */ jsx12(Text9, { children: " " }, x));
5041
+ cells.push(/* @__PURE__ */ jsx14(Text11, { children: " " }, x));
4634
5042
  } else if (topIsBlack) {
4635
5043
  cells.push(
4636
- /* @__PURE__ */ jsx12(Text9, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
5044
+ /* @__PURE__ */ jsx14(Text11, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
4637
5045
  );
4638
5046
  } else if (bottomIsBlack) {
4639
5047
  cells.push(
4640
- /* @__PURE__ */ jsx12(Text9, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
5048
+ /* @__PURE__ */ jsx14(Text11, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
4641
5049
  );
4642
5050
  } else {
4643
5051
  cells.push(
4644
- /* @__PURE__ */ jsx12(
4645
- Text9,
5052
+ /* @__PURE__ */ jsx14(
5053
+ Text11,
4646
5054
  {
4647
5055
  backgroundColor: rgbString(top),
4648
5056
  color: rgbString(bottom),
@@ -4653,25 +5061,66 @@ function Zorb({ size = 20 }) {
4653
5061
  );
4654
5062
  }
4655
5063
  }
4656
- rows.push(/* @__PURE__ */ jsx12(Text9, { children: cells }, y));
5064
+ rows.push(/* @__PURE__ */ jsx14(Text11, { children: cells }, y));
4657
5065
  }
4658
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4659
- /* @__PURE__ */ jsx12(Text9, { children: " " }),
5066
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
5067
+ /* @__PURE__ */ jsx14(Text11, { children: " " }),
4660
5068
  rows,
4661
- /* @__PURE__ */ jsx12(Text9, { children: " " })
5069
+ /* @__PURE__ */ jsx14(Text11, { children: " " })
4662
5070
  ] });
4663
5071
  }
4664
5072
 
5073
+ // src/components/StyledHelpHeader.tsx
5074
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
5075
+ function StyledHelpHeader({
5076
+ sections
5077
+ }) {
5078
+ const descriptionColumnOffset = getDescriptionColumnOffset(sections);
5079
+ return /* @__PURE__ */ jsxs12(
5080
+ Box12,
5081
+ {
5082
+ flexDirection: "row",
5083
+ borderStyle: "single",
5084
+ borderDimColor: true,
5085
+ paddingX: 1,
5086
+ paddingY: 1,
5087
+ children: [
5088
+ /* @__PURE__ */ jsx15(Box12, { flexShrink: 0, width: descriptionColumnOffset, children: /* @__PURE__ */ jsx15(Zorb, { size: 20 }) }),
5089
+ /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: /* @__PURE__ */ jsx15(Text12, { bold: true, children: "Zora CLI" }) })
5090
+ ]
5091
+ }
5092
+ );
5093
+ }
5094
+
4665
5095
  // src/index.tsx
4666
- import { jsx as jsx13 } from "react/jsx-runtime";
5096
+ import { jsx as jsx16 } from "react/jsx-runtime";
4667
5097
  if (process.env.ZORA_API_TARGET) {
4668
5098
  setApiBaseUrl(process.env.ZORA_API_TARGET);
4669
5099
  }
4670
- var version = true ? "0.3.0" : JSON.parse(
5100
+ var version = true ? "0.3.1" : JSON.parse(
4671
5101
  readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
4672
5102
  ).version;
5103
+ function styledHelpWriteOut(showHeader) {
5104
+ return (str) => {
5105
+ if (supportsTruecolor()) {
5106
+ const sections = parseHelpSections(str);
5107
+ if (sections.length > 0) {
5108
+ const header = showHeader ? /* @__PURE__ */ jsx16(StyledHelpHeader, { sections }) : void 0;
5109
+ renderOnce(/* @__PURE__ */ jsx16(StyledHelp, { sections, header }));
5110
+ return;
5111
+ }
5112
+ }
5113
+ process.stdout.write(str);
5114
+ };
5115
+ }
4673
5116
  var buildProgram = () => {
4674
- const program2 = new Command12().name("zora").description("Zora CLI").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
5117
+ const program2 = new Command12().name("zora").description("A developer CLI for the Zora platform").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
5118
+ const helpWidth = (process.stdout.columns || 80) - 4;
5119
+ program2.configureHelp({ helpWidth });
5120
+ program2.configureOutput({ writeOut: styledHelpWriteOut(true) });
5121
+ program2.action(() => {
5122
+ program2.outputHelp();
5123
+ });
4675
5124
  program2.addCommand(authCommand);
4676
5125
  program2.addCommand(balanceCommand);
4677
5126
  program2.addCommand(buyCommand);
@@ -4683,9 +5132,17 @@ var buildProgram = () => {
4683
5132
  program2.addCommand(walletCommand);
4684
5133
  program2.addCommand(sellCommand);
4685
5134
  program2.addCommand(sendCommand);
5135
+ const applyToSubcommands = (parent) => {
5136
+ for (const cmd of parent.commands) {
5137
+ cmd.configureHelp({ helpWidth });
5138
+ cmd.configureOutput({ writeOut: styledHelpWriteOut(false) });
5139
+ applyToSubcommands(cmd);
5140
+ }
5141
+ };
5142
+ applyToSubcommands(program2);
4686
5143
  program2.hook("preAction", (_thisCommand, actionCommand) => {
4687
5144
  const expected = actionCommand.registeredArguments.length;
4688
- if (expected > 0 && actionCommand.args.length < expected) {
5145
+ if (expected > 0 && actionCommand.args.length === 0) {
4689
5146
  actionCommand.outputHelp();
4690
5147
  process.exit(1);
4691
5148
  }
@@ -4694,10 +5151,6 @@ var buildProgram = () => {
4694
5151
  };
4695
5152
  var program = buildProgram();
4696
5153
  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
5154
  console.warn(
4702
5155
  "\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
4703
5156
  );