@zoralabs/cli 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.js +1709 -643
- package/package.json +4 -3
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 Command12 } from "commander";
|
|
5
5
|
import { ExitPromptError } from "@inquirer/core";
|
|
6
6
|
import "fs";
|
|
7
7
|
import { setApiBaseUrl } from "@zoralabs/coins-sdk";
|
|
@@ -18,6 +18,56 @@ import {
|
|
|
18
18
|
chmodSync
|
|
19
19
|
} from "fs";
|
|
20
20
|
import { join } from "path";
|
|
21
|
+
|
|
22
|
+
// src/lib/errors.ts
|
|
23
|
+
import { BaseError as ViemBaseError, InsufficientFundsError } from "viem";
|
|
24
|
+
function formatError(err) {
|
|
25
|
+
if (!(err instanceof Error)) return String(err);
|
|
26
|
+
const msg = err.message;
|
|
27
|
+
return msg.length > 120 ? msg.slice(0, 120) + "..." : msg;
|
|
28
|
+
}
|
|
29
|
+
function tradeErrorMessage(err) {
|
|
30
|
+
if (!(err instanceof Error)) return String(err);
|
|
31
|
+
if (err instanceof ViemBaseError) {
|
|
32
|
+
const insufficient = err.walk((e) => e instanceof InsufficientFundsError);
|
|
33
|
+
if (insufficient)
|
|
34
|
+
return "Not enough funds. Try a lower amount or run 'zora balance spendable' to check your balance.";
|
|
35
|
+
return err.shortMessage;
|
|
36
|
+
}
|
|
37
|
+
return apiErrorMessage(err);
|
|
38
|
+
}
|
|
39
|
+
function apiErrorMessage(err) {
|
|
40
|
+
if (!(err instanceof Error)) return String(err);
|
|
41
|
+
const code = err.code;
|
|
42
|
+
if (code === "ECONNREFUSED" || code === "ENOTFOUND")
|
|
43
|
+
return "Can't connect. Check your internet connection.";
|
|
44
|
+
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT")
|
|
45
|
+
return "Request timed out. Try again.";
|
|
46
|
+
const status = err.status;
|
|
47
|
+
if (status === 429)
|
|
48
|
+
return "Rate limited. Wait a moment or run 'zora auth configure' for higher limits.";
|
|
49
|
+
if (status === 401 || status === 403)
|
|
50
|
+
return "Auth failed. Run 'zora auth configure' to update your API key.";
|
|
51
|
+
if (typeof status === "number" && status >= 500)
|
|
52
|
+
return "Zora is temporarily unavailable. Try again later.";
|
|
53
|
+
return formatError(err);
|
|
54
|
+
}
|
|
55
|
+
function bannedCoinMessage(address) {
|
|
56
|
+
return `The coin at ${address} is unavailable because it violates the Zora terms of service.`;
|
|
57
|
+
}
|
|
58
|
+
function bannedCoinBuyMessage(address) {
|
|
59
|
+
return `Unable to buy ${address} because it violates the Zora terms of service. Already own this coin? Run zora sell ${address} --all to exit your position.`;
|
|
60
|
+
}
|
|
61
|
+
function fsErrorMessage(err, path) {
|
|
62
|
+
if (!(err instanceof Error)) return String(err);
|
|
63
|
+
const code = err.code;
|
|
64
|
+
if (code === "EACCES") return `Permission denied accessing ${path}.`;
|
|
65
|
+
if (code === "EISDIR")
|
|
66
|
+
return `Expected a file but found a directory at ${path}.`;
|
|
67
|
+
return formatError(err);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/config.ts
|
|
21
71
|
import { homedir, platform } from "os";
|
|
22
72
|
function getConfigDir() {
|
|
23
73
|
if (platform() === "win32") {
|
|
@@ -55,7 +105,7 @@ function readConfig() {
|
|
|
55
105
|
parsed = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
56
106
|
} catch (err) {
|
|
57
107
|
console.error(
|
|
58
|
-
`Warning: could not parse ${CONFIG_FILE}: ${err
|
|
108
|
+
`Warning: could not parse ${CONFIG_FILE}: ${formatError(err)}. Run 'zora auth configure' to fix.`
|
|
59
109
|
);
|
|
60
110
|
configReadOnly = true;
|
|
61
111
|
return { version: CONFIG_VERSION };
|
|
@@ -64,7 +114,7 @@ function readConfig() {
|
|
|
64
114
|
assertVersion(parsed, CONFIG_VERSION, CONFIG_FILE);
|
|
65
115
|
} catch (err) {
|
|
66
116
|
console.error(
|
|
67
|
-
`Warning: ${err
|
|
117
|
+
`Warning: ${formatError(err)}. Delete ${CONFIG_FILE} or run 'zora auth configure' to reset.`
|
|
68
118
|
);
|
|
69
119
|
configReadOnly = true;
|
|
70
120
|
return { version: CONFIG_VERSION };
|
|
@@ -155,18 +205,29 @@ function maskKey(key) {
|
|
|
155
205
|
}
|
|
156
206
|
|
|
157
207
|
// src/lib/output.ts
|
|
158
|
-
var VALID_OUTPUT_MODES = ["table", "json", "live"];
|
|
159
208
|
var getOutputMode = (cmd, defaultMode) => {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
209
|
+
const globals = cmd.optsWithGlobals();
|
|
210
|
+
const json = globals.json ?? false;
|
|
211
|
+
const live = globals.live ?? false;
|
|
212
|
+
const static_ = globals.static ?? false;
|
|
213
|
+
const set = [
|
|
214
|
+
json && "--json",
|
|
215
|
+
live && "--live",
|
|
216
|
+
static_ && "--static"
|
|
217
|
+
].filter(Boolean);
|
|
218
|
+
if (set.length > 1) {
|
|
219
|
+
return outputErrorAndExit(
|
|
220
|
+
false,
|
|
221
|
+
`${set.join(", ")} cannot be used together.`,
|
|
222
|
+
"Choose one: --json, --live, or --static"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (json) return "json";
|
|
226
|
+
if (live) return "live";
|
|
227
|
+
if (static_) return "static";
|
|
228
|
+
return defaultMode;
|
|
168
229
|
};
|
|
169
|
-
var getJson = (cmd) =>
|
|
230
|
+
var getJson = (cmd) => cmd.optsWithGlobals().json ?? false;
|
|
170
231
|
var getYes = (cmd) => cmd.optsWithGlobals().yes ?? false;
|
|
171
232
|
var outputJson = (data) => {
|
|
172
233
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -188,14 +249,18 @@ var outputData = (json, opts) => {
|
|
|
188
249
|
if (json) {
|
|
189
250
|
outputJson(opts.json);
|
|
190
251
|
} else {
|
|
191
|
-
opts.
|
|
252
|
+
opts.render();
|
|
192
253
|
}
|
|
193
254
|
};
|
|
194
|
-
var getLiveConfig = (cmd,
|
|
195
|
-
const mode = getOutputMode(cmd, defaultMode);
|
|
255
|
+
var getLiveConfig = (cmd, mode) => {
|
|
196
256
|
const live = mode === "live";
|
|
197
|
-
const intervalRaw = parseInt(cmd.
|
|
257
|
+
const intervalRaw = parseInt(cmd.opts().refresh, 10);
|
|
198
258
|
const intervalSeconds = isNaN(intervalRaw) || intervalRaw < 5 ? 30 : intervalRaw;
|
|
259
|
+
if (!live && cmd.getOptionValueSource("refresh") === "cli") {
|
|
260
|
+
console.warn(
|
|
261
|
+
"\x1B[33mWarning:\x1B[0m --refresh has no effect without --live"
|
|
262
|
+
);
|
|
263
|
+
}
|
|
199
264
|
return { live, intervalSeconds };
|
|
200
265
|
};
|
|
201
266
|
|
|
@@ -282,9 +347,7 @@ var resolveAccount = (json = false) => {
|
|
|
282
347
|
try {
|
|
283
348
|
return privateKeyToAccount(normalizeKey(key));
|
|
284
349
|
} catch (err) {
|
|
285
|
-
console.error(
|
|
286
|
-
`\u2717 Invalid private key: ${err instanceof Error ? err.message : String(err)}`
|
|
287
|
-
);
|
|
350
|
+
console.error(`\u2717 Invalid private key: ${formatError(err)}`);
|
|
288
351
|
console.error(" Run 'zora setup --force' to replace it.");
|
|
289
352
|
return process.exit(1);
|
|
290
353
|
}
|
|
@@ -374,7 +437,7 @@ var getClient = () => {
|
|
|
374
437
|
return client;
|
|
375
438
|
};
|
|
376
439
|
var commonProperties = () => ({
|
|
377
|
-
cli_version: true ? "0.
|
|
440
|
+
cli_version: true ? "0.3.1" : "development",
|
|
378
441
|
os: process.platform,
|
|
379
442
|
arch: process.arch,
|
|
380
443
|
node_version: process.version
|
|
@@ -447,7 +510,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
447
510
|
status: "env_override",
|
|
448
511
|
message: "API key is set via ZORA_API_KEY environment variable."
|
|
449
512
|
},
|
|
450
|
-
|
|
513
|
+
render: () => console.log(
|
|
451
514
|
"API key is set via ZORA_API_KEY environment variable. Unset it to configure manually."
|
|
452
515
|
)
|
|
453
516
|
});
|
|
@@ -475,7 +538,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
475
538
|
saveApiKey(trimmed);
|
|
476
539
|
outputData(json, {
|
|
477
540
|
json: { saved: true, path: getConfigPath() },
|
|
478
|
-
|
|
541
|
+
render: () => console.log(`API key saved to ${getConfigPath()}`)
|
|
479
542
|
});
|
|
480
543
|
track("cli_auth_configure", {
|
|
481
544
|
output_format: json ? "json" : "text"
|
|
@@ -483,7 +546,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
483
546
|
} catch (err) {
|
|
484
547
|
outputErrorAndExit(
|
|
485
548
|
json,
|
|
486
|
-
`Failed to save API key: ${err
|
|
549
|
+
`Failed to save API key: ${fsErrorMessage(err, getConfigPath())}`
|
|
487
550
|
);
|
|
488
551
|
}
|
|
489
552
|
});
|
|
@@ -493,7 +556,7 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
493
556
|
if (!apiKey) {
|
|
494
557
|
outputData(json, {
|
|
495
558
|
json: { authenticated: false },
|
|
496
|
-
|
|
559
|
+
render: () => {
|
|
497
560
|
console.log(
|
|
498
561
|
"No API key configured. The CLI works without one, but requests are rate-limited."
|
|
499
562
|
);
|
|
@@ -512,7 +575,7 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
512
575
|
const source = getEnvApiKey() ? "env (ZORA_API_KEY)" : getConfigPath();
|
|
513
576
|
outputData(json, {
|
|
514
577
|
json: { authenticated: true, key: maskKey(apiKey), source },
|
|
515
|
-
|
|
578
|
+
render: () => {
|
|
516
579
|
console.log(`Authenticated: ${maskKey(apiKey)}`);
|
|
517
580
|
console.log(`Source: ${source}`);
|
|
518
581
|
}
|
|
@@ -577,7 +640,8 @@ var Table = ({
|
|
|
577
640
|
title,
|
|
578
641
|
subtitle,
|
|
579
642
|
fullWidth = true,
|
|
580
|
-
footer
|
|
643
|
+
footer,
|
|
644
|
+
selectedRow
|
|
581
645
|
}) => {
|
|
582
646
|
const widths = computeColumnWidths(columns, fullWidth);
|
|
583
647
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
|
|
@@ -589,26 +653,31 @@ var Table = ({
|
|
|
589
653
|
] })
|
|
590
654
|
] }),
|
|
591
655
|
/* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, i) => /* @__PURE__ */ jsx(Box, { width: widths[i], children: /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, wrap: "truncate", children: col.header }) }, col.header)) }),
|
|
592
|
-
data.map((row, i) =>
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
656
|
+
data.map((row, i) => {
|
|
657
|
+
const isSelected = selectedRow === i;
|
|
658
|
+
return /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, colIdx) => {
|
|
659
|
+
const colWidth = widths[colIdx];
|
|
660
|
+
const value = col.noTruncate ? col.accessor(row) : truncate(col.accessor(row), colWidth - 2);
|
|
661
|
+
const colorName = col.color?.(row);
|
|
662
|
+
return /* @__PURE__ */ jsx(Box, { width: colWidth, children: /* @__PURE__ */ jsx(
|
|
663
|
+
Text,
|
|
664
|
+
{
|
|
665
|
+
color: colorName,
|
|
666
|
+
bold: isSelected,
|
|
667
|
+
inverse: isSelected,
|
|
668
|
+
wrap: col.noTruncate ? "wrap" : "truncate",
|
|
669
|
+
children: value
|
|
670
|
+
}
|
|
671
|
+
) }, col.header);
|
|
672
|
+
}) }, i);
|
|
673
|
+
}),
|
|
605
674
|
footer && /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: footer }) })
|
|
606
675
|
] });
|
|
607
676
|
};
|
|
608
677
|
|
|
609
678
|
// src/lib/format.ts
|
|
610
679
|
import { format, formatDistanceStrict } from "date-fns";
|
|
611
|
-
import {
|
|
680
|
+
import { formatUnits } from "viem";
|
|
612
681
|
function formatCompactUsd(value) {
|
|
613
682
|
if (!value || Number(value) === 0) return "$0";
|
|
614
683
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -657,16 +726,28 @@ function formatCreatedAt(isoDate, now) {
|
|
|
657
726
|
if (isNaN(date.getTime())) return "-";
|
|
658
727
|
return `${formatRelativeTime(date, now)} (${formatAbsoluteTime(date)})`;
|
|
659
728
|
}
|
|
660
|
-
var
|
|
661
|
-
const
|
|
662
|
-
const parts =
|
|
663
|
-
if (!parts[1])
|
|
664
|
-
|
|
665
|
-
|
|
729
|
+
var formatAmountDisplay = (amount, decimals) => {
|
|
730
|
+
const formatted = formatUnits(amount, decimals);
|
|
731
|
+
const parts = formatted.split(".");
|
|
732
|
+
if (!parts[1]) {
|
|
733
|
+
return new Intl.NumberFormat("en-US", {
|
|
734
|
+
maximumFractionDigits: 2
|
|
735
|
+
}).format(Number(formatted));
|
|
736
|
+
}
|
|
737
|
+
const twoDecimal = `${parts[0]}.${parts[1].slice(0, 2)}`;
|
|
738
|
+
if (Number(twoDecimal) === 0 && amount > 0n) {
|
|
739
|
+
const sigIndex = parts[1].search(/[1-9]/);
|
|
740
|
+
const maxDecimals = sigIndex === -1 ? 6 : Math.min(sigIndex + 4, parts[1].length);
|
|
741
|
+
const truncated = `${parts[0]}.${parts[1].slice(0, maxDecimals)}`;
|
|
742
|
+
return new Intl.NumberFormat("en-US", {
|
|
743
|
+
maximumFractionDigits: maxDecimals
|
|
744
|
+
}).format(Number(truncated));
|
|
745
|
+
}
|
|
746
|
+
return new Intl.NumberFormat("en-US", {
|
|
747
|
+
maximumFractionDigits: 2
|
|
748
|
+
}).format(Number(formatted));
|
|
666
749
|
};
|
|
667
|
-
var
|
|
668
|
-
maximumFractionDigits: 2
|
|
669
|
-
}).format(Number(coinsOut));
|
|
750
|
+
var truncateAddress = (address) => `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
670
751
|
|
|
671
752
|
// src/lib/balance-format.ts
|
|
672
753
|
var COIN_DECIMALS = 18;
|
|
@@ -800,6 +881,7 @@ var BalanceView = ({
|
|
|
800
881
|
fetchData,
|
|
801
882
|
sort,
|
|
802
883
|
mode = "full",
|
|
884
|
+
initialCursor,
|
|
803
885
|
autoRefresh = false,
|
|
804
886
|
intervalSeconds = 30
|
|
805
887
|
}) => {
|
|
@@ -808,37 +890,64 @@ var BalanceView = ({
|
|
|
808
890
|
const [isRefreshing, setIsRefreshing] = useState2(false);
|
|
809
891
|
const [error, setError] = useState2(null);
|
|
810
892
|
const [data, setData] = useState2(null);
|
|
893
|
+
const paginated = mode === "coins";
|
|
894
|
+
const [page, setPage] = useState2(1);
|
|
895
|
+
const [cursorHistory, setCursorHistory] = useState2(
|
|
896
|
+
[]
|
|
897
|
+
);
|
|
898
|
+
const [currentCursor, setCurrentCursor] = useState2(
|
|
899
|
+
initialCursor
|
|
900
|
+
);
|
|
811
901
|
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
812
902
|
const [manualRefreshCount, setManualRefreshCount] = useState2(0);
|
|
813
903
|
const hasLoadedOnce = useRef(false);
|
|
814
|
-
const load = useCallback2(
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
904
|
+
const load = useCallback2(
|
|
905
|
+
async (cursor) => {
|
|
906
|
+
if (hasLoadedOnce.current) {
|
|
907
|
+
setIsRefreshing(true);
|
|
908
|
+
} else {
|
|
909
|
+
setLoading(true);
|
|
910
|
+
}
|
|
911
|
+
setError(null);
|
|
912
|
+
try {
|
|
913
|
+
const result = await fetchData(cursor);
|
|
914
|
+
setData(result);
|
|
915
|
+
hasLoadedOnce.current = true;
|
|
916
|
+
} catch (err) {
|
|
917
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
918
|
+
}
|
|
919
|
+
setLoading(false);
|
|
920
|
+
setIsRefreshing(false);
|
|
921
|
+
},
|
|
922
|
+
[fetchData]
|
|
923
|
+
);
|
|
831
924
|
useEffect2(() => {
|
|
832
|
-
load();
|
|
833
|
-
}, [refreshCount, manualRefreshCount]);
|
|
925
|
+
load(currentCursor);
|
|
926
|
+
}, [load, refreshCount, manualRefreshCount, currentCursor]);
|
|
834
927
|
useInput((input, key) => {
|
|
835
928
|
if (input === "q" || key.escape) {
|
|
836
929
|
exit();
|
|
837
930
|
return;
|
|
838
931
|
}
|
|
839
|
-
if (
|
|
932
|
+
if (loading) return;
|
|
933
|
+
if (input === "r") {
|
|
840
934
|
triggerManualRefresh();
|
|
841
935
|
setManualRefreshCount((c) => c + 1);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (!paginated) return;
|
|
939
|
+
const canGoNext = data?.pageInfo?.hasNextPage && data.pageInfo.endCursor;
|
|
940
|
+
const canGoPrev = cursorHistory.length > 0;
|
|
941
|
+
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
942
|
+
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
943
|
+
setCurrentCursor(data.pageInfo.endCursor);
|
|
944
|
+
setPage((p) => p + 1);
|
|
945
|
+
}
|
|
946
|
+
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
947
|
+
const prev = cursorHistory[cursorHistory.length - 1];
|
|
948
|
+
setCursorHistory((h) => h.slice(0, -1));
|
|
949
|
+
setCurrentCursor(prev);
|
|
950
|
+
setPage((p) => p - 1);
|
|
842
951
|
}
|
|
843
952
|
});
|
|
844
953
|
if (error && !data) {
|
|
@@ -866,8 +975,10 @@ var BalanceView = ({
|
|
|
866
975
|
] }) });
|
|
867
976
|
}
|
|
868
977
|
if (!data) return null;
|
|
869
|
-
const hints = [
|
|
870
|
-
if (
|
|
978
|
+
const hints = [];
|
|
979
|
+
if (paginated && cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
980
|
+
if (paginated && data?.pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
981
|
+
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
871
982
|
hints.push("q quit");
|
|
872
983
|
const footer = hints.join(" \xB7 ");
|
|
873
984
|
const showWallet = mode === "full" || mode === "wallet";
|
|
@@ -910,7 +1021,7 @@ var BalanceView = ({
|
|
|
910
1021
|
columns: balanceColumns,
|
|
911
1022
|
data: data.rankedBalances,
|
|
912
1023
|
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
913
|
-
subtitle: `${data.rankedBalances.length} of ${data.total}`
|
|
1024
|
+
subtitle: paginated ? `Page ${page} \xB7 ${data.rankedBalances.length} result${data.rankedBalances.length !== 1 ? "s" : ""}` : `${data.rankedBalances.length} of ${data.total}`
|
|
914
1025
|
}
|
|
915
1026
|
) : null,
|
|
916
1027
|
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: footer }) })
|
|
@@ -922,7 +1033,7 @@ import { getTokenInfo } from "@zoralabs/coins-sdk";
|
|
|
922
1033
|
import {
|
|
923
1034
|
createPublicClient as createPublicClient2,
|
|
924
1035
|
erc20Abi,
|
|
925
|
-
formatUnits,
|
|
1036
|
+
formatUnits as formatUnits2,
|
|
926
1037
|
http
|
|
927
1038
|
} from "viem";
|
|
928
1039
|
import { base as base2 } from "viem/chains";
|
|
@@ -957,7 +1068,7 @@ var fetchTokenPriceUsd = async (address, chainId = BASE_CHAIN_ID) => {
|
|
|
957
1068
|
return res.data?.erc20Token?.currency?.priceUsd ? Number(res.data.erc20Token.currency.priceUsd) : null;
|
|
958
1069
|
} catch (err) {
|
|
959
1070
|
console.warn(
|
|
960
|
-
`Warning: failed to fetch price for ${address}: ${
|
|
1071
|
+
`Warning: failed to fetch price for ${address}: ${formatError(err)}`
|
|
961
1072
|
);
|
|
962
1073
|
return null;
|
|
963
1074
|
}
|
|
@@ -1007,7 +1118,7 @@ var fetchWalletBalances = async (walletAddress) => {
|
|
|
1007
1118
|
});
|
|
1008
1119
|
const visible = resolved.filter((r) => r.balance > 0n || r.token.isNative);
|
|
1009
1120
|
const intermediate = visible.map(({ token, balance, priceUsd }) => {
|
|
1010
|
-
const human =
|
|
1121
|
+
const human = formatUnits2(balance, token.decimals);
|
|
1011
1122
|
const usdValue = priceUsd !== null ? Number(human) * priceUsd : null;
|
|
1012
1123
|
return { token, human, priceUsd, usdValue };
|
|
1013
1124
|
});
|
|
@@ -1094,7 +1205,7 @@ function resolveContext(json) {
|
|
|
1094
1205
|
function renderWallet(json, walletResult) {
|
|
1095
1206
|
outputData(json, {
|
|
1096
1207
|
json: { wallet: walletResult.walletBalancesJson },
|
|
1097
|
-
|
|
1208
|
+
render: () => {
|
|
1098
1209
|
renderOnce(
|
|
1099
1210
|
/* @__PURE__ */ jsx3(
|
|
1100
1211
|
Table,
|
|
@@ -1108,7 +1219,7 @@ function renderWallet(json, walletResult) {
|
|
|
1108
1219
|
}
|
|
1109
1220
|
});
|
|
1110
1221
|
}
|
|
1111
|
-
function renderCoins(json, balances, total, sort) {
|
|
1222
|
+
function renderCoins(json, balances, total, sort, limit, pageInfo) {
|
|
1112
1223
|
const rankedBalances = balances.map((balance, index) => ({
|
|
1113
1224
|
...balance,
|
|
1114
1225
|
rank: index + 1
|
|
@@ -1117,14 +1228,16 @@ function renderCoins(json, balances, total, sort) {
|
|
|
1117
1228
|
json: {
|
|
1118
1229
|
coins: rankedBalances.map(
|
|
1119
1230
|
(balance) => formatBalanceJson(balance, balance.rank)
|
|
1120
|
-
)
|
|
1231
|
+
),
|
|
1232
|
+
pageInfo: pageInfo ?? null
|
|
1121
1233
|
},
|
|
1122
|
-
|
|
1234
|
+
render: () => {
|
|
1123
1235
|
if (balances.length === 0) {
|
|
1124
1236
|
console.log("\n No coin balances found.\n");
|
|
1125
1237
|
console.log(" Buy coins to see them here:");
|
|
1126
1238
|
console.log(" zora buy <address> --eth 0.001\n");
|
|
1127
1239
|
} else {
|
|
1240
|
+
const footer = pageInfo?.hasNextPage && pageInfo.endCursor ? `Next page: zora balance coins --sort ${sort} --limit ${limit} --after ${pageInfo.endCursor}` : void 0;
|
|
1128
1241
|
renderOnce(
|
|
1129
1242
|
/* @__PURE__ */ jsx3(
|
|
1130
1243
|
Table,
|
|
@@ -1132,7 +1245,8 @@ function renderCoins(json, balances, total, sort) {
|
|
|
1132
1245
|
columns: balanceColumns,
|
|
1133
1246
|
data: rankedBalances,
|
|
1134
1247
|
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1135
|
-
subtitle: `${balances.length} of ${total}
|
|
1248
|
+
subtitle: `${balances.length} of ${total}`,
|
|
1249
|
+
footer
|
|
1136
1250
|
}
|
|
1137
1251
|
)
|
|
1138
1252
|
);
|
|
@@ -1140,19 +1254,17 @@ function renderCoins(json, balances, total, sort) {
|
|
|
1140
1254
|
}
|
|
1141
1255
|
});
|
|
1142
1256
|
}
|
|
1143
|
-
async function fetchCoins(json, address, sort, limit) {
|
|
1257
|
+
async function fetchCoins(json, address, sort, limit, after) {
|
|
1144
1258
|
let response;
|
|
1145
1259
|
try {
|
|
1146
1260
|
response = await getProfileBalances({
|
|
1147
1261
|
identifier: address,
|
|
1148
1262
|
count: limit,
|
|
1149
|
-
sortOption: SORT_MAP[sort]
|
|
1263
|
+
sortOption: SORT_MAP[sort],
|
|
1264
|
+
after
|
|
1150
1265
|
});
|
|
1151
1266
|
} catch (err) {
|
|
1152
|
-
outputErrorAndExit(
|
|
1153
|
-
json,
|
|
1154
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1155
|
-
);
|
|
1267
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
1156
1268
|
}
|
|
1157
1269
|
if (response.error) {
|
|
1158
1270
|
outputErrorAndExit(
|
|
@@ -1165,7 +1277,8 @@ async function fetchCoins(json, address, sort, limit) {
|
|
|
1165
1277
|
(e) => e.node
|
|
1166
1278
|
);
|
|
1167
1279
|
const total = response.data?.profile?.coinBalances?.count ?? balances.length;
|
|
1168
|
-
|
|
1280
|
+
const pageInfo = response.data?.profile?.coinBalances?.pageInfo;
|
|
1281
|
+
return { balances, total, pageInfo };
|
|
1169
1282
|
}
|
|
1170
1283
|
function validateCoinOpts(json, sort, limitStr) {
|
|
1171
1284
|
if (!SORT_MAP[sort]) {
|
|
@@ -1184,11 +1297,15 @@ function validateCoinOpts(json, sort, limitStr) {
|
|
|
1184
1297
|
}
|
|
1185
1298
|
return { sort, limit };
|
|
1186
1299
|
}
|
|
1187
|
-
var balanceCommand = new Command2("balance").description("Show balances in your wallet").
|
|
1300
|
+
var balanceCommand = new Command2("balance").description("Show balances in your wallet").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
1301
|
+
"--refresh <seconds>",
|
|
1302
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1303
|
+
"30"
|
|
1304
|
+
).action(async function() {
|
|
1188
1305
|
const output = getOutputMode(this, "live");
|
|
1189
1306
|
const json = output === "json";
|
|
1190
1307
|
const account = resolveContext(json);
|
|
1191
|
-
const { live, intervalSeconds } = getLiveConfig(this,
|
|
1308
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1192
1309
|
const sort = "usd-value";
|
|
1193
1310
|
const limit = 10;
|
|
1194
1311
|
const fetchBalanceData = async () => {
|
|
@@ -1198,7 +1315,7 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1198
1315
|
]);
|
|
1199
1316
|
if (walletResult.status === "rejected" || coinsResult.status === "rejected") {
|
|
1200
1317
|
const err = walletResult.status === "rejected" ? walletResult.reason : coinsResult.reason;
|
|
1201
|
-
throw
|
|
1318
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
1202
1319
|
}
|
|
1203
1320
|
const rankedBalances = coinsResult.value.balances.map(
|
|
1204
1321
|
(balance, index) => ({
|
|
@@ -1215,10 +1332,7 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1215
1332
|
};
|
|
1216
1333
|
if (json) {
|
|
1217
1334
|
const data = await fetchBalanceData().catch(
|
|
1218
|
-
(err) => outputErrorAndExit(
|
|
1219
|
-
json,
|
|
1220
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1221
|
-
)
|
|
1335
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1222
1336
|
);
|
|
1223
1337
|
outputData(json, {
|
|
1224
1338
|
json: {
|
|
@@ -1227,7 +1341,7 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1227
1341
|
(balance) => formatBalanceJson(balance, balance.rank)
|
|
1228
1342
|
)
|
|
1229
1343
|
},
|
|
1230
|
-
|
|
1344
|
+
render: () => {
|
|
1231
1345
|
}
|
|
1232
1346
|
});
|
|
1233
1347
|
track("cli_balances", {
|
|
@@ -1260,10 +1374,7 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1260
1374
|
});
|
|
1261
1375
|
} else {
|
|
1262
1376
|
const data = await fetchBalanceData().catch(
|
|
1263
|
-
(err) => outputErrorAndExit(
|
|
1264
|
-
json,
|
|
1265
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1266
|
-
)
|
|
1377
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1267
1378
|
);
|
|
1268
1379
|
renderOnce(
|
|
1269
1380
|
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
@@ -1311,15 +1422,19 @@ var balanceCommand = new Command2("balance").description("Show balances in your
|
|
|
1311
1422
|
live: false,
|
|
1312
1423
|
result_count: data.rankedBalances.length,
|
|
1313
1424
|
total_count: data.total,
|
|
1314
|
-
output_format: "
|
|
1425
|
+
output_format: "static"
|
|
1315
1426
|
});
|
|
1316
1427
|
}
|
|
1317
1428
|
});
|
|
1318
|
-
balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").
|
|
1429
|
+
balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
1430
|
+
"--refresh <seconds>",
|
|
1431
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1432
|
+
"30"
|
|
1433
|
+
).action(async function() {
|
|
1319
1434
|
const output = getOutputMode(this, "live");
|
|
1320
1435
|
const json = output === "json";
|
|
1321
1436
|
const account = resolveContext(json);
|
|
1322
|
-
const { live, intervalSeconds } = getLiveConfig(this,
|
|
1437
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1323
1438
|
const fetchSpendableData = async () => {
|
|
1324
1439
|
const walletResult = await fetchWalletBalances(account.address);
|
|
1325
1440
|
return {
|
|
@@ -1331,14 +1446,11 @@ balanceCommand.command("spendable").description("Show wallet token balances (ETH
|
|
|
1331
1446
|
};
|
|
1332
1447
|
if (json) {
|
|
1333
1448
|
const data = await fetchSpendableData().catch(
|
|
1334
|
-
(err) => outputErrorAndExit(
|
|
1335
|
-
json,
|
|
1336
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1337
|
-
)
|
|
1449
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1338
1450
|
);
|
|
1339
1451
|
outputData(json, {
|
|
1340
1452
|
json: { wallet: data.walletBalancesJson },
|
|
1341
|
-
|
|
1453
|
+
render: () => {
|
|
1342
1454
|
}
|
|
1343
1455
|
});
|
|
1344
1456
|
} else if (live) {
|
|
@@ -1356,26 +1468,29 @@ balanceCommand.command("spendable").description("Show wallet token balances (ETH
|
|
|
1356
1468
|
);
|
|
1357
1469
|
} else {
|
|
1358
1470
|
const walletResult = await fetchWalletBalances(account.address).catch(
|
|
1359
|
-
(err) => outputErrorAndExit(
|
|
1360
|
-
json,
|
|
1361
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1362
|
-
)
|
|
1471
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1363
1472
|
);
|
|
1364
1473
|
renderWallet(json, walletResult);
|
|
1365
1474
|
}
|
|
1366
1475
|
});
|
|
1367
|
-
balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "usd-value").option("--limit <n>", "Number of results (max 20)", "10").
|
|
1476
|
+
balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "usd-value").option("--limit <n>", "Number of results (max 20)", "10").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
1477
|
+
"--refresh <seconds>",
|
|
1478
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1479
|
+
"30"
|
|
1480
|
+
).option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
|
|
1368
1481
|
const output = getOutputMode(this, "live");
|
|
1369
1482
|
const json = output === "json";
|
|
1370
1483
|
const { sort, limit } = validateCoinOpts(json, opts.sort, opts.limit);
|
|
1484
|
+
const after = opts.after;
|
|
1371
1485
|
const account = resolveContext(json);
|
|
1372
|
-
const { live, intervalSeconds } = getLiveConfig(this,
|
|
1373
|
-
const
|
|
1374
|
-
const { balances, total } = await fetchCoins(
|
|
1486
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1487
|
+
const fetchCoinsPage = async (cursor) => {
|
|
1488
|
+
const { balances, total, pageInfo } = await fetchCoins(
|
|
1375
1489
|
json,
|
|
1376
1490
|
account.address,
|
|
1377
1491
|
sort,
|
|
1378
|
-
limit
|
|
1492
|
+
limit,
|
|
1493
|
+
cursor
|
|
1379
1494
|
);
|
|
1380
1495
|
const rankedBalances = balances.map((balance, index) => ({
|
|
1381
1496
|
...balance,
|
|
@@ -1385,43 +1500,53 @@ balanceCommand.command("coins").description("Show coin positions").option("--sor
|
|
|
1385
1500
|
walletBalances: [],
|
|
1386
1501
|
walletBalancesJson: [],
|
|
1387
1502
|
rankedBalances,
|
|
1388
|
-
total
|
|
1503
|
+
total,
|
|
1504
|
+
pageInfo
|
|
1389
1505
|
};
|
|
1390
1506
|
};
|
|
1391
1507
|
if (json) {
|
|
1392
|
-
const data = await
|
|
1393
|
-
renderCoins(
|
|
1508
|
+
const data = await fetchCoinsPage(after);
|
|
1509
|
+
renderCoins(
|
|
1510
|
+
json,
|
|
1511
|
+
data.rankedBalances,
|
|
1512
|
+
data.total,
|
|
1513
|
+
sort,
|
|
1514
|
+
limit,
|
|
1515
|
+
data.pageInfo
|
|
1516
|
+
);
|
|
1394
1517
|
} else if (live) {
|
|
1395
1518
|
await renderLive(
|
|
1396
1519
|
/* @__PURE__ */ jsx3(
|
|
1397
1520
|
BalanceView,
|
|
1398
1521
|
{
|
|
1399
|
-
fetchData:
|
|
1522
|
+
fetchData: fetchCoinsPage,
|
|
1400
1523
|
sort,
|
|
1401
1524
|
mode: "coins",
|
|
1525
|
+
initialCursor: after,
|
|
1402
1526
|
autoRefresh: live,
|
|
1403
1527
|
intervalSeconds
|
|
1404
1528
|
}
|
|
1405
1529
|
)
|
|
1406
1530
|
);
|
|
1407
1531
|
} else {
|
|
1408
|
-
const { balances, total } = await fetchCoins(
|
|
1532
|
+
const { balances, total, pageInfo } = await fetchCoins(
|
|
1409
1533
|
json,
|
|
1410
1534
|
account.address,
|
|
1411
1535
|
sort,
|
|
1412
|
-
limit
|
|
1536
|
+
limit,
|
|
1537
|
+
after
|
|
1413
1538
|
);
|
|
1414
|
-
renderCoins(json, balances, total, sort);
|
|
1539
|
+
renderCoins(json, balances, total, sort, limit, pageInfo);
|
|
1415
1540
|
}
|
|
1416
1541
|
});
|
|
1417
1542
|
|
|
1418
1543
|
// src/commands/buy.ts
|
|
1419
1544
|
import { Command as Command3 } from "commander";
|
|
1420
1545
|
import confirm2 from "@inquirer/confirm";
|
|
1421
|
-
import { parseUnits, formatUnits as
|
|
1546
|
+
import { parseUnits, formatUnits as formatUnits4, isAddress } from "viem";
|
|
1422
1547
|
import {
|
|
1423
1548
|
setApiKey as setApiKey2,
|
|
1424
|
-
getCoin,
|
|
1549
|
+
getCoin as getCoin2,
|
|
1425
1550
|
tradeCoin,
|
|
1426
1551
|
createTradeCall
|
|
1427
1552
|
} from "@zoralabs/coins-sdk";
|
|
@@ -1429,7 +1554,7 @@ import {
|
|
|
1429
1554
|
// src/lib/trade-helpers.ts
|
|
1430
1555
|
import {
|
|
1431
1556
|
parseEther,
|
|
1432
|
-
formatUnits as
|
|
1557
|
+
formatUnits as formatUnits3,
|
|
1433
1558
|
isAddressEqual,
|
|
1434
1559
|
parseEventLogs,
|
|
1435
1560
|
erc20Abi as erc20Abi2
|
|
@@ -1462,25 +1587,6 @@ var parsePercentageLikeValue = (value) => {
|
|
|
1462
1587
|
const parsed = Number(value);
|
|
1463
1588
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1464
1589
|
};
|
|
1465
|
-
var formatAmountDisplay = (amount, decimals) => {
|
|
1466
|
-
const formatted = formatUnits2(amount, decimals);
|
|
1467
|
-
const parts = formatted.split(".");
|
|
1468
|
-
if (!parts[1]) {
|
|
1469
|
-
return new Intl.NumberFormat("en-US", {
|
|
1470
|
-
maximumFractionDigits: 2
|
|
1471
|
-
}).format(Number(formatted));
|
|
1472
|
-
}
|
|
1473
|
-
const twoDecimal = `${parts[0]}.${parts[1].slice(0, 2)}`;
|
|
1474
|
-
let maxDecimals = 2;
|
|
1475
|
-
if (Number(twoDecimal) === 0 && amount > 0n) {
|
|
1476
|
-
const sigIndex = parts[1].search(/[1-9]/);
|
|
1477
|
-
maxDecimals = sigIndex === -1 ? 6 : Math.min(sigIndex + 4, parts[1].length);
|
|
1478
|
-
}
|
|
1479
|
-
const truncated = `${parts[0]}.${parts[1].slice(0, maxDecimals)}`;
|
|
1480
|
-
return new Intl.NumberFormat("en-US", {
|
|
1481
|
-
maximumFractionDigits: maxDecimals
|
|
1482
|
-
}).format(Number(truncated));
|
|
1483
|
-
};
|
|
1484
1590
|
var getReceivedAmountFromReceipt = ({
|
|
1485
1591
|
receipt,
|
|
1486
1592
|
tokenAddress,
|
|
@@ -1546,12 +1652,12 @@ var printQuote = (json, info) => {
|
|
|
1546
1652
|
coin: info.coinSymbol,
|
|
1547
1653
|
address: info.address,
|
|
1548
1654
|
spend: {
|
|
1549
|
-
amount:
|
|
1655
|
+
amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
|
|
1550
1656
|
raw: info.amountIn.toString(),
|
|
1551
1657
|
symbol: info.inputTokenSymbol
|
|
1552
1658
|
},
|
|
1553
1659
|
estimated: {
|
|
1554
|
-
amount:
|
|
1660
|
+
amount: formatUnits3(BigInt(info.amountOut), 18),
|
|
1555
1661
|
raw: info.amountOut,
|
|
1556
1662
|
symbol: info.coinSymbol
|
|
1557
1663
|
},
|
|
@@ -1559,29 +1665,33 @@ var printQuote = (json, info) => {
|
|
|
1559
1665
|
});
|
|
1560
1666
|
return;
|
|
1561
1667
|
}
|
|
1668
|
+
const spendFormatted = formatAmountDisplay(
|
|
1669
|
+
info.amountIn,
|
|
1670
|
+
info.inputTokenDecimals
|
|
1671
|
+
);
|
|
1672
|
+
const coinsFormatted = formatAmountDisplay(BigInt(info.amountOut), 18);
|
|
1562
1673
|
console.log(`
|
|
1563
|
-
Buy ${info.coinName}
|
|
1674
|
+
Buy \x1B[1m${info.coinName}\x1B[0m`);
|
|
1675
|
+
console.log(` ${info.coinType} \xB7 ${info.address}
|
|
1564
1676
|
`);
|
|
1565
|
-
console.log(` Amount ${info.
|
|
1566
|
-
console.log(` You get ~${
|
|
1677
|
+
console.log(` Amount ${spendFormatted} ${info.inputTokenSymbol}`);
|
|
1678
|
+
console.log(` You get ~${coinsFormatted} ${info.coinSymbol}`);
|
|
1567
1679
|
console.log(` Slippage ${info.slippagePct}%
|
|
1568
1680
|
`);
|
|
1569
1681
|
};
|
|
1570
1682
|
var printTradeResult = (json, info) => {
|
|
1571
|
-
const receivedAmount = formatUnits2(info.receivedAmountOut, 18);
|
|
1572
|
-
const receivedFormatted = formatCoinsDisplay(receivedAmount);
|
|
1573
1683
|
if (json) {
|
|
1574
1684
|
outputJson({
|
|
1575
1685
|
action: "buy",
|
|
1576
1686
|
coin: info.coinSymbol,
|
|
1577
1687
|
address: info.address,
|
|
1578
1688
|
spent: {
|
|
1579
|
-
amount:
|
|
1689
|
+
amount: formatUnits3(info.amountIn, info.inputTokenDecimals),
|
|
1580
1690
|
raw: info.amountIn.toString(),
|
|
1581
1691
|
symbol: info.inputTokenSymbol
|
|
1582
1692
|
},
|
|
1583
1693
|
received: {
|
|
1584
|
-
amount:
|
|
1694
|
+
amount: formatUnits3(info.receivedAmountOut, 18),
|
|
1585
1695
|
raw: info.receivedAmountOut.toString(),
|
|
1586
1696
|
symbol: info.coinSymbol
|
|
1587
1697
|
},
|
|
@@ -1589,65 +1699,274 @@ var printTradeResult = (json, info) => {
|
|
|
1589
1699
|
});
|
|
1590
1700
|
return;
|
|
1591
1701
|
}
|
|
1702
|
+
const spentFormatted = formatAmountDisplay(
|
|
1703
|
+
info.amountIn,
|
|
1704
|
+
info.inputTokenDecimals
|
|
1705
|
+
);
|
|
1706
|
+
const receivedFormatted = formatAmountDisplay(info.receivedAmountOut, 18);
|
|
1592
1707
|
console.log(`
|
|
1593
|
-
Bought ${info.coinName}
|
|
1708
|
+
Bought \x1B[1m${info.coinName}\x1B[0m`);
|
|
1709
|
+
console.log(` ${info.coinType} \xB7 ${info.address}
|
|
1594
1710
|
`);
|
|
1595
|
-
console.log(` Spent ${
|
|
1711
|
+
console.log(` Spent ${spentFormatted} ${info.inputTokenSymbol}`);
|
|
1596
1712
|
console.log(` Received ${receivedFormatted} ${info.coinSymbol}`);
|
|
1597
1713
|
console.log(` Tx ${info.txHash}
|
|
1598
1714
|
`);
|
|
1599
1715
|
};
|
|
1600
1716
|
|
|
1717
|
+
// src/lib/coin-ref.ts
|
|
1718
|
+
import { getCoin, getProfile, getTrend } from "@zoralabs/coins-sdk";
|
|
1719
|
+
var TYPE_KEYWORDS = /* @__PURE__ */ new Set(["creator-coin", "trend"]);
|
|
1720
|
+
var CoinArgError = class extends Error {
|
|
1721
|
+
suggestion;
|
|
1722
|
+
constructor(message, suggestion) {
|
|
1723
|
+
super(message);
|
|
1724
|
+
this.suggestion = suggestion;
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
function parsePositionalCoinArgs(firstArg, secondArg) {
|
|
1728
|
+
if (TYPE_KEYWORDS.has(firstArg)) {
|
|
1729
|
+
if (!secondArg) {
|
|
1730
|
+
throw new CoinArgError(
|
|
1731
|
+
`Missing identifier after "${firstArg}".`,
|
|
1732
|
+
`Usage: zora <command> ${firstArg} <name>`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
return { kind: "typed", type: firstArg, identifier: secondArg };
|
|
1736
|
+
}
|
|
1737
|
+
if (firstArg.startsWith("0x")) {
|
|
1738
|
+
return { kind: "address", address: firstArg };
|
|
1739
|
+
}
|
|
1740
|
+
return { kind: "ambiguous-name", name: firstArg };
|
|
1741
|
+
}
|
|
1742
|
+
function coinArgsToRef(parsed) {
|
|
1743
|
+
switch (parsed.kind) {
|
|
1744
|
+
case "typed":
|
|
1745
|
+
return { kind: "prefixed", type: parsed.type, name: parsed.identifier };
|
|
1746
|
+
case "address":
|
|
1747
|
+
return { kind: "address", address: parsed.address };
|
|
1748
|
+
case "ambiguous-name":
|
|
1749
|
+
return { kind: "ambiguous", name: parsed.name };
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function resolveAmbiguousName(name) {
|
|
1753
|
+
const [creatorResult, trendResult] = await Promise.all([
|
|
1754
|
+
resolveByCreatorName(name),
|
|
1755
|
+
resolveByTrendTicker(name)
|
|
1756
|
+
]);
|
|
1757
|
+
const creatorFound = creatorResult.kind === "found" ? creatorResult.coin : null;
|
|
1758
|
+
const trendFound = trendResult.kind === "found" ? trendResult.coin : null;
|
|
1759
|
+
if (creatorFound && trendFound) {
|
|
1760
|
+
return { kind: "ambiguous", creator: creatorFound, trend: trendFound };
|
|
1761
|
+
}
|
|
1762
|
+
if (creatorFound) {
|
|
1763
|
+
return { kind: "found", coin: creatorFound };
|
|
1764
|
+
}
|
|
1765
|
+
if (trendFound) {
|
|
1766
|
+
return { kind: "found", coin: trendFound };
|
|
1767
|
+
}
|
|
1768
|
+
return {
|
|
1769
|
+
kind: "not-found",
|
|
1770
|
+
message: `No coin found matching "${name}".`
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
function formatAmbiguousError(name, creator, trend, command) {
|
|
1774
|
+
const creatorMcap = formatCompactUsd(creator.marketCap);
|
|
1775
|
+
const trendMcap = formatCompactUsd(trend.marketCap);
|
|
1776
|
+
return {
|
|
1777
|
+
message: [
|
|
1778
|
+
`Multiple coins match "${name}":`,
|
|
1779
|
+
` creator-coin ${creator.name} ${creatorMcap} mcap`,
|
|
1780
|
+
` trend ${trend.name} ${trendMcap} mcap`
|
|
1781
|
+
].join("\n"),
|
|
1782
|
+
suggestion: `Use: zora ${command} creator-coin ${name} or zora ${command} trend ${name}`
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
var COIN_TYPE_MAP = {
|
|
1786
|
+
CONTENT: "post",
|
|
1787
|
+
CREATOR: "creator-coin",
|
|
1788
|
+
TREND: "trend"
|
|
1789
|
+
};
|
|
1790
|
+
function mapCoinType(raw) {
|
|
1791
|
+
if (!raw) return "unknown";
|
|
1792
|
+
return COIN_TYPE_MAP[raw] ?? "unknown";
|
|
1793
|
+
}
|
|
1794
|
+
function coinFromToken(token) {
|
|
1795
|
+
return {
|
|
1796
|
+
name: token.name ?? "Unknown",
|
|
1797
|
+
address: token.address ?? "",
|
|
1798
|
+
coinType: mapCoinType(token.coinType),
|
|
1799
|
+
marketCap: token.marketCap ?? "0",
|
|
1800
|
+
marketCapDelta24h: token.marketCapDelta24h ?? "0",
|
|
1801
|
+
volume24h: token.volume24h ?? "0",
|
|
1802
|
+
uniqueHolders: token.uniqueHolders ?? 0,
|
|
1803
|
+
createdAt: token.createdAt,
|
|
1804
|
+
creatorAddress: token.creatorAddress,
|
|
1805
|
+
creatorHandle: token.creatorProfile?.handle,
|
|
1806
|
+
platformBlocked: token.platformBlocked ?? false
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
async function resolveByAddress(address) {
|
|
1810
|
+
const response = await getCoin({ address });
|
|
1811
|
+
if (response.error || !response.data?.zora20Token) {
|
|
1812
|
+
return {
|
|
1813
|
+
kind: "not-found",
|
|
1814
|
+
message: `No coin found at address ${address}`
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
return { kind: "found", coin: coinFromToken(response.data.zora20Token) };
|
|
1818
|
+
}
|
|
1819
|
+
async function resolveByTrendTicker(ticker) {
|
|
1820
|
+
const response = await getTrend({ ticker });
|
|
1821
|
+
if (response.error || !response.data?.trendCoin) {
|
|
1822
|
+
return {
|
|
1823
|
+
kind: "not-found",
|
|
1824
|
+
message: `No trend coin found with ticker "${ticker}"`
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
return { kind: "found", coin: coinFromToken(response.data.trendCoin) };
|
|
1828
|
+
}
|
|
1829
|
+
async function resolveByCreatorName(name) {
|
|
1830
|
+
const response = await getProfile({ identifier: name });
|
|
1831
|
+
if (response.error || !response.data?.profile) {
|
|
1832
|
+
return {
|
|
1833
|
+
kind: "not-found",
|
|
1834
|
+
message: `No creator found with name "${name}"`
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
const profile = response.data.profile;
|
|
1838
|
+
if (!profile.creatorCoin) {
|
|
1839
|
+
return {
|
|
1840
|
+
kind: "not-found",
|
|
1841
|
+
message: `"${name}" does not have a creator coin`
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
return resolveByAddress(profile.creatorCoin.address);
|
|
1845
|
+
}
|
|
1846
|
+
async function resolveCoin(ref) {
|
|
1847
|
+
switch (ref.kind) {
|
|
1848
|
+
case "address":
|
|
1849
|
+
return resolveByAddress(ref.address);
|
|
1850
|
+
case "prefixed":
|
|
1851
|
+
if (ref.type === "trend") {
|
|
1852
|
+
return resolveByTrendTicker(ref.name);
|
|
1853
|
+
}
|
|
1854
|
+
return resolveByCreatorName(ref.name);
|
|
1855
|
+
case "ambiguous":
|
|
1856
|
+
return resolveByCreatorName(ref.name);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1601
1860
|
// src/commands/buy.ts
|
|
1602
|
-
var buyCommand = new Command3("buy").description("Buy a coin").argument(
|
|
1861
|
+
var buyCommand = new Command3("buy").description("Buy a coin").argument(
|
|
1862
|
+
"[typeOrId]",
|
|
1863
|
+
"Type prefix (creator-coin, trend) or coin address/name"
|
|
1864
|
+
).argument("[identifier]", "Coin name (when type prefix is given)").option("--eth <value>", "Buy with ETH amount").option("--usd <value>", "Buy with USD equivalent (use with --token)").option("--token <asset>", "Token to spend: eth, usdc, zora", "eth").option("--percent <value>", "Buy with percentage of ETH balance").option("--all", "Swap all ETH for coin").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(typeOrId, identifier, opts) {
|
|
1603
1865
|
const json = getJson(this);
|
|
1604
1866
|
const debug = opts.debug === true;
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
const inputToken = BASE_TRADE_TOKENS[tokenKey];
|
|
1616
|
-
const amountMode = getAmountMode(
|
|
1617
|
-
json,
|
|
1618
|
-
opts,
|
|
1619
|
-
BUY_AMOUNT_CHECKS,
|
|
1620
|
-
"--eth, --usd, --percent, or --all"
|
|
1621
|
-
);
|
|
1622
|
-
const slippagePct = parsePercentageLikeValue(opts.slippage);
|
|
1623
|
-
if (slippagePct === void 0 || slippagePct < 0 || slippagePct > 99) {
|
|
1624
|
-
outputErrorAndExit(
|
|
1625
|
-
json,
|
|
1626
|
-
"Invalid --slippage value. Must be between 0 and 99."
|
|
1627
|
-
);
|
|
1867
|
+
let parsed;
|
|
1868
|
+
try {
|
|
1869
|
+
parsed = parsePositionalCoinArgs(typeOrId, identifier);
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
if (err instanceof CoinArgError) {
|
|
1872
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
1873
|
+
}
|
|
1874
|
+
throw err;
|
|
1628
1875
|
}
|
|
1629
|
-
const slippage = slippagePct / 100;
|
|
1630
1876
|
const apiKey = getApiKey();
|
|
1631
1877
|
if (apiKey) {
|
|
1632
1878
|
setApiKey2(apiKey);
|
|
1633
1879
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1880
|
+
let coinAddress;
|
|
1881
|
+
if (parsed.kind === "address") {
|
|
1882
|
+
if (!isAddress(parsed.address)) {
|
|
1883
|
+
outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
coinAddress = parsed.address;
|
|
1887
|
+
} else if (parsed.kind === "ambiguous-name") {
|
|
1888
|
+
let ambResult;
|
|
1889
|
+
try {
|
|
1890
|
+
ambResult = await resolveAmbiguousName(parsed.name);
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
outputErrorAndExit(
|
|
1893
|
+
json,
|
|
1894
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1895
|
+
);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
if (ambResult.kind === "not-found") {
|
|
1899
|
+
outputErrorAndExit(json, ambResult.message);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
if (ambResult.kind === "ambiguous") {
|
|
1903
|
+
const { message, suggestion } = formatAmbiguousError(
|
|
1904
|
+
parsed.name,
|
|
1905
|
+
ambResult.creator,
|
|
1906
|
+
ambResult.trend,
|
|
1907
|
+
"buy"
|
|
1908
|
+
);
|
|
1909
|
+
outputErrorAndExit(json, message, suggestion);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
coinAddress = ambResult.coin.address;
|
|
1913
|
+
} else {
|
|
1914
|
+
const ref = coinArgsToRef(parsed);
|
|
1915
|
+
try {
|
|
1916
|
+
const result = await resolveCoin(ref);
|
|
1917
|
+
if (result.kind === "not-found") {
|
|
1918
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
coinAddress = result.coin.address;
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
outputErrorAndExit(
|
|
1924
|
+
json,
|
|
1925
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1926
|
+
);
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const tokenKey = opts.token.toLowerCase();
|
|
1931
|
+
if (!(tokenKey in BASE_TRADE_TOKENS)) {
|
|
1932
|
+
outputErrorAndExit(
|
|
1933
|
+
json,
|
|
1934
|
+
`Invalid --token value: ${opts.token}. Use: eth, usdc, zora`
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
const inputToken = BASE_TRADE_TOKENS[tokenKey];
|
|
1938
|
+
const amountMode = getAmountMode(
|
|
1939
|
+
json,
|
|
1940
|
+
opts,
|
|
1941
|
+
BUY_AMOUNT_CHECKS,
|
|
1942
|
+
"--eth, --usd, --percent, or --all"
|
|
1943
|
+
);
|
|
1944
|
+
const slippagePct = parsePercentageLikeValue(opts.slippage);
|
|
1945
|
+
if (slippagePct === void 0 || slippagePct < 0 || slippagePct > 99) {
|
|
1641
1946
|
outputErrorAndExit(
|
|
1642
1947
|
json,
|
|
1643
|
-
|
|
1948
|
+
"Invalid --slippage value. Must be between 0 and 99."
|
|
1644
1949
|
);
|
|
1645
1950
|
}
|
|
1951
|
+
const slippage = slippagePct / 100;
|
|
1952
|
+
const account = resolveAccount(json);
|
|
1953
|
+
const { publicClient, walletClient } = createClients(account);
|
|
1954
|
+
let token;
|
|
1955
|
+
try {
|
|
1956
|
+
const response = await getCoin2({ address: coinAddress });
|
|
1957
|
+
token = response.data?.zora20Token;
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
|
|
1960
|
+
}
|
|
1646
1961
|
if (!token) {
|
|
1647
1962
|
outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
|
|
1648
1963
|
}
|
|
1964
|
+
if (token.platformBlocked) {
|
|
1965
|
+
outputErrorAndExit(json, bannedCoinBuyMessage(coinAddress));
|
|
1966
|
+
}
|
|
1649
1967
|
const coinName = token.name;
|
|
1650
1968
|
const coinSymbol = token.symbol;
|
|
1969
|
+
const coinType = mapCoinType(token.coinType);
|
|
1651
1970
|
let amountIn;
|
|
1652
1971
|
if (amountMode === "usd") {
|
|
1653
1972
|
const usdVal = parsePercentageLikeValue(opts.usd);
|
|
@@ -1682,7 +2001,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("<addres
|
|
|
1682
2001
|
}
|
|
1683
2002
|
if (debug) {
|
|
1684
2003
|
console.error(
|
|
1685
|
-
`[debug] $${usdVal} USD = ${
|
|
2004
|
+
`[debug] $${usdVal} USD = ${formatUnits4(amountIn, inputToken.decimals)} ${inputToken.symbol} (price: $${priceUsd})`
|
|
1686
2005
|
);
|
|
1687
2006
|
}
|
|
1688
2007
|
} else if (amountMode === "eth") {
|
|
@@ -1735,7 +2054,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("<addres
|
|
|
1735
2054
|
if (isEth && balance <= gasReserve) {
|
|
1736
2055
|
outputErrorAndExit(
|
|
1737
2056
|
json,
|
|
1738
|
-
`Balance too low (${
|
|
2057
|
+
`Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
|
|
1739
2058
|
);
|
|
1740
2059
|
}
|
|
1741
2060
|
const spendableBalance = balance - gasReserve;
|
|
@@ -1765,7 +2084,7 @@ var buyCommand = new Command3("buy").description("Buy a coin").argument("<addres
|
|
|
1765
2084
|
const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
|
|
1766
2085
|
if (priceUsd != null) {
|
|
1767
2086
|
swapAmountUsd = Number(
|
|
1768
|
-
(Number(
|
|
2087
|
+
(Number(formatUnits4(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
|
|
1769
2088
|
);
|
|
1770
2089
|
}
|
|
1771
2090
|
}
|
|
@@ -1816,29 +2135,23 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1816
2135
|
}
|
|
1817
2136
|
outputErrorAndExit(
|
|
1818
2137
|
json,
|
|
1819
|
-
`Quote failed: ${
|
|
2138
|
+
`Quote failed: ${apiErrorMessage(err)}`,
|
|
1820
2139
|
"Check the coin address is valid and try again. Use --debug for full error details."
|
|
1821
2140
|
);
|
|
1822
2141
|
}
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2142
|
+
const quoteInfo = {
|
|
2143
|
+
coinName,
|
|
2144
|
+
coinSymbol,
|
|
2145
|
+
coinType,
|
|
2146
|
+
address: coinAddress,
|
|
2147
|
+
amountIn,
|
|
2148
|
+
inputTokenSymbol: inputToken.symbol,
|
|
2149
|
+
inputTokenDecimals: inputToken.decimals,
|
|
2150
|
+
amountOut,
|
|
2151
|
+
slippagePct
|
|
2152
|
+
};
|
|
1829
2153
|
if (opts.quote) {
|
|
1830
|
-
printQuote(json,
|
|
1831
|
-
coinName,
|
|
1832
|
-
coinSymbol,
|
|
1833
|
-
address: coinAddress,
|
|
1834
|
-
spendAmount: `${spendFormatted} ${inputToken.symbol}`,
|
|
1835
|
-
amountIn,
|
|
1836
|
-
inputTokenSymbol: inputToken.symbol,
|
|
1837
|
-
inputTokenDecimals: inputToken.decimals,
|
|
1838
|
-
coinsFormatted,
|
|
1839
|
-
amountOut,
|
|
1840
|
-
slippagePct
|
|
1841
|
-
});
|
|
2154
|
+
printQuote(json, quoteInfo);
|
|
1842
2155
|
track("cli_buy", {
|
|
1843
2156
|
action: "quote",
|
|
1844
2157
|
coin_address: coinAddress,
|
|
@@ -1849,23 +2162,12 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1849
2162
|
valueUsd: swapAmountUsd,
|
|
1850
2163
|
swapCoinType: token.coinType ?? null,
|
|
1851
2164
|
slippage: slippagePct,
|
|
1852
|
-
output_format: json ? "json" : "
|
|
2165
|
+
output_format: json ? "json" : "static"
|
|
1853
2166
|
});
|
|
1854
2167
|
return;
|
|
1855
2168
|
}
|
|
1856
2169
|
if (!opts.yes) {
|
|
1857
|
-
printQuote(false,
|
|
1858
|
-
coinName,
|
|
1859
|
-
coinSymbol,
|
|
1860
|
-
address: coinAddress,
|
|
1861
|
-
spendAmount: `${spendFormatted} ${inputToken.symbol}`,
|
|
1862
|
-
amountIn,
|
|
1863
|
-
inputTokenSymbol: inputToken.symbol,
|
|
1864
|
-
inputTokenDecimals: inputToken.decimals,
|
|
1865
|
-
coinsFormatted,
|
|
1866
|
-
amountOut,
|
|
1867
|
-
slippagePct
|
|
1868
|
-
});
|
|
2170
|
+
printQuote(false, quoteInfo);
|
|
1869
2171
|
const ok = await confirm2({
|
|
1870
2172
|
message: "Confirm?",
|
|
1871
2173
|
default: false
|
|
@@ -1897,15 +2199,12 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1897
2199
|
valueUsd: swapAmountUsd,
|
|
1898
2200
|
swapCoinType,
|
|
1899
2201
|
slippage: slippagePct,
|
|
1900
|
-
output_format: json ? "json" : "
|
|
2202
|
+
output_format: json ? "json" : "static",
|
|
1901
2203
|
success: false,
|
|
1902
2204
|
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
1903
2205
|
});
|
|
1904
2206
|
await shutdownAnalytics();
|
|
1905
|
-
outputErrorAndExit(
|
|
1906
|
-
json,
|
|
1907
|
-
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1908
|
-
);
|
|
2207
|
+
outputErrorAndExit(json, tradeErrorMessage(err));
|
|
1909
2208
|
}
|
|
1910
2209
|
txHash = receipt.transactionHash;
|
|
1911
2210
|
try {
|
|
@@ -1925,8 +2224,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1925
2224
|
printTradeResult(json, {
|
|
1926
2225
|
coinName,
|
|
1927
2226
|
coinSymbol,
|
|
2227
|
+
coinType,
|
|
1928
2228
|
address: coinAddress,
|
|
1929
|
-
spendAmount,
|
|
1930
2229
|
amountIn,
|
|
1931
2230
|
inputTokenSymbol: inputToken.symbol,
|
|
1932
2231
|
inputTokenDecimals: inputToken.decimals,
|
|
@@ -1947,7 +2246,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1947
2246
|
transactionHash: txHash,
|
|
1948
2247
|
logIndex: swapLogIndex,
|
|
1949
2248
|
slippage: slippagePct,
|
|
1950
|
-
output_format: json ? "json" : "
|
|
2249
|
+
output_format: json ? "json" : "static",
|
|
1951
2250
|
success: true,
|
|
1952
2251
|
tx_hash: txHash
|
|
1953
2252
|
});
|
|
@@ -1960,9 +2259,6 @@ import {
|
|
|
1960
2259
|
getCoinsTopVolume24h,
|
|
1961
2260
|
getCoinsMostValuable,
|
|
1962
2261
|
getCoinsNew,
|
|
1963
|
-
getCoinsTopGainers,
|
|
1964
|
-
getCoinsLastTraded,
|
|
1965
|
-
getCoinsLastTradedUnique,
|
|
1966
2262
|
getExploreTopVolumeAll24h,
|
|
1967
2263
|
getExploreTopVolumeCreators24h,
|
|
1968
2264
|
getExploreNewAll,
|
|
@@ -1985,9 +2281,6 @@ var SORT_LABELS2 = {
|
|
|
1985
2281
|
mcap: "Top by Market Cap",
|
|
1986
2282
|
volume: "Top by 24h Volume",
|
|
1987
2283
|
new: "New",
|
|
1988
|
-
gainers: "Top Gainers (24h)",
|
|
1989
|
-
"last-traded": "Last Traded",
|
|
1990
|
-
"last-traded-unique": "Last Traded (Unique)",
|
|
1991
2284
|
trending: "Trending",
|
|
1992
2285
|
featured: "Featured"
|
|
1993
2286
|
};
|
|
@@ -2007,11 +2300,47 @@ var COIN_TYPE_DISPLAY = {
|
|
|
2007
2300
|
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
2008
2301
|
import { Box as Box4, Text as Text4, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
2009
2302
|
import Spinner2 from "ink-spinner";
|
|
2303
|
+
|
|
2304
|
+
// src/lib/clipboard.ts
|
|
2305
|
+
import { execFileSync } from "child_process";
|
|
2306
|
+
import { platform as platform2 } from "os";
|
|
2307
|
+
var copyToClipboard = (text) => {
|
|
2308
|
+
const os = platform2();
|
|
2309
|
+
try {
|
|
2310
|
+
if (os === "darwin") {
|
|
2311
|
+
execFileSync("pbcopy", {
|
|
2312
|
+
input: text,
|
|
2313
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2314
|
+
});
|
|
2315
|
+
} else if (os === "linux") {
|
|
2316
|
+
execFileSync("xclip", ["-selection", "clipboard"], {
|
|
2317
|
+
input: text,
|
|
2318
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2319
|
+
});
|
|
2320
|
+
} else if (os === "win32") {
|
|
2321
|
+
execFileSync("clip", {
|
|
2322
|
+
input: text,
|
|
2323
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2324
|
+
});
|
|
2325
|
+
} else {
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
return true;
|
|
2329
|
+
} catch {
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
|
|
2334
|
+
// src/components/ExploreView.tsx
|
|
2010
2335
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2011
2336
|
var COLUMNS = [
|
|
2012
2337
|
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2013
2338
|
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
2014
|
-
{
|
|
2339
|
+
{
|
|
2340
|
+
header: "Address",
|
|
2341
|
+
width: 14,
|
|
2342
|
+
accessor: (c) => c.address ? truncateAddress(c.address) : ""
|
|
2343
|
+
},
|
|
2015
2344
|
{
|
|
2016
2345
|
header: "Type",
|
|
2017
2346
|
width: 14,
|
|
@@ -2061,6 +2390,11 @@ var ExploreView = ({
|
|
|
2061
2390
|
const cache = useRef2(/* @__PURE__ */ new Map());
|
|
2062
2391
|
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
2063
2392
|
const [manualRefreshCount, setManualRefreshCount] = useState3(0);
|
|
2393
|
+
const [selectedRow, setSelectedRow] = useState3(0);
|
|
2394
|
+
const [copyFeedback, setCopyFeedback] = useState3(null);
|
|
2395
|
+
useEffect3(() => {
|
|
2396
|
+
setSelectedRow((r) => Math.min(r, Math.max(0, coins.length - 1)));
|
|
2397
|
+
}, [coins.length]);
|
|
2064
2398
|
const loadPage = useCallback3(
|
|
2065
2399
|
async (cursor) => {
|
|
2066
2400
|
const cacheKey = cursor ?? CACHE_KEY_FIRST;
|
|
@@ -2101,24 +2435,44 @@ var ExploreView = ({
|
|
|
2101
2435
|
return;
|
|
2102
2436
|
}
|
|
2103
2437
|
if (loading) return;
|
|
2438
|
+
if (key.upArrow || input === "k") {
|
|
2439
|
+
setSelectedRow((r) => Math.max(0, r - 1));
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
if (key.downArrow || input === "j") {
|
|
2443
|
+
setSelectedRow((r) => Math.min(coins.length - 1, r + 1));
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
if (input === "c") {
|
|
2447
|
+
const coin = coins[selectedRow];
|
|
2448
|
+
if (coin?.address) {
|
|
2449
|
+
const ok = copyToClipboard(coin.address);
|
|
2450
|
+
setCopyFeedback(ok ? "Copied!" : "Copy failed");
|
|
2451
|
+
setTimeout(() => setCopyFeedback(null), 1500);
|
|
2452
|
+
}
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2104
2455
|
const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
|
|
2105
2456
|
const canGoPrev = cursorHistory.length > 0;
|
|
2106
2457
|
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
2107
2458
|
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
2108
2459
|
setCurrentCursor(pageInfo.endCursor);
|
|
2109
2460
|
setPage((p) => p + 1);
|
|
2461
|
+
setSelectedRow(0);
|
|
2110
2462
|
}
|
|
2111
2463
|
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
2112
2464
|
const prev = cursorHistory[cursorHistory.length - 1];
|
|
2113
2465
|
setCursorHistory((h) => h.slice(0, -1));
|
|
2114
2466
|
setCurrentCursor(prev);
|
|
2115
2467
|
setPage((p) => p - 1);
|
|
2468
|
+
setSelectedRow(0);
|
|
2116
2469
|
}
|
|
2117
2470
|
if (input === "r") {
|
|
2118
2471
|
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2119
2472
|
cache.current.delete(cacheKey);
|
|
2120
2473
|
triggerManualRefresh();
|
|
2121
2474
|
setManualRefreshCount((c) => c + 1);
|
|
2475
|
+
setSelectedRow(0);
|
|
2122
2476
|
}
|
|
2123
2477
|
});
|
|
2124
2478
|
if (error) {
|
|
@@ -2172,12 +2526,13 @@ var ExploreView = ({
|
|
|
2172
2526
|
rank: (page - 1) * limit + i + 1
|
|
2173
2527
|
}));
|
|
2174
2528
|
const hints = [];
|
|
2529
|
+
hints.push("\u2191\u2193 select");
|
|
2530
|
+
hints.push("c copy address");
|
|
2175
2531
|
if (cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
2176
2532
|
if (pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
2177
|
-
hints.push("r refresh");
|
|
2178
|
-
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
2533
|
+
hints.push(autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh");
|
|
2179
2534
|
hints.push("q quit");
|
|
2180
|
-
const footer = hints.join(" \xB7 ");
|
|
2535
|
+
const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
|
|
2181
2536
|
return /* @__PURE__ */ jsx4(
|
|
2182
2537
|
Table,
|
|
2183
2538
|
{
|
|
@@ -2185,7 +2540,8 @@ var ExploreView = ({
|
|
|
2185
2540
|
columns: COLUMNS,
|
|
2186
2541
|
title,
|
|
2187
2542
|
subtitle,
|
|
2188
|
-
footer
|
|
2543
|
+
footer,
|
|
2544
|
+
selectedRow
|
|
2189
2545
|
}
|
|
2190
2546
|
);
|
|
2191
2547
|
};
|
|
@@ -2211,15 +2567,6 @@ var QUERY_MAP = {
|
|
|
2211
2567
|
"creator-coin": getCreatorCoins,
|
|
2212
2568
|
post: getCoinsNew
|
|
2213
2569
|
},
|
|
2214
|
-
gainers: {
|
|
2215
|
-
post: getCoinsTopGainers
|
|
2216
|
-
},
|
|
2217
|
-
"last-traded": {
|
|
2218
|
-
post: getCoinsLastTraded
|
|
2219
|
-
},
|
|
2220
|
-
"last-traded-unique": {
|
|
2221
|
-
post: getCoinsLastTradedUnique
|
|
2222
|
-
},
|
|
2223
2570
|
trending: {
|
|
2224
2571
|
all: getTrendingAll,
|
|
2225
2572
|
trend: getTrendingTrends,
|
|
@@ -2231,12 +2578,42 @@ var QUERY_MAP = {
|
|
|
2231
2578
|
post: getExploreFeaturedVideos
|
|
2232
2579
|
}
|
|
2233
2580
|
};
|
|
2581
|
+
var STATIC_COLUMNS = [
|
|
2582
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2583
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
2584
|
+
{ header: "Address", width: 48, accessor: (c) => c.address ?? "" },
|
|
2585
|
+
{
|
|
2586
|
+
header: "Type",
|
|
2587
|
+
width: 14,
|
|
2588
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
header: "Market Cap",
|
|
2592
|
+
width: 12,
|
|
2593
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
2594
|
+
},
|
|
2595
|
+
{
|
|
2596
|
+
header: "24h Vol",
|
|
2597
|
+
width: 12,
|
|
2598
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
header: "24h Change",
|
|
2602
|
+
width: 11,
|
|
2603
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
2604
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2605
|
+
}
|
|
2606
|
+
];
|
|
2234
2607
|
var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
|
|
2235
2608
|
var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
|
|
2236
2609
|
"--type <type>",
|
|
2237
2610
|
"Filter by type: all, trend, creator-coin, post (availability varies by sort)",
|
|
2238
|
-
"
|
|
2239
|
-
).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").
|
|
2611
|
+
"creator-coin"
|
|
2612
|
+
).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
2613
|
+
"--refresh <seconds>",
|
|
2614
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
2615
|
+
"30"
|
|
2616
|
+
).action(async function(opts) {
|
|
2240
2617
|
const output = getOutputMode(this, "live");
|
|
2241
2618
|
const json = output === "json";
|
|
2242
2619
|
const sort = opts.sort;
|
|
@@ -2275,10 +2652,7 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2275
2652
|
try {
|
|
2276
2653
|
response = await queryFn({ count: limit, after });
|
|
2277
2654
|
} catch (err) {
|
|
2278
|
-
outputErrorAndExit(
|
|
2279
|
-
json,
|
|
2280
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2281
|
-
);
|
|
2655
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
2282
2656
|
}
|
|
2283
2657
|
if (response.error) {
|
|
2284
2658
|
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
@@ -2298,7 +2672,7 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2298
2672
|
output_format: "json"
|
|
2299
2673
|
});
|
|
2300
2674
|
} else {
|
|
2301
|
-
const { live, intervalSeconds } = getLiveConfig(this,
|
|
2675
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
2302
2676
|
const fetchPage = async (cursor) => {
|
|
2303
2677
|
const response = await queryFn({ count: limit, after: cursor });
|
|
2304
2678
|
if (response.error) {
|
|
@@ -2310,29 +2684,55 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2310
2684
|
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2311
2685
|
return { coins, pageInfo };
|
|
2312
2686
|
};
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2687
|
+
if (live) {
|
|
2688
|
+
await renderLive(
|
|
2689
|
+
/* @__PURE__ */ jsx5(
|
|
2690
|
+
ExploreView,
|
|
2691
|
+
{
|
|
2692
|
+
fetchPage,
|
|
2693
|
+
sort,
|
|
2694
|
+
type,
|
|
2695
|
+
limit,
|
|
2696
|
+
initialCursor: after,
|
|
2697
|
+
autoRefresh: live,
|
|
2698
|
+
intervalSeconds
|
|
2699
|
+
}
|
|
2700
|
+
)
|
|
2701
|
+
);
|
|
2702
|
+
track("cli_explore", {
|
|
2703
|
+
sort,
|
|
2704
|
+
type,
|
|
2705
|
+
limit,
|
|
2706
|
+
live,
|
|
2707
|
+
interval: intervalSeconds,
|
|
2708
|
+
paginated: after !== void 0,
|
|
2709
|
+
output_format: "live"
|
|
2710
|
+
});
|
|
2711
|
+
} else {
|
|
2712
|
+
const { coins } = await fetchPage(after).catch(
|
|
2713
|
+
(err) => outputErrorAndExit(
|
|
2714
|
+
false,
|
|
2715
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2716
|
+
)
|
|
2717
|
+
);
|
|
2718
|
+
const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
|
|
2719
|
+
const rankedCoins = coins.map((c, i) => ({
|
|
2720
|
+
...c,
|
|
2721
|
+
rank: i + 1
|
|
2722
|
+
}));
|
|
2723
|
+
renderOnce(
|
|
2724
|
+
/* @__PURE__ */ jsx5(Table, { columns: STATIC_COLUMNS, data: rankedCoins, title })
|
|
2725
|
+
);
|
|
2726
|
+
track("cli_explore", {
|
|
2727
|
+
sort,
|
|
2728
|
+
type,
|
|
2729
|
+
limit,
|
|
2730
|
+
live: false,
|
|
2731
|
+
paginated: after !== void 0,
|
|
2732
|
+
result_count: coins.length,
|
|
2733
|
+
output_format: "static"
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2336
2736
|
}
|
|
2337
2737
|
});
|
|
2338
2738
|
|
|
@@ -2340,94 +2740,6 @@ var exploreCommand = new Command4("explore").description("Browse top, new, and h
|
|
|
2340
2740
|
import { Command as Command5 } from "commander";
|
|
2341
2741
|
import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
|
|
2342
2742
|
|
|
2343
|
-
// src/lib/coin-ref.ts
|
|
2344
|
-
import { getCoin as getCoin2, getProfile, getTrend } from "@zoralabs/coins-sdk";
|
|
2345
|
-
var COIN_TYPE_MAP = {
|
|
2346
|
-
CONTENT: "post",
|
|
2347
|
-
CREATOR: "creator-coin",
|
|
2348
|
-
TREND: "trend"
|
|
2349
|
-
};
|
|
2350
|
-
function mapCoinType(raw) {
|
|
2351
|
-
if (!raw) return "unknown";
|
|
2352
|
-
return COIN_TYPE_MAP[raw] ?? "unknown";
|
|
2353
|
-
}
|
|
2354
|
-
function coinFromToken(token) {
|
|
2355
|
-
return {
|
|
2356
|
-
name: token.name ?? "Unknown",
|
|
2357
|
-
address: token.address ?? "",
|
|
2358
|
-
coinType: mapCoinType(token.coinType),
|
|
2359
|
-
marketCap: token.marketCap ?? "0",
|
|
2360
|
-
marketCapDelta24h: token.marketCapDelta24h ?? "0",
|
|
2361
|
-
volume24h: token.volume24h ?? "0",
|
|
2362
|
-
uniqueHolders: token.uniqueHolders ?? 0,
|
|
2363
|
-
createdAt: token.createdAt,
|
|
2364
|
-
creatorAddress: token.creatorAddress,
|
|
2365
|
-
creatorHandle: token.creatorProfile?.handle
|
|
2366
|
-
};
|
|
2367
|
-
}
|
|
2368
|
-
function parseCoinRef(identifier, type) {
|
|
2369
|
-
if (identifier.startsWith("0x")) {
|
|
2370
|
-
return { kind: "address", address: identifier };
|
|
2371
|
-
}
|
|
2372
|
-
if (type === "creator-coin") {
|
|
2373
|
-
return { kind: "prefixed", type: "creator-coin", name: identifier };
|
|
2374
|
-
}
|
|
2375
|
-
if (type === "trend") {
|
|
2376
|
-
return { kind: "prefixed", type: "trend", name: identifier };
|
|
2377
|
-
}
|
|
2378
|
-
return { kind: "ambiguous", name: identifier };
|
|
2379
|
-
}
|
|
2380
|
-
async function resolveByAddress(address) {
|
|
2381
|
-
const response = await getCoin2({ address });
|
|
2382
|
-
if (response.error || !response.data?.zora20Token) {
|
|
2383
|
-
return {
|
|
2384
|
-
kind: "not-found",
|
|
2385
|
-
message: `No coin found at address ${address}`
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
return { kind: "found", coin: coinFromToken(response.data.zora20Token) };
|
|
2389
|
-
}
|
|
2390
|
-
async function resolveByTrendTicker(ticker) {
|
|
2391
|
-
const response = await getTrend({ ticker });
|
|
2392
|
-
if (response.error || !response.data?.trendCoin) {
|
|
2393
|
-
return {
|
|
2394
|
-
kind: "not-found",
|
|
2395
|
-
message: `No trend coin found with ticker "${ticker}"`
|
|
2396
|
-
};
|
|
2397
|
-
}
|
|
2398
|
-
return { kind: "found", coin: coinFromToken(response.data.trendCoin) };
|
|
2399
|
-
}
|
|
2400
|
-
async function resolveByCreatorName(name) {
|
|
2401
|
-
const response = await getProfile({ identifier: name });
|
|
2402
|
-
if (response.error || !response.data?.profile) {
|
|
2403
|
-
return {
|
|
2404
|
-
kind: "not-found",
|
|
2405
|
-
message: `No creator found with name "${name}"`
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
const profile = response.data.profile;
|
|
2409
|
-
if (!profile.creatorCoin) {
|
|
2410
|
-
return {
|
|
2411
|
-
kind: "not-found",
|
|
2412
|
-
message: `"${name}" does not have a creator coin`
|
|
2413
|
-
};
|
|
2414
|
-
}
|
|
2415
|
-
return resolveByAddress(profile.creatorCoin.address);
|
|
2416
|
-
}
|
|
2417
|
-
async function resolveCoin(ref) {
|
|
2418
|
-
switch (ref.kind) {
|
|
2419
|
-
case "address":
|
|
2420
|
-
return resolveByAddress(ref.address);
|
|
2421
|
-
case "prefixed":
|
|
2422
|
-
if (ref.type === "trend") {
|
|
2423
|
-
return resolveByTrendTicker(ref.name);
|
|
2424
|
-
}
|
|
2425
|
-
return resolveByCreatorName(ref.name);
|
|
2426
|
-
case "ambiguous":
|
|
2427
|
-
return resolveByCreatorName(ref.name);
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
2743
|
// src/components/CoinDetail.tsx
|
|
2432
2744
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
2433
2745
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
@@ -2482,62 +2794,108 @@ function formatCoinJson(coin) {
|
|
|
2482
2794
|
creatorHandle: coin.creatorHandle ?? null
|
|
2483
2795
|
};
|
|
2484
2796
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2797
|
+
function outputCoin(json, coin) {
|
|
2798
|
+
outputData(json, {
|
|
2799
|
+
json: formatCoinJson(coin),
|
|
2800
|
+
render: () => {
|
|
2801
|
+
renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin }));
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
var getCommand = new Command5("get").description("Look up a coin by address or name").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
2806
|
+
"[identifier]",
|
|
2807
|
+
"Coin address (0x...) or name (when type prefix is given)"
|
|
2808
|
+
).action(async function(typeOrId, identifier) {
|
|
2487
2809
|
const json = getJson(this);
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2810
|
+
let parsed;
|
|
2811
|
+
try {
|
|
2812
|
+
parsed = parsePositionalCoinArgs(typeOrId, identifier);
|
|
2813
|
+
} catch (err) {
|
|
2814
|
+
if (err instanceof CoinArgError) {
|
|
2815
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
2816
|
+
}
|
|
2817
|
+
throw err;
|
|
2494
2818
|
}
|
|
2495
|
-
const type = opts.type;
|
|
2496
|
-
if (type === "post" && !identifier.startsWith("0x")) {
|
|
2497
|
-
outputErrorAndExit(
|
|
2498
|
-
json,
|
|
2499
|
-
"Posts can only be looked up by address.",
|
|
2500
|
-
"Use: zora get 0x..."
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
const ref = parseCoinRef(identifier, opts.type);
|
|
2504
2819
|
const apiKey = getApiKey();
|
|
2505
2820
|
if (apiKey) {
|
|
2506
2821
|
setApiKey4(apiKey);
|
|
2507
2822
|
}
|
|
2823
|
+
if (parsed.kind === "ambiguous-name") {
|
|
2824
|
+
let ambResult;
|
|
2825
|
+
try {
|
|
2826
|
+
ambResult = await resolveAmbiguousName(parsed.name);
|
|
2827
|
+
} catch (err) {
|
|
2828
|
+
outputErrorAndExit(
|
|
2829
|
+
json,
|
|
2830
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2831
|
+
);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (ambResult.kind === "not-found") {
|
|
2835
|
+
outputErrorAndExit(json, ambResult.message);
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
if (ambResult.kind === "ambiguous") {
|
|
2839
|
+
if (json) {
|
|
2840
|
+
outputData(json, {
|
|
2841
|
+
json: {
|
|
2842
|
+
matches: [
|
|
2843
|
+
{ type: "creator-coin", ...formatCoinJson(ambResult.creator) },
|
|
2844
|
+
{ type: "trend", ...formatCoinJson(ambResult.trend) }
|
|
2845
|
+
],
|
|
2846
|
+
hint: `Use: zora get creator-coin ${parsed.name} or zora get trend ${parsed.name}`
|
|
2847
|
+
},
|
|
2848
|
+
render: () => {
|
|
2849
|
+
}
|
|
2850
|
+
});
|
|
2851
|
+
} else {
|
|
2852
|
+
outputCoin(false, ambResult.creator);
|
|
2853
|
+
console.log("");
|
|
2854
|
+
outputCoin(false, ambResult.trend);
|
|
2855
|
+
console.log(
|
|
2856
|
+
`
|
|
2857
|
+
\x1B[2mUse \`zora get creator-coin ${parsed.name}\` or \`zora get trend ${parsed.name}\` for a specific type.\x1B[0m`
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
track("cli_get", {
|
|
2861
|
+
lookup_type: "name",
|
|
2862
|
+
found: true,
|
|
2863
|
+
ambiguous: true,
|
|
2864
|
+
output_format: json ? "json" : "text"
|
|
2865
|
+
});
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
outputCoin(json, ambResult.coin);
|
|
2869
|
+
track("cli_get", {
|
|
2870
|
+
lookup_type: "name",
|
|
2871
|
+
found: true,
|
|
2872
|
+
coin_type: ambResult.coin.coinType,
|
|
2873
|
+
output_format: json ? "json" : "text"
|
|
2874
|
+
});
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
const ref = coinArgsToRef(parsed);
|
|
2508
2878
|
let result;
|
|
2509
2879
|
try {
|
|
2510
2880
|
result = await resolveCoin(ref);
|
|
2511
2881
|
} catch (err) {
|
|
2512
|
-
outputErrorAndExit(
|
|
2513
|
-
json,
|
|
2514
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2515
|
-
);
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
if (type && result.kind === "found" && result.coin.coinType !== type) {
|
|
2519
|
-
outputErrorAndExit(
|
|
2520
|
-
json,
|
|
2521
|
-
`Coin at ${result.coin.address} is a ${result.coin.coinType}, not a ${type}.`,
|
|
2522
|
-
`Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
|
|
2523
|
-
);
|
|
2882
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
2524
2883
|
return;
|
|
2525
2884
|
}
|
|
2526
2885
|
if (result.kind === "not-found") {
|
|
2527
2886
|
outputErrorAndExit(json, result.message);
|
|
2528
2887
|
return;
|
|
2529
2888
|
}
|
|
2530
|
-
|
|
2531
|
-
json
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
});
|
|
2889
|
+
if (result.coin.platformBlocked) {
|
|
2890
|
+
outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
outputCoin(json, result.coin);
|
|
2536
2894
|
track("cli_get", {
|
|
2537
|
-
lookup_type:
|
|
2538
|
-
coin_type_filter: type
|
|
2539
|
-
found:
|
|
2540
|
-
coin_type: result.
|
|
2895
|
+
lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
|
|
2896
|
+
coin_type_filter: parsed.kind === "typed" ? parsed.type : null,
|
|
2897
|
+
found: true,
|
|
2898
|
+
coin_type: result.coin.coinType,
|
|
2541
2899
|
output_format: json ? "json" : "text"
|
|
2542
2900
|
});
|
|
2543
2901
|
});
|
|
@@ -2610,7 +2968,6 @@ var downsample = (values, maxWidth) => {
|
|
|
2610
2968
|
|
|
2611
2969
|
// src/commands/price-history.tsx
|
|
2612
2970
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2613
|
-
var VALID_TYPES2 = ["creator-coin", "post", "trend"];
|
|
2614
2971
|
var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
|
|
2615
2972
|
var INTERVAL_TO_API_FIELD = {
|
|
2616
2973
|
"1h": "oneHour",
|
|
@@ -2651,54 +3008,7 @@ var fetchPriceHistory = async (address, interval) => {
|
|
|
2651
3008
|
price: Number(p.closePrice)
|
|
2652
3009
|
}));
|
|
2653
3010
|
};
|
|
2654
|
-
|
|
2655
|
-
"--interval <interval>",
|
|
2656
|
-
`Time range: ${VALID_INTERVALS.join(", ")}`,
|
|
2657
|
-
"1w"
|
|
2658
|
-
).action(async function(identifier, opts) {
|
|
2659
|
-
const json = getJson(this);
|
|
2660
|
-
const interval = opts.interval ?? "1w";
|
|
2661
|
-
if (!VALID_INTERVALS.includes(interval)) {
|
|
2662
|
-
outputErrorAndExit(
|
|
2663
|
-
json,
|
|
2664
|
-
`Invalid --interval value: ${interval}.`,
|
|
2665
|
-
`Supported: ${VALID_INTERVALS.join(", ")}`
|
|
2666
|
-
);
|
|
2667
|
-
}
|
|
2668
|
-
if (opts.type !== void 0 && !VALID_TYPES2.includes(opts.type)) {
|
|
2669
|
-
outputErrorAndExit(
|
|
2670
|
-
json,
|
|
2671
|
-
`Invalid --type value: ${opts.type}.`,
|
|
2672
|
-
`Supported: ${VALID_TYPES2.join(", ")}`
|
|
2673
|
-
);
|
|
2674
|
-
}
|
|
2675
|
-
if (opts.type === "post" && !identifier.startsWith("0x")) {
|
|
2676
|
-
outputErrorAndExit(
|
|
2677
|
-
json,
|
|
2678
|
-
"Posts can only be looked up by address.",
|
|
2679
|
-
"Use: zora price-history 0x..."
|
|
2680
|
-
);
|
|
2681
|
-
}
|
|
2682
|
-
const ref = parseCoinRef(identifier, opts.type);
|
|
2683
|
-
const apiKey = getApiKey();
|
|
2684
|
-
if (apiKey) {
|
|
2685
|
-
setApiKey5(apiKey);
|
|
2686
|
-
}
|
|
2687
|
-
let result;
|
|
2688
|
-
try {
|
|
2689
|
-
result = await resolveCoin(ref);
|
|
2690
|
-
} catch (err) {
|
|
2691
|
-
outputErrorAndExit(
|
|
2692
|
-
json,
|
|
2693
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2694
|
-
);
|
|
2695
|
-
return;
|
|
2696
|
-
}
|
|
2697
|
-
if (result.kind === "not-found") {
|
|
2698
|
-
outputErrorAndExit(json, result.message, result.suggestion);
|
|
2699
|
-
return;
|
|
2700
|
-
}
|
|
2701
|
-
const { coin } = result;
|
|
3011
|
+
async function showPriceHistory(json, coin, interval) {
|
|
2702
3012
|
let prices;
|
|
2703
3013
|
try {
|
|
2704
3014
|
prices = await fetchPriceHistory(coin.address, interval);
|
|
@@ -2724,9 +3034,7 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
|
|
|
2724
3034
|
priceValues[0],
|
|
2725
3035
|
priceValues[priceValues.length - 1]
|
|
2726
3036
|
);
|
|
2727
|
-
const sparklineText = sparkline(
|
|
2728
|
-
downsample(priceValues, MAX_SPARKLINE_WIDTH)
|
|
2729
|
-
);
|
|
3037
|
+
const sparklineText = sparkline(downsample(priceValues, MAX_SPARKLINE_WIDTH));
|
|
2730
3038
|
outputData(json, {
|
|
2731
3039
|
json: {
|
|
2732
3040
|
coin: coin.name,
|
|
@@ -2740,7 +3048,7 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
|
|
|
2740
3048
|
price: p.price
|
|
2741
3049
|
}))
|
|
2742
3050
|
},
|
|
2743
|
-
|
|
3051
|
+
render: () => {
|
|
2744
3052
|
renderOnce(
|
|
2745
3053
|
/* @__PURE__ */ jsx9(
|
|
2746
3054
|
PriceHistory,
|
|
@@ -2757,11 +3065,139 @@ var priceHistoryCommand = new Command6("price-history").description("Display pri
|
|
|
2757
3065
|
);
|
|
2758
3066
|
}
|
|
2759
3067
|
});
|
|
3068
|
+
return prices.length;
|
|
3069
|
+
}
|
|
3070
|
+
var priceHistoryCommand = new Command6("price-history").description("Display price history for a coin").argument("[typeOrId]", "Type prefix (creator-coin, trend) or identifier").argument(
|
|
3071
|
+
"[identifier]",
|
|
3072
|
+
"Coin address (0x...) or name (when type prefix is given)"
|
|
3073
|
+
).option(
|
|
3074
|
+
"--interval <interval>",
|
|
3075
|
+
`Time range: ${VALID_INTERVALS.join(", ")}`,
|
|
3076
|
+
"1w"
|
|
3077
|
+
).action(async function(typeOrId, identifier, opts) {
|
|
3078
|
+
const json = getJson(this);
|
|
3079
|
+
const interval = opts.interval ?? "1w";
|
|
3080
|
+
if (!VALID_INTERVALS.includes(interval)) {
|
|
3081
|
+
outputErrorAndExit(
|
|
3082
|
+
json,
|
|
3083
|
+
`Invalid --interval value: ${interval}.`,
|
|
3084
|
+
`Supported: ${VALID_INTERVALS.join(", ")}`
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
let parsed;
|
|
3088
|
+
try {
|
|
3089
|
+
parsed = parsePositionalCoinArgs(typeOrId, identifier);
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
if (err instanceof CoinArgError) {
|
|
3092
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
3093
|
+
}
|
|
3094
|
+
throw err;
|
|
3095
|
+
}
|
|
3096
|
+
const apiKey = getApiKey();
|
|
3097
|
+
if (apiKey) {
|
|
3098
|
+
setApiKey5(apiKey);
|
|
3099
|
+
}
|
|
3100
|
+
if (parsed.kind === "ambiguous-name") {
|
|
3101
|
+
let ambResult;
|
|
3102
|
+
try {
|
|
3103
|
+
ambResult = await resolveAmbiguousName(parsed.name);
|
|
3104
|
+
} catch (err) {
|
|
3105
|
+
outputErrorAndExit(
|
|
3106
|
+
json,
|
|
3107
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3108
|
+
);
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
if (ambResult.kind === "not-found") {
|
|
3112
|
+
outputErrorAndExit(json, ambResult.message);
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
if (ambResult.kind === "ambiguous") {
|
|
3116
|
+
if (json) {
|
|
3117
|
+
const [creatorPrices, trendPrices] = await Promise.all([
|
|
3118
|
+
fetchPriceHistory(ambResult.creator.address, interval),
|
|
3119
|
+
fetchPriceHistory(ambResult.trend.address, interval)
|
|
3120
|
+
]);
|
|
3121
|
+
outputData(json, {
|
|
3122
|
+
json: {
|
|
3123
|
+
matches: [
|
|
3124
|
+
{
|
|
3125
|
+
type: "creator-coin",
|
|
3126
|
+
coin: ambResult.creator.name,
|
|
3127
|
+
prices: creatorPrices
|
|
3128
|
+
},
|
|
3129
|
+
{
|
|
3130
|
+
type: "trend",
|
|
3131
|
+
coin: ambResult.trend.name,
|
|
3132
|
+
prices: trendPrices
|
|
3133
|
+
}
|
|
3134
|
+
],
|
|
3135
|
+
hint: `Use: zora price-history creator-coin ${parsed.name} or zora price-history trend ${parsed.name}`
|
|
3136
|
+
},
|
|
3137
|
+
render: () => {
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
} else {
|
|
3141
|
+
await showPriceHistory(
|
|
3142
|
+
false,
|
|
3143
|
+
ambResult.creator,
|
|
3144
|
+
interval
|
|
3145
|
+
);
|
|
3146
|
+
console.log("");
|
|
3147
|
+
await showPriceHistory(false, ambResult.trend, interval);
|
|
3148
|
+
console.log(
|
|
3149
|
+
`
|
|
3150
|
+
\x1B[2mUse \`zora price-history creator-coin ${parsed.name}\` or \`zora price-history trend ${parsed.name}\` for a specific type.\x1B[0m`
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
track("cli_price_history", {
|
|
3154
|
+
lookup_type: "name",
|
|
3155
|
+
ambiguous: true,
|
|
3156
|
+
interval,
|
|
3157
|
+
output_format: json ? "json" : "text"
|
|
3158
|
+
});
|
|
3159
|
+
return;
|
|
3160
|
+
}
|
|
3161
|
+
const dataPoints2 = await showPriceHistory(
|
|
3162
|
+
json,
|
|
3163
|
+
ambResult.coin,
|
|
3164
|
+
interval
|
|
3165
|
+
);
|
|
3166
|
+
track("cli_price_history", {
|
|
3167
|
+
lookup_type: "name",
|
|
3168
|
+
coin_type: ambResult.coin.coinType,
|
|
3169
|
+
interval,
|
|
3170
|
+
data_points: dataPoints2,
|
|
3171
|
+
output_format: json ? "json" : "text"
|
|
3172
|
+
});
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
const ref = coinArgsToRef(parsed);
|
|
3176
|
+
let result;
|
|
3177
|
+
try {
|
|
3178
|
+
result = await resolveCoin(ref);
|
|
3179
|
+
} catch (err) {
|
|
3180
|
+
outputErrorAndExit(
|
|
3181
|
+
json,
|
|
3182
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3183
|
+
);
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
if (result.kind === "not-found") {
|
|
3187
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
if (result.coin.platformBlocked) {
|
|
3191
|
+
outputErrorAndExit(json, bannedCoinMessage(result.coin.address));
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
const { coin } = result;
|
|
3195
|
+
const dataPoints = await showPriceHistory(json, coin, interval);
|
|
2760
3196
|
track("cli_price_history", {
|
|
2761
|
-
lookup_type:
|
|
3197
|
+
lookup_type: typeOrId.startsWith("0x") ? "address" : "name",
|
|
2762
3198
|
coin_type: coin.coinType,
|
|
2763
3199
|
interval,
|
|
2764
|
-
data_points:
|
|
3200
|
+
data_points: dataPoints,
|
|
2765
3201
|
output_format: json ? "json" : "text"
|
|
2766
3202
|
});
|
|
2767
3203
|
});
|
|
@@ -2771,7 +3207,7 @@ import { Command as Command7 } from "commander";
|
|
|
2771
3207
|
import confirm3 from "@inquirer/confirm";
|
|
2772
3208
|
import {
|
|
2773
3209
|
erc20Abi as erc20Abi3,
|
|
2774
|
-
formatUnits as
|
|
3210
|
+
formatUnits as formatUnits5,
|
|
2775
3211
|
isAddress as isAddress2,
|
|
2776
3212
|
parseUnits as parseUnits2
|
|
2777
3213
|
} from "viem";
|
|
@@ -2788,12 +3224,12 @@ function printSellQuote(output, info) {
|
|
|
2788
3224
|
coin: info.coinSymbol,
|
|
2789
3225
|
address: info.address,
|
|
2790
3226
|
sell: {
|
|
2791
|
-
amount:
|
|
3227
|
+
amount: formatUnits5(info.amountIn, info.coinDecimals),
|
|
2792
3228
|
raw: info.amountIn.toString(),
|
|
2793
3229
|
symbol: info.coinSymbol
|
|
2794
3230
|
},
|
|
2795
3231
|
estimated: {
|
|
2796
|
-
amount:
|
|
3232
|
+
amount: formatUnits5(BigInt(info.quoteAmountOut), info.outputDecimals),
|
|
2797
3233
|
raw: info.quoteAmountOut,
|
|
2798
3234
|
symbol: info.outputSymbol
|
|
2799
3235
|
},
|
|
@@ -2802,7 +3238,8 @@ function printSellQuote(output, info) {
|
|
|
2802
3238
|
return;
|
|
2803
3239
|
}
|
|
2804
3240
|
console.log(`
|
|
2805
|
-
Sell ${info.coinName}
|
|
3241
|
+
Sell \x1B[1m${info.coinName}\x1B[0m`);
|
|
3242
|
+
console.log(` ${info.coinType} \xB7 ${info.address}
|
|
2806
3243
|
`);
|
|
2807
3244
|
console.log(` Amount ${info.soldFormatted} ${info.coinSymbol}`);
|
|
2808
3245
|
console.log(
|
|
@@ -2812,7 +3249,7 @@ function printSellQuote(output, info) {
|
|
|
2812
3249
|
`);
|
|
2813
3250
|
}
|
|
2814
3251
|
function printSellResult(output, info) {
|
|
2815
|
-
const receivedAmount =
|
|
3252
|
+
const receivedAmount = formatUnits5(
|
|
2816
3253
|
info.receivedAmountOut,
|
|
2817
3254
|
info.outputDecimals
|
|
2818
3255
|
);
|
|
@@ -2826,7 +3263,7 @@ function printSellResult(output, info) {
|
|
|
2826
3263
|
coin: info.coinSymbol,
|
|
2827
3264
|
address: info.address,
|
|
2828
3265
|
sold: {
|
|
2829
|
-
amount:
|
|
3266
|
+
amount: formatUnits5(info.amountIn, info.coinDecimals),
|
|
2830
3267
|
raw: info.amountIn.toString(),
|
|
2831
3268
|
symbol: info.coinSymbol
|
|
2832
3269
|
},
|
|
@@ -2841,7 +3278,8 @@ function printSellResult(output, info) {
|
|
|
2841
3278
|
return;
|
|
2842
3279
|
}
|
|
2843
3280
|
console.log(`
|
|
2844
|
-
Sold ${info.coinName}
|
|
3281
|
+
Sold \x1B[1m${info.coinName}\x1B[0m`);
|
|
3282
|
+
console.log(` ${info.coinType} \xB7 ${info.address}
|
|
2845
3283
|
`);
|
|
2846
3284
|
console.log(` Sold ${info.soldFormatted} ${info.coinSymbol}`);
|
|
2847
3285
|
console.log(
|
|
@@ -2853,13 +3291,76 @@ function printSellResult(output, info) {
|
|
|
2853
3291
|
console.log(` Tx ${info.txHash}
|
|
2854
3292
|
`);
|
|
2855
3293
|
}
|
|
2856
|
-
var sellCommand = new Command7("sell").description("Sell a coin").argument(
|
|
3294
|
+
var sellCommand = new Command7("sell").description("Sell a coin").argument(
|
|
3295
|
+
"[typeOrId]",
|
|
3296
|
+
"Type prefix (creator-coin, trend) or coin address/name"
|
|
3297
|
+
).argument("[identifier]", "Coin name (when type prefix is given)").option("--amount <value>", "Sell specific number of coins").option("--usd <value>", "Sell USD equivalent worth of coins").option("--percent <value>", "Sell percentage of coin balance").option("--all", "Sell entire coin balance").option("--to <asset>", "Receive asset: eth, usdc, zora", "eth").option("--token <asset>", "Receive asset: eth, usdc, zora (alias for --to)").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(typeOrId, identifier, opts) {
|
|
2857
3298
|
const json = getJson(this);
|
|
2858
3299
|
const debug = opts.debug === true;
|
|
2859
|
-
|
|
2860
|
-
|
|
3300
|
+
let parsed;
|
|
3301
|
+
try {
|
|
3302
|
+
parsed = parsePositionalCoinArgs(typeOrId, identifier);
|
|
3303
|
+
} catch (err) {
|
|
3304
|
+
if (err instanceof CoinArgError) {
|
|
3305
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
3306
|
+
}
|
|
3307
|
+
throw err;
|
|
3308
|
+
}
|
|
3309
|
+
const apiKey = getApiKey();
|
|
3310
|
+
if (apiKey) {
|
|
3311
|
+
setApiKey6(apiKey);
|
|
2861
3312
|
}
|
|
2862
|
-
|
|
3313
|
+
let coinAddress;
|
|
3314
|
+
if (parsed.kind === "address") {
|
|
3315
|
+
if (!isAddress2(parsed.address)) {
|
|
3316
|
+
outputErrorAndExit(json, `Invalid address: ${parsed.address}`);
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
coinAddress = parsed.address;
|
|
3320
|
+
} else if (parsed.kind === "ambiguous-name") {
|
|
3321
|
+
let ambResult;
|
|
3322
|
+
try {
|
|
3323
|
+
ambResult = await resolveAmbiguousName(parsed.name);
|
|
3324
|
+
} catch (err) {
|
|
3325
|
+
outputErrorAndExit(
|
|
3326
|
+
json,
|
|
3327
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3328
|
+
);
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
if (ambResult.kind === "not-found") {
|
|
3332
|
+
outputErrorAndExit(json, ambResult.message);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
if (ambResult.kind === "ambiguous") {
|
|
3336
|
+
const { message, suggestion } = formatAmbiguousError(
|
|
3337
|
+
parsed.name,
|
|
3338
|
+
ambResult.creator,
|
|
3339
|
+
ambResult.trend,
|
|
3340
|
+
"sell"
|
|
3341
|
+
);
|
|
3342
|
+
outputErrorAndExit(json, message, suggestion);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
coinAddress = ambResult.coin.address;
|
|
3346
|
+
} else {
|
|
3347
|
+
const ref = coinArgsToRef(parsed);
|
|
3348
|
+
try {
|
|
3349
|
+
const result = await resolveCoin(ref);
|
|
3350
|
+
if (result.kind === "not-found") {
|
|
3351
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
coinAddress = result.coin.address;
|
|
3355
|
+
} catch (err) {
|
|
3356
|
+
outputErrorAndExit(
|
|
3357
|
+
json,
|
|
3358
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3359
|
+
);
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
const output = json ? "json" : "static";
|
|
2863
3364
|
const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
|
|
2864
3365
|
if (!(outputAsset in BASE_TRADE_TOKENS)) {
|
|
2865
3366
|
outputErrorAndExit(
|
|
@@ -2882,10 +3383,6 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("<add
|
|
|
2882
3383
|
);
|
|
2883
3384
|
}
|
|
2884
3385
|
const slippage = slippagePct / 100;
|
|
2885
|
-
const apiKey = getApiKey();
|
|
2886
|
-
if (apiKey) {
|
|
2887
|
-
setApiKey6(apiKey);
|
|
2888
|
-
}
|
|
2889
3386
|
const account = resolveAccount(json);
|
|
2890
3387
|
const { publicClient, walletClient } = createClients(account);
|
|
2891
3388
|
let token;
|
|
@@ -2893,16 +3390,14 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("<add
|
|
|
2893
3390
|
const response = await getCoin3({ address: coinAddress });
|
|
2894
3391
|
token = response.data?.zora20Token;
|
|
2895
3392
|
} catch (err) {
|
|
2896
|
-
outputErrorAndExit(
|
|
2897
|
-
json,
|
|
2898
|
-
`Failed to fetch coin: ${err instanceof Error ? err.message : String(err)}`
|
|
2899
|
-
);
|
|
3393
|
+
outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
|
|
2900
3394
|
}
|
|
2901
3395
|
if (!token) {
|
|
2902
3396
|
outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
|
|
2903
3397
|
}
|
|
2904
3398
|
const coinName = token.name;
|
|
2905
3399
|
const coinSymbol = token.symbol;
|
|
3400
|
+
const coinType = mapCoinType(token.coinType);
|
|
2906
3401
|
const coinDecimals = Number(token.decimals ?? 18);
|
|
2907
3402
|
let amountIn;
|
|
2908
3403
|
if (amountMode === "usd") {
|
|
@@ -2929,7 +3424,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("<add
|
|
|
2929
3424
|
}
|
|
2930
3425
|
if (debug) {
|
|
2931
3426
|
console.error(
|
|
2932
|
-
`[debug] $${usdVal} USD = ${
|
|
3427
|
+
`[debug] $${usdVal} USD = ${formatUnits5(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd})`
|
|
2933
3428
|
);
|
|
2934
3429
|
}
|
|
2935
3430
|
} else if (amountMode === "amount") {
|
|
@@ -2984,7 +3479,7 @@ var sellCommand = new Command7("sell").description("Sell a coin").argument("<add
|
|
|
2984
3479
|
const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
|
|
2985
3480
|
if (coinPriceUsd !== null && coinPriceUsd > 0) {
|
|
2986
3481
|
swapAmountUsd = Number(
|
|
2987
|
-
(Number(
|
|
3482
|
+
(Number(formatUnits5(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
|
|
2988
3483
|
2
|
|
2989
3484
|
)
|
|
2990
3485
|
);
|
|
@@ -3037,7 +3532,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3037
3532
|
}
|
|
3038
3533
|
outputErrorAndExit(
|
|
3039
3534
|
json,
|
|
3040
|
-
`Quote failed: ${
|
|
3535
|
+
`Quote failed: ${apiErrorMessage(err)}`,
|
|
3041
3536
|
"Check the coin address and amount, then try again. Use --debug for full error details."
|
|
3042
3537
|
);
|
|
3043
3538
|
}
|
|
@@ -3050,6 +3545,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3050
3545
|
printSellQuote(output, {
|
|
3051
3546
|
coinName,
|
|
3052
3547
|
coinSymbol,
|
|
3548
|
+
coinType,
|
|
3053
3549
|
address: coinAddress,
|
|
3054
3550
|
soldFormatted,
|
|
3055
3551
|
amountIn,
|
|
@@ -3076,9 +3572,10 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3076
3572
|
return;
|
|
3077
3573
|
}
|
|
3078
3574
|
if (!opts.yes) {
|
|
3079
|
-
printSellQuote("
|
|
3575
|
+
printSellQuote("static", {
|
|
3080
3576
|
coinName,
|
|
3081
3577
|
coinSymbol,
|
|
3578
|
+
coinType,
|
|
3082
3579
|
address: coinAddress,
|
|
3083
3580
|
soldFormatted,
|
|
3084
3581
|
amountIn,
|
|
@@ -3098,103 +3595,497 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
3098
3595
|
process.exit(0);
|
|
3099
3596
|
}
|
|
3100
3597
|
}
|
|
3101
|
-
let receipt;
|
|
3102
|
-
let txHash;
|
|
3103
|
-
let receivedAmountOut = BigInt(quoteAmountOut);
|
|
3104
|
-
let receivedSource = "quote";
|
|
3105
|
-
let swapLogIndex = null;
|
|
3106
|
-
const swapCoinType = token.coinType ?? null;
|
|
3107
|
-
try {
|
|
3108
|
-
receipt = await tradeCoin2({
|
|
3109
|
-
tradeParameters,
|
|
3110
|
-
walletClient,
|
|
3111
|
-
publicClient,
|
|
3112
|
-
account
|
|
3113
|
-
});
|
|
3114
|
-
} catch (err) {
|
|
3115
|
-
track("cli_sell", {
|
|
3116
|
-
action: "trade",
|
|
3117
|
-
coin_address: coinAddress,
|
|
3118
|
-
coin_name: coinName,
|
|
3119
|
-
coin_symbol: coinSymbol,
|
|
3120
|
-
amount_mode: amountMode,
|
|
3121
|
-
swap_amount_usd: swapAmountUsd,
|
|
3122
|
-
valueUsd: swapAmountUsd,
|
|
3123
|
-
swapCoinType,
|
|
3124
|
-
output_asset: outputAsset,
|
|
3125
|
-
slippage: slippagePct,
|
|
3126
|
-
output_format: output,
|
|
3127
|
-
success: false,
|
|
3128
|
-
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3129
|
-
});
|
|
3130
|
-
await shutdownAnalytics();
|
|
3598
|
+
let receipt;
|
|
3599
|
+
let txHash;
|
|
3600
|
+
let receivedAmountOut = BigInt(quoteAmountOut);
|
|
3601
|
+
let receivedSource = "quote";
|
|
3602
|
+
let swapLogIndex = null;
|
|
3603
|
+
const swapCoinType = token.coinType ?? null;
|
|
3604
|
+
try {
|
|
3605
|
+
receipt = await tradeCoin2({
|
|
3606
|
+
tradeParameters,
|
|
3607
|
+
walletClient,
|
|
3608
|
+
publicClient,
|
|
3609
|
+
account
|
|
3610
|
+
});
|
|
3611
|
+
} catch (err) {
|
|
3612
|
+
track("cli_sell", {
|
|
3613
|
+
action: "trade",
|
|
3614
|
+
coin_address: coinAddress,
|
|
3615
|
+
coin_name: coinName,
|
|
3616
|
+
coin_symbol: coinSymbol,
|
|
3617
|
+
amount_mode: amountMode,
|
|
3618
|
+
swap_amount_usd: swapAmountUsd,
|
|
3619
|
+
valueUsd: swapAmountUsd,
|
|
3620
|
+
swapCoinType,
|
|
3621
|
+
output_asset: outputAsset,
|
|
3622
|
+
slippage: slippagePct,
|
|
3623
|
+
output_format: output,
|
|
3624
|
+
success: false,
|
|
3625
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3626
|
+
});
|
|
3627
|
+
await shutdownAnalytics();
|
|
3628
|
+
outputErrorAndExit(json, tradeErrorMessage(err));
|
|
3629
|
+
}
|
|
3630
|
+
txHash = receipt.transactionHash;
|
|
3631
|
+
if (outputToken.trade.type === "erc20") {
|
|
3632
|
+
try {
|
|
3633
|
+
const result = getReceivedAmountFromReceipt({
|
|
3634
|
+
receipt,
|
|
3635
|
+
tokenAddress: outputToken.trade.address,
|
|
3636
|
+
recipient: account.address
|
|
3637
|
+
});
|
|
3638
|
+
receivedAmountOut = result.amount;
|
|
3639
|
+
swapLogIndex = result.logIndex;
|
|
3640
|
+
receivedSource = "receipt";
|
|
3641
|
+
} catch {
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
printSellResult(output, {
|
|
3645
|
+
coinName,
|
|
3646
|
+
coinSymbol,
|
|
3647
|
+
coinType,
|
|
3648
|
+
address: coinAddress,
|
|
3649
|
+
amountIn,
|
|
3650
|
+
coinDecimals,
|
|
3651
|
+
soldFormatted,
|
|
3652
|
+
receivedAmountOut,
|
|
3653
|
+
outputSymbol: outputToken.symbol,
|
|
3654
|
+
outputDecimals: outputToken.decimals,
|
|
3655
|
+
receivedSource,
|
|
3656
|
+
txHash
|
|
3657
|
+
});
|
|
3658
|
+
track("cli_sell", {
|
|
3659
|
+
action: "trade",
|
|
3660
|
+
coin_address: coinAddress,
|
|
3661
|
+
coin_name: coinName,
|
|
3662
|
+
coin_symbol: coinSymbol,
|
|
3663
|
+
amount_mode: amountMode,
|
|
3664
|
+
swap_amount_usd: swapAmountUsd,
|
|
3665
|
+
valueUsd: swapAmountUsd,
|
|
3666
|
+
swapCoinType,
|
|
3667
|
+
transactionHash: txHash,
|
|
3668
|
+
logIndex: swapLogIndex,
|
|
3669
|
+
output_asset: outputAsset,
|
|
3670
|
+
slippage: slippagePct,
|
|
3671
|
+
output_format: output,
|
|
3672
|
+
success: true,
|
|
3673
|
+
tx_hash: txHash
|
|
3674
|
+
});
|
|
3675
|
+
});
|
|
3676
|
+
|
|
3677
|
+
// src/commands/profile.tsx
|
|
3678
|
+
import { Command as Command8 } from "commander";
|
|
3679
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
3680
|
+
import {
|
|
3681
|
+
getProfileCoins,
|
|
3682
|
+
getProfileBalances as getProfileBalances2,
|
|
3683
|
+
setApiKey as setApiKey7
|
|
3684
|
+
} from "@zoralabs/coins-sdk";
|
|
3685
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3686
|
+
|
|
3687
|
+
// src/components/ProfileView.tsx
|
|
3688
|
+
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
3689
|
+
import { Box as Box7, Text as Text7, useInput as useInput3, useApp as useApp3 } from "ink";
|
|
3690
|
+
import Spinner3 from "ink-spinner";
|
|
3691
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3692
|
+
var TAB_NAMES = ["Posts", "Holdings"];
|
|
3693
|
+
var postColumns = [
|
|
3694
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
3695
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
3696
|
+
{
|
|
3697
|
+
header: "Type",
|
|
3698
|
+
width: 14,
|
|
3699
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
3700
|
+
},
|
|
3701
|
+
{
|
|
3702
|
+
header: "Market Cap",
|
|
3703
|
+
width: 12,
|
|
3704
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
3705
|
+
},
|
|
3706
|
+
{
|
|
3707
|
+
header: "24h Vol",
|
|
3708
|
+
width: 12,
|
|
3709
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
header: "24h Change",
|
|
3713
|
+
width: 11,
|
|
3714
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
3715
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
3716
|
+
},
|
|
3717
|
+
{
|
|
3718
|
+
header: "Created",
|
|
3719
|
+
width: 16,
|
|
3720
|
+
accessor: (c) => {
|
|
3721
|
+
if (!c.createdAt) return "-";
|
|
3722
|
+
const date = new Date(c.createdAt);
|
|
3723
|
+
if (isNaN(date.getTime())) return "-";
|
|
3724
|
+
return formatRelativeTime(date);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
];
|
|
3728
|
+
var ProfileView = ({
|
|
3729
|
+
fetchData,
|
|
3730
|
+
identifier,
|
|
3731
|
+
autoRefresh = false,
|
|
3732
|
+
intervalSeconds = 30
|
|
3733
|
+
}) => {
|
|
3734
|
+
const { exit } = useApp3();
|
|
3735
|
+
const [activeTab, setActiveTab] = useState4(0);
|
|
3736
|
+
const [loading, setLoading] = useState4(true);
|
|
3737
|
+
const [isRefreshing, setIsRefreshing] = useState4(false);
|
|
3738
|
+
const [error, setError] = useState4(null);
|
|
3739
|
+
const [data, setData] = useState4(null);
|
|
3740
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
3741
|
+
const [manualRefreshCount, setManualRefreshCount] = useState4(0);
|
|
3742
|
+
const hasLoadedOnce = useRef3(false);
|
|
3743
|
+
const load = useCallback4(async () => {
|
|
3744
|
+
if (hasLoadedOnce.current) {
|
|
3745
|
+
setIsRefreshing(true);
|
|
3746
|
+
} else {
|
|
3747
|
+
setLoading(true);
|
|
3748
|
+
}
|
|
3749
|
+
setError(null);
|
|
3750
|
+
try {
|
|
3751
|
+
const result = await fetchData();
|
|
3752
|
+
setData(result);
|
|
3753
|
+
hasLoadedOnce.current = true;
|
|
3754
|
+
} catch (err) {
|
|
3755
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3756
|
+
}
|
|
3757
|
+
setLoading(false);
|
|
3758
|
+
setIsRefreshing(false);
|
|
3759
|
+
}, [fetchData]);
|
|
3760
|
+
useEffect4(() => {
|
|
3761
|
+
load();
|
|
3762
|
+
}, [load, refreshCount, manualRefreshCount]);
|
|
3763
|
+
useInput3((input, key) => {
|
|
3764
|
+
if (input === "q" || key.escape) {
|
|
3765
|
+
exit();
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
if (input === "r" && !loading) {
|
|
3769
|
+
triggerManualRefresh();
|
|
3770
|
+
setManualRefreshCount((c) => c + 1);
|
|
3771
|
+
}
|
|
3772
|
+
if (key.leftArrow || input === "1") {
|
|
3773
|
+
setActiveTab(0);
|
|
3774
|
+
}
|
|
3775
|
+
if (key.rightArrow || input === "2") {
|
|
3776
|
+
setActiveTab(1);
|
|
3777
|
+
}
|
|
3778
|
+
});
|
|
3779
|
+
if (error && !data) {
|
|
3780
|
+
return /* @__PURE__ */ jsxs7(
|
|
3781
|
+
Box7,
|
|
3782
|
+
{
|
|
3783
|
+
flexDirection: "column",
|
|
3784
|
+
paddingLeft: 1,
|
|
3785
|
+
paddingTop: 1,
|
|
3786
|
+
paddingBottom: 1,
|
|
3787
|
+
children: [
|
|
3788
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
3789
|
+
"Error: ",
|
|
3790
|
+
error
|
|
3791
|
+
] }),
|
|
3792
|
+
/* @__PURE__ */ jsx10(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: "Press q to exit" }) })
|
|
3793
|
+
]
|
|
3794
|
+
}
|
|
3795
|
+
);
|
|
3796
|
+
}
|
|
3797
|
+
if (loading && !data) {
|
|
3798
|
+
return /* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
3799
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3800
|
+
" Loading profile\u2026"
|
|
3801
|
+
] }) });
|
|
3802
|
+
}
|
|
3803
|
+
if (!data) return null;
|
|
3804
|
+
const hints = [
|
|
3805
|
+
"\u2190 \u2192 switch tab",
|
|
3806
|
+
autoRefresh ? `r refresh (${secondsUntilRefresh}s)` : "r refresh"
|
|
3807
|
+
];
|
|
3808
|
+
hints.push("q quit");
|
|
3809
|
+
const footer = hints.join(" \xB7 ");
|
|
3810
|
+
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
3811
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
3812
|
+
isRefreshing && /* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
3813
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3814
|
+
" Refreshing\u2026"
|
|
3815
|
+
] }) }),
|
|
3816
|
+
/* @__PURE__ */ jsxs7(Box7, { paddingLeft: 1, paddingTop: 1, gap: 2, children: [
|
|
3817
|
+
TAB_NAMES.map((name, i) => /* @__PURE__ */ jsx10(Text7, { bold: activeTab === i, dimColor: activeTab !== i, children: activeTab === i ? `[${name}]` : name }, name)),
|
|
3818
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
3819
|
+
" ",
|
|
3820
|
+
identifier
|
|
3821
|
+
] })
|
|
3822
|
+
] }),
|
|
3823
|
+
activeTab === 0 ? rankedPosts.length === 0 ? /* @__PURE__ */ jsx10(
|
|
3824
|
+
Box7,
|
|
3825
|
+
{
|
|
3826
|
+
flexDirection: "column",
|
|
3827
|
+
paddingLeft: 1,
|
|
3828
|
+
paddingTop: 1,
|
|
3829
|
+
paddingBottom: 1,
|
|
3830
|
+
children: /* @__PURE__ */ jsx10(Text7, { children: "No posts found for this profile." })
|
|
3831
|
+
}
|
|
3832
|
+
) : /* @__PURE__ */ jsx10(
|
|
3833
|
+
Table,
|
|
3834
|
+
{
|
|
3835
|
+
columns: postColumns,
|
|
3836
|
+
data: rankedPosts,
|
|
3837
|
+
title: "Posts",
|
|
3838
|
+
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
3839
|
+
}
|
|
3840
|
+
) : data.holdings.length === 0 ? /* @__PURE__ */ jsx10(
|
|
3841
|
+
Box7,
|
|
3842
|
+
{
|
|
3843
|
+
flexDirection: "column",
|
|
3844
|
+
paddingLeft: 1,
|
|
3845
|
+
paddingTop: 1,
|
|
3846
|
+
paddingBottom: 1,
|
|
3847
|
+
children: /* @__PURE__ */ jsx10(Text7, { children: "No holdings found for this profile." })
|
|
3848
|
+
}
|
|
3849
|
+
) : /* @__PURE__ */ jsx10(
|
|
3850
|
+
Table,
|
|
3851
|
+
{
|
|
3852
|
+
columns: balanceColumns,
|
|
3853
|
+
data: data.holdings,
|
|
3854
|
+
title: "Holdings",
|
|
3855
|
+
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
3856
|
+
}
|
|
3857
|
+
),
|
|
3858
|
+
/* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: footer }) })
|
|
3859
|
+
] });
|
|
3860
|
+
};
|
|
3861
|
+
|
|
3862
|
+
// src/commands/profile.tsx
|
|
3863
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3864
|
+
var extractErrorMessage2 = (error) => {
|
|
3865
|
+
if (typeof error === "object" && error !== null && "error" in error) {
|
|
3866
|
+
return String(error.error);
|
|
3867
|
+
}
|
|
3868
|
+
return JSON.stringify(error);
|
|
3869
|
+
};
|
|
3870
|
+
var resolveApiKey = (json) => {
|
|
3871
|
+
const apiKey = getApiKey();
|
|
3872
|
+
if (!apiKey) {
|
|
3131
3873
|
outputErrorAndExit(
|
|
3132
3874
|
json,
|
|
3133
|
-
|
|
3875
|
+
"Not authenticated. Run 'zora auth configure' to set your API key."
|
|
3134
3876
|
);
|
|
3135
3877
|
}
|
|
3136
|
-
|
|
3137
|
-
|
|
3878
|
+
setApiKey7(apiKey);
|
|
3879
|
+
};
|
|
3880
|
+
var formatPostJson = (post, rank) => ({
|
|
3881
|
+
rank,
|
|
3882
|
+
name: post.name,
|
|
3883
|
+
symbol: post.symbol,
|
|
3884
|
+
coinType: COIN_TYPE_DISPLAY[post.coinType] ?? post.coinType,
|
|
3885
|
+
address: post.address,
|
|
3886
|
+
marketCap: post.marketCap ?? null,
|
|
3887
|
+
marketCapDelta24h: post.marketCapDelta24h ?? null,
|
|
3888
|
+
volume24h: post.volume24h ?? null,
|
|
3889
|
+
createdAt: post.createdAt ?? null
|
|
3890
|
+
});
|
|
3891
|
+
var formatHoldingJson = (balance) => {
|
|
3892
|
+
const priceUsd = balance.coin?.tokenPrice?.priceInUsdc ? Number(balance.coin.tokenPrice.priceInUsdc) : null;
|
|
3893
|
+
const usdValue = priceUsd !== null ? Number((parseRawBalance(balance.balance) * priceUsd).toFixed(6)) : null;
|
|
3894
|
+
return {
|
|
3895
|
+
rank: balance.rank,
|
|
3896
|
+
name: balance.coin?.name ?? null,
|
|
3897
|
+
symbol: balance.coin?.symbol ?? null,
|
|
3898
|
+
coinType: balance.coin?.coinType ?? null,
|
|
3899
|
+
address: balance.coin?.address ?? null,
|
|
3900
|
+
balance: normalizeTokenAmount(balance.balance),
|
|
3901
|
+
usdValue,
|
|
3902
|
+
priceUsd,
|
|
3903
|
+
marketCap: balance.coin?.marketCap ? Number(balance.coin.marketCap) : null
|
|
3904
|
+
};
|
|
3905
|
+
};
|
|
3906
|
+
var fetchProfileData = async (identifier) => {
|
|
3907
|
+
const [postsResult, holdingsResult] = await Promise.allSettled([
|
|
3908
|
+
getProfileCoins({ identifier, count: 20 }),
|
|
3909
|
+
getProfileBalances2({ identifier, count: 20, sortOption: "USD_VALUE" })
|
|
3910
|
+
]);
|
|
3911
|
+
if (postsResult.status === "rejected") {
|
|
3912
|
+
throw new Error(
|
|
3913
|
+
postsResult.reason instanceof Error ? postsResult.reason.message : String(postsResult.reason)
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
3916
|
+
if (holdingsResult.status === "rejected") {
|
|
3917
|
+
throw new Error(
|
|
3918
|
+
holdingsResult.reason instanceof Error ? holdingsResult.reason.message : String(holdingsResult.reason)
|
|
3919
|
+
);
|
|
3920
|
+
}
|
|
3921
|
+
if (postsResult.value.error) {
|
|
3922
|
+
throw new Error(
|
|
3923
|
+
`API error (posts): ${extractErrorMessage2(postsResult.value.error)}`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
if (holdingsResult.value.error) {
|
|
3927
|
+
throw new Error(
|
|
3928
|
+
`API error (holdings): ${extractErrorMessage2(holdingsResult.value.error)}`
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
const postEdges = postsResult.value.data?.profile?.createdCoins?.edges ?? [];
|
|
3932
|
+
const posts = postEdges.map((e) => e.node);
|
|
3933
|
+
const postsCount = postsResult.value.data?.profile?.createdCoins?.count ?? posts.length;
|
|
3934
|
+
const holdingEdges = holdingsResult.value.data?.profile?.coinBalances?.edges ?? [];
|
|
3935
|
+
const holdings = holdingEdges.map(
|
|
3936
|
+
(e, i) => ({
|
|
3937
|
+
...e.node,
|
|
3938
|
+
rank: i + 1
|
|
3939
|
+
})
|
|
3940
|
+
);
|
|
3941
|
+
const holdingsCount = holdingsResult.value.data?.profile?.coinBalances?.count ?? holdings.length;
|
|
3942
|
+
return { posts, postsCount, holdings, holdingsCount };
|
|
3943
|
+
};
|
|
3944
|
+
var profileCommand = new Command8("profile").description("View profile activity (posts and holdings)").argument(
|
|
3945
|
+
"[identifier]",
|
|
3946
|
+
"Wallet address or profile handle (defaults to your wallet)"
|
|
3947
|
+
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
3948
|
+
"--refresh <seconds>",
|
|
3949
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
3950
|
+
"30"
|
|
3951
|
+
).action(async function(identifierArg) {
|
|
3952
|
+
const output = getOutputMode(this, "live");
|
|
3953
|
+
const json = output === "json";
|
|
3954
|
+
resolveApiKey(json);
|
|
3955
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
3956
|
+
let identifier = identifierArg;
|
|
3957
|
+
if (!identifier) {
|
|
3958
|
+
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
3959
|
+
const key = envKey || getPrivateKey();
|
|
3960
|
+
if (!key) {
|
|
3961
|
+
outputErrorAndExit(
|
|
3962
|
+
json,
|
|
3963
|
+
"No identifier provided and no wallet configured.",
|
|
3964
|
+
"Pass an address or handle, or run 'zora setup' first."
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3138
3967
|
try {
|
|
3139
|
-
|
|
3140
|
-
receipt,
|
|
3141
|
-
tokenAddress: outputToken.trade.address,
|
|
3142
|
-
recipient: account.address
|
|
3143
|
-
});
|
|
3144
|
-
receivedAmountOut = result.amount;
|
|
3145
|
-
swapLogIndex = result.logIndex;
|
|
3146
|
-
receivedSource = "receipt";
|
|
3968
|
+
identifier = privateKeyToAccount3(normalizeKey(key)).address;
|
|
3147
3969
|
} catch {
|
|
3970
|
+
outputErrorAndExit(
|
|
3971
|
+
json,
|
|
3972
|
+
"Invalid wallet key. Run 'zora setup --force' to replace it."
|
|
3973
|
+
);
|
|
3148
3974
|
}
|
|
3149
3975
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3976
|
+
if (json) {
|
|
3977
|
+
const data = await fetchProfileData(identifier).catch(
|
|
3978
|
+
(err) => outputErrorAndExit(
|
|
3979
|
+
json,
|
|
3980
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3981
|
+
)
|
|
3982
|
+
);
|
|
3983
|
+
outputData(json, {
|
|
3984
|
+
json: {
|
|
3985
|
+
posts: data.posts.map((p, i) => formatPostJson(p, i + 1)),
|
|
3986
|
+
holdings: data.holdings.map(formatHoldingJson)
|
|
3987
|
+
},
|
|
3988
|
+
render: () => {
|
|
3989
|
+
}
|
|
3990
|
+
});
|
|
3991
|
+
track("cli_profile", {
|
|
3992
|
+
identifier,
|
|
3993
|
+
output_format: "json",
|
|
3994
|
+
posts_count: data.postsCount,
|
|
3995
|
+
holdings_count: data.holdingsCount
|
|
3996
|
+
});
|
|
3997
|
+
} else if (live) {
|
|
3998
|
+
const fetchData = () => fetchProfileData(identifier);
|
|
3999
|
+
await renderLive(
|
|
4000
|
+
/* @__PURE__ */ jsx11(
|
|
4001
|
+
ProfileView,
|
|
4002
|
+
{
|
|
4003
|
+
fetchData,
|
|
4004
|
+
identifier,
|
|
4005
|
+
autoRefresh: live,
|
|
4006
|
+
intervalSeconds
|
|
4007
|
+
}
|
|
4008
|
+
)
|
|
4009
|
+
);
|
|
4010
|
+
track("cli_profile", {
|
|
4011
|
+
identifier,
|
|
4012
|
+
output_format: "live",
|
|
4013
|
+
live,
|
|
4014
|
+
interval: intervalSeconds
|
|
4015
|
+
});
|
|
4016
|
+
} else {
|
|
4017
|
+
const data = await fetchProfileData(identifier).catch(
|
|
4018
|
+
(err) => outputErrorAndExit(
|
|
4019
|
+
json,
|
|
4020
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4021
|
+
)
|
|
4022
|
+
);
|
|
4023
|
+
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
4024
|
+
renderOnce(
|
|
4025
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
4026
|
+
rankedPosts.length === 0 ? /* @__PURE__ */ jsx11(
|
|
4027
|
+
Box8,
|
|
4028
|
+
{
|
|
4029
|
+
flexDirection: "column",
|
|
4030
|
+
paddingLeft: 1,
|
|
4031
|
+
paddingTop: 1,
|
|
4032
|
+
paddingBottom: 1,
|
|
4033
|
+
children: /* @__PURE__ */ jsx11(Box8, { children: /* @__PURE__ */ jsx11(Text8, { children: "No posts found for this profile." }) })
|
|
4034
|
+
}
|
|
4035
|
+
) : /* @__PURE__ */ jsx11(
|
|
4036
|
+
Table,
|
|
4037
|
+
{
|
|
4038
|
+
columns: postColumns,
|
|
4039
|
+
data: rankedPosts,
|
|
4040
|
+
title: "Posts",
|
|
4041
|
+
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
4042
|
+
}
|
|
4043
|
+
),
|
|
4044
|
+
data.holdings.length === 0 ? /* @__PURE__ */ jsx11(
|
|
4045
|
+
Box8,
|
|
4046
|
+
{
|
|
4047
|
+
flexDirection: "column",
|
|
4048
|
+
paddingLeft: 1,
|
|
4049
|
+
paddingTop: 1,
|
|
4050
|
+
paddingBottom: 1,
|
|
4051
|
+
children: /* @__PURE__ */ jsx11(Box8, { children: /* @__PURE__ */ jsx11(Text8, { children: "No holdings found for this profile." }) })
|
|
4052
|
+
}
|
|
4053
|
+
) : /* @__PURE__ */ jsx11(
|
|
4054
|
+
Table,
|
|
4055
|
+
{
|
|
4056
|
+
columns: balanceColumns,
|
|
4057
|
+
data: data.holdings,
|
|
4058
|
+
title: "Holdings",
|
|
4059
|
+
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
4060
|
+
}
|
|
4061
|
+
)
|
|
4062
|
+
] })
|
|
4063
|
+
);
|
|
4064
|
+
track("cli_profile", {
|
|
4065
|
+
identifier,
|
|
4066
|
+
output_format: "static",
|
|
4067
|
+
posts_count: data.postsCount,
|
|
4068
|
+
holdings_count: data.holdingsCount
|
|
4069
|
+
});
|
|
4070
|
+
}
|
|
3180
4071
|
});
|
|
3181
4072
|
|
|
3182
4073
|
// src/commands/send.ts
|
|
3183
|
-
import { Command as
|
|
4074
|
+
import { Command as Command9 } from "commander";
|
|
3184
4075
|
import confirm4 from "@inquirer/confirm";
|
|
3185
4076
|
import {
|
|
3186
4077
|
erc20Abi as erc20Abi4,
|
|
3187
|
-
formatUnits as
|
|
4078
|
+
formatUnits as formatUnits6,
|
|
3188
4079
|
isAddress as isAddress3,
|
|
3189
4080
|
parseUnits as parseUnits3
|
|
3190
4081
|
} from "viem";
|
|
3191
|
-
import { setApiKey as
|
|
4082
|
+
import { setApiKey as setApiKey8 } from "@zoralabs/coins-sdk";
|
|
3192
4083
|
var SEND_AMOUNT_CHECKS = {
|
|
3193
4084
|
amount: (opts) => opts.amount !== void 0,
|
|
3194
4085
|
percent: (opts) => opts.percent !== void 0,
|
|
3195
4086
|
all: (opts) => opts.all === true
|
|
3196
4087
|
};
|
|
3197
|
-
var
|
|
4088
|
+
var KNOWN_TOKEN_NAMES = /* @__PURE__ */ new Set(["eth", "usdc", "zora"]);
|
|
3198
4089
|
function printSendPreview(info) {
|
|
3199
4090
|
const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
|
|
3200
4091
|
console.log(`
|
|
@@ -3215,7 +4106,7 @@ function printSendResult(json, info) {
|
|
|
3215
4106
|
coin: info.symbol,
|
|
3216
4107
|
address: info.address,
|
|
3217
4108
|
sent: {
|
|
3218
|
-
amount:
|
|
4109
|
+
amount: formatUnits6(info.amount, info.decimals),
|
|
3219
4110
|
raw: info.amount.toString(),
|
|
3220
4111
|
symbol: info.symbol,
|
|
3221
4112
|
amountUsd: info.amountUsd
|
|
@@ -3236,34 +4127,35 @@ function printSendResult(json, info) {
|
|
|
3236
4127
|
console.log(` Tx ${info.txHash}
|
|
3237
4128
|
`);
|
|
3238
4129
|
}
|
|
3239
|
-
var sendCommand = new
|
|
4130
|
+
var sendCommand = new Command9("send").description("Send coins or ETH to an address").argument(
|
|
4131
|
+
"[typeOrId]",
|
|
4132
|
+
"Token (eth, usdc, zora), type prefix (creator-coin, trend), or coin address/name"
|
|
4133
|
+
).argument("[identifier]", "Coin name (when type prefix is given)").option("--to <address>", "Recipient address (0x...)").option("--amount <value>", "Send specific amount").option("--percent <value>", "Send percentage of balance (1-100)").option("--all", "Send entire balance").option("--yes", "Skip confirmation").action(async function(firstArg, secondArg, opts) {
|
|
3240
4134
|
const json = getJson(this);
|
|
3241
|
-
if (!
|
|
4135
|
+
if (!opts.to) {
|
|
3242
4136
|
outputErrorAndExit(
|
|
3243
4137
|
json,
|
|
3244
|
-
|
|
3245
|
-
"
|
|
4138
|
+
"Missing --to flag.",
|
|
4139
|
+
"Usage: zora send <identifier> --to <address>"
|
|
3246
4140
|
);
|
|
3247
4141
|
}
|
|
3248
|
-
|
|
3249
|
-
if (opts.type !== void 0 && !VALID_TYPES3.includes(opts.type)) {
|
|
4142
|
+
if (!isAddress3(opts.to)) {
|
|
3250
4143
|
outputErrorAndExit(
|
|
3251
4144
|
json,
|
|
3252
|
-
`Invalid
|
|
3253
|
-
|
|
4145
|
+
`Invalid recipient address: ${opts.to}`,
|
|
4146
|
+
"Must be a valid 0x address."
|
|
3254
4147
|
);
|
|
3255
4148
|
}
|
|
4149
|
+
const recipient = opts.to;
|
|
3256
4150
|
const amountMode = getAmountMode(
|
|
3257
4151
|
json,
|
|
3258
4152
|
opts,
|
|
3259
4153
|
SEND_AMOUNT_CHECKS,
|
|
3260
4154
|
"--amount, --percent, or --all"
|
|
3261
4155
|
);
|
|
3262
|
-
const
|
|
4156
|
+
const isKnownToken = KNOWN_TOKEN_NAMES.has(firstArg.toLowerCase());
|
|
4157
|
+
const isEth = firstArg.toLowerCase() === "eth";
|
|
3263
4158
|
if (isEth) {
|
|
3264
|
-
if (opts.type) {
|
|
3265
|
-
outputErrorAndExit(json, "--type is not valid when sending ETH.");
|
|
3266
|
-
}
|
|
3267
4159
|
const account = resolveAccount(json);
|
|
3268
4160
|
const { publicClient, walletClient } = createClients(account);
|
|
3269
4161
|
const balance = await publicClient.getBalance({
|
|
@@ -3301,14 +4193,14 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3301
4193
|
if (amount + GAS_RESERVE > balance) {
|
|
3302
4194
|
outputErrorAndExit(
|
|
3303
4195
|
json,
|
|
3304
|
-
`Insufficient balance. Have ${
|
|
4196
|
+
`Insufficient balance. Have ${formatAmountDisplay(balance, 18)} ETH (need to reserve ~${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas).`
|
|
3305
4197
|
);
|
|
3306
4198
|
}
|
|
3307
4199
|
} else {
|
|
3308
4200
|
if (balance <= GAS_RESERVE) {
|
|
3309
4201
|
outputErrorAndExit(
|
|
3310
4202
|
json,
|
|
3311
|
-
`Balance too low (${
|
|
4203
|
+
`Balance too low (${formatAmountDisplay(balance, 18)} ETH). Need >${formatAmountDisplay(GAS_RESERVE, 18)} ETH for gas.`
|
|
3312
4204
|
);
|
|
3313
4205
|
}
|
|
3314
4206
|
const spendable = balance - GAS_RESERVE;
|
|
@@ -3331,12 +4223,12 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3331
4223
|
}
|
|
3332
4224
|
}
|
|
3333
4225
|
}
|
|
3334
|
-
const amountFormatted =
|
|
4226
|
+
const amountFormatted = formatAmountDisplay(amount, 18);
|
|
3335
4227
|
let amountUsd = null;
|
|
3336
4228
|
const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
|
|
3337
4229
|
if (ethPriceUsd != null) {
|
|
3338
4230
|
amountUsd = Number(
|
|
3339
|
-
(Number(
|
|
4231
|
+
(Number(formatUnits6(amount, 18)) * ethPriceUsd).toFixed(2)
|
|
3340
4232
|
);
|
|
3341
4233
|
}
|
|
3342
4234
|
if (!opts.yes) {
|
|
@@ -3362,7 +4254,7 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3362
4254
|
} catch (err) {
|
|
3363
4255
|
track("cli_send", {
|
|
3364
4256
|
asset: "eth",
|
|
3365
|
-
output_format: json ? "json" : "
|
|
4257
|
+
output_format: json ? "json" : "static",
|
|
3366
4258
|
success: false,
|
|
3367
4259
|
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3368
4260
|
});
|
|
@@ -3391,19 +4283,13 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3391
4283
|
amount_mode: amountMode,
|
|
3392
4284
|
amount_usd: amountUsd,
|
|
3393
4285
|
transactionHash: txHash,
|
|
3394
|
-
output_format: json ? "json" : "
|
|
4286
|
+
output_format: json ? "json" : "static",
|
|
3395
4287
|
success: true,
|
|
3396
4288
|
tx_hash: txHash
|
|
3397
4289
|
});
|
|
3398
4290
|
} else {
|
|
3399
|
-
const knownTokenKey =
|
|
3400
|
-
const knownToken = knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
|
|
3401
|
-
if (knownToken && opts.type) {
|
|
3402
|
-
outputErrorAndExit(
|
|
3403
|
-
json,
|
|
3404
|
-
`--type is not valid when sending ${knownToken.symbol}.`
|
|
3405
|
-
);
|
|
3406
|
-
}
|
|
4291
|
+
const knownTokenKey = firstArg.toLowerCase();
|
|
4292
|
+
const knownToken = isKnownToken && knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
|
|
3407
4293
|
let tokenAddress;
|
|
3408
4294
|
let tokenName;
|
|
3409
4295
|
if (knownToken) {
|
|
@@ -3413,23 +4299,62 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3413
4299
|
} else {
|
|
3414
4300
|
const apiKey = getApiKey();
|
|
3415
4301
|
if (apiKey) {
|
|
3416
|
-
|
|
4302
|
+
setApiKey8(apiKey);
|
|
3417
4303
|
}
|
|
3418
|
-
|
|
3419
|
-
let result;
|
|
4304
|
+
let parsed;
|
|
3420
4305
|
try {
|
|
3421
|
-
|
|
4306
|
+
parsed = parsePositionalCoinArgs(firstArg, secondArg);
|
|
3422
4307
|
} catch (err) {
|
|
3423
|
-
|
|
3424
|
-
json,
|
|
3425
|
-
|
|
3426
|
-
|
|
4308
|
+
if (err instanceof CoinArgError) {
|
|
4309
|
+
outputErrorAndExit(json, err.message, err.suggestion);
|
|
4310
|
+
}
|
|
4311
|
+
throw err;
|
|
3427
4312
|
}
|
|
3428
|
-
if (
|
|
3429
|
-
|
|
4313
|
+
if (parsed.kind === "ambiguous-name") {
|
|
4314
|
+
let ambResult;
|
|
4315
|
+
try {
|
|
4316
|
+
ambResult = await resolveAmbiguousName(parsed.name);
|
|
4317
|
+
} catch (err) {
|
|
4318
|
+
outputErrorAndExit(
|
|
4319
|
+
json,
|
|
4320
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4321
|
+
);
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
if (ambResult.kind === "not-found") {
|
|
4325
|
+
outputErrorAndExit(json, ambResult.message);
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
4328
|
+
if (ambResult.kind === "ambiguous") {
|
|
4329
|
+
const { message, suggestion } = formatAmbiguousError(
|
|
4330
|
+
parsed.name,
|
|
4331
|
+
ambResult.creator,
|
|
4332
|
+
ambResult.trend,
|
|
4333
|
+
"send"
|
|
4334
|
+
);
|
|
4335
|
+
outputErrorAndExit(json, message, suggestion);
|
|
4336
|
+
return;
|
|
4337
|
+
}
|
|
4338
|
+
tokenAddress = ambResult.coin.address;
|
|
4339
|
+
tokenName = ambResult.coin.name;
|
|
4340
|
+
} else {
|
|
4341
|
+
const ref = coinArgsToRef(parsed);
|
|
4342
|
+
try {
|
|
4343
|
+
const result = await resolveCoin(ref);
|
|
4344
|
+
if (result.kind === "not-found") {
|
|
4345
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
tokenAddress = result.coin.address;
|
|
4349
|
+
tokenName = result.coin.name;
|
|
4350
|
+
} catch (err) {
|
|
4351
|
+
outputErrorAndExit(
|
|
4352
|
+
json,
|
|
4353
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4354
|
+
);
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
3430
4357
|
}
|
|
3431
|
-
tokenAddress = result.coin.address;
|
|
3432
|
-
tokenName = result.coin.name;
|
|
3433
4358
|
}
|
|
3434
4359
|
const account = resolveAccount(json);
|
|
3435
4360
|
const { publicClient, walletClient } = createClients(account);
|
|
@@ -3527,7 +4452,7 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3527
4452
|
const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
|
|
3528
4453
|
if (priceUsd != null) {
|
|
3529
4454
|
amountUsd = Number(
|
|
3530
|
-
(Number(
|
|
4455
|
+
(Number(formatUnits6(amount, decimals)) * priceUsd).toFixed(2)
|
|
3531
4456
|
);
|
|
3532
4457
|
}
|
|
3533
4458
|
if (!opts.yes) {
|
|
@@ -3558,7 +4483,7 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3558
4483
|
coin_address: tokenAddress,
|
|
3559
4484
|
coin_name: tokenName,
|
|
3560
4485
|
coin_symbol: symbol,
|
|
3561
|
-
output_format: json ? "json" : "
|
|
4486
|
+
output_format: json ? "json" : "static",
|
|
3562
4487
|
success: false,
|
|
3563
4488
|
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3564
4489
|
});
|
|
@@ -3588,7 +4513,7 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3588
4513
|
amount_mode: amountMode,
|
|
3589
4514
|
amount_usd: amountUsd,
|
|
3590
4515
|
transactionHash: txHash,
|
|
3591
|
-
output_format: json ? "json" : "
|
|
4516
|
+
output_format: json ? "json" : "static",
|
|
3592
4517
|
success: true,
|
|
3593
4518
|
tx_hash: txHash
|
|
3594
4519
|
});
|
|
@@ -3596,8 +4521,8 @@ var sendCommand = new Command8("send").description("Send coins or ETH to an addr
|
|
|
3596
4521
|
});
|
|
3597
4522
|
|
|
3598
4523
|
// src/commands/setup.ts
|
|
3599
|
-
import { Command as
|
|
3600
|
-
import { generatePrivateKey, privateKeyToAccount as
|
|
4524
|
+
import { Command as Command10 } from "commander";
|
|
4525
|
+
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
3601
4526
|
|
|
3602
4527
|
// src/lib/strings.ts
|
|
3603
4528
|
var DEPOSIT_INSTRUCTIONS = "Deposit ETH or USDC to this address on Base to start trading.\n\n You can do this from:\n - Coinbase \u2014 withdraw directly to Base\n - Another wallet (MetaMask, Rainbow, etc.) \u2014 send on Base network\n - Bridge from other chains \u2014 use https://superbridge.app/base";
|
|
@@ -3610,7 +4535,7 @@ var BACKUP_WARNING = "Back up this file \u2014 it's the only copy of your key.";
|
|
|
3610
4535
|
var isValidPrivateKey = (key) => /^(0x)?[0-9a-fA-F]{64}$/.test(key);
|
|
3611
4536
|
var toAccount = (json, key, errorPrefix) => {
|
|
3612
4537
|
try {
|
|
3613
|
-
return
|
|
4538
|
+
return privateKeyToAccount4(normalizeKey(key));
|
|
3614
4539
|
} catch {
|
|
3615
4540
|
outputErrorAndExit(
|
|
3616
4541
|
json,
|
|
@@ -3618,7 +4543,7 @@ var toAccount = (json, key, errorPrefix) => {
|
|
|
3618
4543
|
);
|
|
3619
4544
|
}
|
|
3620
4545
|
};
|
|
3621
|
-
var setupCommand = new
|
|
4546
|
+
var setupCommand = new Command10("setup").description("Set up your Zora wallet").option("--create", "Create a new wallet without prompting").option("--force", "Overwrite existing wallet without prompting").option("--yes", "Skip interactive prompt and execute directly").action(async function(options) {
|
|
3622
4547
|
const json = getJson(this);
|
|
3623
4548
|
const nonInteractive = getYes(this);
|
|
3624
4549
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -3633,7 +4558,7 @@ var setupCommand = new Command9("setup").description("Set up your Zora wallet").
|
|
|
3633
4558
|
const account = toAccount(json, envKey, "ZORA_PRIVATE_KEY");
|
|
3634
4559
|
outputData(json, {
|
|
3635
4560
|
json: { source: "env", address: account.address },
|
|
3636
|
-
|
|
4561
|
+
render: () => {
|
|
3637
4562
|
console.log(" Using wallet from ZORA_PRIVATE_KEY.\n");
|
|
3638
4563
|
console.log(` Address: ${account.address}
|
|
3639
4564
|
`);
|
|
@@ -3654,14 +4579,14 @@ var setupCommand = new Command9("setup").description("Set up your Zora wallet").
|
|
|
3654
4579
|
} catch (err) {
|
|
3655
4580
|
outputErrorAndExit(
|
|
3656
4581
|
json,
|
|
3657
|
-
`\u2717 Could not read wallet: ${err
|
|
4582
|
+
`\u2717 Could not read wallet: ${formatError(err)}`,
|
|
3658
4583
|
"Run 'zora setup --force' to overwrite it."
|
|
3659
4584
|
);
|
|
3660
4585
|
}
|
|
3661
4586
|
}
|
|
3662
4587
|
if (existing) {
|
|
3663
4588
|
const account = toAccount(json, existing, "Stored private key");
|
|
3664
|
-
const truncated =
|
|
4589
|
+
const truncated = truncateAddress(account.address);
|
|
3665
4590
|
console.log(` Wallet already configured: ${truncated}
|
|
3666
4591
|
`);
|
|
3667
4592
|
if (!options.force) {
|
|
@@ -3723,7 +4648,7 @@ var setupCommand = new Command9("setup").description("Set up your Zora wallet").
|
|
|
3723
4648
|
address: account.address,
|
|
3724
4649
|
path: getWalletPath()
|
|
3725
4650
|
},
|
|
3726
|
-
|
|
4651
|
+
render: () => {
|
|
3727
4652
|
console.log("\n\u2713 Wallet imported\n");
|
|
3728
4653
|
console.log(` Address: ${account.address}`);
|
|
3729
4654
|
console.log(` Private key: saved to ${getWalletPath()}
|
|
@@ -3758,7 +4683,7 @@ var setupCommand = new Command9("setup").description("Set up your Zora wallet").
|
|
|
3758
4683
|
address: account.address,
|
|
3759
4684
|
path: getWalletPath()
|
|
3760
4685
|
},
|
|
3761
|
-
|
|
4686
|
+
render: () => {
|
|
3762
4687
|
console.log("\n\u2713 Wallet created\n");
|
|
3763
4688
|
console.log(` Address: ${account.address}`);
|
|
3764
4689
|
console.log(` Private key: saved to ${getWalletPath()}
|
|
@@ -3777,8 +4702,8 @@ var setupCommand = new Command9("setup").description("Set up your Zora wallet").
|
|
|
3777
4702
|
});
|
|
3778
4703
|
|
|
3779
4704
|
// src/commands/wallet.ts
|
|
3780
|
-
import { Command as
|
|
3781
|
-
import { privateKeyToAccount as
|
|
4705
|
+
import { Command as Command11 } from "commander";
|
|
4706
|
+
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
3782
4707
|
var resolvePrivateKey = () => {
|
|
3783
4708
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
3784
4709
|
if (envKey) {
|
|
@@ -3790,7 +4715,7 @@ var resolvePrivateKey = () => {
|
|
|
3790
4715
|
}
|
|
3791
4716
|
return void 0;
|
|
3792
4717
|
};
|
|
3793
|
-
var walletCommand = new
|
|
4718
|
+
var walletCommand = new Command11("wallet").description(
|
|
3794
4719
|
"Manage your Zora wallet"
|
|
3795
4720
|
);
|
|
3796
4721
|
walletCommand.command("info").description("Show wallet address and storage location").action(function() {
|
|
@@ -3801,7 +4726,7 @@ walletCommand.command("info").description("Show wallet address and storage locat
|
|
|
3801
4726
|
}
|
|
3802
4727
|
let account;
|
|
3803
4728
|
try {
|
|
3804
|
-
account =
|
|
4729
|
+
account = privateKeyToAccount5(normalizeKey(resolved.key));
|
|
3805
4730
|
} catch {
|
|
3806
4731
|
const msg = resolved.source === "env" ? "ZORA_PRIVATE_KEY is not a valid private key." : "Stored private key is invalid.";
|
|
3807
4732
|
const suggestion = resolved.source === "env" ? void 0 : "Run 'zora setup --force' to replace it.";
|
|
@@ -3810,7 +4735,7 @@ walletCommand.command("info").description("Show wallet address and storage locat
|
|
|
3810
4735
|
const source = resolved.source === "env" ? "env (ZORA_PRIVATE_KEY)" : getWalletPath();
|
|
3811
4736
|
outputData(json, {
|
|
3812
4737
|
json: { address: account.address, source },
|
|
3813
|
-
|
|
4738
|
+
render: () => {
|
|
3814
4739
|
console.log(` Address: ${account.address}`);
|
|
3815
4740
|
console.log(` Source: ${source}`);
|
|
3816
4741
|
}
|
|
@@ -3849,8 +4774,103 @@ walletCommand.command("export").description("Print the raw private key to stdout
|
|
|
3849
4774
|
});
|
|
3850
4775
|
});
|
|
3851
4776
|
|
|
4777
|
+
// src/components/StyledHelp.tsx
|
|
4778
|
+
import { Text as Text10, Box as Box10 } from "ink";
|
|
4779
|
+
|
|
4780
|
+
// src/components/KeyValueTable.tsx
|
|
4781
|
+
import { Text as Text9, Box as Box9 } from "ink";
|
|
4782
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4783
|
+
function KeyValueTable({
|
|
4784
|
+
rows,
|
|
4785
|
+
labelWidth
|
|
4786
|
+
}) {
|
|
4787
|
+
const pad = labelWidth ?? Math.max(0, ...rows.map((r) => r.label.length)) + 2;
|
|
4788
|
+
return /* @__PURE__ */ jsx12(Box9, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
4789
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: row.label.padEnd(pad) }),
|
|
4790
|
+
/* @__PURE__ */ jsx12(Text9, { dimColor: true, children: row.value })
|
|
4791
|
+
] }, i)) });
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
// src/lib/parse-help.ts
|
|
4795
|
+
var DEFAULT_DESC_COLUMN = 38;
|
|
4796
|
+
var TWO_COLUMN_REGEX = /^(.+\S)([ \t]{2,})(\S.*)/m;
|
|
4797
|
+
function getDescriptionColumnOffset(sections) {
|
|
4798
|
+
for (const section of sections) {
|
|
4799
|
+
const match = section.content.match(TWO_COLUMN_REGEX);
|
|
4800
|
+
if (match) {
|
|
4801
|
+
return match[1].length + match[2].length;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
return DEFAULT_DESC_COLUMN;
|
|
4805
|
+
}
|
|
4806
|
+
function parseHelpSections(text) {
|
|
4807
|
+
const sections = [];
|
|
4808
|
+
let currentTitle = "";
|
|
4809
|
+
let currentLines = [];
|
|
4810
|
+
for (const line of text.split("\n")) {
|
|
4811
|
+
const match = line.match(/^([A-Z]\w+):(.*)/);
|
|
4812
|
+
if (match) {
|
|
4813
|
+
if (currentTitle) {
|
|
4814
|
+
const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
|
|
4815
|
+
sections.push({ title: currentTitle, content });
|
|
4816
|
+
}
|
|
4817
|
+
currentTitle = match[1];
|
|
4818
|
+
currentLines = match[2].trim() ? [match[2].trim()] : [];
|
|
4819
|
+
} else if (currentTitle) {
|
|
4820
|
+
currentLines.push(line.startsWith(" ") ? line.slice(2) : line);
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
if (currentTitle) {
|
|
4824
|
+
const content = currentLines.join("\n").replace(/^\n+|\n+$/g, "");
|
|
4825
|
+
sections.push({ title: currentTitle, content });
|
|
4826
|
+
}
|
|
4827
|
+
return sections.filter((s) => s.content);
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// src/components/StyledHelp.tsx
|
|
4831
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4832
|
+
function StyledHelp({
|
|
4833
|
+
sections,
|
|
4834
|
+
header
|
|
4835
|
+
}) {
|
|
4836
|
+
const descriptionColumnOffset = getDescriptionColumnOffset(sections);
|
|
4837
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
|
|
4838
|
+
header,
|
|
4839
|
+
sections.map((section, i) => {
|
|
4840
|
+
const hasTwoColumns = TWO_COLUMN_REGEX.test(section.content);
|
|
4841
|
+
const rows = hasTwoColumns ? section.content.split("\n").map((line) => {
|
|
4842
|
+
const m = line.match(TWO_COLUMN_REGEX);
|
|
4843
|
+
if (m)
|
|
4844
|
+
return {
|
|
4845
|
+
label: m[1],
|
|
4846
|
+
value: m[3][0].toUpperCase() + m[3].slice(1)
|
|
4847
|
+
};
|
|
4848
|
+
return { label: "", value: line.trimStart() };
|
|
4849
|
+
}) : null;
|
|
4850
|
+
return /* @__PURE__ */ jsxs10(
|
|
4851
|
+
Box10,
|
|
4852
|
+
{
|
|
4853
|
+
flexDirection: "column",
|
|
4854
|
+
borderStyle: "single",
|
|
4855
|
+
borderDimColor: true,
|
|
4856
|
+
paddingX: 1,
|
|
4857
|
+
paddingY: 1,
|
|
4858
|
+
children: [
|
|
4859
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: section.title }),
|
|
4860
|
+
rows ? /* @__PURE__ */ jsx13(KeyValueTable, { rows, labelWidth: descriptionColumnOffset }) : /* @__PURE__ */ jsx13(Text10, { children: section.content })
|
|
4861
|
+
]
|
|
4862
|
+
},
|
|
4863
|
+
i
|
|
4864
|
+
);
|
|
4865
|
+
})
|
|
4866
|
+
] });
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
// src/components/StyledHelpHeader.tsx
|
|
4870
|
+
import { Text as Text12, Box as Box12 } from "ink";
|
|
4871
|
+
|
|
3852
4872
|
// src/components/Zorb.tsx
|
|
3853
|
-
import { Text as
|
|
4873
|
+
import { Text as Text11, Box as Box11 } from "ink";
|
|
3854
4874
|
|
|
3855
4875
|
// src/lib/zorb-pixels.ts
|
|
3856
4876
|
function supportsTruecolor() {
|
|
@@ -3995,7 +5015,7 @@ function generateZorbPixels(size) {
|
|
|
3995
5015
|
}
|
|
3996
5016
|
|
|
3997
5017
|
// src/components/Zorb.tsx
|
|
3998
|
-
import { jsx as
|
|
5018
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3999
5019
|
var LOWER_HALF_BLOCK = "\u2584";
|
|
4000
5020
|
var UPPER_HALF_BLOCK = "\u2580";
|
|
4001
5021
|
function rgbString([r, g, b]) {
|
|
@@ -4018,19 +5038,19 @@ function Zorb({ size = 20 }) {
|
|
|
4018
5038
|
const topIsBlack = isBlack(top);
|
|
4019
5039
|
const bottomIsBlack = isBlack(bottom);
|
|
4020
5040
|
if (topIsBlack && bottomIsBlack) {
|
|
4021
|
-
cells.push(/* @__PURE__ */
|
|
5041
|
+
cells.push(/* @__PURE__ */ jsx14(Text11, { children: " " }, x));
|
|
4022
5042
|
} else if (topIsBlack) {
|
|
4023
5043
|
cells.push(
|
|
4024
|
-
/* @__PURE__ */
|
|
5044
|
+
/* @__PURE__ */ jsx14(Text11, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
|
|
4025
5045
|
);
|
|
4026
5046
|
} else if (bottomIsBlack) {
|
|
4027
5047
|
cells.push(
|
|
4028
|
-
/* @__PURE__ */
|
|
5048
|
+
/* @__PURE__ */ jsx14(Text11, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
|
|
4029
5049
|
);
|
|
4030
5050
|
} else {
|
|
4031
5051
|
cells.push(
|
|
4032
|
-
/* @__PURE__ */
|
|
4033
|
-
|
|
5052
|
+
/* @__PURE__ */ jsx14(
|
|
5053
|
+
Text11,
|
|
4034
5054
|
{
|
|
4035
5055
|
backgroundColor: rgbString(top),
|
|
4036
5056
|
color: rgbString(bottom),
|
|
@@ -4041,50 +5061,96 @@ function Zorb({ size = 20 }) {
|
|
|
4041
5061
|
);
|
|
4042
5062
|
}
|
|
4043
5063
|
}
|
|
4044
|
-
rows.push(/* @__PURE__ */
|
|
5064
|
+
rows.push(/* @__PURE__ */ jsx14(Text11, { children: cells }, y));
|
|
4045
5065
|
}
|
|
4046
|
-
return /* @__PURE__ */
|
|
4047
|
-
/* @__PURE__ */
|
|
5066
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
5067
|
+
/* @__PURE__ */ jsx14(Text11, { children: " " }),
|
|
4048
5068
|
rows,
|
|
4049
|
-
/* @__PURE__ */
|
|
5069
|
+
/* @__PURE__ */ jsx14(Text11, { children: " " })
|
|
4050
5070
|
] });
|
|
4051
5071
|
}
|
|
4052
5072
|
|
|
5073
|
+
// src/components/StyledHelpHeader.tsx
|
|
5074
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
5075
|
+
function StyledHelpHeader({
|
|
5076
|
+
sections
|
|
5077
|
+
}) {
|
|
5078
|
+
const descriptionColumnOffset = getDescriptionColumnOffset(sections);
|
|
5079
|
+
return /* @__PURE__ */ jsxs12(
|
|
5080
|
+
Box12,
|
|
5081
|
+
{
|
|
5082
|
+
flexDirection: "row",
|
|
5083
|
+
borderStyle: "single",
|
|
5084
|
+
borderDimColor: true,
|
|
5085
|
+
paddingX: 1,
|
|
5086
|
+
paddingY: 1,
|
|
5087
|
+
children: [
|
|
5088
|
+
/* @__PURE__ */ jsx15(Box12, { flexShrink: 0, width: descriptionColumnOffset, children: /* @__PURE__ */ jsx15(Zorb, { size: 20 }) }),
|
|
5089
|
+
/* @__PURE__ */ jsx15(Box12, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: /* @__PURE__ */ jsx15(Text12, { bold: true, children: "Zora CLI" }) })
|
|
5090
|
+
]
|
|
5091
|
+
}
|
|
5092
|
+
);
|
|
5093
|
+
}
|
|
5094
|
+
|
|
4053
5095
|
// src/index.tsx
|
|
4054
|
-
import { jsx as
|
|
5096
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
4055
5097
|
if (process.env.ZORA_API_TARGET) {
|
|
4056
5098
|
setApiBaseUrl(process.env.ZORA_API_TARGET);
|
|
4057
5099
|
}
|
|
4058
|
-
var version = true ? "0.
|
|
5100
|
+
var version = true ? "0.3.1" : JSON.parse(
|
|
4059
5101
|
readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
|
|
4060
5102
|
).version;
|
|
5103
|
+
function styledHelpWriteOut(showHeader) {
|
|
5104
|
+
return (str) => {
|
|
5105
|
+
if (supportsTruecolor()) {
|
|
5106
|
+
const sections = parseHelpSections(str);
|
|
5107
|
+
if (sections.length > 0) {
|
|
5108
|
+
const header = showHeader ? /* @__PURE__ */ jsx16(StyledHelpHeader, { sections }) : void 0;
|
|
5109
|
+
renderOnce(/* @__PURE__ */ jsx16(StyledHelp, { sections, header }));
|
|
5110
|
+
return;
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
process.stdout.write(str);
|
|
5114
|
+
};
|
|
5115
|
+
}
|
|
4061
5116
|
var buildProgram = () => {
|
|
4062
|
-
const program2 = new
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
);
|
|
5117
|
+
const program2 = new Command12().name("zora").description("A developer CLI for the Zora platform").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
|
|
5118
|
+
const helpWidth = (process.stdout.columns || 80) - 4;
|
|
5119
|
+
program2.configureHelp({ helpWidth });
|
|
5120
|
+
program2.configureOutput({ writeOut: styledHelpWriteOut(true) });
|
|
5121
|
+
program2.action(() => {
|
|
5122
|
+
program2.outputHelp();
|
|
5123
|
+
});
|
|
4070
5124
|
program2.addCommand(authCommand);
|
|
4071
5125
|
program2.addCommand(balanceCommand);
|
|
4072
5126
|
program2.addCommand(buyCommand);
|
|
4073
5127
|
program2.addCommand(exploreCommand);
|
|
4074
5128
|
program2.addCommand(getCommand);
|
|
4075
5129
|
program2.addCommand(priceHistoryCommand);
|
|
5130
|
+
program2.addCommand(profileCommand);
|
|
4076
5131
|
program2.addCommand(setupCommand);
|
|
4077
5132
|
program2.addCommand(walletCommand);
|
|
4078
5133
|
program2.addCommand(sellCommand);
|
|
4079
5134
|
program2.addCommand(sendCommand);
|
|
5135
|
+
const applyToSubcommands = (parent) => {
|
|
5136
|
+
for (const cmd of parent.commands) {
|
|
5137
|
+
cmd.configureHelp({ helpWidth });
|
|
5138
|
+
cmd.configureOutput({ writeOut: styledHelpWriteOut(false) });
|
|
5139
|
+
applyToSubcommands(cmd);
|
|
5140
|
+
}
|
|
5141
|
+
};
|
|
5142
|
+
applyToSubcommands(program2);
|
|
5143
|
+
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
5144
|
+
const expected = actionCommand.registeredArguments.length;
|
|
5145
|
+
if (expected > 0 && actionCommand.args.length === 0) {
|
|
5146
|
+
actionCommand.outputHelp();
|
|
5147
|
+
process.exit(1);
|
|
5148
|
+
}
|
|
5149
|
+
});
|
|
4080
5150
|
return program2;
|
|
4081
5151
|
};
|
|
4082
5152
|
var program = buildProgram();
|
|
4083
5153
|
if (!process.env.VITEST) {
|
|
4084
|
-
const showingHelp = process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h");
|
|
4085
|
-
if (showingHelp && !process.argv.includes("--output") && supportsTruecolor()) {
|
|
4086
|
-
renderOnce(/* @__PURE__ */ jsx11(Zorb, { size: 20 }));
|
|
4087
|
-
}
|
|
4088
5154
|
console.warn(
|
|
4089
5155
|
"\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
|
|
4090
5156
|
);
|