@zoralabs/cli 1.0.0 → 1.1.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.
- package/README.md +27 -19
- package/dist/index.js +2664 -1013
- package/package.json +5 -3
- package/scripts/postinstall.mjs +72 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
5
|
import { ExitPromptError } from "@inquirer/core";
|
|
6
6
|
import "fs";
|
|
7
7
|
import { setApiBaseUrl } from "@zoralabs/coins-sdk";
|
|
@@ -20,11 +20,117 @@ import {
|
|
|
20
20
|
import { join } from "path";
|
|
21
21
|
|
|
22
22
|
// src/lib/errors.ts
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
BaseError as ViemBaseError,
|
|
25
|
+
ContractFunctionRevertedError,
|
|
26
|
+
InsufficientFundsError,
|
|
27
|
+
decodeErrorResult
|
|
28
|
+
} from "viem";
|
|
29
|
+
var TRADE_ERROR_ABI = [
|
|
30
|
+
{ type: "error", name: "SlippageBoundsExceeded", inputs: [] },
|
|
31
|
+
{ type: "error", name: "InsufficientLiquidity", inputs: [] },
|
|
32
|
+
{ type: "error", name: "InsufficientFunds", inputs: [] },
|
|
33
|
+
{ type: "error", name: "EthAmountTooSmall", inputs: [] },
|
|
34
|
+
{ type: "error", name: "EthAmountMismatch", inputs: [] },
|
|
35
|
+
{ type: "error", name: "ERC20TransferAmountMismatch", inputs: [] },
|
|
36
|
+
{ type: "error", name: "EthTransferFailed", inputs: [] },
|
|
37
|
+
{ type: "error", name: "EthTransferInvalid", inputs: [] },
|
|
38
|
+
{ type: "error", name: "MarketNotGraduated", inputs: [] },
|
|
39
|
+
{ type: "error", name: "MarketAlreadyGraduated", inputs: [] },
|
|
40
|
+
{ type: "error", name: "NotEnoughLiquidity", inputs: [] },
|
|
41
|
+
{ type: "error", name: "InsufficientOutputAmount", inputs: [] },
|
|
42
|
+
{ type: "error", name: "InvalidPrice", inputs: [] },
|
|
43
|
+
{ type: "error", name: "InvalidPriceOrLiquidity", inputs: [] },
|
|
44
|
+
{ type: "error", name: "PriceOverflow", inputs: [] },
|
|
45
|
+
{
|
|
46
|
+
type: "error",
|
|
47
|
+
name: "SwapReverted",
|
|
48
|
+
inputs: [{ name: "error", type: "bytes" }]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "error",
|
|
52
|
+
name: "OnlyPool",
|
|
53
|
+
inputs: [
|
|
54
|
+
{ name: "sender", type: "address" },
|
|
55
|
+
{ name: "pool", type: "address" }
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{ type: "error", name: "OnlyWeth", inputs: [] }
|
|
59
|
+
];
|
|
60
|
+
var TRADE_ERROR_MESSAGES = {
|
|
61
|
+
// Slippage / price
|
|
62
|
+
SlippageBoundsExceeded: "Price moved too much during your trade. Try increasing --slippage (e.g. --slippage 3) or reducing the amount.",
|
|
63
|
+
InsufficientOutputAmount: "Trade would produce less output than the minimum. Try increasing --slippage or reducing the amount.",
|
|
64
|
+
InvalidPrice: "Invalid price calculation. The pool may be in an unusual state. Try again later.",
|
|
65
|
+
InvalidPriceOrLiquidity: "Invalid price or liquidity state. The pool may be in an unusual state. Try again later.",
|
|
66
|
+
PriceOverflow: "Price calculation overflow. Try a smaller trade amount.",
|
|
67
|
+
// Liquidity
|
|
68
|
+
InsufficientLiquidity: "Not enough liquidity in the pool for this trade. Try a smaller amount.",
|
|
69
|
+
NotEnoughLiquidity: "Not enough liquidity in the pool for this trade. Try a smaller amount.",
|
|
70
|
+
InsufficientFunds: "Not enough funds. Try a lower amount or run 'zora balance spendable' to check your balance.",
|
|
71
|
+
// Transfer
|
|
72
|
+
EthAmountTooSmall: "ETH amount is too small to execute this trade. Try a larger amount.",
|
|
73
|
+
EthAmountMismatch: "ETH amount sent doesn't match the expected value. Please report this issue.",
|
|
74
|
+
ERC20TransferAmountMismatch: "Token transfer amount mismatch. The token may have a transfer fee. Try a different amount.",
|
|
75
|
+
EthTransferFailed: "ETH transfer failed. The recipient may not accept ETH.",
|
|
76
|
+
EthTransferInvalid: "Invalid ETH transfer. This trade uses a token pair that doesn't involve ETH.",
|
|
77
|
+
SwapReverted: "The underlying swap failed. Try a different amount or increasing --slippage.",
|
|
78
|
+
// Market state
|
|
79
|
+
MarketNotGraduated: "This coin's market hasn't graduated yet. It may not support this trade type.",
|
|
80
|
+
MarketAlreadyGraduated: "This coin's market has already graduated. Try trading through the graduated pool.",
|
|
81
|
+
// Access (internal routing — unlikely to reach users)
|
|
82
|
+
OnlyPool: "This function can only be called by the pool contract.",
|
|
83
|
+
OnlyWeth: "This function only accepts WETH."
|
|
84
|
+
};
|
|
85
|
+
function findHexData(value) {
|
|
86
|
+
if (typeof value === "string" && value.startsWith("0x") && value.length >= 10) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
if (value && typeof value === "object" && "data" in value) {
|
|
90
|
+
return findHexData(value.data);
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
function extractRevertData(err) {
|
|
95
|
+
let current = err;
|
|
96
|
+
while (current && typeof current === "object") {
|
|
97
|
+
const data = findHexData(current.data);
|
|
98
|
+
if (data) return data;
|
|
99
|
+
current = current.cause;
|
|
100
|
+
}
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
function decodeTradeRevert(err) {
|
|
104
|
+
const revertError = err.walk(
|
|
105
|
+
(e) => e instanceof ContractFunctionRevertedError
|
|
106
|
+
);
|
|
107
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
108
|
+
const errorName = revertError.data?.errorName;
|
|
109
|
+
if (errorName && errorName !== "Error" && errorName !== "Panic") {
|
|
110
|
+
return TRADE_ERROR_MESSAGES[errorName] ?? `Transaction reverted: ${errorName}`;
|
|
111
|
+
}
|
|
112
|
+
if (revertError.reason) {
|
|
113
|
+
return `Transaction reverted: ${revertError.reason}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const revertData = extractRevertData(err);
|
|
117
|
+
if (revertData) {
|
|
118
|
+
try {
|
|
119
|
+
const decoded = decodeErrorResult({
|
|
120
|
+
abi: TRADE_ERROR_ABI,
|
|
121
|
+
data: revertData
|
|
122
|
+
});
|
|
123
|
+
return TRADE_ERROR_MESSAGES[decoded.errorName] ?? `Transaction reverted: ${decoded.errorName}`;
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
var MAX_ERROR_LENGTH = 120;
|
|
24
130
|
function formatError(err) {
|
|
25
131
|
if (!(err instanceof Error)) return String(err);
|
|
26
132
|
const msg = err.message;
|
|
27
|
-
return msg.length >
|
|
133
|
+
return msg.length > MAX_ERROR_LENGTH ? msg.slice(0, MAX_ERROR_LENGTH) + "..." : msg;
|
|
28
134
|
}
|
|
29
135
|
function tradeErrorMessage(err) {
|
|
30
136
|
if (!(err instanceof Error)) return String(err);
|
|
@@ -32,6 +138,8 @@ function tradeErrorMessage(err) {
|
|
|
32
138
|
const insufficient = err.walk((e) => e instanceof InsufficientFundsError);
|
|
33
139
|
if (insufficient)
|
|
34
140
|
return "Not enough funds. Try a lower amount or run 'zora balance spendable' to check your balance.";
|
|
141
|
+
const decoded = decodeTradeRevert(err);
|
|
142
|
+
if (decoded) return decoded;
|
|
35
143
|
return err.shortMessage;
|
|
36
144
|
}
|
|
37
145
|
return apiErrorMessage(err);
|
|
@@ -52,6 +160,12 @@ function apiErrorMessage(err) {
|
|
|
52
160
|
return "Zora is temporarily unavailable. Try again later.";
|
|
53
161
|
return formatError(err);
|
|
54
162
|
}
|
|
163
|
+
function extractErrorMessage(error) {
|
|
164
|
+
if (typeof error === "object" && error !== null && "error" in error) {
|
|
165
|
+
return String(error.error);
|
|
166
|
+
}
|
|
167
|
+
return JSON.stringify(error);
|
|
168
|
+
}
|
|
55
169
|
function bannedCoinMessage(address) {
|
|
56
170
|
return `The coin at ${address} is unavailable because it violates the Zora terms of service.`;
|
|
57
171
|
}
|
|
@@ -67,6 +181,21 @@ function fsErrorMessage(err, path) {
|
|
|
67
181
|
return formatError(err);
|
|
68
182
|
}
|
|
69
183
|
|
|
184
|
+
// src/lib/exit.ts
|
|
185
|
+
var SUCCESS = 0;
|
|
186
|
+
var ERROR = 1;
|
|
187
|
+
var CliExitError = class extends Error {
|
|
188
|
+
exitCode;
|
|
189
|
+
constructor(code) {
|
|
190
|
+
super(`process.exit(${code})`);
|
|
191
|
+
this.name = "CliExitError";
|
|
192
|
+
this.exitCode = code;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var safeExit = (code) => {
|
|
196
|
+
throw new CliExitError(code);
|
|
197
|
+
};
|
|
198
|
+
|
|
70
199
|
// src/lib/config.ts
|
|
71
200
|
import { homedir, platform } from "os";
|
|
72
201
|
function getConfigDir() {
|
|
@@ -164,7 +293,7 @@ function getEnvApiKey() {
|
|
|
164
293
|
console.error(
|
|
165
294
|
"ZORA_API_KEY is set but empty. Provide a valid key or unset the variable."
|
|
166
295
|
);
|
|
167
|
-
|
|
296
|
+
safeExit(ERROR);
|
|
168
297
|
}
|
|
169
298
|
return envKey;
|
|
170
299
|
}
|
|
@@ -243,7 +372,7 @@ var outputErrorAndExit = (json, message, suggestion) => {
|
|
|
243
372
|
console.error(`\x1B[2m${suggestion}\x1B[0m`);
|
|
244
373
|
}
|
|
245
374
|
}
|
|
246
|
-
|
|
375
|
+
safeExit(ERROR);
|
|
247
376
|
};
|
|
248
377
|
var outputData = (json, opts) => {
|
|
249
378
|
if (json) {
|
|
@@ -346,14 +475,14 @@ var resolveAccount = (json = false) => {
|
|
|
346
475
|
console.error(
|
|
347
476
|
"No wallet configured. Run 'zora setup' to create or import one."
|
|
348
477
|
);
|
|
349
|
-
|
|
478
|
+
safeExit(ERROR);
|
|
350
479
|
}
|
|
351
480
|
try {
|
|
352
481
|
return privateKeyToAccount(normalizeKey(key));
|
|
353
482
|
} catch (err) {
|
|
354
483
|
console.error(`\u2717 Invalid private key: ${formatError(err)}`);
|
|
355
484
|
console.error(" Run 'zora setup --force' to replace it.");
|
|
356
|
-
|
|
485
|
+
safeExit(ERROR);
|
|
357
486
|
}
|
|
358
487
|
};
|
|
359
488
|
function formatRpcError(error) {
|
|
@@ -365,6 +494,27 @@ function formatRpcError(error) {
|
|
|
365
494
|
}
|
|
366
495
|
return JSON.stringify(error);
|
|
367
496
|
}
|
|
497
|
+
function extractRpcHexData(data) {
|
|
498
|
+
if (typeof data === "string" && data.startsWith("0x") && data.length >= 10) {
|
|
499
|
+
return data;
|
|
500
|
+
}
|
|
501
|
+
if (data && typeof data === "object" && "data" in data) {
|
|
502
|
+
return extractRpcHexData(data.data);
|
|
503
|
+
}
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
506
|
+
function buildRpcError(rpcError) {
|
|
507
|
+
if (rpcError && typeof rpcError === "object" && "code" in rpcError && typeof rpcError.code === "number") {
|
|
508
|
+
const typed = rpcError;
|
|
509
|
+
const err = new Error(typed.message ?? "RPC error");
|
|
510
|
+
err.code = typed.code;
|
|
511
|
+
if (typed.data !== void 0) {
|
|
512
|
+
err.data = extractRpcHexData(typed.data) ?? typed.data;
|
|
513
|
+
}
|
|
514
|
+
return err;
|
|
515
|
+
}
|
|
516
|
+
return new Error(`CLI RPC request failed: ${formatRpcError(rpcError)}`);
|
|
517
|
+
}
|
|
368
518
|
function createCliRpcTransport(chainId = base.id) {
|
|
369
519
|
return custom({
|
|
370
520
|
async request({
|
|
@@ -382,15 +532,11 @@ function createCliRpcTransport(chainId = base.id) {
|
|
|
382
532
|
throw new Error(`CLI RPC request failed: ${formatRpcError(err)}`);
|
|
383
533
|
}
|
|
384
534
|
if (response.error) {
|
|
385
|
-
throw
|
|
386
|
-
`CLI RPC request failed: ${formatRpcError(response.error)}`
|
|
387
|
-
);
|
|
535
|
+
throw buildRpcError(response.error);
|
|
388
536
|
}
|
|
389
537
|
const payload = response.data;
|
|
390
538
|
if (payload && typeof payload === "object" && "error" in payload && payload.error) {
|
|
391
|
-
throw
|
|
392
|
-
`CLI RPC request failed: ${formatRpcError(payload.error)}`
|
|
393
|
-
);
|
|
539
|
+
throw buildRpcError(payload.error);
|
|
394
540
|
}
|
|
395
541
|
if (payload && typeof payload === "object" && "result" in payload) {
|
|
396
542
|
return payload.result;
|
|
@@ -441,7 +587,7 @@ var getClient = () => {
|
|
|
441
587
|
return client;
|
|
442
588
|
};
|
|
443
589
|
var commonProperties = () => ({
|
|
444
|
-
cli_version: true ? "1.
|
|
590
|
+
cli_version: true ? "1.1.0" : "development",
|
|
445
591
|
os: process.platform,
|
|
446
592
|
arch: process.arch,
|
|
447
593
|
node_version: process.version
|
|
@@ -595,7 +741,7 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
595
741
|
|
|
596
742
|
// src/commands/balance.tsx
|
|
597
743
|
import { Command as Command2 } from "commander";
|
|
598
|
-
import { Box as
|
|
744
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
599
745
|
import { getProfileBalances, setApiKey } from "@zoralabs/coins-sdk";
|
|
600
746
|
|
|
601
747
|
// src/lib/render.tsx
|
|
@@ -684,6 +830,28 @@ var Table = ({
|
|
|
684
830
|
// src/lib/format.ts
|
|
685
831
|
import { format, formatDistanceStrict } from "date-fns";
|
|
686
832
|
import { formatUnits } from "viem";
|
|
833
|
+
|
|
834
|
+
// src/lib/types.ts
|
|
835
|
+
var SORT_LABELS = {
|
|
836
|
+
mcap: "Top by Market Cap",
|
|
837
|
+
volume: "Top by 24h Volume",
|
|
838
|
+
new: "New",
|
|
839
|
+
trending: "Trending",
|
|
840
|
+
featured: "Featured"
|
|
841
|
+
};
|
|
842
|
+
var TYPE_LABELS = {
|
|
843
|
+
all: "all",
|
|
844
|
+
trend: "trends",
|
|
845
|
+
"creator-coin": "creator coins",
|
|
846
|
+
post: "posts"
|
|
847
|
+
};
|
|
848
|
+
var COIN_TYPE_DISPLAY = {
|
|
849
|
+
CONTENT: "post",
|
|
850
|
+
CREATOR: "creator-coin",
|
|
851
|
+
TREND: "trend"
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// src/lib/format.ts
|
|
687
855
|
function formatCompactUsd(value) {
|
|
688
856
|
if (!value || Number(value) === 0) return "$0";
|
|
689
857
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -753,6 +921,9 @@ var formatAmountDisplay = (amount, decimals) => {
|
|
|
753
921
|
maximumFractionDigits: 2
|
|
754
922
|
}).format(Number(formatted));
|
|
755
923
|
};
|
|
924
|
+
var formatCoinsDisplay = (coinsOut) => new Intl.NumberFormat("en-US", {
|
|
925
|
+
maximumFractionDigits: 2
|
|
926
|
+
}).format(Number(coinsOut));
|
|
756
927
|
function computeMarketCapChange24h(marketCap, marketCapDelta24h) {
|
|
757
928
|
if (marketCap === null || marketCapDelta24h === null || marketCap - marketCapDelta24h === 0)
|
|
758
929
|
return null;
|
|
@@ -761,6 +932,17 @@ function computeMarketCapChange24h(marketCap, marketCapDelta24h) {
|
|
|
761
932
|
);
|
|
762
933
|
}
|
|
763
934
|
var truncateAddress = (address) => `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
935
|
+
function formatCoinType(coinType) {
|
|
936
|
+
if (!coinType) return "";
|
|
937
|
+
return COIN_TYPE_DISPLAY[coinType] ?? coinType;
|
|
938
|
+
}
|
|
939
|
+
function formatCoinName(coin) {
|
|
940
|
+
if (!coin) return "Unknown";
|
|
941
|
+
if (coin.coinType === "CONTENT" && !coin.name && coin.address) {
|
|
942
|
+
return truncateAddress(coin.address);
|
|
943
|
+
}
|
|
944
|
+
return coin.name ?? "Unknown";
|
|
945
|
+
}
|
|
764
946
|
|
|
765
947
|
// src/lib/balance-format.ts
|
|
766
948
|
var COIN_DECIMALS = 18;
|
|
@@ -779,11 +961,17 @@ var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
|
|
|
779
961
|
return rawBalance;
|
|
780
962
|
}
|
|
781
963
|
};
|
|
782
|
-
var
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
964
|
+
var computeBalanceUsdValue = (balance, marketValueUsd, priceInUsdc) => {
|
|
965
|
+
if (marketValueUsd != null && marketValueUsd !== "") {
|
|
966
|
+
const parsed = Number(marketValueUsd);
|
|
967
|
+
if (!Number.isFinite(parsed)) return null;
|
|
968
|
+
return Number(parsed.toFixed(6));
|
|
969
|
+
}
|
|
970
|
+
if (!priceInUsdc) return null;
|
|
971
|
+
const price = Number(priceInUsdc);
|
|
972
|
+
if (!Number.isFinite(price)) return null;
|
|
973
|
+
const value = parseRawBalance(balance) * price;
|
|
974
|
+
return Number(value.toFixed(6));
|
|
787
975
|
};
|
|
788
976
|
var formatBalance = (balance) => {
|
|
789
977
|
const n = parseRawBalance(balance);
|
|
@@ -803,7 +991,7 @@ var trimTrailingZeros = (value) => {
|
|
|
803
991
|
};
|
|
804
992
|
|
|
805
993
|
// src/lib/balance-columns.tsx
|
|
806
|
-
var
|
|
994
|
+
var SORT_LABELS2 = {
|
|
807
995
|
"usd-value": "USD Value",
|
|
808
996
|
balance: "Balance",
|
|
809
997
|
"market-cap": "Market Cap",
|
|
@@ -824,14 +1012,18 @@ var balanceColumns = [
|
|
|
824
1012
|
{ header: "#", width: 5, accessor: (row) => String(row.rank) },
|
|
825
1013
|
{
|
|
826
1014
|
header: "Name",
|
|
827
|
-
width:
|
|
828
|
-
accessor: (row) => row.coin
|
|
1015
|
+
width: 20,
|
|
1016
|
+
accessor: (row) => formatCoinName(row.coin)
|
|
829
1017
|
},
|
|
830
1018
|
{
|
|
831
|
-
header: "
|
|
832
|
-
width:
|
|
833
|
-
|
|
834
|
-
|
|
1019
|
+
header: "Address",
|
|
1020
|
+
width: 14,
|
|
1021
|
+
accessor: (row) => row.coin?.address ? truncateAddress(row.coin.address) : ""
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
header: "Type",
|
|
1025
|
+
width: 14,
|
|
1026
|
+
accessor: (row) => formatCoinType(row.coin?.coinType)
|
|
835
1027
|
},
|
|
836
1028
|
{
|
|
837
1029
|
header: "Balance",
|
|
@@ -841,7 +1033,16 @@ var balanceColumns = [
|
|
|
841
1033
|
{
|
|
842
1034
|
header: "USD Value",
|
|
843
1035
|
width: 14,
|
|
844
|
-
accessor: (row) =>
|
|
1036
|
+
accessor: (row) => {
|
|
1037
|
+
const value = computeBalanceUsdValue(
|
|
1038
|
+
row.balance,
|
|
1039
|
+
row.valuation?.marketValueUsd,
|
|
1040
|
+
row.coin?.tokenPrice?.priceInUsdc
|
|
1041
|
+
);
|
|
1042
|
+
if (value === null) return "-";
|
|
1043
|
+
if (value < 0.01) return "<$0.01";
|
|
1044
|
+
return formatUsd(value);
|
|
1045
|
+
}
|
|
845
1046
|
},
|
|
846
1047
|
{
|
|
847
1048
|
header: "Market Cap",
|
|
@@ -855,6 +1056,7 @@ var balanceColumns = [
|
|
|
855
1056
|
color: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).color
|
|
856
1057
|
}
|
|
857
1058
|
];
|
|
1059
|
+
var API_KEY_BANNER = "Valuation is more accurately evaluated when using an API key. Run `zora setup` to configure one.";
|
|
858
1060
|
|
|
859
1061
|
// src/hooks/use-auto-refresh.ts
|
|
860
1062
|
import { useState, useEffect, useCallback } from "react";
|
|
@@ -863,10 +1065,11 @@ var useAutoRefresh = (intervalSeconds, enabled) => {
|
|
|
863
1065
|
const [secondsUntilRefresh, setSecondsUntilRefresh] = useState(intervalSeconds);
|
|
864
1066
|
const [resetCount, setResetCount] = useState(0);
|
|
865
1067
|
const triggerManualRefresh = useCallback(() => {
|
|
866
|
-
if (!enabled) return;
|
|
867
1068
|
setRefreshCount((c) => c + 1);
|
|
868
|
-
|
|
869
|
-
|
|
1069
|
+
if (enabled) {
|
|
1070
|
+
setSecondsUntilRefresh(intervalSeconds);
|
|
1071
|
+
setResetCount((c) => c + 1);
|
|
1072
|
+
}
|
|
870
1073
|
}, [enabled, intervalSeconds]);
|
|
871
1074
|
useEffect(() => {
|
|
872
1075
|
if (!enabled) return;
|
|
@@ -882,10 +1085,41 @@ var useAutoRefresh = (intervalSeconds, enabled) => {
|
|
|
882
1085
|
}, 1e3);
|
|
883
1086
|
return () => clearInterval(ticker);
|
|
884
1087
|
}, [enabled, intervalSeconds, resetCount]);
|
|
885
|
-
|
|
886
|
-
|
|
1088
|
+
return {
|
|
1089
|
+
refreshCount,
|
|
1090
|
+
secondsUntilRefresh: enabled ? secondsUntilRefresh : 0,
|
|
1091
|
+
triggerManualRefresh
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// src/lib/clipboard.ts
|
|
1096
|
+
import { execFileSync } from "child_process";
|
|
1097
|
+
import { platform as platform2 } from "os";
|
|
1098
|
+
var copyToClipboard = (text) => {
|
|
1099
|
+
const os = platform2();
|
|
1100
|
+
try {
|
|
1101
|
+
if (os === "darwin") {
|
|
1102
|
+
execFileSync("pbcopy", {
|
|
1103
|
+
input: text,
|
|
1104
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
1105
|
+
});
|
|
1106
|
+
} else if (os === "linux") {
|
|
1107
|
+
execFileSync("xclip", ["-selection", "clipboard"], {
|
|
1108
|
+
input: text,
|
|
1109
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
1110
|
+
});
|
|
1111
|
+
} else if (os === "win32") {
|
|
1112
|
+
execFileSync("clip", {
|
|
1113
|
+
input: text,
|
|
1114
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
1115
|
+
});
|
|
1116
|
+
} else {
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
} catch {
|
|
1121
|
+
return false;
|
|
887
1122
|
}
|
|
888
|
-
return { refreshCount, secondsUntilRefresh, triggerManualRefresh };
|
|
889
1123
|
};
|
|
890
1124
|
|
|
891
1125
|
// src/components/BalanceView.tsx
|
|
@@ -894,74 +1128,76 @@ var BalanceView = ({
|
|
|
894
1128
|
fetchData,
|
|
895
1129
|
sort,
|
|
896
1130
|
mode = "full",
|
|
897
|
-
initialCursor,
|
|
898
1131
|
autoRefresh = false,
|
|
899
|
-
intervalSeconds = 30
|
|
1132
|
+
intervalSeconds = 30,
|
|
1133
|
+
hasApiKey = false
|
|
900
1134
|
}) => {
|
|
901
1135
|
const { exit } = useApp();
|
|
902
1136
|
const [loading, setLoading] = useState2(true);
|
|
903
1137
|
const [isRefreshing, setIsRefreshing] = useState2(false);
|
|
904
1138
|
const [error, setError] = useState2(null);
|
|
905
1139
|
const [data, setData] = useState2(null);
|
|
906
|
-
const paginated = mode === "coins";
|
|
907
|
-
const [page, setPage] = useState2(1);
|
|
908
|
-
const [cursorHistory, setCursorHistory] = useState2(
|
|
909
|
-
[]
|
|
910
|
-
);
|
|
911
|
-
const [currentCursor, setCurrentCursor] = useState2(
|
|
912
|
-
initialCursor
|
|
913
|
-
);
|
|
914
1140
|
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
915
|
-
const [manualRefreshCount, setManualRefreshCount] = useState2(0);
|
|
916
1141
|
const hasLoadedOnce = useRef(false);
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
)
|
|
1142
|
+
const [selectedRow, setSelectedRow] = useState2(0);
|
|
1143
|
+
const [copyFeedback, setCopyFeedback] = useState2(null);
|
|
1144
|
+
const load = useCallback2(async () => {
|
|
1145
|
+
if (hasLoadedOnce.current) {
|
|
1146
|
+
setIsRefreshing(true);
|
|
1147
|
+
} else {
|
|
1148
|
+
setLoading(true);
|
|
1149
|
+
}
|
|
1150
|
+
setError(null);
|
|
1151
|
+
try {
|
|
1152
|
+
const result = await fetchData();
|
|
1153
|
+
setData(result);
|
|
1154
|
+
hasLoadedOnce.current = true;
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1157
|
+
}
|
|
1158
|
+
setLoading(false);
|
|
1159
|
+
setIsRefreshing(false);
|
|
1160
|
+
}, [fetchData]);
|
|
1161
|
+
useEffect2(() => {
|
|
1162
|
+
load();
|
|
1163
|
+
}, [load, refreshCount]);
|
|
1164
|
+
const showCoins = mode === "full";
|
|
937
1165
|
useEffect2(() => {
|
|
938
|
-
|
|
939
|
-
|
|
1166
|
+
if (data && showCoins) {
|
|
1167
|
+
setSelectedRow(
|
|
1168
|
+
(r) => Math.min(r, Math.max(0, data.rankedBalances.length - 1))
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
}, [data, showCoins]);
|
|
940
1172
|
useInput((input, key) => {
|
|
941
1173
|
if (input === "q" || key.escape) {
|
|
942
1174
|
exit();
|
|
943
1175
|
return;
|
|
944
1176
|
}
|
|
945
1177
|
if (loading) return;
|
|
1178
|
+
if (showCoins && data && data.rankedBalances.length > 0) {
|
|
1179
|
+
if (key.upArrow || input === "k") {
|
|
1180
|
+
setSelectedRow((r) => Math.max(0, r - 1));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (key.downArrow || input === "j") {
|
|
1184
|
+
setSelectedRow((r) => Math.min(data.rankedBalances.length - 1, r + 1));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (input === "c" || key.return) {
|
|
1188
|
+
const coin = data.rankedBalances[selectedRow]?.coin;
|
|
1189
|
+
if (coin?.address) {
|
|
1190
|
+
const ok = copyToClipboard(coin.address);
|
|
1191
|
+
setCopyFeedback(ok ? "Copied!" : "Copy failed");
|
|
1192
|
+
setTimeout(() => setCopyFeedback(null), 1500);
|
|
1193
|
+
}
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
946
1197
|
if (input === "r") {
|
|
947
1198
|
triggerManualRefresh();
|
|
948
|
-
setManualRefreshCount((c) => c + 1);
|
|
949
1199
|
return;
|
|
950
1200
|
}
|
|
951
|
-
if (!paginated) return;
|
|
952
|
-
const canGoNext = data?.pageInfo?.hasNextPage && data.pageInfo.endCursor;
|
|
953
|
-
const canGoPrev = cursorHistory.length > 0;
|
|
954
|
-
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
955
|
-
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
956
|
-
setCurrentCursor(data.pageInfo.endCursor);
|
|
957
|
-
setPage((p) => p + 1);
|
|
958
|
-
}
|
|
959
|
-
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
960
|
-
const prev = cursorHistory[cursorHistory.length - 1];
|
|
961
|
-
setCursorHistory((h) => h.slice(0, -1));
|
|
962
|
-
setCurrentCursor(prev);
|
|
963
|
-
setPage((p) => p - 1);
|
|
964
|
-
}
|
|
965
1201
|
});
|
|
966
1202
|
if (error && !data) {
|
|
967
1203
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -989,13 +1225,14 @@ var BalanceView = ({
|
|
|
989
1225
|
}
|
|
990
1226
|
if (!data) return null;
|
|
991
1227
|
const hints = [];
|
|
992
|
-
if (
|
|
993
|
-
|
|
1228
|
+
if (showCoins && data.rankedBalances.length > 0) {
|
|
1229
|
+
hints.push("\u2191\u2193 select");
|
|
1230
|
+
hints.push("enter/c copy address");
|
|
1231
|
+
}
|
|
994
1232
|
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
995
1233
|
hints.push("q quit");
|
|
996
|
-
const footer = hints.join(" \xB7 ");
|
|
1234
|
+
const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
|
|
997
1235
|
const showWallet = mode === "full" || mode === "wallet";
|
|
998
|
-
const showCoins = mode === "full" || mode === "coins";
|
|
999
1236
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1000
1237
|
isRefreshing && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1001
1238
|
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
@@ -1033,94 +1270,328 @@ var BalanceView = ({
|
|
|
1033
1270
|
{
|
|
1034
1271
|
columns: balanceColumns,
|
|
1035
1272
|
data: data.rankedBalances,
|
|
1036
|
-
title: `Coins \xB7 sorted by ${
|
|
1037
|
-
subtitle:
|
|
1273
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
|
|
1274
|
+
subtitle: `${data.rankedBalances.length} of ${data.total}`,
|
|
1275
|
+
selectedRow
|
|
1038
1276
|
}
|
|
1039
1277
|
) : null,
|
|
1278
|
+
!hasApiKey && showCoins && data.rankedBalances.length > 0 && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: API_KEY_BANNER }) }),
|
|
1040
1279
|
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: footer }) })
|
|
1041
1280
|
] });
|
|
1042
1281
|
};
|
|
1043
1282
|
|
|
1044
|
-
// src/
|
|
1045
|
-
import {
|
|
1283
|
+
// src/components/BalanceCoinsView.tsx
|
|
1284
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1285
|
+
|
|
1286
|
+
// src/components/PaginatedTableView.tsx
|
|
1046
1287
|
import {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
} from "
|
|
1052
|
-
import {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
{
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1288
|
+
useState as useState3,
|
|
1289
|
+
useEffect as useEffect3,
|
|
1290
|
+
useCallback as useCallback3,
|
|
1291
|
+
useRef as useRef2
|
|
1292
|
+
} from "react";
|
|
1293
|
+
import { Box as Box3, Text as Text3, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
1294
|
+
import Spinner2 from "ink-spinner";
|
|
1295
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1296
|
+
var CACHE_KEY_FIRST = "__first__";
|
|
1297
|
+
var CACHE_TTL_MS = 6e4;
|
|
1298
|
+
function PaginatedTableView({
|
|
1299
|
+
fetchPage,
|
|
1300
|
+
columns,
|
|
1301
|
+
title,
|
|
1302
|
+
loadingText,
|
|
1303
|
+
emptyState: emptyState8,
|
|
1304
|
+
getAddress,
|
|
1305
|
+
limit = 10,
|
|
1306
|
+
initialCursor,
|
|
1307
|
+
autoRefresh = false,
|
|
1308
|
+
intervalSeconds = 30,
|
|
1309
|
+
formatSubtitle
|
|
1310
|
+
}) {
|
|
1311
|
+
const { exit } = useApp2();
|
|
1312
|
+
const [loading, setLoading] = useState3(true);
|
|
1313
|
+
const [error, setError] = useState3(null);
|
|
1314
|
+
const [items, setItems] = useState3([]);
|
|
1315
|
+
const [total, setTotal] = useState3(0);
|
|
1316
|
+
const [pageInfo, setPageInfo] = useState3(null);
|
|
1317
|
+
const [page, setPage] = useState3(1);
|
|
1318
|
+
const targetPage = useRef2(1);
|
|
1319
|
+
const [cursorHistory, setCursorHistory] = useState3(
|
|
1320
|
+
[]
|
|
1321
|
+
);
|
|
1322
|
+
const [currentCursor, setCurrentCursor] = useState3(
|
|
1323
|
+
initialCursor
|
|
1324
|
+
);
|
|
1325
|
+
const cache = useRef2(/* @__PURE__ */ new Map());
|
|
1326
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
1327
|
+
const [selectedRow, setSelectedRow] = useState3(0);
|
|
1328
|
+
const [copyFeedback, setCopyFeedback] = useState3(null);
|
|
1329
|
+
useEffect3(() => {
|
|
1330
|
+
setSelectedRow((r) => Math.min(r, Math.max(0, items.length - 1)));
|
|
1331
|
+
}, [items.length]);
|
|
1332
|
+
const loadPage = useCallback3(
|
|
1333
|
+
async (cursor) => {
|
|
1334
|
+
const cacheKey = cursor ?? CACHE_KEY_FIRST;
|
|
1335
|
+
const cached = cache.current.get(cacheKey);
|
|
1336
|
+
const isFresh = cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS;
|
|
1337
|
+
if (isFresh) {
|
|
1338
|
+
setItems(cached.result.items);
|
|
1339
|
+
setTotal(cached.result.count ?? cached.result.items.length);
|
|
1340
|
+
setPageInfo(cached.result.pageInfo ?? null);
|
|
1341
|
+
setPage(targetPage.current);
|
|
1342
|
+
setError(null);
|
|
1343
|
+
setLoading(false);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
setLoading(true);
|
|
1347
|
+
setError(null);
|
|
1348
|
+
try {
|
|
1349
|
+
const result = await fetchPage(cursor);
|
|
1350
|
+
cache.current.set(cacheKey, { result, fetchedAt: Date.now() });
|
|
1351
|
+
setItems(result.items);
|
|
1352
|
+
setTotal(result.count ?? result.items.length);
|
|
1353
|
+
setPageInfo(result.pageInfo ?? null);
|
|
1354
|
+
setPage(targetPage.current);
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1357
|
+
}
|
|
1358
|
+
setLoading(false);
|
|
1359
|
+
},
|
|
1360
|
+
[fetchPage]
|
|
1361
|
+
);
|
|
1362
|
+
useEffect3(() => {
|
|
1363
|
+
if (refreshCount === 0) return;
|
|
1364
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
1365
|
+
cache.current.delete(cacheKey);
|
|
1366
|
+
}, [refreshCount, currentCursor]);
|
|
1367
|
+
useEffect3(() => {
|
|
1368
|
+
loadPage(currentCursor);
|
|
1369
|
+
}, [currentCursor, loadPage, refreshCount]);
|
|
1370
|
+
useInput2((input, key) => {
|
|
1371
|
+
if (input === "q" || key.escape) {
|
|
1372
|
+
exit();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (loading) return;
|
|
1376
|
+
if (key.upArrow || input === "k") {
|
|
1377
|
+
setSelectedRow((r) => Math.max(0, r - 1));
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (key.downArrow || input === "j") {
|
|
1381
|
+
setSelectedRow((r) => Math.min(items.length - 1, r + 1));
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (input === "c" || key.return) {
|
|
1385
|
+
const item = items[selectedRow];
|
|
1386
|
+
if (item) {
|
|
1387
|
+
const address = getAddress(item);
|
|
1388
|
+
if (address) {
|
|
1389
|
+
const ok = copyToClipboard(address);
|
|
1390
|
+
setCopyFeedback(ok ? "Copied!" : "Copy failed");
|
|
1391
|
+
setTimeout(() => setCopyFeedback(null), 1500);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
|
|
1397
|
+
const canGoPrev = cursorHistory.length > 0;
|
|
1398
|
+
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
1399
|
+
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
1400
|
+
setCurrentCursor(pageInfo?.endCursor);
|
|
1401
|
+
targetPage.current += 1;
|
|
1402
|
+
setSelectedRow(0);
|
|
1403
|
+
}
|
|
1404
|
+
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
1405
|
+
const prev = cursorHistory[cursorHistory.length - 1];
|
|
1406
|
+
setCursorHistory((h) => h.slice(0, -1));
|
|
1407
|
+
setCurrentCursor(prev);
|
|
1408
|
+
targetPage.current -= 1;
|
|
1409
|
+
setSelectedRow(0);
|
|
1410
|
+
}
|
|
1411
|
+
if (input === "r") {
|
|
1412
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
1413
|
+
cache.current.delete(cacheKey);
|
|
1414
|
+
triggerManualRefresh();
|
|
1415
|
+
setSelectedRow(0);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
if (error) {
|
|
1419
|
+
return /* @__PURE__ */ jsxs3(
|
|
1420
|
+
Box3,
|
|
1421
|
+
{
|
|
1422
|
+
flexDirection: "column",
|
|
1423
|
+
paddingLeft: 1,
|
|
1424
|
+
paddingTop: 1,
|
|
1425
|
+
paddingBottom: 1,
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
1428
|
+
"Error: ",
|
|
1429
|
+
error
|
|
1430
|
+
] }),
|
|
1431
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Press q to exit" }) })
|
|
1432
|
+
]
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
if (loading) {
|
|
1437
|
+
return /* @__PURE__ */ jsx3(Box3, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1438
|
+
/* @__PURE__ */ jsx3(Spinner2, { type: "dots" }),
|
|
1439
|
+
" ",
|
|
1440
|
+
loadingText
|
|
1441
|
+
] }) });
|
|
1442
|
+
}
|
|
1443
|
+
if (items.length === 0) {
|
|
1444
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: emptyState8 });
|
|
1445
|
+
}
|
|
1446
|
+
const rankedItems = items.map((item, i) => ({
|
|
1447
|
+
...item,
|
|
1448
|
+
rank: (page - 1) * limit + i + 1
|
|
1449
|
+
}));
|
|
1450
|
+
const subtitle = formatSubtitle ? formatSubtitle({ page, itemCount: items.length, total }) : `Page ${page} \xB7 ${items.length} of ${total}`;
|
|
1451
|
+
const hints = [];
|
|
1452
|
+
hints.push("\u2191\u2193 select");
|
|
1453
|
+
hints.push("enter/c copy address");
|
|
1454
|
+
if (cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
1455
|
+
if (pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
1456
|
+
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
1457
|
+
hints.push("q quit");
|
|
1458
|
+
const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
|
|
1459
|
+
return /* @__PURE__ */ jsx3(
|
|
1460
|
+
Table,
|
|
1461
|
+
{
|
|
1462
|
+
data: rankedItems,
|
|
1463
|
+
columns,
|
|
1464
|
+
title,
|
|
1465
|
+
subtitle,
|
|
1466
|
+
footer,
|
|
1467
|
+
selectedRow
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/components/BalanceCoinsView.tsx
|
|
1473
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1474
|
+
var emptyState = /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
1475
|
+
/* @__PURE__ */ jsx4(Text4, { children: "No coin balances found." }),
|
|
1476
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
1478
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1479
|
+
" zora buy ",
|
|
1480
|
+
"<address>",
|
|
1481
|
+
" --eth 0.001"
|
|
1482
|
+
] })
|
|
1483
|
+
] }),
|
|
1484
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
1485
|
+
] });
|
|
1486
|
+
var BalanceCoinsView = ({
|
|
1487
|
+
fetchPage,
|
|
1488
|
+
sort,
|
|
1489
|
+
limit,
|
|
1490
|
+
initialCursor,
|
|
1491
|
+
autoRefresh,
|
|
1492
|
+
intervalSeconds,
|
|
1493
|
+
hasApiKey = false
|
|
1494
|
+
}) => {
|
|
1495
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
1496
|
+
/* @__PURE__ */ jsx4(
|
|
1497
|
+
PaginatedTableView,
|
|
1498
|
+
{
|
|
1499
|
+
fetchPage,
|
|
1500
|
+
columns: balanceColumns,
|
|
1501
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
|
|
1502
|
+
loadingText: "Loading\u2026",
|
|
1503
|
+
emptyState,
|
|
1504
|
+
getAddress: (item) => item.coin?.address,
|
|
1505
|
+
limit,
|
|
1506
|
+
initialCursor,
|
|
1507
|
+
autoRefresh,
|
|
1508
|
+
intervalSeconds
|
|
1509
|
+
}
|
|
1510
|
+
),
|
|
1511
|
+
!hasApiKey && /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: API_KEY_BANNER }) })
|
|
1512
|
+
] });
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
// src/lib/wallet-balances.ts
|
|
1516
|
+
import { getTokenInfo } from "@zoralabs/coins-sdk";
|
|
1517
|
+
import {
|
|
1518
|
+
createPublicClient as createPublicClient2,
|
|
1519
|
+
erc20Abi,
|
|
1520
|
+
formatUnits as formatUnits2,
|
|
1521
|
+
http
|
|
1522
|
+
} from "viem";
|
|
1523
|
+
import { base as base2 } from "viem/chains";
|
|
1524
|
+
var TRACKED_TOKENS = [
|
|
1525
|
+
{
|
|
1526
|
+
name: "Ether",
|
|
1527
|
+
symbol: "ETH",
|
|
1528
|
+
address: WETH_ADDRESS,
|
|
1529
|
+
decimals: 18,
|
|
1530
|
+
priceAddress: WETH_ADDRESS,
|
|
1531
|
+
isNative: true
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
name: "USD Coin",
|
|
1535
|
+
symbol: "USDC",
|
|
1536
|
+
address: USDC_ADDRESS,
|
|
1537
|
+
decimals: USDC_DECIMALS,
|
|
1538
|
+
priceAddress: USDC_ADDRESS,
|
|
1539
|
+
fixedPriceUsd: 1
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
name: "ZORA",
|
|
1543
|
+
symbol: "ZORA",
|
|
1544
|
+
address: ZORA_ADDRESS,
|
|
1545
|
+
decimals: 18,
|
|
1546
|
+
priceAddress: ZORA_ADDRESS
|
|
1547
|
+
}
|
|
1548
|
+
];
|
|
1549
|
+
var fetchTokenPriceUsd = async (address, chainId = BASE_CHAIN_ID) => {
|
|
1550
|
+
try {
|
|
1551
|
+
const res = await getTokenInfo({ address, chainId });
|
|
1552
|
+
return res.data?.erc20Token?.currency?.priceUsd ? Number(res.data.erc20Token.currency.priceUsd) : null;
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
console.warn(
|
|
1555
|
+
`Warning: failed to fetch price for ${address}: ${formatError(err)}`
|
|
1556
|
+
);
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
var fetchWalletBalances = async (walletAddress) => {
|
|
1561
|
+
const publicClient = createPublicClient2({ chain: base2, transport: http() });
|
|
1562
|
+
const nativeToken = TRACKED_TOKENS.find((t) => t.isNative);
|
|
1563
|
+
const erc20Tokens = TRACKED_TOKENS.filter((t) => !t.isNative);
|
|
1564
|
+
const [ethBalance, multicallResults] = await Promise.all([
|
|
1565
|
+
publicClient.getBalance({ address: walletAddress }),
|
|
1566
|
+
publicClient.multicall({
|
|
1567
|
+
contracts: erc20Tokens.map((t) => ({
|
|
1568
|
+
address: t.address,
|
|
1569
|
+
abi: erc20Abi,
|
|
1570
|
+
functionName: "balanceOf",
|
|
1571
|
+
args: [walletAddress]
|
|
1572
|
+
}))
|
|
1573
|
+
})
|
|
1574
|
+
]);
|
|
1575
|
+
const rawBalances = /* @__PURE__ */ new Map();
|
|
1576
|
+
if (nativeToken) rawBalances.set(nativeToken, ethBalance);
|
|
1577
|
+
erc20Tokens.forEach((token, i) => {
|
|
1578
|
+
if (multicallResults[i].status === "success") {
|
|
1579
|
+
rawBalances.set(token, multicallResults[i].result);
|
|
1580
|
+
} else {
|
|
1581
|
+
console.warn(`Warning: failed to fetch balance for ${token.symbol}`);
|
|
1582
|
+
rawBalances.set(token, 0n);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
const priceResults = await Promise.allSettled(
|
|
1586
|
+
TRACKED_TOKENS.map(async (token) => {
|
|
1587
|
+
const balance = rawBalances.get(token) ?? 0n;
|
|
1588
|
+
let priceUsd = null;
|
|
1589
|
+
if (token.fixedPriceUsd != null) {
|
|
1590
|
+
priceUsd = token.fixedPriceUsd;
|
|
1591
|
+
} else if (balance > 0n || token.isNative) {
|
|
1592
|
+
priceUsd = await fetchTokenPriceUsd(token.priceAddress);
|
|
1593
|
+
}
|
|
1594
|
+
return { token, balance, priceUsd };
|
|
1124
1595
|
})
|
|
1125
1596
|
);
|
|
1126
1597
|
const resolved = priceResults.map((result, i) => {
|
|
@@ -1157,20 +1628,14 @@ var fetchWalletBalances = async (walletAddress) => {
|
|
|
1157
1628
|
};
|
|
1158
1629
|
|
|
1159
1630
|
// src/commands/balance.tsx
|
|
1160
|
-
import { jsx as
|
|
1631
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1161
1632
|
var SORT_MAP = {
|
|
1162
1633
|
"usd-value": "USD_VALUE",
|
|
1163
1634
|
balance: "BALANCE",
|
|
1164
1635
|
"market-cap": "MARKET_CAP",
|
|
1165
1636
|
"price-change": "PRICE_CHANGE"
|
|
1166
1637
|
};
|
|
1167
|
-
var SORT_OPTIONS = Object.keys(
|
|
1168
|
-
var extractErrorMessage = (error) => {
|
|
1169
|
-
if (typeof error === "object" && error !== null && "error" in error) {
|
|
1170
|
-
return String(error.error);
|
|
1171
|
-
}
|
|
1172
|
-
return JSON.stringify(error);
|
|
1173
|
-
};
|
|
1638
|
+
var SORT_OPTIONS = Object.keys(SORT_LABELS2).join(", ");
|
|
1174
1639
|
var formatBalanceJson = (balance, rank) => {
|
|
1175
1640
|
const priceUsd = balance.coin?.tokenPrice?.priceInUsdc;
|
|
1176
1641
|
const marketCap = balance.coin?.marketCap ? Number(balance.coin.marketCap) : null;
|
|
@@ -1178,7 +1643,11 @@ var formatBalanceJson = (balance, rank) => {
|
|
|
1178
1643
|
const volume24h = balance.coin?.volume24h ? Number(balance.coin.volume24h) : null;
|
|
1179
1644
|
const totalVolume = balance.coin?.totalVolume ? Number(balance.coin.totalVolume) : null;
|
|
1180
1645
|
const priceUsdValue = priceUsd ? Number(priceUsd) : null;
|
|
1181
|
-
const usdValue =
|
|
1646
|
+
const usdValue = computeBalanceUsdValue(
|
|
1647
|
+
balance.balance,
|
|
1648
|
+
balance.valuation?.marketValueUsd,
|
|
1649
|
+
priceUsd
|
|
1650
|
+
);
|
|
1182
1651
|
const marketCapChange24h = computeMarketCapChange24h(
|
|
1183
1652
|
marketCap,
|
|
1184
1653
|
marketCapDelta24h
|
|
@@ -1187,6 +1656,7 @@ var formatBalanceJson = (balance, rank) => {
|
|
|
1187
1656
|
rank,
|
|
1188
1657
|
name: balance.coin?.name ?? null,
|
|
1189
1658
|
symbol: balance.coin?.symbol ?? null,
|
|
1659
|
+
type: formatCoinType(balance.coin?.coinType) || null,
|
|
1190
1660
|
coinType: balance.coin?.coinType ?? null,
|
|
1191
1661
|
chainId: balance.coin?.chainId ?? null,
|
|
1192
1662
|
address: balance.coin?.address ?? null,
|
|
@@ -1208,14 +1678,14 @@ function resolveContext(json) {
|
|
|
1208
1678
|
if (apiKey) {
|
|
1209
1679
|
setApiKey(apiKey);
|
|
1210
1680
|
}
|
|
1211
|
-
return account;
|
|
1681
|
+
return { account, hasApiKey: !!apiKey };
|
|
1212
1682
|
}
|
|
1213
1683
|
function renderWallet(json, walletResult) {
|
|
1214
1684
|
outputData(json, {
|
|
1215
1685
|
json: { wallet: walletResult.walletBalancesJson },
|
|
1216
1686
|
render: () => {
|
|
1217
1687
|
renderOnce(
|
|
1218
|
-
/* @__PURE__ */
|
|
1688
|
+
/* @__PURE__ */ jsx5(
|
|
1219
1689
|
Table,
|
|
1220
1690
|
{
|
|
1221
1691
|
columns: walletColumns,
|
|
@@ -1227,7 +1697,7 @@ function renderWallet(json, walletResult) {
|
|
|
1227
1697
|
}
|
|
1228
1698
|
});
|
|
1229
1699
|
}
|
|
1230
|
-
function renderCoins(json, balances, total, sort, limit, pageInfo) {
|
|
1700
|
+
function renderCoins(json, balances, total, sort, limit, pageInfo, hasApiKey) {
|
|
1231
1701
|
const rankedBalances = balances.map((balance, index) => ({
|
|
1232
1702
|
...balance,
|
|
1233
1703
|
rank: index + 1
|
|
@@ -1247,16 +1717,19 @@ function renderCoins(json, balances, total, sort, limit, pageInfo) {
|
|
|
1247
1717
|
} else {
|
|
1248
1718
|
const footer = pageInfo?.hasNextPage && pageInfo.endCursor ? `Next page: zora balance coins --sort ${sort} --limit ${limit} --after ${pageInfo.endCursor}` : void 0;
|
|
1249
1719
|
renderOnce(
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1720
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1721
|
+
/* @__PURE__ */ jsx5(
|
|
1722
|
+
Table,
|
|
1723
|
+
{
|
|
1724
|
+
columns: balanceColumns,
|
|
1725
|
+
data: rankedBalances,
|
|
1726
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
|
|
1727
|
+
subtitle: `${balances.length} of ${total}`,
|
|
1728
|
+
footer
|
|
1729
|
+
}
|
|
1730
|
+
),
|
|
1731
|
+
!hasApiKey && /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: API_KEY_BANNER }) })
|
|
1732
|
+
] })
|
|
1260
1733
|
);
|
|
1261
1734
|
}
|
|
1262
1735
|
}
|
|
@@ -1312,7 +1785,7 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1312
1785
|
).action(async function() {
|
|
1313
1786
|
const output = getOutputMode(this, "live");
|
|
1314
1787
|
const json = output === "json";
|
|
1315
|
-
const account = resolveContext(json);
|
|
1788
|
+
const { account, hasApiKey } = resolveContext(json);
|
|
1316
1789
|
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1317
1790
|
const sort = "usd-value";
|
|
1318
1791
|
const limit = 10;
|
|
@@ -1362,14 +1835,15 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1362
1835
|
});
|
|
1363
1836
|
} else if (live) {
|
|
1364
1837
|
await renderLive(
|
|
1365
|
-
/* @__PURE__ */
|
|
1838
|
+
/* @__PURE__ */ jsx5(
|
|
1366
1839
|
BalanceView,
|
|
1367
1840
|
{
|
|
1368
1841
|
fetchData: fetchBalanceData,
|
|
1369
1842
|
sort,
|
|
1370
1843
|
mode: "full",
|
|
1371
1844
|
autoRefresh: live,
|
|
1372
|
-
intervalSeconds
|
|
1845
|
+
intervalSeconds,
|
|
1846
|
+
hasApiKey
|
|
1373
1847
|
}
|
|
1374
1848
|
)
|
|
1375
1849
|
);
|
|
@@ -1385,8 +1859,8 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1385
1859
|
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1386
1860
|
);
|
|
1387
1861
|
renderOnce(
|
|
1388
|
-
/* @__PURE__ */
|
|
1389
|
-
/* @__PURE__ */
|
|
1862
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1863
|
+
/* @__PURE__ */ jsx5(
|
|
1390
1864
|
Table,
|
|
1391
1865
|
{
|
|
1392
1866
|
columns: walletColumns,
|
|
@@ -1394,18 +1868,18 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1394
1868
|
title: "Wallet"
|
|
1395
1869
|
}
|
|
1396
1870
|
),
|
|
1397
|
-
data.rankedBalances.length === 0 ? /* @__PURE__ */
|
|
1398
|
-
|
|
1871
|
+
data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs5(
|
|
1872
|
+
Box5,
|
|
1399
1873
|
{
|
|
1400
1874
|
flexDirection: "column",
|
|
1401
1875
|
paddingLeft: 1,
|
|
1402
1876
|
paddingTop: 1,
|
|
1403
1877
|
paddingBottom: 1,
|
|
1404
1878
|
children: [
|
|
1405
|
-
/* @__PURE__ */
|
|
1406
|
-
/* @__PURE__ */
|
|
1407
|
-
/* @__PURE__ */
|
|
1408
|
-
/* @__PURE__ */
|
|
1879
|
+
/* @__PURE__ */ jsx5(Text5, { children: "No coin balances found." }),
|
|
1880
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
1881
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
1882
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1409
1883
|
" zora buy ",
|
|
1410
1884
|
"<address>",
|
|
1411
1885
|
" --eth 0.001"
|
|
@@ -1413,15 +1887,18 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1413
1887
|
] })
|
|
1414
1888
|
]
|
|
1415
1889
|
}
|
|
1416
|
-
) : /* @__PURE__ */
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1890
|
+
) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1891
|
+
/* @__PURE__ */ jsx5(
|
|
1892
|
+
Table,
|
|
1893
|
+
{
|
|
1894
|
+
columns: balanceColumns,
|
|
1895
|
+
data: data.rankedBalances,
|
|
1896
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
|
|
1897
|
+
subtitle: `${data.rankedBalances.length} of ${data.total}`
|
|
1898
|
+
}
|
|
1899
|
+
),
|
|
1900
|
+
!hasApiKey && /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: API_KEY_BANNER }) })
|
|
1901
|
+
] })
|
|
1425
1902
|
] })
|
|
1426
1903
|
);
|
|
1427
1904
|
track("cli_balances", {
|
|
@@ -1441,7 +1918,7 @@ balanceCommand.command("spendable").description("Show wallet token balances (ETH
|
|
|
1441
1918
|
).action(async function() {
|
|
1442
1919
|
const output = getOutputMode(this, "live");
|
|
1443
1920
|
const json = output === "json";
|
|
1444
|
-
const account = resolveContext(json);
|
|
1921
|
+
const { account } = resolveContext(json);
|
|
1445
1922
|
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1446
1923
|
const fetchSpendableData = async () => {
|
|
1447
1924
|
const walletResult = await fetchWalletBalances(account.address);
|
|
@@ -1463,7 +1940,7 @@ balanceCommand.command("spendable").description("Show wallet token balances (ETH
|
|
|
1463
1940
|
});
|
|
1464
1941
|
} else if (live) {
|
|
1465
1942
|
await renderLive(
|
|
1466
|
-
/* @__PURE__ */
|
|
1943
|
+
/* @__PURE__ */ jsx5(
|
|
1467
1944
|
BalanceView,
|
|
1468
1945
|
{
|
|
1469
1946
|
fetchData: fetchSpendableData,
|
|
@@ -1490,7 +1967,7 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
|
|
|
1490
1967
|
const json = output === "json";
|
|
1491
1968
|
const { sort, limit } = validateCoinOpts(json, opts.sort, opts.limit);
|
|
1492
1969
|
const after = opts.after;
|
|
1493
|
-
const account = resolveContext(json);
|
|
1970
|
+
const { account, hasApiKey } = resolveContext(json);
|
|
1494
1971
|
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1495
1972
|
const fetchCoinsPage = async (cursor) => {
|
|
1496
1973
|
const { balances, total, pageInfo } = await fetchCoins(
|
|
@@ -1500,39 +1977,23 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
|
|
|
1500
1977
|
limit,
|
|
1501
1978
|
cursor
|
|
1502
1979
|
);
|
|
1503
|
-
|
|
1504
|
-
...balance,
|
|
1505
|
-
rank: index + 1
|
|
1506
|
-
}));
|
|
1507
|
-
return {
|
|
1508
|
-
walletBalances: [],
|
|
1509
|
-
walletBalancesJson: [],
|
|
1510
|
-
rankedBalances,
|
|
1511
|
-
total,
|
|
1512
|
-
pageInfo
|
|
1513
|
-
};
|
|
1980
|
+
return { items: balances, count: total, pageInfo };
|
|
1514
1981
|
};
|
|
1515
1982
|
if (json) {
|
|
1516
|
-
const
|
|
1517
|
-
renderCoins(
|
|
1518
|
-
json,
|
|
1519
|
-
data.rankedBalances,
|
|
1520
|
-
data.total,
|
|
1521
|
-
sort,
|
|
1522
|
-
limit,
|
|
1523
|
-
data.pageInfo
|
|
1524
|
-
);
|
|
1983
|
+
const { items, count, pageInfo } = await fetchCoinsPage(after);
|
|
1984
|
+
renderCoins(json, items, count ?? items.length, sort, limit, pageInfo);
|
|
1525
1985
|
} else if (live) {
|
|
1526
1986
|
await renderLive(
|
|
1527
|
-
/* @__PURE__ */
|
|
1528
|
-
|
|
1987
|
+
/* @__PURE__ */ jsx5(
|
|
1988
|
+
BalanceCoinsView,
|
|
1529
1989
|
{
|
|
1530
|
-
|
|
1990
|
+
fetchPage: fetchCoinsPage,
|
|
1531
1991
|
sort,
|
|
1532
|
-
|
|
1992
|
+
limit,
|
|
1533
1993
|
initialCursor: after,
|
|
1534
1994
|
autoRefresh: live,
|
|
1535
|
-
intervalSeconds
|
|
1995
|
+
intervalSeconds,
|
|
1996
|
+
hasApiKey
|
|
1536
1997
|
}
|
|
1537
1998
|
)
|
|
1538
1999
|
);
|
|
@@ -1544,7 +2005,7 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
|
|
|
1544
2005
|
limit,
|
|
1545
2006
|
after
|
|
1546
2007
|
);
|
|
1547
|
-
renderCoins(json, balances, total, sort, limit, pageInfo);
|
|
2008
|
+
renderCoins(json, balances, total, sort, limit, pageInfo, hasApiKey);
|
|
1548
2009
|
}
|
|
1549
2010
|
});
|
|
1550
2011
|
|
|
@@ -1761,6 +2222,19 @@ function coinArgsToRef(parsed) {
|
|
|
1761
2222
|
return { kind: "ambiguous", name: parsed.name };
|
|
1762
2223
|
}
|
|
1763
2224
|
}
|
|
2225
|
+
async function resolveAmbiguousByNameAndBalance(name, getBalance) {
|
|
2226
|
+
const result = await resolveAmbiguousName(name);
|
|
2227
|
+
if (result.kind !== "ambiguous") return result;
|
|
2228
|
+
const [creatorBal, trendBal] = await Promise.all([
|
|
2229
|
+
getBalance(result.creator.address),
|
|
2230
|
+
getBalance(result.trend.address)
|
|
2231
|
+
]);
|
|
2232
|
+
if (creatorBal > 0n && trendBal === 0n)
|
|
2233
|
+
return { kind: "found", coin: result.creator };
|
|
2234
|
+
if (trendBal > 0n && creatorBal === 0n)
|
|
2235
|
+
return { kind: "found", coin: result.trend };
|
|
2236
|
+
return result;
|
|
2237
|
+
}
|
|
1764
2238
|
async function resolveAmbiguousName(name) {
|
|
1765
2239
|
const [creatorResult, trendResult] = await Promise.all([
|
|
1766
2240
|
resolveByCreatorName(name),
|
|
@@ -1811,6 +2285,7 @@ function coinFromToken(token) {
|
|
|
1811
2285
|
marketCap: token.marketCap ?? "0",
|
|
1812
2286
|
marketCapDelta24h: token.marketCapDelta24h ?? "0",
|
|
1813
2287
|
volume24h: token.volume24h ?? "0",
|
|
2288
|
+
totalSupply: token.totalSupply ?? "0",
|
|
1814
2289
|
uniqueHolders: token.uniqueHolders ?? 0,
|
|
1815
2290
|
createdAt: token.createdAt,
|
|
1816
2291
|
creatorAddress: token.creatorAddress,
|
|
@@ -2138,7 +2613,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2138
2613
|
if (errorType === "LIQUIDITY" || msg.includes("Not enough liquidity")) {
|
|
2139
2614
|
if (json) {
|
|
2140
2615
|
outputJson({ error: errorBody ?? msg });
|
|
2141
|
-
|
|
2616
|
+
safeExit(ERROR);
|
|
2142
2617
|
}
|
|
2143
2618
|
outputErrorAndExit(
|
|
2144
2619
|
json,
|
|
@@ -2186,7 +2661,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2186
2661
|
default: false
|
|
2187
2662
|
});
|
|
2188
2663
|
if (!ok) {
|
|
2189
|
-
|
|
2664
|
+
safeExit(SUCCESS);
|
|
2190
2665
|
}
|
|
2191
2666
|
}
|
|
2192
2667
|
let receipt;
|
|
@@ -2290,66 +2765,16 @@ import {
|
|
|
2290
2765
|
getTrendingTrends
|
|
2291
2766
|
} from "@zoralabs/coins-sdk";
|
|
2292
2767
|
|
|
2293
|
-
// src/lib/types.ts
|
|
2294
|
-
var SORT_LABELS2 = {
|
|
2295
|
-
mcap: "Top by Market Cap",
|
|
2296
|
-
volume: "Top by 24h Volume",
|
|
2297
|
-
new: "New",
|
|
2298
|
-
trending: "Trending",
|
|
2299
|
-
featured: "Featured"
|
|
2300
|
-
};
|
|
2301
|
-
var TYPE_LABELS = {
|
|
2302
|
-
all: "all",
|
|
2303
|
-
trend: "trends",
|
|
2304
|
-
"creator-coin": "creator coins",
|
|
2305
|
-
post: "posts"
|
|
2306
|
-
};
|
|
2307
|
-
var COIN_TYPE_DISPLAY = {
|
|
2308
|
-
CONTENT: "post",
|
|
2309
|
-
CREATOR: "creator-coin",
|
|
2310
|
-
TREND: "trend"
|
|
2311
|
-
};
|
|
2312
|
-
|
|
2313
|
-
// src/components/ExploreView.tsx
|
|
2314
|
-
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
2315
|
-
import { Box as Box4, Text as Text4, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
2316
|
-
import Spinner2 from "ink-spinner";
|
|
2317
|
-
|
|
2318
|
-
// src/lib/clipboard.ts
|
|
2319
|
-
import { execFileSync } from "child_process";
|
|
2320
|
-
import { platform as platform2 } from "os";
|
|
2321
|
-
var copyToClipboard = (text) => {
|
|
2322
|
-
const os = platform2();
|
|
2323
|
-
try {
|
|
2324
|
-
if (os === "darwin") {
|
|
2325
|
-
execFileSync("pbcopy", {
|
|
2326
|
-
input: text,
|
|
2327
|
-
stdio: ["pipe", "ignore", "ignore"]
|
|
2328
|
-
});
|
|
2329
|
-
} else if (os === "linux") {
|
|
2330
|
-
execFileSync("xclip", ["-selection", "clipboard"], {
|
|
2331
|
-
input: text,
|
|
2332
|
-
stdio: ["pipe", "ignore", "ignore"]
|
|
2333
|
-
});
|
|
2334
|
-
} else if (os === "win32") {
|
|
2335
|
-
execFileSync("clip", {
|
|
2336
|
-
input: text,
|
|
2337
|
-
stdio: ["pipe", "ignore", "ignore"]
|
|
2338
|
-
});
|
|
2339
|
-
} else {
|
|
2340
|
-
return false;
|
|
2341
|
-
}
|
|
2342
|
-
return true;
|
|
2343
|
-
} catch {
|
|
2344
|
-
return false;
|
|
2345
|
-
}
|
|
2346
|
-
};
|
|
2347
|
-
|
|
2348
2768
|
// src/components/ExploreView.tsx
|
|
2349
|
-
import {
|
|
2769
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
2770
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2350
2771
|
var COLUMNS = [
|
|
2351
2772
|
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2352
|
-
{
|
|
2773
|
+
{
|
|
2774
|
+
header: "Name",
|
|
2775
|
+
width: 20,
|
|
2776
|
+
accessor: (c) => formatCoinName(c)
|
|
2777
|
+
},
|
|
2353
2778
|
{
|
|
2354
2779
|
header: "Address",
|
|
2355
2780
|
width: 14,
|
|
@@ -2358,7 +2783,7 @@ var COLUMNS = [
|
|
|
2358
2783
|
{
|
|
2359
2784
|
header: "Type",
|
|
2360
2785
|
width: 14,
|
|
2361
|
-
accessor: (c) =>
|
|
2786
|
+
accessor: (c) => formatCoinType(c.coinType)
|
|
2362
2787
|
},
|
|
2363
2788
|
{
|
|
2364
2789
|
header: "Market Cap",
|
|
@@ -2377,191 +2802,49 @@ var COLUMNS = [
|
|
|
2377
2802
|
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2378
2803
|
}
|
|
2379
2804
|
];
|
|
2380
|
-
var
|
|
2381
|
-
|
|
2805
|
+
var emptyState2 = /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
2806
|
+
/* @__PURE__ */ jsx6(Text6, { children: "No coins found." }),
|
|
2807
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2808
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Try a different sort or type:" }),
|
|
2809
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " zora explore --sort volume --type all" }),
|
|
2810
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " zora explore --sort new --type all" })
|
|
2811
|
+
] }),
|
|
2812
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Press q to exit" }) })
|
|
2813
|
+
] });
|
|
2382
2814
|
var ExploreView = ({
|
|
2383
2815
|
fetchPage,
|
|
2384
2816
|
sort,
|
|
2385
2817
|
type,
|
|
2386
2818
|
limit,
|
|
2387
2819
|
initialCursor,
|
|
2388
|
-
cacheTtlMs = CACHE_TTL_MS,
|
|
2389
2820
|
autoRefresh = false,
|
|
2390
2821
|
intervalSeconds = 30
|
|
2391
2822
|
}) => {
|
|
2392
|
-
const {
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
[]
|
|
2400
|
-
);
|
|
2401
|
-
const [currentCursor, setCurrentCursor] = useState3(
|
|
2402
|
-
initialCursor
|
|
2403
|
-
);
|
|
2404
|
-
const cache = useRef2(/* @__PURE__ */ new Map());
|
|
2405
|
-
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
2406
|
-
const [manualRefreshCount, setManualRefreshCount] = useState3(0);
|
|
2407
|
-
const [selectedRow, setSelectedRow] = useState3(0);
|
|
2408
|
-
const [copyFeedback, setCopyFeedback] = useState3(null);
|
|
2409
|
-
useEffect3(() => {
|
|
2410
|
-
setSelectedRow((r) => Math.min(r, Math.max(0, coins.length - 1)));
|
|
2411
|
-
}, [coins.length]);
|
|
2412
|
-
const loadPage = useCallback3(
|
|
2413
|
-
async (cursor) => {
|
|
2414
|
-
const cacheKey = cursor ?? CACHE_KEY_FIRST;
|
|
2415
|
-
const cached = cache.current.get(cacheKey);
|
|
2416
|
-
const isFresh = cached && Date.now() - cached.fetchedAt < cacheTtlMs;
|
|
2417
|
-
if (isFresh) {
|
|
2418
|
-
setCoins(cached.result.coins);
|
|
2419
|
-
setPageInfo(cached.result.pageInfo ?? null);
|
|
2420
|
-
setError(null);
|
|
2421
|
-
setLoading(false);
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
setLoading(true);
|
|
2425
|
-
setError(null);
|
|
2426
|
-
try {
|
|
2427
|
-
const result = await fetchPage(cursor);
|
|
2428
|
-
cache.current.set(cacheKey, { result, fetchedAt: Date.now() });
|
|
2429
|
-
setCoins(result.coins);
|
|
2430
|
-
setPageInfo(result.pageInfo ?? null);
|
|
2431
|
-
} catch (err) {
|
|
2432
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2433
|
-
}
|
|
2434
|
-
setLoading(false);
|
|
2435
|
-
},
|
|
2436
|
-
[fetchPage, cacheTtlMs]
|
|
2437
|
-
);
|
|
2438
|
-
useEffect3(() => {
|
|
2439
|
-
if (refreshCount === 0) return;
|
|
2440
|
-
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2441
|
-
cache.current.delete(cacheKey);
|
|
2442
|
-
}, [refreshCount, currentCursor]);
|
|
2443
|
-
useEffect3(() => {
|
|
2444
|
-
loadPage(currentCursor);
|
|
2445
|
-
}, [currentCursor, loadPage, refreshCount, manualRefreshCount]);
|
|
2446
|
-
useInput2((input, key) => {
|
|
2447
|
-
if (input === "q" || key.escape) {
|
|
2448
|
-
exit();
|
|
2449
|
-
return;
|
|
2450
|
-
}
|
|
2451
|
-
if (loading) return;
|
|
2452
|
-
if (key.upArrow || input === "k") {
|
|
2453
|
-
setSelectedRow((r) => Math.max(0, r - 1));
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
|
-
if (key.downArrow || input === "j") {
|
|
2457
|
-
setSelectedRow((r) => Math.min(coins.length - 1, r + 1));
|
|
2458
|
-
return;
|
|
2459
|
-
}
|
|
2460
|
-
if (input === "c") {
|
|
2461
|
-
const coin = coins[selectedRow];
|
|
2462
|
-
if (coin?.address) {
|
|
2463
|
-
const ok = copyToClipboard(coin.address);
|
|
2464
|
-
setCopyFeedback(ok ? "Copied!" : "Copy failed");
|
|
2465
|
-
setTimeout(() => setCopyFeedback(null), 1500);
|
|
2466
|
-
}
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
|
|
2470
|
-
const canGoPrev = cursorHistory.length > 0;
|
|
2471
|
-
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
2472
|
-
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
2473
|
-
setCurrentCursor(pageInfo.endCursor);
|
|
2474
|
-
setPage((p) => p + 1);
|
|
2475
|
-
setSelectedRow(0);
|
|
2476
|
-
}
|
|
2477
|
-
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
2478
|
-
const prev = cursorHistory[cursorHistory.length - 1];
|
|
2479
|
-
setCursorHistory((h) => h.slice(0, -1));
|
|
2480
|
-
setCurrentCursor(prev);
|
|
2481
|
-
setPage((p) => p - 1);
|
|
2482
|
-
setSelectedRow(0);
|
|
2483
|
-
}
|
|
2484
|
-
if (input === "r") {
|
|
2485
|
-
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2486
|
-
cache.current.delete(cacheKey);
|
|
2487
|
-
triggerManualRefresh();
|
|
2488
|
-
setManualRefreshCount((c) => c + 1);
|
|
2489
|
-
setSelectedRow(0);
|
|
2490
|
-
}
|
|
2491
|
-
});
|
|
2492
|
-
if (error) {
|
|
2493
|
-
return /* @__PURE__ */ jsxs4(
|
|
2494
|
-
Box4,
|
|
2495
|
-
{
|
|
2496
|
-
flexDirection: "column",
|
|
2497
|
-
paddingLeft: 1,
|
|
2498
|
-
paddingTop: 1,
|
|
2499
|
-
paddingBottom: 1,
|
|
2500
|
-
children: [
|
|
2501
|
-
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
2502
|
-
"Error: ",
|
|
2503
|
-
error
|
|
2504
|
-
] }),
|
|
2505
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2506
|
-
]
|
|
2507
|
-
}
|
|
2508
|
-
);
|
|
2509
|
-
}
|
|
2510
|
-
if (loading) {
|
|
2511
|
-
return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
2512
|
-
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
2513
|
-
" Loading\u2026"
|
|
2514
|
-
] }) });
|
|
2515
|
-
}
|
|
2516
|
-
if (coins.length === 0) {
|
|
2517
|
-
return /* @__PURE__ */ jsxs4(
|
|
2518
|
-
Box4,
|
|
2519
|
-
{
|
|
2520
|
-
flexDirection: "column",
|
|
2521
|
-
paddingLeft: 1,
|
|
2522
|
-
paddingTop: 1,
|
|
2523
|
-
paddingBottom: 1,
|
|
2524
|
-
children: [
|
|
2525
|
-
/* @__PURE__ */ jsx4(Text4, { children: "No coins found." }),
|
|
2526
|
-
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2527
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Try a different sort or type:" }),
|
|
2528
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort volume --type all" }),
|
|
2529
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort new --type all" })
|
|
2530
|
-
] }),
|
|
2531
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2532
|
-
]
|
|
2533
|
-
}
|
|
2534
|
-
);
|
|
2535
|
-
}
|
|
2536
|
-
const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
|
|
2537
|
-
const subtitle = `Page ${page} \xB7 ${coins.length} result${coins.length !== 1 ? "s" : ""}`;
|
|
2538
|
-
const rankedCoins = coins.map((c, i) => ({
|
|
2539
|
-
...c,
|
|
2540
|
-
rank: (page - 1) * limit + i + 1
|
|
2541
|
-
}));
|
|
2542
|
-
const hints = [];
|
|
2543
|
-
hints.push("\u2191\u2193 select");
|
|
2544
|
-
hints.push("c copy address");
|
|
2545
|
-
if (cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
2546
|
-
if (pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
2547
|
-
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
2548
|
-
hints.push("q quit");
|
|
2549
|
-
const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
|
|
2550
|
-
return /* @__PURE__ */ jsx4(
|
|
2551
|
-
Table,
|
|
2823
|
+
const title = type !== "all" ? `${SORT_LABELS[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS[sort];
|
|
2824
|
+
const formatSubtitle = ({
|
|
2825
|
+
page,
|
|
2826
|
+
itemCount
|
|
2827
|
+
}) => `Page ${page} \xB7 ${itemCount} result${itemCount !== 1 ? "s" : ""}`;
|
|
2828
|
+
return /* @__PURE__ */ jsx6(
|
|
2829
|
+
PaginatedTableView,
|
|
2552
2830
|
{
|
|
2553
|
-
|
|
2831
|
+
fetchPage,
|
|
2554
2832
|
columns: COLUMNS,
|
|
2555
2833
|
title,
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2834
|
+
loadingText: "Loading\u2026",
|
|
2835
|
+
emptyState: emptyState2,
|
|
2836
|
+
getAddress: (coin) => coin.address,
|
|
2837
|
+
limit,
|
|
2838
|
+
initialCursor,
|
|
2839
|
+
autoRefresh,
|
|
2840
|
+
intervalSeconds,
|
|
2841
|
+
formatSubtitle
|
|
2559
2842
|
}
|
|
2560
2843
|
);
|
|
2561
2844
|
};
|
|
2562
2845
|
|
|
2563
2846
|
// src/commands/explore.tsx
|
|
2564
|
-
import { jsx as
|
|
2847
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2565
2848
|
var formatExploreCoinJson = (node) => {
|
|
2566
2849
|
const marketCap = node.marketCap ? Number(node.marketCap) : null;
|
|
2567
2850
|
const marketCapDelta24h = node.marketCapDelta24h ? Number(node.marketCapDelta24h) : null;
|
|
@@ -2570,7 +2853,7 @@ var formatExploreCoinJson = (node) => {
|
|
|
2570
2853
|
marketCapDelta24h
|
|
2571
2854
|
);
|
|
2572
2855
|
const priceUsd = node.tokenPrice?.priceInUsdc ? Number(node.tokenPrice.priceInUsdc) : null;
|
|
2573
|
-
const coinType = node.coinType
|
|
2856
|
+
const coinType = formatCoinType(node.coinType) || null;
|
|
2574
2857
|
const socials = node.creatorProfile?.socialAccounts;
|
|
2575
2858
|
const socialAccounts = socials ? {
|
|
2576
2859
|
instagram: socials.instagram ?? null,
|
|
@@ -2635,12 +2918,12 @@ var QUERY_MAP = {
|
|
|
2635
2918
|
};
|
|
2636
2919
|
var STATIC_COLUMNS = [
|
|
2637
2920
|
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2638
|
-
{ header: "Name", width: 20, accessor: (c) => c
|
|
2921
|
+
{ header: "Name", width: 20, accessor: (c) => formatCoinName(c) },
|
|
2639
2922
|
{ header: "Address", width: 48, accessor: (c) => c.address ?? "" },
|
|
2640
2923
|
{
|
|
2641
2924
|
header: "Type",
|
|
2642
2925
|
width: 14,
|
|
2643
|
-
accessor: (c) =>
|
|
2926
|
+
accessor: (c) => formatCoinType(c.coinType)
|
|
2644
2927
|
},
|
|
2645
2928
|
{
|
|
2646
2929
|
header: "Market Cap",
|
|
@@ -2659,7 +2942,7 @@ var STATIC_COLUMNS = [
|
|
|
2659
2942
|
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2660
2943
|
}
|
|
2661
2944
|
];
|
|
2662
|
-
var SORT_OPTIONS2 = Object.keys(
|
|
2945
|
+
var SORT_OPTIONS2 = Object.keys(SORT_LABELS).join(", ");
|
|
2663
2946
|
var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
|
|
2664
2947
|
"--type <type>",
|
|
2665
2948
|
"Filter by type: all, trend, creator-coin, post (availability varies by sort)",
|
|
@@ -2710,14 +2993,14 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2710
2993
|
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
2711
2994
|
}
|
|
2712
2995
|
if (response.error) {
|
|
2713
|
-
|
|
2714
|
-
|
|
2996
|
+
outputErrorAndExit(
|
|
2997
|
+
json,
|
|
2998
|
+
`API error: ${extractErrorMessage(response.error)}`
|
|
2999
|
+
);
|
|
2715
3000
|
}
|
|
2716
3001
|
const edges = response.data?.exploreList?.edges ?? [];
|
|
2717
3002
|
const rawNodes = edges.map((e) => e.node);
|
|
2718
|
-
const coins = rawNodes.map(
|
|
2719
|
-
(node, i) => formatExploreCoinJson(node)
|
|
2720
|
-
);
|
|
3003
|
+
const coins = rawNodes.map((node, i) => formatExploreCoinJson(node));
|
|
2721
3004
|
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2722
3005
|
outputJson({ coins, pageInfo: pageInfo ?? null });
|
|
2723
3006
|
track("cli_explore", {
|
|
@@ -2734,17 +3017,16 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2734
3017
|
const fetchPage = async (cursor) => {
|
|
2735
3018
|
const response = await queryFn({ count: limit, after: cursor });
|
|
2736
3019
|
if (response.error) {
|
|
2737
|
-
|
|
2738
|
-
throw new Error(msg);
|
|
3020
|
+
throw new Error(extractErrorMessage(response.error));
|
|
2739
3021
|
}
|
|
2740
3022
|
const edges = response.data?.exploreList?.edges ?? [];
|
|
2741
|
-
const
|
|
3023
|
+
const items = edges.map((e) => e.node);
|
|
2742
3024
|
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2743
|
-
return {
|
|
3025
|
+
return { items, pageInfo };
|
|
2744
3026
|
};
|
|
2745
3027
|
if (live) {
|
|
2746
3028
|
await renderLive(
|
|
2747
|
-
/* @__PURE__ */
|
|
3029
|
+
/* @__PURE__ */ jsx7(
|
|
2748
3030
|
ExploreView,
|
|
2749
3031
|
{
|
|
2750
3032
|
fetchPage,
|
|
@@ -2767,19 +3049,19 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2767
3049
|
output_format: "live"
|
|
2768
3050
|
});
|
|
2769
3051
|
} else {
|
|
2770
|
-
const {
|
|
3052
|
+
const { items } = await fetchPage(after).catch(
|
|
2771
3053
|
(err) => outputErrorAndExit(
|
|
2772
3054
|
false,
|
|
2773
3055
|
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2774
3056
|
)
|
|
2775
3057
|
);
|
|
2776
|
-
const title = type !== "all" ? `${
|
|
2777
|
-
const rankedCoins =
|
|
3058
|
+
const title = type !== "all" ? `${SORT_LABELS[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS[sort];
|
|
3059
|
+
const rankedCoins = items.map((c, i) => ({
|
|
2778
3060
|
...c,
|
|
2779
3061
|
rank: i + 1
|
|
2780
3062
|
}));
|
|
2781
3063
|
renderOnce(
|
|
2782
|
-
/* @__PURE__ */
|
|
3064
|
+
/* @__PURE__ */ jsx7(Table, { columns: STATIC_COLUMNS, data: rankedCoins, title })
|
|
2783
3065
|
);
|
|
2784
3066
|
track("cli_explore", {
|
|
2785
3067
|
sort,
|
|
@@ -2787,7 +3069,7 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2787
3069
|
limit,
|
|
2788
3070
|
live: false,
|
|
2789
3071
|
paginated: after !== void 0,
|
|
2790
|
-
result_count:
|
|
3072
|
+
result_count: items.length,
|
|
2791
3073
|
output_format: "static"
|
|
2792
3074
|
});
|
|
2793
3075
|
}
|
|
@@ -2796,27 +3078,28 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2796
3078
|
|
|
2797
3079
|
// src/commands/get.tsx
|
|
2798
3080
|
import { Command as Command5 } from "commander";
|
|
2799
|
-
import {
|
|
3081
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
3082
|
+
import { setApiKey as setApiKey4, getCoinHolders, getCoinSwaps } from "@zoralabs/coins-sdk";
|
|
2800
3083
|
|
|
2801
3084
|
// src/components/CoinDetail.tsx
|
|
2802
|
-
import { Box as
|
|
2803
|
-
import { jsx as
|
|
3085
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
3086
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2804
3087
|
var LABEL_WIDTH = 18;
|
|
2805
3088
|
function Row({
|
|
2806
3089
|
label,
|
|
2807
3090
|
children
|
|
2808
3091
|
}) {
|
|
2809
|
-
return /* @__PURE__ */
|
|
2810
|
-
/* @__PURE__ */
|
|
2811
|
-
/* @__PURE__ */
|
|
3092
|
+
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
3093
|
+
/* @__PURE__ */ jsx8(Box7, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: label }) }),
|
|
3094
|
+
/* @__PURE__ */ jsx8(Text7, { children })
|
|
2812
3095
|
] });
|
|
2813
3096
|
}
|
|
2814
3097
|
function CoinDetail({ coin }) {
|
|
2815
3098
|
const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
|
|
2816
|
-
return /* @__PURE__ */
|
|
2817
|
-
/* @__PURE__ */
|
|
2818
|
-
/* @__PURE__ */
|
|
2819
|
-
/* @__PURE__ */
|
|
3099
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
|
|
3100
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
3101
|
+
/* @__PURE__ */ jsx8(Text7, { bold: true, children: coin.name }),
|
|
3102
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2820
3103
|
coin.coinType,
|
|
2821
3104
|
" ",
|
|
2822
3105
|
"\xB7",
|
|
@@ -2824,175 +3107,423 @@ function CoinDetail({ coin }) {
|
|
|
2824
3107
|
coin.address
|
|
2825
3108
|
] })
|
|
2826
3109
|
] }),
|
|
2827
|
-
/* @__PURE__ */
|
|
2828
|
-
/* @__PURE__ */
|
|
2829
|
-
/* @__PURE__ */
|
|
2830
|
-
/* @__PURE__ */
|
|
2831
|
-
/* @__PURE__ */
|
|
2832
|
-
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */
|
|
2833
|
-
/* @__PURE__ */
|
|
3110
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
3111
|
+
/* @__PURE__ */ jsx8(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
|
|
3112
|
+
/* @__PURE__ */ jsx8(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
|
|
3113
|
+
/* @__PURE__ */ jsx8(Row, { label: "24h Change", children: /* @__PURE__ */ jsx8(Text7, { color: change.color, children: change.text }) }),
|
|
3114
|
+
/* @__PURE__ */ jsx8(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
|
|
3115
|
+
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx8(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
|
|
3116
|
+
/* @__PURE__ */ jsx8(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
|
|
2834
3117
|
] }),
|
|
2835
|
-
/* @__PURE__ */
|
|
3118
|
+
/* @__PURE__ */ jsx8(Box7, { marginBottom: 1 })
|
|
2836
3119
|
] });
|
|
2837
3120
|
}
|
|
2838
3121
|
|
|
2839
|
-
// src/
|
|
2840
|
-
import {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3122
|
+
// src/components/PriceHistory.tsx
|
|
3123
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
3124
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3125
|
+
var LABEL_WIDTH2 = 18;
|
|
3126
|
+
var Row2 = ({
|
|
3127
|
+
label,
|
|
3128
|
+
children
|
|
3129
|
+
}) => /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
3130
|
+
/* @__PURE__ */ jsx9(Box8, { width: LABEL_WIDTH2, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: label }) }),
|
|
3131
|
+
/* @__PURE__ */ jsx9(Text8, { children })
|
|
3132
|
+
] });
|
|
3133
|
+
var PriceHistory = ({
|
|
3134
|
+
coin,
|
|
3135
|
+
coinType,
|
|
3136
|
+
interval,
|
|
3137
|
+
high,
|
|
3138
|
+
low,
|
|
3139
|
+
change,
|
|
3140
|
+
sparklineText,
|
|
3141
|
+
compact = false
|
|
3142
|
+
}) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingLeft: 1, children: [
|
|
3143
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
3144
|
+
!compact && /* @__PURE__ */ jsx9(Row2, { label: "Coin", children: coin }),
|
|
3145
|
+
!compact && /* @__PURE__ */ jsx9(Row2, { label: "Type", children: coinType }),
|
|
3146
|
+
/* @__PURE__ */ jsx9(Row2, { label: "Interval", children: interval }),
|
|
3147
|
+
/* @__PURE__ */ jsx9(Row2, { label: "High", children: high }),
|
|
3148
|
+
/* @__PURE__ */ jsx9(Row2, { label: "Low", children: low }),
|
|
3149
|
+
/* @__PURE__ */ jsx9(Row2, { label: "Change", children: /* @__PURE__ */ jsx9(Text8, { color: change.color, children: change.text }) })
|
|
3150
|
+
] }),
|
|
3151
|
+
sparklineText.length > 0 && /* @__PURE__ */ jsx9(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx9(Text8, { children: sparklineText }) }),
|
|
3152
|
+
/* @__PURE__ */ jsx9(Box8, { marginBottom: 1 })
|
|
3153
|
+
] });
|
|
3154
|
+
|
|
3155
|
+
// src/components/CoinView.tsx
|
|
3156
|
+
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
3157
|
+
import { Box as Box11, Text as Text11, useInput as useInput3, useApp as useApp3 } from "ink";
|
|
3158
|
+
import Spinner3 from "ink-spinner";
|
|
3159
|
+
|
|
3160
|
+
// src/components/CoinHoldersView.tsx
|
|
3161
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
3162
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3163
|
+
function formatPct(pct) {
|
|
3164
|
+
if (pct < 0.01) return "<0.01%";
|
|
3165
|
+
return `${pct.toFixed(1)}%`;
|
|
2854
3166
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
3167
|
+
var makeHolderColumns = (ctx) => [
|
|
3168
|
+
{ header: "#", width: 5, accessor: (r) => String(r.rank) },
|
|
3169
|
+
{
|
|
3170
|
+
header: "Holder",
|
|
3171
|
+
width: 20,
|
|
3172
|
+
accessor: (r) => r.ownerProfile?.handle ?? r.ownerAddress
|
|
3173
|
+
},
|
|
3174
|
+
{
|
|
3175
|
+
header: "Balance",
|
|
3176
|
+
width: 18,
|
|
3177
|
+
accessor: (r) => formatBalance(r.balance)
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
header: "% Supply",
|
|
3181
|
+
width: 10,
|
|
3182
|
+
accessor: (r) => {
|
|
3183
|
+
if (ctx.totalSupplyNum <= 0) return "-";
|
|
3184
|
+
const balanceNum = parseRawBalance(r.balance);
|
|
3185
|
+
return formatPct(balanceNum / ctx.totalSupplyNum * 100);
|
|
2860
3186
|
}
|
|
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) {
|
|
2867
|
-
const json = getJson(this);
|
|
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;
|
|
2876
3187
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
3188
|
+
];
|
|
3189
|
+
var emptyState3 = /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
3190
|
+
/* @__PURE__ */ jsx10(Text9, { children: "No holders found for this coin." }),
|
|
3191
|
+
/* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "Press q to exit" }) })
|
|
3192
|
+
] });
|
|
3193
|
+
var CoinHoldersView = ({
|
|
3194
|
+
fetchPage,
|
|
3195
|
+
coinName,
|
|
3196
|
+
totalSupplyNum,
|
|
3197
|
+
limit,
|
|
3198
|
+
autoRefresh,
|
|
3199
|
+
intervalSeconds
|
|
3200
|
+
}) => {
|
|
3201
|
+
const columns = makeHolderColumns({ totalSupplyNum });
|
|
3202
|
+
return /* @__PURE__ */ jsx10(
|
|
3203
|
+
PaginatedTableView,
|
|
3204
|
+
{
|
|
3205
|
+
fetchPage,
|
|
3206
|
+
columns,
|
|
3207
|
+
title: `Top holders \xB7 ${coinName}`,
|
|
3208
|
+
loadingText: "Loading holders\u2026",
|
|
3209
|
+
emptyState: emptyState3,
|
|
3210
|
+
getAddress: (holder) => holder.ownerAddress,
|
|
3211
|
+
limit,
|
|
3212
|
+
autoRefresh,
|
|
3213
|
+
intervalSeconds
|
|
3214
|
+
}
|
|
3215
|
+
);
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3218
|
+
// src/components/CoinTradesView.tsx
|
|
3219
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
3220
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3221
|
+
function formatTradeUsd(priceUsdc) {
|
|
3222
|
+
if (!priceUsdc) return "-";
|
|
3223
|
+
const value = Number(priceUsdc);
|
|
3224
|
+
if (value === 0) return "$0.00";
|
|
3225
|
+
return new Intl.NumberFormat("en-US", {
|
|
3226
|
+
style: "currency",
|
|
3227
|
+
currency: "USD",
|
|
3228
|
+
minimumFractionDigits: 2,
|
|
3229
|
+
maximumFractionDigits: 2
|
|
3230
|
+
}).format(value);
|
|
3231
|
+
}
|
|
3232
|
+
var coinTradeColumns = [
|
|
3233
|
+
{ header: "#", width: 4, accessor: (t) => String(t.rank) },
|
|
3234
|
+
{
|
|
3235
|
+
header: "Type",
|
|
3236
|
+
width: 6,
|
|
3237
|
+
accessor: (t) => t.activityType ?? "?",
|
|
3238
|
+
color: (t) => t.activityType === "BUY" ? "green" : t.activityType === "SELL" ? "red" : void 0
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
header: "User",
|
|
3242
|
+
width: 18,
|
|
3243
|
+
accessor: (t) => t.senderProfile?.handle ?? truncateAddress(t.senderAddress)
|
|
3244
|
+
},
|
|
3245
|
+
{
|
|
3246
|
+
header: "Amount",
|
|
3247
|
+
width: 22,
|
|
3248
|
+
accessor: (t) => {
|
|
3249
|
+
const prefix = t.activityType === "BUY" ? "+" : "-";
|
|
3250
|
+
return `${prefix}${formatCoinsDisplay(t.coinAmount)} coins`;
|
|
3251
|
+
}
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
header: "Value",
|
|
3255
|
+
width: 12,
|
|
3256
|
+
accessor: (t) => formatTradeUsd(t.currencyAmountWithPrice.priceUsdc)
|
|
3257
|
+
},
|
|
3258
|
+
{
|
|
3259
|
+
header: "When",
|
|
3260
|
+
width: 14,
|
|
3261
|
+
accessor: (t) => {
|
|
3262
|
+
if (!t.blockTimestamp) return "-";
|
|
3263
|
+
const date = new Date(t.blockTimestamp);
|
|
3264
|
+
if (isNaN(date.getTime())) return "-";
|
|
3265
|
+
return formatRelativeTime(date);
|
|
3266
|
+
}
|
|
2880
3267
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
3268
|
+
];
|
|
3269
|
+
var emptyState4 = /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
3270
|
+
/* @__PURE__ */ jsx11(Text10, { children: "No trades found for this coin." }),
|
|
3271
|
+
/* @__PURE__ */ jsx11(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "Press q to exit" }) })
|
|
3272
|
+
] });
|
|
3273
|
+
var CoinTradesView = ({
|
|
3274
|
+
fetchPage,
|
|
3275
|
+
coinName,
|
|
3276
|
+
limit,
|
|
3277
|
+
autoRefresh,
|
|
3278
|
+
intervalSeconds
|
|
3279
|
+
}) => {
|
|
3280
|
+
return /* @__PURE__ */ jsx11(
|
|
3281
|
+
PaginatedTableView,
|
|
3282
|
+
{
|
|
3283
|
+
fetchPage,
|
|
3284
|
+
columns: coinTradeColumns,
|
|
3285
|
+
title: `Recent trades \xB7 ${coinName}`,
|
|
3286
|
+
loadingText: "Loading trades\u2026",
|
|
3287
|
+
emptyState: emptyState4,
|
|
3288
|
+
getAddress: (trade) => trade.senderAddress,
|
|
3289
|
+
limit,
|
|
3290
|
+
autoRefresh,
|
|
3291
|
+
intervalSeconds
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
};
|
|
3295
|
+
|
|
3296
|
+
// src/components/CoinView.tsx
|
|
3297
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3298
|
+
var TAB_NAMES = ["Price History", "Trades", "Holders"];
|
|
3299
|
+
var TRADE_TAB_COLUMNS = coinTradeColumns.filter(
|
|
3300
|
+
(col) => col.header !== "#"
|
|
3301
|
+
);
|
|
3302
|
+
var CoinView = ({
|
|
3303
|
+
fetchData,
|
|
3304
|
+
initialData,
|
|
3305
|
+
autoRefresh = false,
|
|
3306
|
+
intervalSeconds = 30
|
|
3307
|
+
}) => {
|
|
3308
|
+
const { exit } = useApp3();
|
|
3309
|
+
const [activeTab, setActiveTab] = useState4(0);
|
|
3310
|
+
const [loading, setLoading] = useState4(!initialData);
|
|
3311
|
+
const [isRefreshing, setIsRefreshing] = useState4(false);
|
|
3312
|
+
const [error, setError] = useState4(null);
|
|
3313
|
+
const [data, setData] = useState4(initialData ?? null);
|
|
3314
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
3315
|
+
const [manualRefreshCount, setManualRefreshCount] = useState4(0);
|
|
3316
|
+
const hasLoadedOnce = useRef3(!!initialData);
|
|
3317
|
+
const load = useCallback4(async () => {
|
|
3318
|
+
if (hasLoadedOnce.current) {
|
|
3319
|
+
setIsRefreshing(true);
|
|
3320
|
+
} else {
|
|
3321
|
+
setLoading(true);
|
|
3322
|
+
}
|
|
3323
|
+
setError(null);
|
|
2883
3324
|
try {
|
|
2884
|
-
|
|
3325
|
+
const result = await fetchData();
|
|
3326
|
+
setData(result);
|
|
3327
|
+
hasLoadedOnce.current = true;
|
|
2885
3328
|
} catch (err) {
|
|
2886
|
-
|
|
2887
|
-
json,
|
|
2888
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2889
|
-
);
|
|
2890
|
-
return;
|
|
3329
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2891
3330
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
3331
|
+
setLoading(false);
|
|
3332
|
+
setIsRefreshing(false);
|
|
3333
|
+
}, [fetchData]);
|
|
3334
|
+
useEffect4(() => {
|
|
3335
|
+
if (initialData && refreshCount === 0 && manualRefreshCount === 0) return;
|
|
3336
|
+
load();
|
|
3337
|
+
}, [load, refreshCount, manualRefreshCount]);
|
|
3338
|
+
useInput3((input, key) => {
|
|
3339
|
+
if (input === "q" || key.escape) {
|
|
3340
|
+
exit();
|
|
2894
3341
|
return;
|
|
2895
3342
|
}
|
|
2896
|
-
if (
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3343
|
+
if (input === "r" && !loading) {
|
|
3344
|
+
triggerManualRefresh();
|
|
3345
|
+
setManualRefreshCount((c) => c + 1);
|
|
3346
|
+
}
|
|
3347
|
+
if (key.leftArrow) {
|
|
3348
|
+
setActiveTab((t) => Math.max(0, t - 1));
|
|
3349
|
+
}
|
|
3350
|
+
if (key.rightArrow) {
|
|
3351
|
+
setActiveTab((t) => Math.min(2, t + 1));
|
|
3352
|
+
}
|
|
3353
|
+
if (input === "1") setActiveTab(0);
|
|
3354
|
+
if (input === "2") setActiveTab(1);
|
|
3355
|
+
if (input === "3") setActiveTab(2);
|
|
3356
|
+
});
|
|
3357
|
+
if (error && !data) {
|
|
3358
|
+
return /* @__PURE__ */ jsxs11(
|
|
3359
|
+
Box11,
|
|
3360
|
+
{
|
|
3361
|
+
flexDirection: "column",
|
|
3362
|
+
paddingLeft: 1,
|
|
3363
|
+
paddingTop: 1,
|
|
3364
|
+
paddingBottom: 1,
|
|
3365
|
+
children: [
|
|
3366
|
+
/* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
|
|
3367
|
+
"Error: ",
|
|
3368
|
+
error
|
|
3369
|
+
] }),
|
|
3370
|
+
/* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Press q to exit" }) })
|
|
3371
|
+
]
|
|
3372
|
+
}
|
|
3373
|
+
);
|
|
3374
|
+
}
|
|
3375
|
+
if (loading && !data) {
|
|
3376
|
+
return /* @__PURE__ */ jsx12(Box11, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
3377
|
+
/* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
|
|
3378
|
+
" Loading coin\u2026"
|
|
3379
|
+
] }) });
|
|
3380
|
+
}
|
|
3381
|
+
if (!data) return null;
|
|
3382
|
+
const hints = [];
|
|
3383
|
+
if (TAB_NAMES.length > 1) {
|
|
3384
|
+
hints.push("\u2190 \u2192 switch tab");
|
|
3385
|
+
}
|
|
3386
|
+
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
3387
|
+
hints.push("q quit");
|
|
3388
|
+
const footer = hints.join(" \xB7 ");
|
|
3389
|
+
const renderTab = () => {
|
|
3390
|
+
switch (activeTab) {
|
|
3391
|
+
case 0:
|
|
3392
|
+
return data.priceHistory ? /* @__PURE__ */ jsx12(
|
|
3393
|
+
PriceHistory,
|
|
3394
|
+
{
|
|
3395
|
+
coin: data.coin.name,
|
|
3396
|
+
coinType: data.coin.coinType,
|
|
3397
|
+
interval: data.priceHistory.interval,
|
|
3398
|
+
high: data.priceHistory.high,
|
|
3399
|
+
low: data.priceHistory.low,
|
|
3400
|
+
change: data.priceHistory.change,
|
|
3401
|
+
sparklineText: data.priceHistory.sparklineText,
|
|
3402
|
+
compact: true
|
|
3403
|
+
}
|
|
3404
|
+
) : /* @__PURE__ */ jsx12(
|
|
3405
|
+
Box11,
|
|
3406
|
+
{
|
|
3407
|
+
flexDirection: "column",
|
|
3408
|
+
paddingLeft: 1,
|
|
3409
|
+
paddingTop: 1,
|
|
3410
|
+
paddingBottom: 1,
|
|
3411
|
+
children: /* @__PURE__ */ jsx12(Text11, { children: "No price data available." })
|
|
3412
|
+
}
|
|
3413
|
+
);
|
|
3414
|
+
case 1:
|
|
3415
|
+
return data.trades.length > 0 ? /* @__PURE__ */ jsx12(Table, { columns: TRADE_TAB_COLUMNS, data: data.trades }) : /* @__PURE__ */ jsx12(
|
|
3416
|
+
Box11,
|
|
3417
|
+
{
|
|
3418
|
+
flexDirection: "column",
|
|
3419
|
+
paddingLeft: 1,
|
|
3420
|
+
paddingTop: 1,
|
|
3421
|
+
paddingBottom: 1,
|
|
3422
|
+
children: /* @__PURE__ */ jsx12(Text11, { children: "No trades found." })
|
|
3423
|
+
}
|
|
3424
|
+
);
|
|
3425
|
+
case 2: {
|
|
3426
|
+
const holders = data.holders;
|
|
3427
|
+
if (!holders || holders.error) {
|
|
3428
|
+
return /* @__PURE__ */ jsx12(
|
|
3429
|
+
Box11,
|
|
3430
|
+
{
|
|
3431
|
+
flexDirection: "column",
|
|
3432
|
+
paddingLeft: 1,
|
|
3433
|
+
paddingTop: 1,
|
|
3434
|
+
paddingBottom: 1,
|
|
3435
|
+
children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: holders?.error ? `Could not load holders: ${holders.error}` : "Holder data not available." })
|
|
3436
|
+
}
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
if (holders.holders.length === 0) {
|
|
3440
|
+
return /* @__PURE__ */ jsx12(
|
|
3441
|
+
Box11,
|
|
3442
|
+
{
|
|
3443
|
+
flexDirection: "column",
|
|
3444
|
+
paddingLeft: 1,
|
|
3445
|
+
paddingTop: 1,
|
|
3446
|
+
paddingBottom: 1,
|
|
3447
|
+
children: /* @__PURE__ */ jsx12(Text11, { children: "No holders found for this coin." })
|
|
3448
|
+
}
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
const totalSupplyNum = Number(data.coin.totalSupply);
|
|
3452
|
+
const columns = makeHolderColumns({ totalSupplyNum });
|
|
3453
|
+
const rankedHolders = holders.holders.map((h, i) => ({
|
|
3454
|
+
...h,
|
|
3455
|
+
rank: i + 1
|
|
3456
|
+
}));
|
|
3457
|
+
return /* @__PURE__ */ jsx12(
|
|
3458
|
+
Table,
|
|
3459
|
+
{
|
|
3460
|
+
columns,
|
|
3461
|
+
data: rankedHolders,
|
|
3462
|
+
title: "Holders",
|
|
3463
|
+
subtitle: `${rankedHolders.length} of ${holders.totalCount}`
|
|
2907
3464
|
}
|
|
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
3465
|
);
|
|
2917
3466
|
}
|
|
2918
|
-
track("cli_get", {
|
|
2919
|
-
lookup_type: "name",
|
|
2920
|
-
found: true,
|
|
2921
|
-
ambiguous: true,
|
|
2922
|
-
output_format: json ? "json" : "text"
|
|
2923
|
-
});
|
|
2924
|
-
return;
|
|
2925
3467
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3468
|
+
};
|
|
3469
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
3470
|
+
isRefreshing && /* @__PURE__ */ jsx12(Box11, { paddingLeft: 1, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
3471
|
+
/* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
|
|
3472
|
+
" Refreshing\u2026"
|
|
3473
|
+
] }) }),
|
|
3474
|
+
error && data && /* @__PURE__ */ jsx12(Box11, { paddingLeft: 1, children: /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
|
|
3475
|
+
"\u26A0 Refresh failed: ",
|
|
3476
|
+
error
|
|
3477
|
+
] }) }),
|
|
3478
|
+
/* @__PURE__ */ jsx12(CoinDetail, { coin: data.coin }),
|
|
3479
|
+
/* @__PURE__ */ jsx12(Box11, { paddingLeft: 1, gap: 2, children: TAB_NAMES.map((name, i) => /* @__PURE__ */ jsx12(Text11, { bold: activeTab === i, dimColor: activeTab !== i, children: activeTab === i ? `[${name}]` : name }, name)) }),
|
|
3480
|
+
renderTab(),
|
|
3481
|
+
/* @__PURE__ */ jsx12(Box11, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: footer }) })
|
|
3482
|
+
] });
|
|
3483
|
+
};
|
|
3484
|
+
|
|
3485
|
+
// src/lib/price-history.ts
|
|
3486
|
+
import { apiGet } from "@zoralabs/coins-sdk";
|
|
3487
|
+
var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
|
|
3488
|
+
var INTERVAL_TO_API_FIELD = {
|
|
3489
|
+
"1h": "oneHour",
|
|
3490
|
+
"24h": "oneDay",
|
|
3491
|
+
"1w": "oneWeek",
|
|
3492
|
+
"1m": "oneMonth",
|
|
3493
|
+
ALL: "all"
|
|
3494
|
+
};
|
|
3495
|
+
var formatPrice = (price) => {
|
|
3496
|
+
if (price >= 1) {
|
|
3497
|
+
return `$${price.toFixed(2)}`;
|
|
2946
3498
|
}
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
2949
|
-
return;
|
|
3499
|
+
if (price >= 0.01) {
|
|
3500
|
+
return `$${price.toFixed(4)}`;
|
|
2950
3501
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3502
|
+
return `$${price.toPrecision(4)}`;
|
|
3503
|
+
};
|
|
3504
|
+
var formatChange = (first, last) => {
|
|
3505
|
+
if (first === 0) return { text: "-", color: void 0 };
|
|
3506
|
+
const pct = (last - first) / first * 100;
|
|
3507
|
+
const prefix = pct >= 0 ? "+" : "";
|
|
3508
|
+
const text = `${prefix}${pct.toFixed(1)}%`;
|
|
3509
|
+
const color = pct > 0 ? "green" : pct < 0 ? "red" : void 0;
|
|
3510
|
+
return { text, color };
|
|
3511
|
+
};
|
|
3512
|
+
var fetchPriceHistory = async (address, interval) => {
|
|
3513
|
+
const response = await apiGet("/coinPriceHistory", {
|
|
3514
|
+
address
|
|
2958
3515
|
});
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
label,
|
|
2971
|
-
children
|
|
2972
|
-
}) => /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2973
|
-
/* @__PURE__ */ jsx8(Box6, { width: LABEL_WIDTH2, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: label }) }),
|
|
2974
|
-
/* @__PURE__ */ jsx8(Text6, { children })
|
|
2975
|
-
] });
|
|
2976
|
-
var PriceHistory = ({
|
|
2977
|
-
coin,
|
|
2978
|
-
coinType,
|
|
2979
|
-
interval,
|
|
2980
|
-
high,
|
|
2981
|
-
low,
|
|
2982
|
-
change,
|
|
2983
|
-
sparklineText
|
|
2984
|
-
}) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2985
|
-
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2986
|
-
/* @__PURE__ */ jsx8(Row2, { label: "Coin", children: coin }),
|
|
2987
|
-
/* @__PURE__ */ jsx8(Row2, { label: "Type", children: coinType }),
|
|
2988
|
-
/* @__PURE__ */ jsx8(Row2, { label: "Interval", children: interval }),
|
|
2989
|
-
/* @__PURE__ */ jsx8(Row2, { label: "High", children: high }),
|
|
2990
|
-
/* @__PURE__ */ jsx8(Row2, { label: "Low", children: low }),
|
|
2991
|
-
/* @__PURE__ */ jsx8(Row2, { label: "Change", children: /* @__PURE__ */ jsx8(Text6, { color: change.color, children: change.text }) })
|
|
2992
|
-
] }),
|
|
2993
|
-
sparklineText.length > 0 && /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text6, { children: sparklineText }) }),
|
|
2994
|
-
/* @__PURE__ */ jsx8(Box6, { marginBottom: 1 })
|
|
2995
|
-
] });
|
|
3516
|
+
const data = response.data;
|
|
3517
|
+
const token = data?.zora20Token;
|
|
3518
|
+
if (!token) return [];
|
|
3519
|
+
const field = INTERVAL_TO_API_FIELD[interval];
|
|
3520
|
+
const points = token[field];
|
|
3521
|
+
if (!points || points.length === 0) return [];
|
|
3522
|
+
return points.map((p) => ({
|
|
3523
|
+
timestamp: p.timestamp,
|
|
3524
|
+
price: Number(p.closePrice)
|
|
3525
|
+
}));
|
|
3526
|
+
};
|
|
2996
3527
|
|
|
2997
3528
|
// src/lib/sparkline.ts
|
|
2998
3529
|
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
@@ -3024,49 +3555,315 @@ var downsample = (values, maxWidth) => {
|
|
|
3024
3555
|
return result;
|
|
3025
3556
|
};
|
|
3026
3557
|
|
|
3027
|
-
// src/commands/
|
|
3028
|
-
import { jsx as
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3558
|
+
// src/commands/get.tsx
|
|
3559
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3560
|
+
function formatCoinJson(coin) {
|
|
3561
|
+
return {
|
|
3562
|
+
name: coin.name,
|
|
3563
|
+
address: coin.address,
|
|
3564
|
+
coinType: coin.coinType,
|
|
3565
|
+
marketCap: coin.marketCap,
|
|
3566
|
+
marketCapDelta24h: coin.marketCapDelta24h,
|
|
3567
|
+
volume24h: coin.volume24h,
|
|
3568
|
+
uniqueHolders: coin.uniqueHolders,
|
|
3569
|
+
createdAt: coin.createdAt ?? null,
|
|
3570
|
+
creatorAddress: coin.creatorAddress ?? null,
|
|
3571
|
+
creatorHandle: coin.creatorHandle ?? null
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
var resolveApiKey = () => {
|
|
3575
|
+
const apiKey = getApiKey();
|
|
3576
|
+
if (apiKey) {
|
|
3577
|
+
setApiKey4(apiKey);
|
|
3578
|
+
}
|
|
3036
3579
|
};
|
|
3037
|
-
var
|
|
3038
|
-
|
|
3039
|
-
|
|
3580
|
+
var CoinResolutionError = class extends Error {
|
|
3581
|
+
suggestion;
|
|
3582
|
+
constructor(message, suggestion) {
|
|
3583
|
+
super(message);
|
|
3584
|
+
this.suggestion = suggestion;
|
|
3585
|
+
}
|
|
3586
|
+
};
|
|
3587
|
+
async function resolveCoinOrThrow(typeOrId, identifier, command) {
|
|
3588
|
+
const parsed = parsePositionalCoinArgs(typeOrId, identifier);
|
|
3589
|
+
resolveApiKey();
|
|
3590
|
+
if (parsed.kind === "ambiguous-name") {
|
|
3591
|
+
const ambResult = await resolveAmbiguousName(parsed.name);
|
|
3592
|
+
if (ambResult.kind === "not-found") {
|
|
3593
|
+
throw new Error(ambResult.message);
|
|
3594
|
+
}
|
|
3595
|
+
if (ambResult.kind === "ambiguous") {
|
|
3596
|
+
const { message, suggestion } = formatAmbiguousError(
|
|
3597
|
+
parsed.name,
|
|
3598
|
+
ambResult.creator,
|
|
3599
|
+
ambResult.trend,
|
|
3600
|
+
command
|
|
3601
|
+
);
|
|
3602
|
+
throw new CoinResolutionError(message, suggestion);
|
|
3603
|
+
}
|
|
3604
|
+
return {
|
|
3605
|
+
coin: ambResult.coin,
|
|
3606
|
+
lookupType: "name",
|
|
3607
|
+
coinTypeFilter: null
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
const ref = coinArgsToRef(parsed);
|
|
3611
|
+
const result = await resolveCoin(ref);
|
|
3612
|
+
if (result.kind === "not-found") {
|
|
3613
|
+
throw new CoinResolutionError(result.message, result.suggestion);
|
|
3614
|
+
}
|
|
3615
|
+
if (result.coin.platformBlocked) {
|
|
3616
|
+
throw new Error(bannedCoinMessage(result.coin.address));
|
|
3617
|
+
}
|
|
3618
|
+
return {
|
|
3619
|
+
coin: result.coin,
|
|
3620
|
+
lookupType: typeOrId.startsWith("0x") ? "address" : "name",
|
|
3621
|
+
coinTypeFilter: parsed.kind === "typed" ? parsed.type : null
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
async function resolveAndValidateCoin(json, typeOrId, identifier, command) {
|
|
3625
|
+
try {
|
|
3626
|
+
return await resolveCoinOrThrow(typeOrId, identifier, command);
|
|
3627
|
+
} catch (err) {
|
|
3628
|
+
if (err instanceof CoinArgError) {
|
|
3629
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
3630
|
+
}
|
|
3631
|
+
if (err instanceof CoinResolutionError) {
|
|
3632
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
3633
|
+
}
|
|
3634
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3635
|
+
outputErrorAndExit(json, msg);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
async function buildPriceHistoryData(address, interval) {
|
|
3639
|
+
let prices;
|
|
3640
|
+
try {
|
|
3641
|
+
prices = await fetchPriceHistory(address, interval);
|
|
3642
|
+
} catch {
|
|
3643
|
+
return null;
|
|
3644
|
+
}
|
|
3645
|
+
if (prices.length === 0) return null;
|
|
3646
|
+
const priceValues = prices.map((p) => p.price);
|
|
3647
|
+
const high = Math.max(...priceValues);
|
|
3648
|
+
const low = Math.min(...priceValues);
|
|
3649
|
+
const change = formatChange(
|
|
3650
|
+
priceValues[0],
|
|
3651
|
+
priceValues[priceValues.length - 1]
|
|
3652
|
+
);
|
|
3653
|
+
const sparklineText = sparkline(downsample(priceValues, MAX_SPARKLINE_WIDTH));
|
|
3654
|
+
return {
|
|
3655
|
+
high: formatPrice(high),
|
|
3656
|
+
low: formatPrice(low),
|
|
3657
|
+
change,
|
|
3658
|
+
sparklineText,
|
|
3659
|
+
interval
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
async function fetchHoldersPageForCoin(address, count, after) {
|
|
3663
|
+
const result = await getCoinHolders({
|
|
3664
|
+
chainId: BASE_CHAIN_ID,
|
|
3665
|
+
address,
|
|
3666
|
+
count,
|
|
3667
|
+
after
|
|
3668
|
+
});
|
|
3669
|
+
const token = result.data?.zora20Token;
|
|
3670
|
+
if (!token) return { items: [] };
|
|
3671
|
+
const items = token.tokenBalances.edges.map((e) => ({
|
|
3672
|
+
balance: e.node.balance,
|
|
3673
|
+
ownerAddress: e.node.ownerAddress,
|
|
3674
|
+
ownerProfile: e.node.ownerProfile && !e.node.ownerProfile.platformBlocked ? { handle: e.node.ownerProfile.handle } : void 0
|
|
3675
|
+
}));
|
|
3676
|
+
return {
|
|
3677
|
+
items,
|
|
3678
|
+
count: token.tokenBalances.count,
|
|
3679
|
+
pageInfo: token.tokenBalances.pageInfo
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
async function buildHoldersData(address) {
|
|
3683
|
+
try {
|
|
3684
|
+
const result = await fetchHoldersPageForCoin(address, 10);
|
|
3685
|
+
return {
|
|
3686
|
+
holders: result.items,
|
|
3687
|
+
totalCount: result.count ?? result.items.length
|
|
3688
|
+
};
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
return {
|
|
3691
|
+
holders: [],
|
|
3692
|
+
totalCount: 0,
|
|
3693
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
async function fetchRecentTrades(address) {
|
|
3698
|
+
try {
|
|
3699
|
+
const response = await getCoinSwaps({ address, first: 10 });
|
|
3700
|
+
const edges = response.data?.zora20Token?.swapActivities?.edges ?? [];
|
|
3701
|
+
return edges.map((e) => e.node);
|
|
3702
|
+
} catch {
|
|
3703
|
+
return [];
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
var getCommand = new Command5("get").description("Look up a coin by address or name").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
3707
|
+
"[identifier]",
|
|
3708
|
+
"Coin address (0x...) or name (when type prefix is given)"
|
|
3709
|
+
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
3710
|
+
"--refresh <seconds>",
|
|
3711
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
3712
|
+
"30"
|
|
3713
|
+
).action(async function(typeOrId, identifier) {
|
|
3714
|
+
const output = getOutputMode(this, "live");
|
|
3715
|
+
const json = output === "json";
|
|
3716
|
+
const interval = "1w";
|
|
3717
|
+
const { coin, lookupType, coinTypeFilter } = await resolveAndValidateCoin(
|
|
3718
|
+
json,
|
|
3719
|
+
typeOrId,
|
|
3720
|
+
identifier,
|
|
3721
|
+
"get"
|
|
3722
|
+
);
|
|
3723
|
+
if (json) {
|
|
3724
|
+
const [prices, trades] = await Promise.all([
|
|
3725
|
+
fetchPriceHistory(coin.address, interval).catch(() => []),
|
|
3726
|
+
fetchRecentTrades(coin.address)
|
|
3727
|
+
]);
|
|
3728
|
+
outputData(json, {
|
|
3729
|
+
json: {
|
|
3730
|
+
...formatCoinJson(coin),
|
|
3731
|
+
priceHistory: prices.length > 0 ? {
|
|
3732
|
+
interval,
|
|
3733
|
+
high: Math.max(...prices.map((p) => p.price)),
|
|
3734
|
+
low: Math.min(...prices.map((p) => p.price)),
|
|
3735
|
+
change: prices[0].price === 0 ? null : (prices[prices.length - 1].price - prices[0].price) / prices[0].price,
|
|
3736
|
+
prices: prices.map((p) => ({
|
|
3737
|
+
timestamp: p.timestamp,
|
|
3738
|
+
price: p.price
|
|
3739
|
+
}))
|
|
3740
|
+
} : null,
|
|
3741
|
+
trades: trades.map((t) => ({
|
|
3742
|
+
type: t.activityType ?? null,
|
|
3743
|
+
sender: t.senderAddress,
|
|
3744
|
+
senderHandle: t.senderProfile?.handle ?? null,
|
|
3745
|
+
coinAmount: t.coinAmount,
|
|
3746
|
+
valueUsd: t.currencyAmountWithPrice.priceUsdc ?? null,
|
|
3747
|
+
timestamp: t.blockTimestamp,
|
|
3748
|
+
transactionHash: t.transactionHash
|
|
3749
|
+
}))
|
|
3750
|
+
},
|
|
3751
|
+
render: () => {
|
|
3752
|
+
}
|
|
3753
|
+
});
|
|
3754
|
+
track("cli_get", {
|
|
3755
|
+
lookup_type: lookupType,
|
|
3756
|
+
coin_type_filter: coinTypeFilter,
|
|
3757
|
+
coin_type: coin.coinType,
|
|
3758
|
+
output_format: "json"
|
|
3759
|
+
});
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
3763
|
+
if (live) {
|
|
3764
|
+
const [initialPriceHistory, initialTrades, initialHolders] = await Promise.all([
|
|
3765
|
+
buildPriceHistoryData(coin.address, interval),
|
|
3766
|
+
fetchRecentTrades(coin.address),
|
|
3767
|
+
buildHoldersData(coin.address)
|
|
3768
|
+
]);
|
|
3769
|
+
const fetchData = async () => {
|
|
3770
|
+
const { coin: freshCoin } = await resolveCoinOrThrow(
|
|
3771
|
+
typeOrId,
|
|
3772
|
+
identifier,
|
|
3773
|
+
"get"
|
|
3774
|
+
);
|
|
3775
|
+
const [priceHistory, trades, holders] = await Promise.all([
|
|
3776
|
+
buildPriceHistoryData(freshCoin.address, interval),
|
|
3777
|
+
fetchRecentTrades(freshCoin.address),
|
|
3778
|
+
buildHoldersData(freshCoin.address)
|
|
3779
|
+
]);
|
|
3780
|
+
return { coin: freshCoin, priceHistory, trades, holders };
|
|
3781
|
+
};
|
|
3782
|
+
await renderLive(
|
|
3783
|
+
/* @__PURE__ */ jsx13(
|
|
3784
|
+
CoinView,
|
|
3785
|
+
{
|
|
3786
|
+
fetchData,
|
|
3787
|
+
initialData: {
|
|
3788
|
+
coin,
|
|
3789
|
+
priceHistory: initialPriceHistory,
|
|
3790
|
+
trades: initialTrades,
|
|
3791
|
+
holders: initialHolders
|
|
3792
|
+
},
|
|
3793
|
+
autoRefresh: live,
|
|
3794
|
+
intervalSeconds
|
|
3795
|
+
}
|
|
3796
|
+
)
|
|
3797
|
+
);
|
|
3798
|
+
track("cli_get", {
|
|
3799
|
+
lookup_type: lookupType,
|
|
3800
|
+
coin_type_filter: coinTypeFilter,
|
|
3801
|
+
coin_type: coin.coinType,
|
|
3802
|
+
output_format: "live",
|
|
3803
|
+
interval: intervalSeconds
|
|
3804
|
+
});
|
|
3805
|
+
} else {
|
|
3806
|
+
const [priceHistory, trades] = await Promise.all([
|
|
3807
|
+
buildPriceHistoryData(coin.address, interval),
|
|
3808
|
+
fetchRecentTrades(coin.address)
|
|
3809
|
+
]);
|
|
3810
|
+
renderOnce(
|
|
3811
|
+
/* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
3812
|
+
/* @__PURE__ */ jsx13(CoinDetail, { coin }),
|
|
3813
|
+
priceHistory ? /* @__PURE__ */ jsx13(
|
|
3814
|
+
PriceHistory,
|
|
3815
|
+
{
|
|
3816
|
+
coin: coin.name,
|
|
3817
|
+
coinType: coin.coinType,
|
|
3818
|
+
interval: priceHistory.interval,
|
|
3819
|
+
high: priceHistory.high,
|
|
3820
|
+
low: priceHistory.low,
|
|
3821
|
+
change: priceHistory.change,
|
|
3822
|
+
sparklineText: priceHistory.sparklineText,
|
|
3823
|
+
compact: true
|
|
3824
|
+
}
|
|
3825
|
+
) : /* @__PURE__ */ jsx13(Box12, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "No price data available." }) }),
|
|
3826
|
+
trades.length > 0 ? /* @__PURE__ */ jsx13(
|
|
3827
|
+
Table,
|
|
3828
|
+
{
|
|
3829
|
+
columns: coinTradeColumns.filter((c) => c.header !== "#"),
|
|
3830
|
+
data: trades,
|
|
3831
|
+
title: "Recent Trades"
|
|
3832
|
+
}
|
|
3833
|
+
) : /* @__PURE__ */ jsx13(Box12, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "No trades found." }) })
|
|
3834
|
+
] })
|
|
3835
|
+
);
|
|
3836
|
+
track("cli_get", {
|
|
3837
|
+
lookup_type: lookupType,
|
|
3838
|
+
coin_type_filter: coinTypeFilter,
|
|
3839
|
+
coin_type: coin.coinType,
|
|
3840
|
+
output_format: "static"
|
|
3841
|
+
});
|
|
3040
3842
|
}
|
|
3041
|
-
|
|
3042
|
-
|
|
3843
|
+
});
|
|
3844
|
+
getCommand.command("price-history").description("Display price history for a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
3845
|
+
"[identifier]",
|
|
3846
|
+
"Coin address (0x...) or name (when type prefix is given)"
|
|
3847
|
+
).option(
|
|
3848
|
+
"--interval <interval>",
|
|
3849
|
+
`Time range: ${VALID_INTERVALS.join(", ")}`,
|
|
3850
|
+
"1w"
|
|
3851
|
+
).action(async function(typeOrId, identifier, opts) {
|
|
3852
|
+
const json = getJson(this);
|
|
3853
|
+
const interval = opts.interval ?? "1w";
|
|
3854
|
+
if (!VALID_INTERVALS.includes(interval)) {
|
|
3855
|
+
outputErrorAndExit(
|
|
3856
|
+
json,
|
|
3857
|
+
`Invalid --interval value: ${interval}.`,
|
|
3858
|
+
`Supported: ${VALID_INTERVALS.join(", ")}`
|
|
3859
|
+
);
|
|
3043
3860
|
}
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
const text = `${prefix}${pct.toFixed(1)}%`;
|
|
3051
|
-
const color = pct > 0 ? "green" : pct < 0 ? "red" : void 0;
|
|
3052
|
-
return { text, color };
|
|
3053
|
-
};
|
|
3054
|
-
var fetchPriceHistory = async (address, interval) => {
|
|
3055
|
-
const response = await apiGet("/coinPriceHistory", {
|
|
3056
|
-
address
|
|
3057
|
-
});
|
|
3058
|
-
const data = response.data;
|
|
3059
|
-
const token = data?.zora20Token;
|
|
3060
|
-
if (!token) return [];
|
|
3061
|
-
const field = INTERVAL_TO_API_FIELD[interval];
|
|
3062
|
-
const points = token[field];
|
|
3063
|
-
if (!points || points.length === 0) return [];
|
|
3064
|
-
return points.map((p) => ({
|
|
3065
|
-
timestamp: p.timestamp,
|
|
3066
|
-
price: Number(p.closePrice)
|
|
3067
|
-
}));
|
|
3068
|
-
};
|
|
3069
|
-
async function showPriceHistory(json, coin, interval) {
|
|
3861
|
+
const { coin, lookupType } = await resolveAndValidateCoin(
|
|
3862
|
+
json,
|
|
3863
|
+
typeOrId,
|
|
3864
|
+
identifier,
|
|
3865
|
+
"get price-history"
|
|
3866
|
+
);
|
|
3070
3867
|
let prices;
|
|
3071
3868
|
try {
|
|
3072
3869
|
prices = await fetchPriceHistory(coin.address, interval);
|
|
@@ -3075,7 +3872,6 @@ async function showPriceHistory(json, coin, interval) {
|
|
|
3075
3872
|
json,
|
|
3076
3873
|
`Failed to fetch price data: ${err instanceof Error ? err.message : String(err)}`
|
|
3077
3874
|
);
|
|
3078
|
-
return;
|
|
3079
3875
|
}
|
|
3080
3876
|
if (prices.length === 0) {
|
|
3081
3877
|
outputErrorAndExit(
|
|
@@ -3083,7 +3879,6 @@ async function showPriceHistory(json, coin, interval) {
|
|
|
3083
3879
|
`No price data found for ${coin.name} in the last ${interval}.`,
|
|
3084
3880
|
"Try a longer interval with --interval"
|
|
3085
3881
|
);
|
|
3086
|
-
return;
|
|
3087
3882
|
}
|
|
3088
3883
|
const priceValues = prices.map((p) => p.price);
|
|
3089
3884
|
const high = Math.max(...priceValues);
|
|
@@ -3092,11 +3887,13 @@ async function showPriceHistory(json, coin, interval) {
|
|
|
3092
3887
|
priceValues[0],
|
|
3093
3888
|
priceValues[priceValues.length - 1]
|
|
3094
3889
|
);
|
|
3095
|
-
const sparklineText = sparkline(
|
|
3890
|
+
const sparklineText = sparkline(
|
|
3891
|
+
downsample(priceValues, MAX_SPARKLINE_WIDTH)
|
|
3892
|
+
);
|
|
3096
3893
|
outputData(json, {
|
|
3097
3894
|
json: {
|
|
3098
3895
|
coin: coin.name,
|
|
3099
|
-
|
|
3896
|
+
coinType: coin.coinType,
|
|
3100
3897
|
interval,
|
|
3101
3898
|
high,
|
|
3102
3899
|
low,
|
|
@@ -3108,7 +3905,7 @@ async function showPriceHistory(json, coin, interval) {
|
|
|
3108
3905
|
},
|
|
3109
3906
|
render: () => {
|
|
3110
3907
|
renderOnce(
|
|
3111
|
-
/* @__PURE__ */
|
|
3908
|
+
/* @__PURE__ */ jsx13(
|
|
3112
3909
|
PriceHistory,
|
|
3113
3910
|
{
|
|
3114
3911
|
coin: coin.name,
|
|
@@ -3123,145 +3920,316 @@ async function showPriceHistory(json, coin, interval) {
|
|
|
3123
3920
|
);
|
|
3124
3921
|
}
|
|
3125
3922
|
});
|
|
3126
|
-
|
|
3923
|
+
track("cli_get_price_history", {
|
|
3924
|
+
lookup_type: lookupType,
|
|
3925
|
+
coin_type: coin.coinType,
|
|
3926
|
+
interval,
|
|
3927
|
+
data_points: prices.length,
|
|
3928
|
+
output_format: json ? "json" : "text"
|
|
3929
|
+
});
|
|
3930
|
+
});
|
|
3931
|
+
function formatTradeJson(node) {
|
|
3932
|
+
return {
|
|
3933
|
+
type: node.activityType ?? null,
|
|
3934
|
+
sender: node.senderAddress,
|
|
3935
|
+
senderHandle: node.senderProfile?.handle ?? null,
|
|
3936
|
+
coinAmount: node.coinAmount,
|
|
3937
|
+
valueUsd: node.currencyAmountWithPrice.priceUsdc ?? null,
|
|
3938
|
+
timestamp: node.blockTimestamp,
|
|
3939
|
+
transactionHash: node.transactionHash
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
async function fetchTradesPage(address, count, after) {
|
|
3943
|
+
const response = await getCoinSwaps({ address, first: count, after });
|
|
3944
|
+
if (response.error) {
|
|
3945
|
+
throw new Error(extractErrorMessage(response.error));
|
|
3946
|
+
}
|
|
3947
|
+
const swapActivities = response.data?.zora20Token?.swapActivities;
|
|
3948
|
+
const edges = swapActivities?.edges ?? [];
|
|
3949
|
+
const items = edges.map(
|
|
3950
|
+
(e) => e.node
|
|
3951
|
+
);
|
|
3952
|
+
const count_ = swapActivities?.count ?? items.length;
|
|
3953
|
+
const pageInfo = swapActivities?.pageInfo;
|
|
3954
|
+
return { items, count: count_, pageInfo };
|
|
3127
3955
|
}
|
|
3128
|
-
|
|
3956
|
+
getCommand.command("trades").description("Show recent buy/sell activity on a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
3129
3957
|
"[identifier]",
|
|
3130
3958
|
"Coin address (0x...) or name (when type prefix is given)"
|
|
3131
|
-
).option(
|
|
3132
|
-
"--
|
|
3133
|
-
|
|
3134
|
-
"
|
|
3135
|
-
).action(async function(typeOrId, identifier
|
|
3136
|
-
const
|
|
3137
|
-
const
|
|
3138
|
-
|
|
3959
|
+
).option("--limit <n>", "Number of results (default 10, max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
3960
|
+
"--refresh <seconds>",
|
|
3961
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
3962
|
+
"30"
|
|
3963
|
+
).action(async function(typeOrId, identifier) {
|
|
3964
|
+
const output = getOutputMode(this, "live");
|
|
3965
|
+
const json = output === "json";
|
|
3966
|
+
const limit = Math.min(
|
|
3967
|
+
20,
|
|
3968
|
+
Math.max(1, parseInt(this.opts().limit, 10) || 10)
|
|
3969
|
+
);
|
|
3970
|
+
const after = this.opts().after;
|
|
3971
|
+
if (output === "live" && after) {
|
|
3139
3972
|
outputErrorAndExit(
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3973
|
+
false,
|
|
3974
|
+
"--after cannot be used in live mode.",
|
|
3975
|
+
"Use --static or --json to paginate with a cursor."
|
|
3143
3976
|
);
|
|
3144
3977
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3978
|
+
const { coin } = await resolveAndValidateCoin(
|
|
3979
|
+
json,
|
|
3980
|
+
typeOrId,
|
|
3981
|
+
identifier,
|
|
3982
|
+
"get trades"
|
|
3983
|
+
);
|
|
3984
|
+
if (json) {
|
|
3985
|
+
const result = await fetchTradesPage(coin.address, limit, after).catch(
|
|
3986
|
+
(err) => outputErrorAndExit(
|
|
3987
|
+
json,
|
|
3988
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3989
|
+
)
|
|
3990
|
+
);
|
|
3991
|
+
outputData(json, {
|
|
3992
|
+
json: {
|
|
3993
|
+
coin: { name: coin.name, address: coin.address },
|
|
3994
|
+
trades: result.items.map(formatTradeJson),
|
|
3995
|
+
pageInfo: result.pageInfo ?? null
|
|
3996
|
+
},
|
|
3997
|
+
render: () => {
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
track("cli_get_trades", {
|
|
4001
|
+
result_count: result.items.length,
|
|
4002
|
+
output_format: "json"
|
|
4003
|
+
});
|
|
4004
|
+
} else {
|
|
4005
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
4006
|
+
if (live) {
|
|
4007
|
+
const fetchPage = (cursor) => fetchTradesPage(coin.address, limit, cursor);
|
|
4008
|
+
await renderLive(
|
|
4009
|
+
/* @__PURE__ */ jsx13(
|
|
4010
|
+
CoinTradesView,
|
|
4011
|
+
{
|
|
4012
|
+
fetchPage,
|
|
4013
|
+
coinName: coin.name,
|
|
4014
|
+
limit,
|
|
4015
|
+
autoRefresh: live,
|
|
4016
|
+
intervalSeconds
|
|
4017
|
+
}
|
|
4018
|
+
)
|
|
4019
|
+
);
|
|
4020
|
+
track("cli_get_trades", {
|
|
4021
|
+
output_format: "live",
|
|
4022
|
+
live,
|
|
4023
|
+
interval: intervalSeconds
|
|
4024
|
+
});
|
|
4025
|
+
} else {
|
|
4026
|
+
const result = await fetchTradesPage(coin.address, limit, after).catch(
|
|
4027
|
+
(err) => outputErrorAndExit(
|
|
4028
|
+
json,
|
|
4029
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4030
|
+
)
|
|
4031
|
+
);
|
|
4032
|
+
const rankedTrades = result.items.map((t, i) => ({
|
|
4033
|
+
...t,
|
|
4034
|
+
rank: i + 1
|
|
4035
|
+
}));
|
|
4036
|
+
if (rankedTrades.length === 0) {
|
|
4037
|
+
renderOnce(
|
|
4038
|
+
/* @__PURE__ */ jsx13(
|
|
4039
|
+
Box12,
|
|
4040
|
+
{
|
|
4041
|
+
flexDirection: "column",
|
|
4042
|
+
paddingLeft: 1,
|
|
4043
|
+
paddingTop: 1,
|
|
4044
|
+
paddingBottom: 1,
|
|
4045
|
+
children: /* @__PURE__ */ jsxs12(Text12, { children: [
|
|
4046
|
+
"No trades found for ",
|
|
4047
|
+
coin.name,
|
|
4048
|
+
" (",
|
|
4049
|
+
truncateAddress(coin.address),
|
|
4050
|
+
")"
|
|
4051
|
+
] })
|
|
4052
|
+
}
|
|
4053
|
+
)
|
|
4054
|
+
);
|
|
4055
|
+
} else {
|
|
4056
|
+
const footer = result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? `Next page: zora get trades ${typeOrId}${identifier ? " " + identifier : ""} --limit ${limit} --after ${result.pageInfo.endCursor}` : void 0;
|
|
4057
|
+
renderOnce(
|
|
4058
|
+
/* @__PURE__ */ jsx13(
|
|
4059
|
+
Table,
|
|
4060
|
+
{
|
|
4061
|
+
columns: coinTradeColumns,
|
|
4062
|
+
data: rankedTrades,
|
|
4063
|
+
title: `Recent trades \xB7 ${coin.name}`,
|
|
4064
|
+
subtitle: `${rankedTrades.length} of ${result.count}`,
|
|
4065
|
+
footer
|
|
4066
|
+
}
|
|
4067
|
+
)
|
|
4068
|
+
);
|
|
4069
|
+
}
|
|
4070
|
+
track("cli_get_trades", {
|
|
4071
|
+
result_count: result.items.length,
|
|
4072
|
+
output_format: "static"
|
|
4073
|
+
});
|
|
3151
4074
|
}
|
|
3152
|
-
throw err;
|
|
3153
4075
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
4076
|
+
});
|
|
4077
|
+
getCommand.command("holders").description("Show top holders of a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
4078
|
+
"[identifier]",
|
|
4079
|
+
"Coin address (0x...) or name (when type prefix is given)"
|
|
4080
|
+
).option("--limit <n>", "Number of results per page (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
4081
|
+
"--refresh <seconds>",
|
|
4082
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
4083
|
+
"30"
|
|
4084
|
+
).action(async function(typeOrId, identifier) {
|
|
4085
|
+
const output = getOutputMode(this, "live");
|
|
4086
|
+
const json = output === "json";
|
|
4087
|
+
const opts = this.opts();
|
|
4088
|
+
const limit = parseInt(opts.limit, 10);
|
|
4089
|
+
const after = opts.after;
|
|
4090
|
+
if (isNaN(limit) || limit <= 0 || limit > 20) {
|
|
4091
|
+
outputErrorAndExit(
|
|
4092
|
+
json,
|
|
4093
|
+
`Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
|
|
4094
|
+
"Usage: zora get holders --limit 10"
|
|
4095
|
+
);
|
|
3157
4096
|
}
|
|
3158
|
-
if (
|
|
3159
|
-
|
|
4097
|
+
if (output === "live" && after) {
|
|
4098
|
+
outputErrorAndExit(
|
|
4099
|
+
false,
|
|
4100
|
+
"--after cannot be used in live mode.",
|
|
4101
|
+
"Use --static or --json to paginate with a cursor."
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
const { coin, lookupType } = await resolveAndValidateCoin(
|
|
4105
|
+
json,
|
|
4106
|
+
typeOrId,
|
|
4107
|
+
identifier,
|
|
4108
|
+
"get holders"
|
|
4109
|
+
);
|
|
4110
|
+
const totalSupply = Number(coin.totalSupply);
|
|
4111
|
+
if (json) {
|
|
4112
|
+
let result;
|
|
3160
4113
|
try {
|
|
3161
|
-
|
|
4114
|
+
result = await fetchHoldersPageForCoin(coin.address, limit, after);
|
|
3162
4115
|
} catch (err) {
|
|
3163
4116
|
outputErrorAndExit(
|
|
3164
4117
|
json,
|
|
3165
|
-
`
|
|
4118
|
+
`Failed to fetch holders: ${err instanceof Error ? err.message : String(err)}`
|
|
3166
4119
|
);
|
|
3167
|
-
return;
|
|
3168
|
-
}
|
|
3169
|
-
if (ambResult.kind === "not-found") {
|
|
3170
|
-
outputErrorAndExit(json, ambResult.message);
|
|
3171
|
-
return;
|
|
3172
4120
|
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
4121
|
+
outputData(json, {
|
|
4122
|
+
json: {
|
|
4123
|
+
coin: coin.name,
|
|
4124
|
+
address: coin.address,
|
|
4125
|
+
coinType: coin.coinType,
|
|
4126
|
+
totalHolders: result.count ?? 0,
|
|
4127
|
+
holders: result.items.map((h, i) => ({
|
|
4128
|
+
rank: i + 1,
|
|
4129
|
+
handle: h.ownerProfile?.handle ?? h.ownerAddress,
|
|
4130
|
+
address: h.ownerAddress,
|
|
4131
|
+
balance: formatBalance(h.balance),
|
|
4132
|
+
balanceRaw: h.balance,
|
|
4133
|
+
ownershipPercent: totalSupply > 0 ? parseRawBalance(h.balance) / totalSupply * 100 : 0
|
|
4134
|
+
})),
|
|
4135
|
+
...result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? { nextCursor: result.pageInfo.endCursor } : {}
|
|
4136
|
+
},
|
|
4137
|
+
render: () => {
|
|
4138
|
+
}
|
|
4139
|
+
});
|
|
4140
|
+
track("cli_get_holders", {
|
|
4141
|
+
lookup_type: lookupType,
|
|
4142
|
+
coin_type: coin.coinType,
|
|
4143
|
+
limit,
|
|
4144
|
+
total_holders: result.count ?? 0,
|
|
4145
|
+
output_format: "json"
|
|
4146
|
+
});
|
|
4147
|
+
} else {
|
|
4148
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
4149
|
+
if (live) {
|
|
4150
|
+
const fetchPage = (cursor) => fetchHoldersPageForCoin(coin.address, limit, cursor);
|
|
4151
|
+
await renderLive(
|
|
4152
|
+
/* @__PURE__ */ jsx13(
|
|
4153
|
+
CoinHoldersView,
|
|
4154
|
+
{
|
|
4155
|
+
fetchPage,
|
|
4156
|
+
coinName: coin.name,
|
|
4157
|
+
totalSupplyNum: totalSupply,
|
|
4158
|
+
limit,
|
|
4159
|
+
autoRefresh: live,
|
|
4160
|
+
intervalSeconds
|
|
3196
4161
|
}
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
4162
|
+
)
|
|
4163
|
+
);
|
|
4164
|
+
track("cli_get_holders", {
|
|
4165
|
+
lookup_type: lookupType,
|
|
4166
|
+
coin_type: coin.coinType,
|
|
4167
|
+
limit,
|
|
4168
|
+
output_format: "live",
|
|
4169
|
+
interval: intervalSeconds
|
|
4170
|
+
});
|
|
4171
|
+
} else {
|
|
4172
|
+
let result;
|
|
4173
|
+
try {
|
|
4174
|
+
result = await fetchHoldersPageForCoin(coin.address, limit, after);
|
|
4175
|
+
} catch (err) {
|
|
4176
|
+
outputErrorAndExit(
|
|
4177
|
+
json,
|
|
4178
|
+
`Failed to fetch holders: ${err instanceof Error ? err.message : String(err)}`
|
|
3203
4179
|
);
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
4180
|
+
}
|
|
4181
|
+
if (result.items.length === 0) {
|
|
4182
|
+
renderOnce(
|
|
4183
|
+
/* @__PURE__ */ jsx13(
|
|
4184
|
+
Box12,
|
|
4185
|
+
{
|
|
4186
|
+
flexDirection: "column",
|
|
4187
|
+
paddingLeft: 1,
|
|
4188
|
+
paddingTop: 1,
|
|
4189
|
+
paddingBottom: 1,
|
|
4190
|
+
children: /* @__PURE__ */ jsxs12(Text12, { children: [
|
|
4191
|
+
"No holders found for ",
|
|
4192
|
+
coin.name,
|
|
4193
|
+
"."
|
|
4194
|
+
] })
|
|
4195
|
+
}
|
|
4196
|
+
)
|
|
4197
|
+
);
|
|
4198
|
+
} else {
|
|
4199
|
+
const holderColumns = makeHolderColumns({
|
|
4200
|
+
totalSupplyNum: totalSupply
|
|
4201
|
+
});
|
|
4202
|
+
const rankedItems = result.items.map((item, i) => ({
|
|
4203
|
+
...item,
|
|
4204
|
+
rank: i + 1
|
|
4205
|
+
}));
|
|
4206
|
+
const footer = result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? `Next page: zora get holders ${coin.address} --limit ${limit} --after ${result.pageInfo.endCursor}` : void 0;
|
|
4207
|
+
renderOnce(
|
|
4208
|
+
/* @__PURE__ */ jsx13(
|
|
4209
|
+
Table,
|
|
4210
|
+
{
|
|
4211
|
+
columns: holderColumns,
|
|
4212
|
+
data: rankedItems,
|
|
4213
|
+
title: `Top holders \xB7 ${coin.name}`,
|
|
4214
|
+
subtitle: `${rankedItems.length} of ${result.count ?? rankedItems.length}`,
|
|
4215
|
+
footer
|
|
4216
|
+
}
|
|
4217
|
+
)
|
|
3209
4218
|
);
|
|
3210
4219
|
}
|
|
3211
|
-
track("
|
|
3212
|
-
lookup_type:
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
4220
|
+
track("cli_get_holders", {
|
|
4221
|
+
lookup_type: lookupType,
|
|
4222
|
+
coin_type: coin.coinType,
|
|
4223
|
+
limit,
|
|
4224
|
+
total_holders: result.count ?? 0,
|
|
4225
|
+
output_format: "static"
|
|
3216
4226
|
});
|
|
3217
|
-
return;
|
|
3218
4227
|
}
|
|
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
4228
|
}
|
|
3252
|
-
const { coin } = result;
|
|
3253
|
-
const dataPoints = await showPriceHistory(json, coin, interval);
|
|
3254
|
-
track("cli_price_history", {
|
|
3255
|
-
lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
|
|
3256
|
-
coin_type: coin.coinType,
|
|
3257
|
-
interval,
|
|
3258
|
-
data_points: dataPoints,
|
|
3259
|
-
output_format: json ? "json" : "text"
|
|
3260
|
-
});
|
|
3261
4229
|
});
|
|
3262
4230
|
|
|
3263
4231
|
// src/commands/sell.ts
|
|
3264
|
-
import { Command as
|
|
4232
|
+
import { Command as Command6 } from "commander";
|
|
3265
4233
|
import confirm3 from "@inquirer/confirm";
|
|
3266
4234
|
import {
|
|
3267
4235
|
erc20Abi as erc20Abi3,
|
|
@@ -3272,7 +4240,7 @@ import {
|
|
|
3272
4240
|
import {
|
|
3273
4241
|
createTradeCall as createTradeCall2,
|
|
3274
4242
|
getCoin as getCoin3,
|
|
3275
|
-
setApiKey as
|
|
4243
|
+
setApiKey as setApiKey5,
|
|
3276
4244
|
tradeCoin as tradeCoin2
|
|
3277
4245
|
} from "@zoralabs/coins-sdk";
|
|
3278
4246
|
function printSellQuote(output, info) {
|
|
@@ -3349,7 +4317,7 @@ function printSellResult(output, info) {
|
|
|
3349
4317
|
console.log(` Tx ${info.txHash}
|
|
3350
4318
|
`);
|
|
3351
4319
|
}
|
|
3352
|
-
var sellCommand = new
|
|
4320
|
+
var sellCommand = new Command6("sell").description("Sell a coin").argument(
|
|
3353
4321
|
"[typeOrId]",
|
|
3354
4322
|
"Type prefix (creator-coin, trend) or coin address/name"
|
|
3355
4323
|
).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) {
|
|
@@ -3366,9 +4334,10 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument(
|
|
|
3366
4334
|
}
|
|
3367
4335
|
const apiKey = getApiKey();
|
|
3368
4336
|
if (apiKey) {
|
|
3369
|
-
|
|
4337
|
+
setApiKey5(apiKey);
|
|
3370
4338
|
}
|
|
3371
4339
|
let coinAddress;
|
|
4340
|
+
let earlyAccount;
|
|
3372
4341
|
if (parsed.kind === "address") {
|
|
3373
4342
|
if (!isAddress2(parsed.address)) {
|
|
3374
4343
|
outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
|
|
@@ -3378,8 +4347,23 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument(
|
|
|
3378
4347
|
} else if (parsed.kind === "ambiguous-name") {
|
|
3379
4348
|
let ambResult;
|
|
3380
4349
|
try {
|
|
3381
|
-
|
|
4350
|
+
earlyAccount = resolveAccount(json);
|
|
4351
|
+
const { publicClient: earlyPublicClient } = createClients(earlyAccount);
|
|
4352
|
+
ambResult = await resolveAmbiguousByNameAndBalance(
|
|
4353
|
+
parsed.name,
|
|
4354
|
+
(addr) => earlyPublicClient.readContract({
|
|
4355
|
+
abi: erc20Abi3,
|
|
4356
|
+
address: addr,
|
|
4357
|
+
functionName: "balanceOf",
|
|
4358
|
+
args: [earlyAccount.address]
|
|
4359
|
+
})
|
|
4360
|
+
);
|
|
3382
4361
|
} catch (err) {
|
|
4362
|
+
if (debug) {
|
|
4363
|
+
console.error(
|
|
4364
|
+
`[debug] resolve failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4365
|
+
);
|
|
4366
|
+
}
|
|
3383
4367
|
outputErrorAndExit(
|
|
3384
4368
|
json,
|
|
3385
4369
|
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3441,7 +4425,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument(
|
|
|
3441
4425
|
);
|
|
3442
4426
|
}
|
|
3443
4427
|
const slippage = slippagePct / 100;
|
|
3444
|
-
const account = resolveAccount(json);
|
|
4428
|
+
const account = earlyAccount ?? resolveAccount(json);
|
|
3445
4429
|
const { publicClient, walletClient } = createClients(account);
|
|
3446
4430
|
let token;
|
|
3447
4431
|
try {
|
|
@@ -3582,7 +4566,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3582
4566
|
if (errorType === "LIQUIDITY" || msg.includes("Not enough liquidity")) {
|
|
3583
4567
|
if (json) {
|
|
3584
4568
|
outputJson({ error: errorBody ?? msg });
|
|
3585
|
-
|
|
4569
|
+
safeExit(ERROR);
|
|
3586
4570
|
}
|
|
3587
4571
|
outputErrorAndExit(
|
|
3588
4572
|
json,
|
|
@@ -3660,7 +4644,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3660
4644
|
});
|
|
3661
4645
|
if (!ok) {
|
|
3662
4646
|
console.error("Aborted.");
|
|
3663
|
-
|
|
4647
|
+
safeExit(SUCCESS);
|
|
3664
4648
|
}
|
|
3665
4649
|
}
|
|
3666
4650
|
let receipt;
|
|
@@ -3750,24 +4734,28 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3750
4734
|
});
|
|
3751
4735
|
|
|
3752
4736
|
// src/commands/profile.tsx
|
|
3753
|
-
import { Command as
|
|
3754
|
-
import { Box as
|
|
4737
|
+
import { Command as Command7 } from "commander";
|
|
4738
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
3755
4739
|
import {
|
|
3756
4740
|
getProfileCoins,
|
|
3757
4741
|
getProfileBalances as getProfileBalances2,
|
|
3758
|
-
|
|
4742
|
+
getWalletTradeActivity,
|
|
4743
|
+
setApiKey as setApiKey6
|
|
3759
4744
|
} from "@zoralabs/coins-sdk";
|
|
3760
4745
|
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3761
4746
|
|
|
3762
4747
|
// src/components/ProfileView.tsx
|
|
3763
|
-
import { useState as
|
|
3764
|
-
import { Box as
|
|
3765
|
-
import
|
|
3766
|
-
|
|
3767
|
-
|
|
4748
|
+
import { useState as useState5, useEffect as useEffect5, useCallback as useCallback5, useRef as useRef4 } from "react";
|
|
4749
|
+
import { Box as Box15, Text as Text15, useInput as useInput4, useApp as useApp4 } from "ink";
|
|
4750
|
+
import Spinner4 from "ink-spinner";
|
|
4751
|
+
|
|
4752
|
+
// src/components/ProfilePostsView.tsx
|
|
4753
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
4754
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3768
4755
|
var postColumns = [
|
|
3769
4756
|
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
3770
4757
|
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
4758
|
+
{ header: "Address", width: 22, accessor: (c) => c.address ?? "" },
|
|
3771
4759
|
{
|
|
3772
4760
|
header: "Type",
|
|
3773
4761
|
width: 14,
|
|
@@ -3800,22 +4788,121 @@ var postColumns = [
|
|
|
3800
4788
|
}
|
|
3801
4789
|
}
|
|
3802
4790
|
];
|
|
4791
|
+
var emptyState5 = /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
4792
|
+
/* @__PURE__ */ jsx14(Text13, { children: "No posts found for this profile." }),
|
|
4793
|
+
/* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: "Press q to exit" }) })
|
|
4794
|
+
] });
|
|
4795
|
+
var ProfilePostsView = ({
|
|
4796
|
+
fetchPage,
|
|
4797
|
+
identifier,
|
|
4798
|
+
limit,
|
|
4799
|
+
autoRefresh,
|
|
4800
|
+
intervalSeconds
|
|
4801
|
+
}) => {
|
|
4802
|
+
return /* @__PURE__ */ jsx14(
|
|
4803
|
+
PaginatedTableView,
|
|
4804
|
+
{
|
|
4805
|
+
fetchPage,
|
|
4806
|
+
columns: postColumns,
|
|
4807
|
+
title: `Posts \xB7 ${identifier}`,
|
|
4808
|
+
loadingText: "Loading posts\u2026",
|
|
4809
|
+
emptyState: emptyState5,
|
|
4810
|
+
getAddress: (post) => post.address,
|
|
4811
|
+
limit,
|
|
4812
|
+
autoRefresh,
|
|
4813
|
+
intervalSeconds
|
|
4814
|
+
}
|
|
4815
|
+
);
|
|
4816
|
+
};
|
|
4817
|
+
|
|
4818
|
+
// src/components/ProfileTradesView.tsx
|
|
4819
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
4820
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4821
|
+
var tradeColumns = [
|
|
4822
|
+
{ header: "#", width: 4, accessor: (t) => String(t.rank) },
|
|
4823
|
+
{
|
|
4824
|
+
header: "Side",
|
|
4825
|
+
width: 6,
|
|
4826
|
+
accessor: (t) => t.swapActivityType ?? "-",
|
|
4827
|
+
color: (t) => t.swapActivityType === "BUY" ? "green" : t.swapActivityType === "SELL" ? "red" : void 0
|
|
4828
|
+
},
|
|
4829
|
+
{
|
|
4830
|
+
header: "Coin",
|
|
4831
|
+
width: 18,
|
|
4832
|
+
accessor: (t) => t.coin?.name ?? "Unknown"
|
|
4833
|
+
},
|
|
4834
|
+
{
|
|
4835
|
+
header: "Type",
|
|
4836
|
+
width: 10,
|
|
4837
|
+
accessor: (t) => COIN_TYPE_DISPLAY[t.coin?.coinType ?? ""] ?? t.coin?.coinType ?? ""
|
|
4838
|
+
},
|
|
4839
|
+
{
|
|
4840
|
+
header: "Amount",
|
|
4841
|
+
width: 14,
|
|
4842
|
+
accessor: (t) => formatCoinsDisplay(t.coinAmount)
|
|
4843
|
+
},
|
|
4844
|
+
{
|
|
4845
|
+
header: "USD Value",
|
|
4846
|
+
width: 12,
|
|
4847
|
+
accessor: (t) => formatCompactUsd(t.currencyAmountWithPrice.amountUsd)
|
|
4848
|
+
},
|
|
4849
|
+
{
|
|
4850
|
+
header: "When",
|
|
4851
|
+
width: 16,
|
|
4852
|
+
accessor: (t) => {
|
|
4853
|
+
if (!t.blockTimestamp) return "-";
|
|
4854
|
+
const date = new Date(t.blockTimestamp);
|
|
4855
|
+
if (isNaN(date.getTime())) return "-";
|
|
4856
|
+
return formatRelativeTime(date);
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
];
|
|
4860
|
+
var emptyState6 = /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
4861
|
+
/* @__PURE__ */ jsx15(Text14, { children: "No trades found for this profile." }),
|
|
4862
|
+
/* @__PURE__ */ jsx15(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: "Press q to exit" }) })
|
|
4863
|
+
] });
|
|
4864
|
+
var ProfileTradesView = ({
|
|
4865
|
+
fetchPage,
|
|
4866
|
+
identifier,
|
|
4867
|
+
limit,
|
|
4868
|
+
autoRefresh,
|
|
4869
|
+
intervalSeconds
|
|
4870
|
+
}) => {
|
|
4871
|
+
return /* @__PURE__ */ jsx15(
|
|
4872
|
+
PaginatedTableView,
|
|
4873
|
+
{
|
|
4874
|
+
fetchPage,
|
|
4875
|
+
columns: tradeColumns,
|
|
4876
|
+
title: `Trades \xB7 ${identifier}`,
|
|
4877
|
+
loadingText: "Loading trades\u2026",
|
|
4878
|
+
emptyState: emptyState6,
|
|
4879
|
+
getAddress: (trade) => trade.coin?.address,
|
|
4880
|
+
limit,
|
|
4881
|
+
autoRefresh,
|
|
4882
|
+
intervalSeconds
|
|
4883
|
+
}
|
|
4884
|
+
);
|
|
4885
|
+
};
|
|
4886
|
+
|
|
4887
|
+
// src/components/ProfileView.tsx
|
|
4888
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4889
|
+
var TAB_NAMES2 = ["Posts", "Holdings", "Trades"];
|
|
3803
4890
|
var ProfileView = ({
|
|
3804
4891
|
fetchData,
|
|
3805
4892
|
identifier,
|
|
3806
4893
|
autoRefresh = false,
|
|
3807
4894
|
intervalSeconds = 30
|
|
3808
4895
|
}) => {
|
|
3809
|
-
const { exit } =
|
|
3810
|
-
const [activeTab, setActiveTab] =
|
|
3811
|
-
const [loading, setLoading] =
|
|
3812
|
-
const [isRefreshing, setIsRefreshing] =
|
|
3813
|
-
const [error, setError] =
|
|
3814
|
-
const [data, setData] =
|
|
4896
|
+
const { exit } = useApp4();
|
|
4897
|
+
const [activeTab, setActiveTab] = useState5(0);
|
|
4898
|
+
const [loading, setLoading] = useState5(true);
|
|
4899
|
+
const [isRefreshing, setIsRefreshing] = useState5(false);
|
|
4900
|
+
const [error, setError] = useState5(null);
|
|
4901
|
+
const [data, setData] = useState5(null);
|
|
3815
4902
|
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
3816
|
-
const [manualRefreshCount, setManualRefreshCount] =
|
|
3817
|
-
const hasLoadedOnce =
|
|
3818
|
-
const load =
|
|
4903
|
+
const [manualRefreshCount, setManualRefreshCount] = useState5(0);
|
|
4904
|
+
const hasLoadedOnce = useRef4(false);
|
|
4905
|
+
const load = useCallback5(async () => {
|
|
3819
4906
|
if (hasLoadedOnce.current) {
|
|
3820
4907
|
setIsRefreshing(true);
|
|
3821
4908
|
} else {
|
|
@@ -3832,10 +4919,10 @@ var ProfileView = ({
|
|
|
3832
4919
|
setLoading(false);
|
|
3833
4920
|
setIsRefreshing(false);
|
|
3834
4921
|
}, [fetchData]);
|
|
3835
|
-
|
|
4922
|
+
useEffect5(() => {
|
|
3836
4923
|
load();
|
|
3837
4924
|
}, [load, refreshCount, manualRefreshCount]);
|
|
3838
|
-
|
|
4925
|
+
useInput4((input, key) => {
|
|
3839
4926
|
if (input === "q" || key.escape) {
|
|
3840
4927
|
exit();
|
|
3841
4928
|
return;
|
|
@@ -3844,67 +4931,84 @@ var ProfileView = ({
|
|
|
3844
4931
|
triggerManualRefresh();
|
|
3845
4932
|
setManualRefreshCount((c) => c + 1);
|
|
3846
4933
|
}
|
|
3847
|
-
if (
|
|
3848
|
-
|
|
4934
|
+
if (input === "1") setActiveTab(0);
|
|
4935
|
+
if (input === "2") setActiveTab(1);
|
|
4936
|
+
if (input === "3") setActiveTab(2);
|
|
4937
|
+
if (key.leftArrow) {
|
|
4938
|
+
setActiveTab((t) => t > 0 ? t - 1 : t);
|
|
3849
4939
|
}
|
|
3850
|
-
if (key.rightArrow
|
|
3851
|
-
setActiveTab(
|
|
4940
|
+
if (key.rightArrow) {
|
|
4941
|
+
setActiveTab(
|
|
4942
|
+
(t) => t < TAB_NAMES2.length - 1 ? t + 1 : t
|
|
4943
|
+
);
|
|
3852
4944
|
}
|
|
3853
4945
|
});
|
|
3854
4946
|
if (error && !data) {
|
|
3855
|
-
return /* @__PURE__ */
|
|
3856
|
-
|
|
4947
|
+
return /* @__PURE__ */ jsxs15(
|
|
4948
|
+
Box15,
|
|
3857
4949
|
{
|
|
3858
4950
|
flexDirection: "column",
|
|
3859
4951
|
paddingLeft: 1,
|
|
3860
4952
|
paddingTop: 1,
|
|
3861
4953
|
paddingBottom: 1,
|
|
3862
4954
|
children: [
|
|
3863
|
-
/* @__PURE__ */
|
|
4955
|
+
/* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
|
|
3864
4956
|
"Error: ",
|
|
3865
4957
|
error
|
|
3866
4958
|
] }),
|
|
3867
|
-
/* @__PURE__ */
|
|
4959
|
+
/* @__PURE__ */ jsx16(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: "Press q to exit" }) })
|
|
3868
4960
|
]
|
|
3869
4961
|
}
|
|
3870
4962
|
);
|
|
3871
4963
|
}
|
|
3872
4964
|
if (loading && !data) {
|
|
3873
|
-
return /* @__PURE__ */
|
|
3874
|
-
/* @__PURE__ */
|
|
4965
|
+
return /* @__PURE__ */ jsx16(Box15, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { children: [
|
|
4966
|
+
/* @__PURE__ */ jsx16(Spinner4, { type: "dots" }),
|
|
3875
4967
|
" Loading profile\u2026"
|
|
3876
4968
|
] }) });
|
|
3877
4969
|
}
|
|
3878
4970
|
if (!data) return null;
|
|
3879
4971
|
const hints = [
|
|
3880
|
-
"\u2190 \u2192 switch tab",
|
|
4972
|
+
"1/2/3 or \u2190 \u2192 switch tab",
|
|
3881
4973
|
autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh"
|
|
3882
4974
|
];
|
|
3883
4975
|
hints.push("q quit");
|
|
3884
4976
|
const footer = hints.join(" \xB7 ");
|
|
3885
4977
|
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
3886
|
-
return /* @__PURE__ */
|
|
3887
|
-
isRefreshing && /* @__PURE__ */
|
|
3888
|
-
/* @__PURE__ */
|
|
4978
|
+
return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
|
|
4979
|
+
isRefreshing && /* @__PURE__ */ jsx16(Box15, { paddingLeft: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
4980
|
+
/* @__PURE__ */ jsx16(Spinner4, { type: "dots" }),
|
|
3889
4981
|
" Refreshing\u2026"
|
|
3890
4982
|
] }) }),
|
|
3891
|
-
/* @__PURE__ */
|
|
3892
|
-
|
|
3893
|
-
/* @__PURE__ */
|
|
4983
|
+
/* @__PURE__ */ jsxs15(Box15, { paddingLeft: 1, paddingTop: 1, gap: 2, children: [
|
|
4984
|
+
TAB_NAMES2.map((name, i) => /* @__PURE__ */ jsx16(Text15, { bold: activeTab === i, dimColor: activeTab !== i, children: activeTab === i ? `[${name}]` : name }, name)),
|
|
4985
|
+
/* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
3894
4986
|
" ",
|
|
3895
4987
|
identifier
|
|
3896
4988
|
] })
|
|
3897
4989
|
] }),
|
|
3898
|
-
activeTab === 0 ?
|
|
3899
|
-
|
|
4990
|
+
activeTab === 0 ? data.postsError ? /* @__PURE__ */ jsx16(
|
|
4991
|
+
Box15,
|
|
4992
|
+
{
|
|
4993
|
+
flexDirection: "column",
|
|
4994
|
+
paddingLeft: 1,
|
|
4995
|
+
paddingTop: 1,
|
|
4996
|
+
paddingBottom: 1,
|
|
4997
|
+
children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
4998
|
+
"Could not load posts: ",
|
|
4999
|
+
data.postsError
|
|
5000
|
+
] })
|
|
5001
|
+
}
|
|
5002
|
+
) : rankedPosts.length === 0 ? /* @__PURE__ */ jsx16(
|
|
5003
|
+
Box15,
|
|
3900
5004
|
{
|
|
3901
5005
|
flexDirection: "column",
|
|
3902
5006
|
paddingLeft: 1,
|
|
3903
5007
|
paddingTop: 1,
|
|
3904
5008
|
paddingBottom: 1,
|
|
3905
|
-
children: /* @__PURE__ */
|
|
5009
|
+
children: /* @__PURE__ */ jsx16(Text15, { children: "No posts found for this profile." })
|
|
3906
5010
|
}
|
|
3907
|
-
) : /* @__PURE__ */
|
|
5011
|
+
) : /* @__PURE__ */ jsx16(
|
|
3908
5012
|
Table,
|
|
3909
5013
|
{
|
|
3910
5014
|
columns: postColumns,
|
|
@@ -3912,16 +5016,28 @@ var ProfileView = ({
|
|
|
3912
5016
|
title: "Posts",
|
|
3913
5017
|
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
3914
5018
|
}
|
|
3915
|
-
) :
|
|
3916
|
-
|
|
5019
|
+
) : activeTab === 1 ? data.holdingsError ? /* @__PURE__ */ jsx16(
|
|
5020
|
+
Box15,
|
|
5021
|
+
{
|
|
5022
|
+
flexDirection: "column",
|
|
5023
|
+
paddingLeft: 1,
|
|
5024
|
+
paddingTop: 1,
|
|
5025
|
+
paddingBottom: 1,
|
|
5026
|
+
children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
5027
|
+
"Could not load holdings: ",
|
|
5028
|
+
data.holdingsError
|
|
5029
|
+
] })
|
|
5030
|
+
}
|
|
5031
|
+
) : data.holdings.length === 0 ? /* @__PURE__ */ jsx16(
|
|
5032
|
+
Box15,
|
|
3917
5033
|
{
|
|
3918
5034
|
flexDirection: "column",
|
|
3919
5035
|
paddingLeft: 1,
|
|
3920
5036
|
paddingTop: 1,
|
|
3921
5037
|
paddingBottom: 1,
|
|
3922
|
-
children: /* @__PURE__ */
|
|
5038
|
+
children: /* @__PURE__ */ jsx16(Text15, { children: "No holdings found for this profile." })
|
|
3923
5039
|
}
|
|
3924
|
-
) : /* @__PURE__ */
|
|
5040
|
+
) : /* @__PURE__ */ jsx16(
|
|
3925
5041
|
Table,
|
|
3926
5042
|
{
|
|
3927
5043
|
columns: balanceColumns,
|
|
@@ -3929,25 +5045,98 @@ var ProfileView = ({
|
|
|
3929
5045
|
title: "Holdings",
|
|
3930
5046
|
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
3931
5047
|
}
|
|
5048
|
+
) : data.tradesError ? /* @__PURE__ */ jsx16(
|
|
5049
|
+
Box15,
|
|
5050
|
+
{
|
|
5051
|
+
flexDirection: "column",
|
|
5052
|
+
paddingLeft: 1,
|
|
5053
|
+
paddingTop: 1,
|
|
5054
|
+
paddingBottom: 1,
|
|
5055
|
+
children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
|
|
5056
|
+
"Could not load trades: ",
|
|
5057
|
+
data.tradesError
|
|
5058
|
+
] })
|
|
5059
|
+
}
|
|
5060
|
+
) : data.trades.length === 0 ? /* @__PURE__ */ jsx16(
|
|
5061
|
+
Box15,
|
|
5062
|
+
{
|
|
5063
|
+
flexDirection: "column",
|
|
5064
|
+
paddingLeft: 1,
|
|
5065
|
+
paddingTop: 1,
|
|
5066
|
+
paddingBottom: 1,
|
|
5067
|
+
children: /* @__PURE__ */ jsx16(Text15, { children: "No trades found for this profile." })
|
|
5068
|
+
}
|
|
5069
|
+
) : /* @__PURE__ */ jsx16(
|
|
5070
|
+
Table,
|
|
5071
|
+
{
|
|
5072
|
+
columns: tradeColumns,
|
|
5073
|
+
data: data.trades,
|
|
5074
|
+
title: "Trades",
|
|
5075
|
+
subtitle: `${data.trades.length} of ${data.tradesCount}`
|
|
5076
|
+
}
|
|
3932
5077
|
),
|
|
3933
|
-
/* @__PURE__ */
|
|
5078
|
+
/* @__PURE__ */ jsx16(Box15, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: footer }) })
|
|
3934
5079
|
] });
|
|
3935
5080
|
};
|
|
3936
5081
|
|
|
3937
|
-
// src/
|
|
3938
|
-
import {
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
5082
|
+
// src/components/ProfileHoldingsView.tsx
|
|
5083
|
+
import { Box as Box16, Text as Text16 } from "ink";
|
|
5084
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5085
|
+
var emptyState7 = /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: [
|
|
5086
|
+
/* @__PURE__ */ jsx17(Text16, { children: "No holdings found for this profile." }),
|
|
5087
|
+
/* @__PURE__ */ jsxs16(Box16, { marginTop: 1, flexDirection: "column", children: [
|
|
5088
|
+
/* @__PURE__ */ jsx17(Text16, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
5089
|
+
/* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
|
|
5090
|
+
" zora buy ",
|
|
5091
|
+
"<address>",
|
|
5092
|
+
" --eth 0.001"
|
|
5093
|
+
] })
|
|
5094
|
+
] }),
|
|
5095
|
+
/* @__PURE__ */ jsx17(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text16, { dimColor: true, children: "Press q to exit" }) })
|
|
5096
|
+
] });
|
|
5097
|
+
var ProfileHoldingsView = ({
|
|
5098
|
+
fetchPage,
|
|
5099
|
+
identifier,
|
|
5100
|
+
limit,
|
|
5101
|
+
autoRefresh,
|
|
5102
|
+
intervalSeconds
|
|
5103
|
+
}) => {
|
|
5104
|
+
return /* @__PURE__ */ jsx17(
|
|
5105
|
+
PaginatedTableView,
|
|
5106
|
+
{
|
|
5107
|
+
fetchPage,
|
|
5108
|
+
columns: balanceColumns,
|
|
5109
|
+
title: `Holdings \xB7 ${identifier}`,
|
|
5110
|
+
loadingText: "Loading holdings\u2026",
|
|
5111
|
+
emptyState: emptyState7,
|
|
5112
|
+
getAddress: (holding) => holding.coin?.address,
|
|
5113
|
+
limit,
|
|
5114
|
+
autoRefresh,
|
|
5115
|
+
intervalSeconds
|
|
5116
|
+
}
|
|
5117
|
+
);
|
|
3944
5118
|
};
|
|
3945
|
-
|
|
5119
|
+
|
|
5120
|
+
// src/commands/profile.tsx
|
|
5121
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
5122
|
+
var resolveApiKey2 = () => {
|
|
3946
5123
|
const apiKey = getApiKey();
|
|
3947
5124
|
if (apiKey) {
|
|
3948
|
-
|
|
5125
|
+
setApiKey6(apiKey);
|
|
3949
5126
|
}
|
|
3950
5127
|
};
|
|
5128
|
+
var formatTradeJson2 = (trade, rank) => ({
|
|
5129
|
+
rank,
|
|
5130
|
+
side: trade.swapActivityType ?? "UNKNOWN",
|
|
5131
|
+
coinName: trade.coin?.name ?? null,
|
|
5132
|
+
coinSymbol: trade.coin?.symbol ?? null,
|
|
5133
|
+
coinType: trade.coin?.coinType ?? null,
|
|
5134
|
+
coinAddress: trade.coin?.address ?? null,
|
|
5135
|
+
coinAmount: trade.coinAmount,
|
|
5136
|
+
amountUsd: trade.currencyAmountWithPrice.amountUsd ?? null,
|
|
5137
|
+
transactionHash: trade.transactionHash,
|
|
5138
|
+
timestamp: trade.blockTimestamp
|
|
5139
|
+
});
|
|
3951
5140
|
var formatPostJson = (post, rank) => ({
|
|
3952
5141
|
rank,
|
|
3953
5142
|
name: post.name,
|
|
@@ -3974,78 +5163,272 @@ var formatHoldingJson = (balance) => {
|
|
|
3974
5163
|
marketCap: balance.coin?.marketCap ? Number(balance.coin.marketCap) : null
|
|
3975
5164
|
};
|
|
3976
5165
|
};
|
|
5166
|
+
var extractSectionError = (result) => {
|
|
5167
|
+
if (result.status === "rejected") {
|
|
5168
|
+
return result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
5169
|
+
}
|
|
5170
|
+
if (result.value.error) {
|
|
5171
|
+
return extractErrorMessage(result.value.error);
|
|
5172
|
+
}
|
|
5173
|
+
return void 0;
|
|
5174
|
+
};
|
|
3977
5175
|
var fetchProfileData = async (identifier) => {
|
|
3978
|
-
const [postsResult, holdingsResult] = await Promise.allSettled([
|
|
5176
|
+
const [postsResult, holdingsResult, tradesResult] = await Promise.allSettled([
|
|
3979
5177
|
getProfileCoins({ identifier, count: 20 }),
|
|
3980
|
-
getProfileBalances2({ identifier, count: 20, sortOption: "USD_VALUE" })
|
|
5178
|
+
getProfileBalances2({ identifier, count: 20, sortOption: "USD_VALUE" }),
|
|
5179
|
+
getWalletTradeActivity({ identifier, first: 20 })
|
|
3981
5180
|
]);
|
|
3982
|
-
|
|
5181
|
+
const postsError = extractSectionError(postsResult);
|
|
5182
|
+
const holdingsError = extractSectionError(holdingsResult);
|
|
5183
|
+
const tradesError = extractSectionError(tradesResult);
|
|
5184
|
+
if (postsError && holdingsError && tradesError) {
|
|
3983
5185
|
throw new Error(
|
|
3984
|
-
|
|
5186
|
+
`All requests failed \u2014 posts: ${postsError}, holdings: ${holdingsError}, trades: ${tradesError}`
|
|
3985
5187
|
);
|
|
3986
5188
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
5189
|
+
let posts = [];
|
|
5190
|
+
let postsCount = 0;
|
|
5191
|
+
if (!postsError && postsResult.status === "fulfilled") {
|
|
5192
|
+
const postEdges = postsResult.value.data?.profile?.createdCoins?.edges ?? [];
|
|
5193
|
+
posts = postEdges.map((e) => e.node);
|
|
5194
|
+
postsCount = postsResult.value.data?.profile?.createdCoins?.count ?? posts.length;
|
|
5195
|
+
}
|
|
5196
|
+
let holdings = [];
|
|
5197
|
+
let holdingsCount = 0;
|
|
5198
|
+
if (!holdingsError && holdingsResult.status === "fulfilled") {
|
|
5199
|
+
const holdingEdges = holdingsResult.value.data?.profile?.coinBalances?.edges ?? [];
|
|
5200
|
+
holdings = holdingEdges.map((e, i) => ({
|
|
5201
|
+
...e.node,
|
|
5202
|
+
rank: i + 1
|
|
5203
|
+
}));
|
|
5204
|
+
holdingsCount = holdingsResult.value.data?.profile?.coinBalances?.count ?? holdings.length;
|
|
5205
|
+
}
|
|
5206
|
+
let trades = [];
|
|
5207
|
+
let tradesCount = 0;
|
|
5208
|
+
if (!tradesError && tradesResult.status === "fulfilled") {
|
|
5209
|
+
const tradeEdges = tradesResult.value.data?.walletAddressTradeActivity?.edges ?? [];
|
|
5210
|
+
trades = tradeEdges.map((e, i) => ({
|
|
5211
|
+
...e.node,
|
|
5212
|
+
rank: i + 1
|
|
5213
|
+
}));
|
|
5214
|
+
tradesCount = tradesResult.value.data?.walletAddressTradeActivity?.count ?? trades.length;
|
|
5215
|
+
}
|
|
5216
|
+
return {
|
|
5217
|
+
posts,
|
|
5218
|
+
postsCount,
|
|
5219
|
+
postsError,
|
|
5220
|
+
holdings,
|
|
5221
|
+
holdingsCount,
|
|
5222
|
+
holdingsError,
|
|
5223
|
+
trades,
|
|
5224
|
+
tradesCount,
|
|
5225
|
+
tradesError
|
|
5226
|
+
};
|
|
5227
|
+
};
|
|
5228
|
+
var resolveIdentifier = (identifierArg, json) => {
|
|
5229
|
+
if (identifierArg) return identifierArg;
|
|
5230
|
+
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
5231
|
+
const key = envKey || getPrivateKey();
|
|
5232
|
+
if (!key) {
|
|
5233
|
+
return outputErrorAndExit(
|
|
5234
|
+
json,
|
|
5235
|
+
"No identifier provided and no wallet configured.",
|
|
5236
|
+
"Pass an address or handle, or run 'zora setup' first."
|
|
5237
|
+
);
|
|
5238
|
+
}
|
|
5239
|
+
try {
|
|
5240
|
+
return privateKeyToAccount3(normalizeKey(key)).address;
|
|
5241
|
+
} catch {
|
|
5242
|
+
return outputErrorAndExit(
|
|
5243
|
+
json,
|
|
5244
|
+
"Invalid wallet key. Run 'zora setup --force' to replace it."
|
|
5245
|
+
);
|
|
5246
|
+
}
|
|
5247
|
+
};
|
|
5248
|
+
var profileCommand = new Command7("profile").description("View profile activity (posts, holdings, and trades)").argument(
|
|
5249
|
+
"[identifier]",
|
|
5250
|
+
"Wallet address or profile handle (defaults to your wallet)"
|
|
5251
|
+
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
5252
|
+
"--refresh <seconds>",
|
|
5253
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
5254
|
+
"30"
|
|
5255
|
+
).action(async function(identifierArg) {
|
|
5256
|
+
const output = getOutputMode(this, "live");
|
|
5257
|
+
const json = output === "json";
|
|
5258
|
+
resolveApiKey2();
|
|
5259
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
5260
|
+
const identifier = resolveIdentifier(identifierArg, json);
|
|
5261
|
+
if (json) {
|
|
5262
|
+
const data = await fetchProfileData(identifier).catch(
|
|
5263
|
+
(err) => outputErrorAndExit(
|
|
5264
|
+
json,
|
|
5265
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5266
|
+
)
|
|
5267
|
+
);
|
|
5268
|
+
outputData(json, {
|
|
5269
|
+
json: {
|
|
5270
|
+
posts: data.postsError ? { error: data.postsError } : data.posts.map((p, i) => formatPostJson(p, i + 1)),
|
|
5271
|
+
holdings: data.holdingsError ? { error: data.holdingsError } : data.holdings.map(formatHoldingJson),
|
|
5272
|
+
trades: data.tradesError ? { error: data.tradesError } : data.trades.map((t) => formatTradeJson2(t, t.rank))
|
|
5273
|
+
},
|
|
5274
|
+
render: () => {
|
|
5275
|
+
}
|
|
5276
|
+
});
|
|
5277
|
+
track("cli_profile", {
|
|
5278
|
+
identifier,
|
|
5279
|
+
output_format: "json",
|
|
5280
|
+
posts_count: data.postsCount,
|
|
5281
|
+
holdings_count: data.holdingsCount,
|
|
5282
|
+
trades_count: data.tradesCount
|
|
5283
|
+
});
|
|
5284
|
+
} else if (live) {
|
|
5285
|
+
const fetchData = () => fetchProfileData(identifier);
|
|
5286
|
+
await renderLive(
|
|
5287
|
+
/* @__PURE__ */ jsx18(
|
|
5288
|
+
ProfileView,
|
|
5289
|
+
{
|
|
5290
|
+
fetchData,
|
|
5291
|
+
identifier,
|
|
5292
|
+
autoRefresh: live,
|
|
5293
|
+
intervalSeconds
|
|
5294
|
+
}
|
|
5295
|
+
)
|
|
5296
|
+
);
|
|
5297
|
+
track("cli_profile", {
|
|
5298
|
+
identifier,
|
|
5299
|
+
output_format: "live",
|
|
5300
|
+
live,
|
|
5301
|
+
interval: intervalSeconds
|
|
5302
|
+
});
|
|
5303
|
+
} else {
|
|
5304
|
+
const data = await fetchProfileData(identifier).catch(
|
|
5305
|
+
(err) => outputErrorAndExit(
|
|
5306
|
+
json,
|
|
5307
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5308
|
+
)
|
|
5309
|
+
);
|
|
5310
|
+
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
5311
|
+
renderOnce(
|
|
5312
|
+
/* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
|
|
5313
|
+
data.postsError ? /* @__PURE__ */ jsx18(Box17, { paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
|
|
5314
|
+
"Could not load posts: ",
|
|
5315
|
+
data.postsError
|
|
5316
|
+
] }) }) : rankedPosts.length === 0 ? /* @__PURE__ */ jsx18(
|
|
5317
|
+
Box17,
|
|
5318
|
+
{
|
|
5319
|
+
flexDirection: "column",
|
|
5320
|
+
paddingLeft: 1,
|
|
5321
|
+
paddingTop: 1,
|
|
5322
|
+
paddingBottom: 1,
|
|
5323
|
+
children: /* @__PURE__ */ jsx18(Box17, { children: /* @__PURE__ */ jsx18(Text17, { children: "No posts found for this profile." }) })
|
|
5324
|
+
}
|
|
5325
|
+
) : /* @__PURE__ */ jsx18(
|
|
5326
|
+
Table,
|
|
5327
|
+
{
|
|
5328
|
+
columns: postColumns,
|
|
5329
|
+
data: rankedPosts,
|
|
5330
|
+
title: "Posts",
|
|
5331
|
+
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
5332
|
+
}
|
|
5333
|
+
),
|
|
5334
|
+
data.holdingsError ? /* @__PURE__ */ jsx18(Box17, { paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
|
|
5335
|
+
"Could not load holdings: ",
|
|
5336
|
+
data.holdingsError
|
|
5337
|
+
] }) }) : data.holdings.length === 0 ? /* @__PURE__ */ jsx18(
|
|
5338
|
+
Box17,
|
|
5339
|
+
{
|
|
5340
|
+
flexDirection: "column",
|
|
5341
|
+
paddingLeft: 1,
|
|
5342
|
+
paddingTop: 1,
|
|
5343
|
+
paddingBottom: 1,
|
|
5344
|
+
children: /* @__PURE__ */ jsx18(Box17, { children: /* @__PURE__ */ jsx18(Text17, { children: "No holdings found for this profile." }) })
|
|
5345
|
+
}
|
|
5346
|
+
) : /* @__PURE__ */ jsx18(
|
|
5347
|
+
Table,
|
|
5348
|
+
{
|
|
5349
|
+
columns: balanceColumns,
|
|
5350
|
+
data: data.holdings,
|
|
5351
|
+
title: "Holdings",
|
|
5352
|
+
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
5353
|
+
}
|
|
5354
|
+
),
|
|
5355
|
+
data.tradesError ? /* @__PURE__ */ jsx18(Box17, { paddingLeft: 1, paddingTop: 1, paddingBottom: 1, children: /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
|
|
5356
|
+
"Could not load trades: ",
|
|
5357
|
+
data.tradesError
|
|
5358
|
+
] }) }) : data.trades.length === 0 ? /* @__PURE__ */ jsx18(
|
|
5359
|
+
Box17,
|
|
5360
|
+
{
|
|
5361
|
+
flexDirection: "column",
|
|
5362
|
+
paddingLeft: 1,
|
|
5363
|
+
paddingTop: 1,
|
|
5364
|
+
paddingBottom: 1,
|
|
5365
|
+
children: /* @__PURE__ */ jsx18(Box17, { children: /* @__PURE__ */ jsx18(Text17, { children: "No trades found for this profile." }) })
|
|
5366
|
+
}
|
|
5367
|
+
) : /* @__PURE__ */ jsx18(
|
|
5368
|
+
Table,
|
|
5369
|
+
{
|
|
5370
|
+
columns: tradeColumns,
|
|
5371
|
+
data: data.trades,
|
|
5372
|
+
title: "Trades",
|
|
5373
|
+
subtitle: `${data.trades.length} of ${data.tradesCount}`
|
|
5374
|
+
}
|
|
5375
|
+
)
|
|
5376
|
+
] })
|
|
3990
5377
|
);
|
|
5378
|
+
track("cli_profile", {
|
|
5379
|
+
identifier,
|
|
5380
|
+
output_format: "static",
|
|
5381
|
+
posts_count: data.postsCount,
|
|
5382
|
+
holdings_count: data.holdingsCount,
|
|
5383
|
+
trades_count: data.tradesCount
|
|
5384
|
+
});
|
|
3991
5385
|
}
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
5386
|
+
});
|
|
5387
|
+
async function fetchPostsPage(identifier, count, after) {
|
|
5388
|
+
const response = await getProfileCoins({ identifier, count, after });
|
|
5389
|
+
if (response.error) {
|
|
5390
|
+
throw new Error(`API error: ${extractErrorMessage(response.error)}`);
|
|
3996
5391
|
}
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
5392
|
+
const edges = response.data?.profile?.createdCoins?.edges ?? [];
|
|
5393
|
+
const items = edges.map((e) => e.node);
|
|
5394
|
+
const total = response.data?.profile?.createdCoins?.count ?? items.length;
|
|
5395
|
+
const pageInfo = response.data?.profile?.createdCoins?.pageInfo;
|
|
5396
|
+
return { items, count: total, pageInfo };
|
|
5397
|
+
}
|
|
5398
|
+
async function fetchHoldingsPage(identifier, count, sortOption, after) {
|
|
5399
|
+
const response = await getProfileBalances2({
|
|
5400
|
+
identifier,
|
|
5401
|
+
count,
|
|
5402
|
+
sortOption,
|
|
5403
|
+
after
|
|
5404
|
+
});
|
|
5405
|
+
if (response.error) {
|
|
5406
|
+
throw new Error(`API error: ${extractErrorMessage(response.error)}`);
|
|
4001
5407
|
}
|
|
4002
|
-
const
|
|
4003
|
-
const
|
|
4004
|
-
const
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
rank: i + 1
|
|
4010
|
-
})
|
|
4011
|
-
);
|
|
4012
|
-
const holdingsCount = holdingsResult.value.data?.profile?.coinBalances?.count ?? holdings.length;
|
|
4013
|
-
return { posts, postsCount, holdings, holdingsCount };
|
|
4014
|
-
};
|
|
4015
|
-
var profileCommand = new Command8("profile").description("View profile activity (posts and holdings)").argument(
|
|
5408
|
+
const edges = response.data?.profile?.coinBalances?.edges ?? [];
|
|
5409
|
+
const items = edges.map((e) => e.node);
|
|
5410
|
+
const total = response.data?.profile?.coinBalances?.count ?? items.length;
|
|
5411
|
+
const pageInfo = response.data?.profile?.coinBalances?.pageInfo;
|
|
5412
|
+
return { items, count: total, pageInfo };
|
|
5413
|
+
}
|
|
5414
|
+
profileCommand.command("posts").description("View profile posts (created coins) with pagination").argument(
|
|
4016
5415
|
"[identifier]",
|
|
4017
5416
|
"Wallet address or profile handle (defaults to your wallet)"
|
|
4018
|
-
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
5417
|
+
).option("--limit <n>", "Number of results per page (max 20)", "10").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
4019
5418
|
"--refresh <seconds>",
|
|
4020
5419
|
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
4021
5420
|
"30"
|
|
4022
|
-
).action(async function(identifierArg) {
|
|
5421
|
+
).option("--after <cursor>", "Pagination cursor from a previous result").action(async function(identifierArg) {
|
|
4023
5422
|
const output = getOutputMode(this, "live");
|
|
4024
5423
|
const json = output === "json";
|
|
4025
|
-
|
|
5424
|
+
resolveApiKey2();
|
|
5425
|
+
const opts = this.opts();
|
|
5426
|
+
const after = opts.after;
|
|
5427
|
+
const limit = Math.min(20, Math.max(1, parseInt(opts.limit, 10) || 10));
|
|
4026
5428
|
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
4027
|
-
|
|
4028
|
-
if (!identifier) {
|
|
4029
|
-
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
4030
|
-
const key = envKey || getPrivateKey();
|
|
4031
|
-
if (!key) {
|
|
4032
|
-
outputErrorAndExit(
|
|
4033
|
-
json,
|
|
4034
|
-
"No identifier provided and no wallet configured.",
|
|
4035
|
-
"Pass an address or handle, or run 'zora setup' first."
|
|
4036
|
-
);
|
|
4037
|
-
}
|
|
4038
|
-
try {
|
|
4039
|
-
identifier = privateKeyToAccount3(normalizeKey(key)).address;
|
|
4040
|
-
} catch {
|
|
4041
|
-
outputErrorAndExit(
|
|
4042
|
-
json,
|
|
4043
|
-
"Invalid wallet key. Run 'zora setup --force' to replace it."
|
|
4044
|
-
);
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
5429
|
+
const identifier = resolveIdentifier(identifierArg, json);
|
|
4047
5430
|
if (json) {
|
|
4048
|
-
const
|
|
5431
|
+
const result = await fetchPostsPage(identifier, limit, after).catch(
|
|
4049
5432
|
(err) => outputErrorAndExit(
|
|
4050
5433
|
json,
|
|
4051
5434
|
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -4053,96 +5436,358 @@ var profileCommand = new Command8("profile").description("View profile activity
|
|
|
4053
5436
|
);
|
|
4054
5437
|
outputData(json, {
|
|
4055
5438
|
json: {
|
|
4056
|
-
posts:
|
|
4057
|
-
|
|
5439
|
+
posts: result.items.map((p, i) => formatPostJson(p, i + 1)),
|
|
5440
|
+
pageInfo: result.pageInfo ?? null
|
|
4058
5441
|
},
|
|
4059
5442
|
render: () => {
|
|
4060
5443
|
}
|
|
4061
5444
|
});
|
|
4062
|
-
track("
|
|
5445
|
+
track("cli_profile_posts", {
|
|
4063
5446
|
identifier,
|
|
4064
5447
|
output_format: "json",
|
|
4065
|
-
|
|
4066
|
-
holdings_count: data.holdingsCount
|
|
5448
|
+
count: result.count
|
|
4067
5449
|
});
|
|
4068
5450
|
} else if (live) {
|
|
4069
|
-
const
|
|
5451
|
+
const fetchPage = (cursor) => fetchPostsPage(identifier, limit, cursor);
|
|
4070
5452
|
await renderLive(
|
|
4071
|
-
/* @__PURE__ */
|
|
4072
|
-
|
|
5453
|
+
/* @__PURE__ */ jsx18(
|
|
5454
|
+
ProfilePostsView,
|
|
4073
5455
|
{
|
|
4074
|
-
|
|
5456
|
+
fetchPage,
|
|
4075
5457
|
identifier,
|
|
5458
|
+
limit,
|
|
4076
5459
|
autoRefresh: live,
|
|
4077
5460
|
intervalSeconds
|
|
4078
5461
|
}
|
|
4079
5462
|
)
|
|
4080
5463
|
);
|
|
4081
|
-
track("
|
|
5464
|
+
track("cli_profile_posts", {
|
|
4082
5465
|
identifier,
|
|
4083
5466
|
output_format: "live",
|
|
4084
5467
|
live,
|
|
4085
5468
|
interval: intervalSeconds
|
|
4086
5469
|
});
|
|
4087
5470
|
} else {
|
|
4088
|
-
const
|
|
5471
|
+
const result = await fetchPostsPage(identifier, limit, after).catch(
|
|
4089
5472
|
(err) => outputErrorAndExit(
|
|
4090
5473
|
json,
|
|
4091
5474
|
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4092
5475
|
)
|
|
4093
5476
|
);
|
|
4094
|
-
const rankedPosts =
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
5477
|
+
const rankedPosts = result.items.map((p, i) => ({
|
|
5478
|
+
...p,
|
|
5479
|
+
rank: i + 1
|
|
5480
|
+
}));
|
|
5481
|
+
if (rankedPosts.length === 0) {
|
|
5482
|
+
renderOnce(
|
|
5483
|
+
/* @__PURE__ */ jsx18(
|
|
5484
|
+
Box17,
|
|
4099
5485
|
{
|
|
4100
5486
|
flexDirection: "column",
|
|
4101
5487
|
paddingLeft: 1,
|
|
4102
5488
|
paddingTop: 1,
|
|
4103
5489
|
paddingBottom: 1,
|
|
4104
|
-
children: /* @__PURE__ */
|
|
5490
|
+
children: /* @__PURE__ */ jsx18(Text17, { children: "No posts found for this profile." })
|
|
4105
5491
|
}
|
|
4106
|
-
)
|
|
5492
|
+
)
|
|
5493
|
+
);
|
|
5494
|
+
} else {
|
|
5495
|
+
const footer = result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? `Next page: zora profile posts ${identifier} --limit ${limit} --after ${result.pageInfo.endCursor}` : void 0;
|
|
5496
|
+
renderOnce(
|
|
5497
|
+
/* @__PURE__ */ jsx18(
|
|
4107
5498
|
Table,
|
|
4108
5499
|
{
|
|
4109
5500
|
columns: postColumns,
|
|
4110
5501
|
data: rankedPosts,
|
|
4111
5502
|
title: "Posts",
|
|
4112
|
-
subtitle: `${rankedPosts.length} of ${
|
|
5503
|
+
subtitle: `${rankedPosts.length} of ${result.count}`,
|
|
5504
|
+
footer
|
|
4113
5505
|
}
|
|
4114
|
-
)
|
|
4115
|
-
|
|
4116
|
-
|
|
5506
|
+
)
|
|
5507
|
+
);
|
|
5508
|
+
}
|
|
5509
|
+
track("cli_profile_posts", {
|
|
5510
|
+
identifier,
|
|
5511
|
+
output_format: "static",
|
|
5512
|
+
count: result.count
|
|
5513
|
+
});
|
|
5514
|
+
}
|
|
5515
|
+
});
|
|
5516
|
+
var SORT_MAP2 = {
|
|
5517
|
+
"usd-value": "USD_VALUE",
|
|
5518
|
+
balance: "BALANCE",
|
|
5519
|
+
"market-cap": "MARKET_CAP",
|
|
5520
|
+
"price-change": "PRICE_CHANGE"
|
|
5521
|
+
};
|
|
5522
|
+
var SORT_OPTIONS3 = Object.keys(SORT_MAP2).join(", ");
|
|
5523
|
+
profileCommand.command("holdings").description("View profile holdings (coin balances) with pagination").argument(
|
|
5524
|
+
"[identifier]",
|
|
5525
|
+
"Wallet address or profile handle (defaults to your wallet)"
|
|
5526
|
+
).option("--sort <sort>", `Sort by: ${SORT_OPTIONS3}`, "usd-value").option("--limit <n>", "Number of results per page (max 20)", "10").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
5527
|
+
"--refresh <seconds>",
|
|
5528
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
5529
|
+
"30"
|
|
5530
|
+
).option("--after <cursor>", "Pagination cursor from a previous result").action(async function(identifierArg) {
|
|
5531
|
+
const output = getOutputMode(this, "live");
|
|
5532
|
+
const json = output === "json";
|
|
5533
|
+
resolveApiKey2();
|
|
5534
|
+
const opts = this.opts();
|
|
5535
|
+
const after = opts.after;
|
|
5536
|
+
const sort = opts.sort;
|
|
5537
|
+
const sortOption = SORT_MAP2[sort];
|
|
5538
|
+
if (!sortOption) {
|
|
5539
|
+
outputErrorAndExit(
|
|
5540
|
+
json,
|
|
5541
|
+
`Invalid --sort value: ${sort}.`,
|
|
5542
|
+
`Supported: ${SORT_OPTIONS3}`
|
|
5543
|
+
);
|
|
5544
|
+
}
|
|
5545
|
+
const limit = Math.min(20, Math.max(1, parseInt(opts.limit, 10) || 10));
|
|
5546
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
5547
|
+
const identifier = resolveIdentifier(identifierArg, json);
|
|
5548
|
+
if (json) {
|
|
5549
|
+
const result = await fetchHoldingsPage(
|
|
5550
|
+
identifier,
|
|
5551
|
+
limit,
|
|
5552
|
+
sortOption,
|
|
5553
|
+
after
|
|
5554
|
+
).catch(
|
|
5555
|
+
(err) => outputErrorAndExit(
|
|
5556
|
+
json,
|
|
5557
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5558
|
+
)
|
|
5559
|
+
);
|
|
5560
|
+
const rankedHoldings = result.items.map((h, i) => ({
|
|
5561
|
+
...h,
|
|
5562
|
+
rank: i + 1
|
|
5563
|
+
}));
|
|
5564
|
+
outputData(json, {
|
|
5565
|
+
json: {
|
|
5566
|
+
holdings: rankedHoldings.map(formatHoldingJson),
|
|
5567
|
+
pageInfo: result.pageInfo ?? null
|
|
5568
|
+
},
|
|
5569
|
+
render: () => {
|
|
5570
|
+
}
|
|
5571
|
+
});
|
|
5572
|
+
track("cli_profile_holdings", {
|
|
5573
|
+
identifier,
|
|
5574
|
+
output_format: "json",
|
|
5575
|
+
sort,
|
|
5576
|
+
count: result.count
|
|
5577
|
+
});
|
|
5578
|
+
} else if (live) {
|
|
5579
|
+
const fetchPage = (cursor) => fetchHoldingsPage(identifier, limit, sortOption, cursor);
|
|
5580
|
+
await renderLive(
|
|
5581
|
+
/* @__PURE__ */ jsx18(
|
|
5582
|
+
ProfileHoldingsView,
|
|
5583
|
+
{
|
|
5584
|
+
fetchPage,
|
|
5585
|
+
identifier,
|
|
5586
|
+
limit,
|
|
5587
|
+
autoRefresh: live,
|
|
5588
|
+
intervalSeconds
|
|
5589
|
+
}
|
|
5590
|
+
)
|
|
5591
|
+
);
|
|
5592
|
+
track("cli_profile_holdings", {
|
|
5593
|
+
identifier,
|
|
5594
|
+
output_format: "live",
|
|
5595
|
+
live,
|
|
5596
|
+
sort,
|
|
5597
|
+
interval: intervalSeconds
|
|
5598
|
+
});
|
|
5599
|
+
} else {
|
|
5600
|
+
const result = await fetchHoldingsPage(
|
|
5601
|
+
identifier,
|
|
5602
|
+
limit,
|
|
5603
|
+
sortOption,
|
|
5604
|
+
after
|
|
5605
|
+
).catch(
|
|
5606
|
+
(err) => outputErrorAndExit(
|
|
5607
|
+
json,
|
|
5608
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5609
|
+
)
|
|
5610
|
+
);
|
|
5611
|
+
const rankedHoldings = result.items.map((h, i) => ({
|
|
5612
|
+
...h,
|
|
5613
|
+
rank: i + 1
|
|
5614
|
+
}));
|
|
5615
|
+
if (rankedHoldings.length === 0) {
|
|
5616
|
+
renderOnce(
|
|
5617
|
+
/* @__PURE__ */ jsxs17(
|
|
5618
|
+
Box17,
|
|
4117
5619
|
{
|
|
4118
5620
|
flexDirection: "column",
|
|
4119
5621
|
paddingLeft: 1,
|
|
4120
5622
|
paddingTop: 1,
|
|
4121
5623
|
paddingBottom: 1,
|
|
4122
|
-
children:
|
|
5624
|
+
children: [
|
|
5625
|
+
/* @__PURE__ */ jsx18(Text17, { children: "No holdings found for this profile." }),
|
|
5626
|
+
/* @__PURE__ */ jsxs17(Box17, { marginTop: 1, flexDirection: "column", children: [
|
|
5627
|
+
/* @__PURE__ */ jsx18(Text17, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
5628
|
+
/* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
|
|
5629
|
+
" zora buy ",
|
|
5630
|
+
"<address>",
|
|
5631
|
+
" --eth 0.001"
|
|
5632
|
+
] })
|
|
5633
|
+
] })
|
|
5634
|
+
]
|
|
4123
5635
|
}
|
|
4124
|
-
)
|
|
5636
|
+
)
|
|
5637
|
+
);
|
|
5638
|
+
} else {
|
|
5639
|
+
const footer = result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? `Next page: zora profile holdings ${identifier} --sort ${sort} --limit ${limit} --after ${result.pageInfo.endCursor}` : void 0;
|
|
5640
|
+
renderOnce(
|
|
5641
|
+
/* @__PURE__ */ jsx18(
|
|
4125
5642
|
Table,
|
|
4126
5643
|
{
|
|
4127
5644
|
columns: balanceColumns,
|
|
4128
|
-
data:
|
|
4129
|
-
title:
|
|
4130
|
-
subtitle: `${
|
|
5645
|
+
data: rankedHoldings,
|
|
5646
|
+
title: `Holdings \xB7 sorted by ${sort}`,
|
|
5647
|
+
subtitle: `${rankedHoldings.length} of ${result.count}`,
|
|
5648
|
+
footer
|
|
4131
5649
|
}
|
|
4132
5650
|
)
|
|
4133
|
-
|
|
5651
|
+
);
|
|
5652
|
+
}
|
|
5653
|
+
track("cli_profile_holdings", {
|
|
5654
|
+
identifier,
|
|
5655
|
+
output_format: "static",
|
|
5656
|
+
sort,
|
|
5657
|
+
count: result.count
|
|
5658
|
+
});
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
5661
|
+
async function fetchTradesPage2(identifier, count, after) {
|
|
5662
|
+
const response = await getWalletTradeActivity({
|
|
5663
|
+
identifier,
|
|
5664
|
+
first: count,
|
|
5665
|
+
after
|
|
5666
|
+
});
|
|
5667
|
+
if (response.error) {
|
|
5668
|
+
throw new Error(`API error: ${extractErrorMessage(response.error)}`);
|
|
5669
|
+
}
|
|
5670
|
+
const edges = response.data?.walletAddressTradeActivity?.edges ?? [];
|
|
5671
|
+
const items = edges.map((e) => e.node);
|
|
5672
|
+
const total = response.data?.walletAddressTradeActivity?.count ?? items.length;
|
|
5673
|
+
const pageInfo = response.data?.walletAddressTradeActivity?.pageInfo;
|
|
5674
|
+
return { items, count: total, pageInfo };
|
|
5675
|
+
}
|
|
5676
|
+
profileCommand.command("trades").description("View profile trade activity (buys and sells) with pagination").argument(
|
|
5677
|
+
"[identifier]",
|
|
5678
|
+
"Wallet address or profile handle (defaults to your wallet)"
|
|
5679
|
+
).option("--limit <n>", "Number of results per page (max 20)", "10").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
5680
|
+
"--refresh <seconds>",
|
|
5681
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
5682
|
+
"30"
|
|
5683
|
+
).option("--after <cursor>", "Pagination cursor from a previous result").action(async function(identifierArg) {
|
|
5684
|
+
const output = getOutputMode(this, "live");
|
|
5685
|
+
const json = output === "json";
|
|
5686
|
+
resolveApiKey2();
|
|
5687
|
+
const opts = this.opts();
|
|
5688
|
+
const after = opts.after;
|
|
5689
|
+
const limit = Math.min(20, Math.max(1, parseInt(opts.limit, 10) || 10));
|
|
5690
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
5691
|
+
const identifier = resolveIdentifier(identifierArg, json);
|
|
5692
|
+
if (json) {
|
|
5693
|
+
const result = await fetchTradesPage2(identifier, limit, after).catch(
|
|
5694
|
+
(err) => outputErrorAndExit(
|
|
5695
|
+
json,
|
|
5696
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5697
|
+
)
|
|
4134
5698
|
);
|
|
4135
|
-
|
|
5699
|
+
outputData(json, {
|
|
5700
|
+
json: {
|
|
5701
|
+
trades: result.items.map((t, i) => formatTradeJson2(t, i + 1)),
|
|
5702
|
+
pageInfo: result.pageInfo ?? null
|
|
5703
|
+
},
|
|
5704
|
+
render: () => {
|
|
5705
|
+
}
|
|
5706
|
+
});
|
|
5707
|
+
track("cli_profile_trades", {
|
|
5708
|
+
identifier,
|
|
5709
|
+
output_format: "json",
|
|
5710
|
+
count: result.count
|
|
5711
|
+
});
|
|
5712
|
+
} else if (live) {
|
|
5713
|
+
const fetchPage = (cursor) => fetchTradesPage2(identifier, limit, cursor);
|
|
5714
|
+
await renderLive(
|
|
5715
|
+
/* @__PURE__ */ jsx18(
|
|
5716
|
+
ProfileTradesView,
|
|
5717
|
+
{
|
|
5718
|
+
fetchPage,
|
|
5719
|
+
identifier,
|
|
5720
|
+
limit,
|
|
5721
|
+
autoRefresh: live,
|
|
5722
|
+
intervalSeconds
|
|
5723
|
+
}
|
|
5724
|
+
)
|
|
5725
|
+
);
|
|
5726
|
+
track("cli_profile_trades", {
|
|
5727
|
+
identifier,
|
|
5728
|
+
output_format: "live",
|
|
5729
|
+
live,
|
|
5730
|
+
interval: intervalSeconds
|
|
5731
|
+
});
|
|
5732
|
+
} else {
|
|
5733
|
+
const result = await fetchTradesPage2(identifier, limit, after).catch(
|
|
5734
|
+
(err) => outputErrorAndExit(
|
|
5735
|
+
json,
|
|
5736
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5737
|
+
)
|
|
5738
|
+
);
|
|
5739
|
+
const rankedTrades = result.items.map((t, i) => ({
|
|
5740
|
+
...t,
|
|
5741
|
+
rank: i + 1
|
|
5742
|
+
}));
|
|
5743
|
+
if (rankedTrades.length === 0) {
|
|
5744
|
+
renderOnce(
|
|
5745
|
+
/* @__PURE__ */ jsxs17(
|
|
5746
|
+
Box17,
|
|
5747
|
+
{
|
|
5748
|
+
flexDirection: "column",
|
|
5749
|
+
paddingLeft: 1,
|
|
5750
|
+
paddingTop: 1,
|
|
5751
|
+
paddingBottom: 1,
|
|
5752
|
+
children: [
|
|
5753
|
+
/* @__PURE__ */ jsx18(Text17, { children: "No trades found for this profile." }),
|
|
5754
|
+
/* @__PURE__ */ jsxs17(Box17, { marginTop: 1, flexDirection: "column", children: [
|
|
5755
|
+
/* @__PURE__ */ jsx18(Text17, { dimColor: true, children: "Buy coins to see trades here:" }),
|
|
5756
|
+
/* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
|
|
5757
|
+
" zora buy ",
|
|
5758
|
+
"<address>",
|
|
5759
|
+
" --eth 0.001"
|
|
5760
|
+
] })
|
|
5761
|
+
] })
|
|
5762
|
+
]
|
|
5763
|
+
}
|
|
5764
|
+
)
|
|
5765
|
+
);
|
|
5766
|
+
} else {
|
|
5767
|
+
const footer = result.pageInfo?.hasNextPage && result.pageInfo.endCursor ? `Next page: zora profile trades ${identifier} --limit ${limit} --after ${result.pageInfo.endCursor}` : void 0;
|
|
5768
|
+
renderOnce(
|
|
5769
|
+
/* @__PURE__ */ jsx18(
|
|
5770
|
+
Table,
|
|
5771
|
+
{
|
|
5772
|
+
columns: tradeColumns,
|
|
5773
|
+
data: rankedTrades,
|
|
5774
|
+
title: "Trades",
|
|
5775
|
+
subtitle: `${rankedTrades.length} of ${result.count}`,
|
|
5776
|
+
footer
|
|
5777
|
+
}
|
|
5778
|
+
)
|
|
5779
|
+
);
|
|
5780
|
+
}
|
|
5781
|
+
track("cli_profile_trades", {
|
|
4136
5782
|
identifier,
|
|
4137
5783
|
output_format: "static",
|
|
4138
|
-
|
|
4139
|
-
holdings_count: data.holdingsCount
|
|
5784
|
+
count: result.count
|
|
4140
5785
|
});
|
|
4141
5786
|
}
|
|
4142
5787
|
});
|
|
4143
5788
|
|
|
4144
5789
|
// src/commands/send.ts
|
|
4145
|
-
import { Command as
|
|
5790
|
+
import { Command as Command8 } from "commander";
|
|
4146
5791
|
import confirm4 from "@inquirer/confirm";
|
|
4147
5792
|
import {
|
|
4148
5793
|
erc20Abi as erc20Abi4,
|
|
@@ -4150,7 +5795,7 @@ import {
|
|
|
4150
5795
|
isAddress as isAddress3,
|
|
4151
5796
|
parseUnits as parseUnits3
|
|
4152
5797
|
} from "viem";
|
|
4153
|
-
import { setApiKey as
|
|
5798
|
+
import { setApiKey as setApiKey7 } from "@zoralabs/coins-sdk";
|
|
4154
5799
|
var SEND_AMOUNT_CHECKS = {
|
|
4155
5800
|
amount: (opts) => opts.amount !== void 0,
|
|
4156
5801
|
percent: (opts) => opts.percent !== void 0,
|
|
@@ -4198,7 +5843,7 @@ function printSendResult(json, info) {
|
|
|
4198
5843
|
console.log(` Tx ${info.txHash}
|
|
4199
5844
|
`);
|
|
4200
5845
|
}
|
|
4201
|
-
var sendCommand = new
|
|
5846
|
+
var sendCommand = new Command8("send").description("Send coins or ETH to an address").argument(
|
|
4202
5847
|
"[typeOrId]",
|
|
4203
5848
|
"Token (eth, usdc, zora), type prefix (creator-coin, trend), or coin address/name"
|
|
4204
5849
|
).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) {
|
|
@@ -4313,7 +5958,7 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
|
|
|
4313
5958
|
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
4314
5959
|
if (!ok) {
|
|
4315
5960
|
console.error("Aborted.");
|
|
4316
|
-
|
|
5961
|
+
safeExit(SUCCESS);
|
|
4317
5962
|
}
|
|
4318
5963
|
}
|
|
4319
5964
|
let txHash;
|
|
@@ -4370,7 +6015,7 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
|
|
|
4370
6015
|
} else {
|
|
4371
6016
|
const apiKey = getApiKey();
|
|
4372
6017
|
if (apiKey) {
|
|
4373
|
-
|
|
6018
|
+
setApiKey7(apiKey);
|
|
4374
6019
|
}
|
|
4375
6020
|
let parsed;
|
|
4376
6021
|
try {
|
|
@@ -4537,7 +6182,7 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
|
|
|
4537
6182
|
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
4538
6183
|
if (!ok) {
|
|
4539
6184
|
console.error("Aborted.");
|
|
4540
|
-
|
|
6185
|
+
safeExit(SUCCESS);
|
|
4541
6186
|
}
|
|
4542
6187
|
}
|
|
4543
6188
|
let txHash;
|
|
@@ -4592,8 +6237,8 @@ var sendCommand = new Command9("send").description("Send coins or ETH to an addr
|
|
|
4592
6237
|
});
|
|
4593
6238
|
|
|
4594
6239
|
// src/commands/setup.tsx
|
|
4595
|
-
import { Command as
|
|
4596
|
-
import { Text as
|
|
6240
|
+
import { Command as Command9 } from "commander";
|
|
6241
|
+
import { Text as Text18, Box as Box18 } from "ink";
|
|
4597
6242
|
|
|
4598
6243
|
// src/lib/strings.ts
|
|
4599
6244
|
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";
|
|
@@ -4756,7 +6401,7 @@ function warningBox(text) {
|
|
|
4756
6401
|
}
|
|
4757
6402
|
|
|
4758
6403
|
// src/commands/setup.tsx
|
|
4759
|
-
import { jsx as
|
|
6404
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4760
6405
|
function stepLine(step, total, title) {
|
|
4761
6406
|
const cols = (process.stdout.columns || 80) - 4;
|
|
4762
6407
|
if (!useAnsi()) {
|
|
@@ -4773,7 +6418,7 @@ ${BOLD}${DIM}[${step}/${total}]${RESET} ${BOLD}${title}${RESET}`
|
|
|
4773
6418
|
console.log(`${DIM}${"\u2500".repeat(Math.max(cols, 20))}${RESET}
|
|
4774
6419
|
`);
|
|
4775
6420
|
}
|
|
4776
|
-
var setupCommand = new
|
|
6421
|
+
var setupCommand = new Command9("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
6422
|
const json = getJson(this);
|
|
4778
6423
|
const nonInteractive = getYes(this);
|
|
4779
6424
|
if (!json) stepLine(1, 3, "Set up wallet");
|
|
@@ -4841,8 +6486,8 @@ var setupCommand = new Command10("setup").description("Guided first-time setup")
|
|
|
4841
6486
|
if (!json) stepLine(3, 3, "Deposit");
|
|
4842
6487
|
if (!json) {
|
|
4843
6488
|
renderOnce(
|
|
4844
|
-
/* @__PURE__ */
|
|
4845
|
-
|
|
6489
|
+
/* @__PURE__ */ jsxs18(
|
|
6490
|
+
Box18,
|
|
4846
6491
|
{
|
|
4847
6492
|
flexDirection: "column",
|
|
4848
6493
|
borderStyle: "single",
|
|
@@ -4850,14 +6495,14 @@ var setupCommand = new Command10("setup").description("Guided first-time setup")
|
|
|
4850
6495
|
paddingX: 1,
|
|
4851
6496
|
paddingY: 1,
|
|
4852
6497
|
children: [
|
|
4853
|
-
/* @__PURE__ */
|
|
6498
|
+
/* @__PURE__ */ jsxs18(Text18, { children: [
|
|
4854
6499
|
"Your address: ",
|
|
4855
|
-
/* @__PURE__ */
|
|
6500
|
+
/* @__PURE__ */ jsx19(Text18, { bold: true, children: walletResult.address })
|
|
4856
6501
|
] }),
|
|
4857
|
-
/* @__PURE__ */
|
|
6502
|
+
/* @__PURE__ */ jsxs18(Text18, { children: [
|
|
4858
6503
|
"Deposit",
|
|
4859
6504
|
" ",
|
|
4860
|
-
/* @__PURE__ */
|
|
6505
|
+
/* @__PURE__ */ jsx19(Text18, { bold: true, color: "blue", children: "ETH or USDC on Base" }),
|
|
4861
6506
|
" ",
|
|
4862
6507
|
"to start trading."
|
|
4863
6508
|
] })
|
|
@@ -4910,7 +6555,7 @@ async function promptAndSaveApiKey(json, nonInteractive = false) {
|
|
|
4910
6555
|
}
|
|
4911
6556
|
|
|
4912
6557
|
// src/commands/wallet.ts
|
|
4913
|
-
import { Command as
|
|
6558
|
+
import { Command as Command10 } from "commander";
|
|
4914
6559
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
4915
6560
|
var resolvePrivateKey = () => {
|
|
4916
6561
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -4923,7 +6568,7 @@ var resolvePrivateKey = () => {
|
|
|
4923
6568
|
}
|
|
4924
6569
|
return void 0;
|
|
4925
6570
|
};
|
|
4926
|
-
var walletCommand = new
|
|
6571
|
+
var walletCommand = new Command10("wallet").description("Manage your Zora wallet").action(function() {
|
|
4927
6572
|
this.outputHelp();
|
|
4928
6573
|
});
|
|
4929
6574
|
walletCommand.command("info").description("Show wallet address and storage location").action(function() {
|
|
@@ -4973,7 +6618,7 @@ walletCommand.command("export").description("Print the raw private key to stdout
|
|
|
4973
6618
|
);
|
|
4974
6619
|
if (!ok) {
|
|
4975
6620
|
console.error("Aborted.");
|
|
4976
|
-
|
|
6621
|
+
safeExit(SUCCESS);
|
|
4977
6622
|
}
|
|
4978
6623
|
}
|
|
4979
6624
|
console.log(resolved.key);
|
|
@@ -5031,19 +6676,19 @@ walletCommand.command("configure").description("Create or import a wallet").opti
|
|
|
5031
6676
|
});
|
|
5032
6677
|
|
|
5033
6678
|
// src/components/StyledHelp.tsx
|
|
5034
|
-
import { Text as
|
|
6679
|
+
import { Text as Text20, Box as Box20 } from "ink";
|
|
5035
6680
|
|
|
5036
6681
|
// src/components/KeyValueTable.tsx
|
|
5037
|
-
import { Text as
|
|
5038
|
-
import { jsx as
|
|
6682
|
+
import { Text as Text19, Box as Box19 } from "ink";
|
|
6683
|
+
import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
5039
6684
|
function KeyValueTable({
|
|
5040
6685
|
rows,
|
|
5041
6686
|
labelWidth
|
|
5042
6687
|
}) {
|
|
5043
6688
|
const pad = labelWidth ?? Math.max(0, ...rows.map((r) => r.label.length)) + 2;
|
|
5044
|
-
return /* @__PURE__ */
|
|
5045
|
-
/* @__PURE__ */
|
|
5046
|
-
/* @__PURE__ */
|
|
6689
|
+
return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs19(Text19, { children: [
|
|
6690
|
+
/* @__PURE__ */ jsx20(Text19, { bold: true, children: row.label.padEnd(pad) }),
|
|
6691
|
+
/* @__PURE__ */ jsx20(Text19, { dimColor: true, children: row.value })
|
|
5047
6692
|
] }, i)) });
|
|
5048
6693
|
}
|
|
5049
6694
|
|
|
@@ -5084,17 +6729,17 @@ function parseHelpSections(text) {
|
|
|
5084
6729
|
}
|
|
5085
6730
|
|
|
5086
6731
|
// src/components/StyledHelp.tsx
|
|
5087
|
-
import { jsx as
|
|
6732
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5088
6733
|
function StyledHelp({
|
|
5089
6734
|
sections,
|
|
5090
6735
|
header
|
|
5091
6736
|
}) {
|
|
5092
6737
|
const descriptionColumnOffset = getDescriptionColumnOffset(sections);
|
|
5093
|
-
return /* @__PURE__ */
|
|
6738
|
+
return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", gap: 1, children: [
|
|
5094
6739
|
header,
|
|
5095
|
-
/* @__PURE__ */
|
|
5096
|
-
/* @__PURE__ */
|
|
5097
|
-
/* @__PURE__ */
|
|
6740
|
+
/* @__PURE__ */ jsxs20(Box20, { paddingX: 1, children: [
|
|
6741
|
+
/* @__PURE__ */ jsx21(Text20, { color: "yellow", children: "\u26A0 Beta:" }),
|
|
6742
|
+
/* @__PURE__ */ jsx21(Text20, { children: " This CLI is in beta and should be used with caution." })
|
|
5098
6743
|
] }),
|
|
5099
6744
|
sections.map((section, i) => {
|
|
5100
6745
|
const hasTwoColumns = TWO_COLUMN_REGEX.test(section.content);
|
|
@@ -5107,8 +6752,8 @@ function StyledHelp({
|
|
|
5107
6752
|
};
|
|
5108
6753
|
return { label: "", value: line.trimStart() };
|
|
5109
6754
|
}) : null;
|
|
5110
|
-
return /* @__PURE__ */
|
|
5111
|
-
|
|
6755
|
+
return /* @__PURE__ */ jsxs20(
|
|
6756
|
+
Box20,
|
|
5112
6757
|
{
|
|
5113
6758
|
flexDirection: "column",
|
|
5114
6759
|
borderStyle: "single",
|
|
@@ -5116,8 +6761,8 @@ function StyledHelp({
|
|
|
5116
6761
|
paddingX: 1,
|
|
5117
6762
|
paddingY: 1,
|
|
5118
6763
|
children: [
|
|
5119
|
-
/* @__PURE__ */
|
|
5120
|
-
rows ? /* @__PURE__ */
|
|
6764
|
+
/* @__PURE__ */ jsx21(Text20, { bold: true, children: section.title }),
|
|
6765
|
+
rows ? /* @__PURE__ */ jsx21(KeyValueTable, { rows, labelWidth: descriptionColumnOffset }) : /* @__PURE__ */ jsx21(Text20, { children: section.content })
|
|
5121
6766
|
]
|
|
5122
6767
|
},
|
|
5123
6768
|
i
|
|
@@ -5127,10 +6772,10 @@ function StyledHelp({
|
|
|
5127
6772
|
}
|
|
5128
6773
|
|
|
5129
6774
|
// src/components/StyledHelpHeader.tsx
|
|
5130
|
-
import { Text as
|
|
6775
|
+
import { Text as Text22, Box as Box22 } from "ink";
|
|
5131
6776
|
|
|
5132
6777
|
// src/components/Zorb.tsx
|
|
5133
|
-
import { Text as
|
|
6778
|
+
import { Text as Text21, Box as Box21 } from "ink";
|
|
5134
6779
|
|
|
5135
6780
|
// src/lib/zorb-pixels.ts
|
|
5136
6781
|
function supportsTruecolor() {
|
|
@@ -5275,7 +6920,7 @@ function generateZorbPixels(size) {
|
|
|
5275
6920
|
}
|
|
5276
6921
|
|
|
5277
6922
|
// src/components/Zorb.tsx
|
|
5278
|
-
import { jsx as
|
|
6923
|
+
import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
5279
6924
|
var LOWER_HALF_BLOCK = "\u2584";
|
|
5280
6925
|
var UPPER_HALF_BLOCK = "\u2580";
|
|
5281
6926
|
function rgbString([r, g, b]) {
|
|
@@ -5298,19 +6943,19 @@ function Zorb({ size = 20 }) {
|
|
|
5298
6943
|
const topIsBlack = isBlack(top);
|
|
5299
6944
|
const bottomIsBlack = isBlack(bottom);
|
|
5300
6945
|
if (topIsBlack && bottomIsBlack) {
|
|
5301
|
-
cells.push(/* @__PURE__ */
|
|
6946
|
+
cells.push(/* @__PURE__ */ jsx22(Text21, { children: " " }, x));
|
|
5302
6947
|
} else if (topIsBlack) {
|
|
5303
6948
|
cells.push(
|
|
5304
|
-
/* @__PURE__ */
|
|
6949
|
+
/* @__PURE__ */ jsx22(Text21, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
|
|
5305
6950
|
);
|
|
5306
6951
|
} else if (bottomIsBlack) {
|
|
5307
6952
|
cells.push(
|
|
5308
|
-
/* @__PURE__ */
|
|
6953
|
+
/* @__PURE__ */ jsx22(Text21, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
|
|
5309
6954
|
);
|
|
5310
6955
|
} else {
|
|
5311
6956
|
cells.push(
|
|
5312
|
-
/* @__PURE__ */
|
|
5313
|
-
|
|
6957
|
+
/* @__PURE__ */ jsx22(
|
|
6958
|
+
Text21,
|
|
5314
6959
|
{
|
|
5315
6960
|
backgroundColor: rgbString(top),
|
|
5316
6961
|
color: rgbString(bottom),
|
|
@@ -5321,23 +6966,23 @@ function Zorb({ size = 20 }) {
|
|
|
5321
6966
|
);
|
|
5322
6967
|
}
|
|
5323
6968
|
}
|
|
5324
|
-
rows.push(/* @__PURE__ */
|
|
6969
|
+
rows.push(/* @__PURE__ */ jsx22(Text21, { children: cells }, y));
|
|
5325
6970
|
}
|
|
5326
|
-
return /* @__PURE__ */
|
|
5327
|
-
/* @__PURE__ */
|
|
6971
|
+
return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
|
|
6972
|
+
/* @__PURE__ */ jsx22(Text21, { children: " " }),
|
|
5328
6973
|
rows,
|
|
5329
|
-
/* @__PURE__ */
|
|
6974
|
+
/* @__PURE__ */ jsx22(Text21, { children: " " })
|
|
5330
6975
|
] });
|
|
5331
6976
|
}
|
|
5332
6977
|
|
|
5333
6978
|
// src/components/StyledHelpHeader.tsx
|
|
5334
|
-
import { jsx as
|
|
6979
|
+
import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
5335
6980
|
function StyledHelpHeader({
|
|
5336
6981
|
sections
|
|
5337
6982
|
}) {
|
|
5338
6983
|
const descriptionColumnOffset = getDescriptionColumnOffset(sections);
|
|
5339
|
-
return /* @__PURE__ */
|
|
5340
|
-
|
|
6984
|
+
return /* @__PURE__ */ jsxs22(
|
|
6985
|
+
Box22,
|
|
5341
6986
|
{
|
|
5342
6987
|
flexDirection: "row",
|
|
5343
6988
|
borderStyle: "single",
|
|
@@ -5345,23 +6990,23 @@ function StyledHelpHeader({
|
|
|
5345
6990
|
paddingX: 1,
|
|
5346
6991
|
paddingY: 1,
|
|
5347
6992
|
children: [
|
|
5348
|
-
/* @__PURE__ */
|
|
5349
|
-
/* @__PURE__ */
|
|
5350
|
-
/* @__PURE__ */
|
|
6993
|
+
/* @__PURE__ */ jsx23(Box22, { flexShrink: 0, width: descriptionColumnOffset, children: /* @__PURE__ */ jsx23(Zorb, { size: 20 }) }),
|
|
6994
|
+
/* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: [
|
|
6995
|
+
/* @__PURE__ */ jsx23(Text22, { bold: true, children: /* @__PURE__ */ jsxs22(Text22, { backgroundColor: "#3fff00", color: "black", children: [
|
|
5351
6996
|
" ",
|
|
5352
6997
|
"Zora CLI",
|
|
5353
6998
|
" "
|
|
5354
6999
|
] }) }),
|
|
5355
|
-
/* @__PURE__ */
|
|
5356
|
-
/* @__PURE__ */
|
|
7000
|
+
/* @__PURE__ */ jsxs22(Text22, { children: [
|
|
7001
|
+
/* @__PURE__ */ jsx23(Text22, { dimColor: true, children: "Trade what's trending. Run" }),
|
|
5357
7002
|
" ",
|
|
5358
|
-
/* @__PURE__ */
|
|
7003
|
+
/* @__PURE__ */ jsxs22(Text22, { backgroundColor: "#3fff00", color: "black", children: [
|
|
5359
7004
|
" ",
|
|
5360
7005
|
"zora setup",
|
|
5361
7006
|
" "
|
|
5362
7007
|
] }),
|
|
5363
7008
|
" ",
|
|
5364
|
-
/* @__PURE__ */
|
|
7009
|
+
/* @__PURE__ */ jsx23(Text22, { dimColor: true, children: "to get started." })
|
|
5365
7010
|
] })
|
|
5366
7011
|
] })
|
|
5367
7012
|
]
|
|
@@ -5370,11 +7015,11 @@ function StyledHelpHeader({
|
|
|
5370
7015
|
}
|
|
5371
7016
|
|
|
5372
7017
|
// src/index.tsx
|
|
5373
|
-
import { jsx as
|
|
7018
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
5374
7019
|
if (process.env.ZORA_API_TARGET) {
|
|
5375
7020
|
setApiBaseUrl(process.env.ZORA_API_TARGET);
|
|
5376
7021
|
}
|
|
5377
|
-
var version = true ? "1.
|
|
7022
|
+
var version = true ? "1.1.0" : JSON.parse(
|
|
5378
7023
|
readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
|
|
5379
7024
|
).version;
|
|
5380
7025
|
function styledHelpWriteOut(showHeader) {
|
|
@@ -5382,8 +7027,8 @@ function styledHelpWriteOut(showHeader) {
|
|
|
5382
7027
|
if (supportsTruecolor()) {
|
|
5383
7028
|
const sections = parseHelpSections(str);
|
|
5384
7029
|
if (sections.length > 0) {
|
|
5385
|
-
const header = showHeader ? /* @__PURE__ */
|
|
5386
|
-
renderOnce(/* @__PURE__ */
|
|
7030
|
+
const header = showHeader ? /* @__PURE__ */ jsx24(StyledHelpHeader, { sections }) : void 0;
|
|
7031
|
+
renderOnce(/* @__PURE__ */ jsx24(StyledHelp, { sections, header }));
|
|
5387
7032
|
return;
|
|
5388
7033
|
}
|
|
5389
7034
|
}
|
|
@@ -5394,7 +7039,7 @@ function styledHelpWriteOut(showHeader) {
|
|
|
5394
7039
|
};
|
|
5395
7040
|
}
|
|
5396
7041
|
var buildProgram = () => {
|
|
5397
|
-
const program2 = new
|
|
7042
|
+
const program2 = new Command11().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
7043
|
const helpWidth = (process.stdout.columns || 80) - 4;
|
|
5399
7044
|
program2.configureHelp({
|
|
5400
7045
|
helpWidth,
|
|
@@ -5412,7 +7057,6 @@ var buildProgram = () => {
|
|
|
5412
7057
|
program2.addCommand(buyCommand);
|
|
5413
7058
|
program2.addCommand(exploreCommand);
|
|
5414
7059
|
program2.addCommand(getCommand);
|
|
5415
|
-
program2.addCommand(priceHistoryCommand);
|
|
5416
7060
|
program2.addCommand(profileCommand);
|
|
5417
7061
|
program2.addCommand(setupCommand);
|
|
5418
7062
|
program2.addCommand(walletCommand);
|
|
@@ -5431,7 +7075,7 @@ var buildProgram = () => {
|
|
|
5431
7075
|
const expected = actionCommand.registeredArguments.length;
|
|
5432
7076
|
if (expected > 0 && actionCommand.args.length === 0 && !argOptionalCommands.has(actionCommand.name())) {
|
|
5433
7077
|
actionCommand.outputHelp();
|
|
5434
|
-
|
|
7078
|
+
safeExit(ERROR);
|
|
5435
7079
|
}
|
|
5436
7080
|
});
|
|
5437
7081
|
return program2;
|
|
@@ -5439,17 +7083,24 @@ var buildProgram = () => {
|
|
|
5439
7083
|
var program = buildProgram();
|
|
5440
7084
|
if (!process.env.VITEST) {
|
|
5441
7085
|
identify();
|
|
7086
|
+
let exitCode = null;
|
|
5442
7087
|
try {
|
|
5443
7088
|
await program.parseAsync();
|
|
5444
7089
|
} catch (err) {
|
|
5445
7090
|
if (err instanceof ExitPromptError) {
|
|
5446
7091
|
console.log("\nAborted.");
|
|
5447
|
-
|
|
7092
|
+
exitCode = 0;
|
|
7093
|
+
} else if (err instanceof CliExitError) {
|
|
7094
|
+
exitCode = err.exitCode;
|
|
7095
|
+
} else {
|
|
7096
|
+
throw err;
|
|
5448
7097
|
}
|
|
5449
|
-
throw err;
|
|
5450
7098
|
} finally {
|
|
5451
7099
|
await shutdownAnalytics();
|
|
5452
7100
|
}
|
|
7101
|
+
if (exitCode !== null) {
|
|
7102
|
+
process.exit(exitCode);
|
|
7103
|
+
}
|
|
5453
7104
|
}
|
|
5454
7105
|
export {
|
|
5455
7106
|
buildProgram
|