@zoralabs/cli 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.js +2569 -786
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import { Command as
|
|
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,50 @@ 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 fsErrorMessage(err, path) {
|
|
56
|
+
if (!(err instanceof Error)) return String(err);
|
|
57
|
+
const code = err.code;
|
|
58
|
+
if (code === "EACCES") return `Permission denied accessing ${path}.`;
|
|
59
|
+
if (code === "EISDIR")
|
|
60
|
+
return `Expected a file but found a directory at ${path}.`;
|
|
61
|
+
return formatError(err);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/lib/config.ts
|
|
21
65
|
import { homedir, platform } from "os";
|
|
22
66
|
function getConfigDir() {
|
|
23
67
|
if (platform() === "win32") {
|
|
@@ -55,7 +99,7 @@ function readConfig() {
|
|
|
55
99
|
parsed = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
56
100
|
} catch (err) {
|
|
57
101
|
console.error(
|
|
58
|
-
`Warning: could not parse ${CONFIG_FILE}: ${err
|
|
102
|
+
`Warning: could not parse ${CONFIG_FILE}: ${formatError(err)}. Run 'zora auth configure' to fix.`
|
|
59
103
|
);
|
|
60
104
|
configReadOnly = true;
|
|
61
105
|
return { version: CONFIG_VERSION };
|
|
@@ -64,7 +108,7 @@ function readConfig() {
|
|
|
64
108
|
assertVersion(parsed, CONFIG_VERSION, CONFIG_FILE);
|
|
65
109
|
} catch (err) {
|
|
66
110
|
console.error(
|
|
67
|
-
`Warning: ${err
|
|
111
|
+
`Warning: ${formatError(err)}. Delete ${CONFIG_FILE} or run 'zora auth configure' to reset.`
|
|
68
112
|
);
|
|
69
113
|
configReadOnly = true;
|
|
70
114
|
return { version: CONFIG_VERSION };
|
|
@@ -155,7 +199,29 @@ function maskKey(key) {
|
|
|
155
199
|
}
|
|
156
200
|
|
|
157
201
|
// src/lib/output.ts
|
|
158
|
-
var
|
|
202
|
+
var getOutputMode = (cmd, defaultMode) => {
|
|
203
|
+
const globals = cmd.optsWithGlobals();
|
|
204
|
+
const json = globals.json ?? false;
|
|
205
|
+
const live = globals.live ?? false;
|
|
206
|
+
const static_ = globals.static ?? false;
|
|
207
|
+
const set = [
|
|
208
|
+
json && "--json",
|
|
209
|
+
live && "--live",
|
|
210
|
+
static_ && "--static"
|
|
211
|
+
].filter(Boolean);
|
|
212
|
+
if (set.length > 1) {
|
|
213
|
+
return outputErrorAndExit(
|
|
214
|
+
false,
|
|
215
|
+
`${set.join(", ")} cannot be used together.`,
|
|
216
|
+
"Choose one: --json, --live, or --static"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (json) return "json";
|
|
220
|
+
if (live) return "live";
|
|
221
|
+
if (static_) return "static";
|
|
222
|
+
return defaultMode;
|
|
223
|
+
};
|
|
224
|
+
var getJson = (cmd) => cmd.optsWithGlobals().json ?? false;
|
|
159
225
|
var getYes = (cmd) => cmd.optsWithGlobals().yes ?? false;
|
|
160
226
|
var outputJson = (data) => {
|
|
161
227
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -177,8 +243,19 @@ var outputData = (json, opts) => {
|
|
|
177
243
|
if (json) {
|
|
178
244
|
outputJson(opts.json);
|
|
179
245
|
} else {
|
|
180
|
-
opts.
|
|
246
|
+
opts.render();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var getLiveConfig = (cmd, mode) => {
|
|
250
|
+
const live = mode === "live";
|
|
251
|
+
const intervalRaw = parseInt(cmd.opts().refresh, 10);
|
|
252
|
+
const intervalSeconds = isNaN(intervalRaw) || intervalRaw < 5 ? 30 : intervalRaw;
|
|
253
|
+
if (!live && cmd.getOptionValueSource("refresh") === "cli") {
|
|
254
|
+
console.warn(
|
|
255
|
+
"\x1B[33mWarning:\x1B[0m --refresh has no effect without --live"
|
|
256
|
+
);
|
|
181
257
|
}
|
|
258
|
+
return { live, intervalSeconds };
|
|
182
259
|
};
|
|
183
260
|
|
|
184
261
|
// src/lib/prompt.ts
|
|
@@ -264,9 +341,7 @@ var resolveAccount = (json = false) => {
|
|
|
264
341
|
try {
|
|
265
342
|
return privateKeyToAccount(normalizeKey(key));
|
|
266
343
|
} catch (err) {
|
|
267
|
-
console.error(
|
|
268
|
-
`\u2717 Invalid private key: ${err instanceof Error ? err.message : String(err)}`
|
|
269
|
-
);
|
|
344
|
+
console.error(`\u2717 Invalid private key: ${formatError(err)}`);
|
|
270
345
|
console.error(" Run 'zora setup --force' to replace it.");
|
|
271
346
|
return process.exit(1);
|
|
272
347
|
}
|
|
@@ -356,7 +431,7 @@ var getClient = () => {
|
|
|
356
431
|
return client;
|
|
357
432
|
};
|
|
358
433
|
var commonProperties = () => ({
|
|
359
|
-
cli_version: true ? "0.
|
|
434
|
+
cli_version: true ? "0.3.0" : "development",
|
|
360
435
|
os: process.platform,
|
|
361
436
|
arch: process.arch,
|
|
362
437
|
node_version: process.version
|
|
@@ -429,7 +504,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
429
504
|
status: "env_override",
|
|
430
505
|
message: "API key is set via ZORA_API_KEY environment variable."
|
|
431
506
|
},
|
|
432
|
-
|
|
507
|
+
render: () => console.log(
|
|
433
508
|
"API key is set via ZORA_API_KEY environment variable. Unset it to configure manually."
|
|
434
509
|
)
|
|
435
510
|
});
|
|
@@ -457,7 +532,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
457
532
|
saveApiKey(trimmed);
|
|
458
533
|
outputData(json, {
|
|
459
534
|
json: { saved: true, path: getConfigPath() },
|
|
460
|
-
|
|
535
|
+
render: () => console.log(`API key saved to ${getConfigPath()}`)
|
|
461
536
|
});
|
|
462
537
|
track("cli_auth_configure", {
|
|
463
538
|
output_format: json ? "json" : "text"
|
|
@@ -465,7 +540,7 @@ authCommand.command("configure").description("Set your Zora API key").option("--
|
|
|
465
540
|
} catch (err) {
|
|
466
541
|
outputErrorAndExit(
|
|
467
542
|
json,
|
|
468
|
-
`Failed to save API key: ${err
|
|
543
|
+
`Failed to save API key: ${fsErrorMessage(err, getConfigPath())}`
|
|
469
544
|
);
|
|
470
545
|
}
|
|
471
546
|
});
|
|
@@ -475,7 +550,7 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
475
550
|
if (!apiKey) {
|
|
476
551
|
outputData(json, {
|
|
477
552
|
json: { authenticated: false },
|
|
478
|
-
|
|
553
|
+
render: () => {
|
|
479
554
|
console.log(
|
|
480
555
|
"No API key configured. The CLI works without one, but requests are rate-limited."
|
|
481
556
|
);
|
|
@@ -494,7 +569,7 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
494
569
|
const source = getEnvApiKey() ? "env (ZORA_API_KEY)" : getConfigPath();
|
|
495
570
|
outputData(json, {
|
|
496
571
|
json: { authenticated: true, key: maskKey(apiKey), source },
|
|
497
|
-
|
|
572
|
+
render: () => {
|
|
498
573
|
console.log(`Authenticated: ${maskKey(apiKey)}`);
|
|
499
574
|
console.log(`Source: ${source}`);
|
|
500
575
|
}
|
|
@@ -506,86 +581,98 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
506
581
|
});
|
|
507
582
|
});
|
|
508
583
|
|
|
509
|
-
// src/commands/balance.
|
|
510
|
-
import { Command as
|
|
511
|
-
import {
|
|
584
|
+
// src/commands/balance.tsx
|
|
585
|
+
import { Command as Command2 } from "commander";
|
|
586
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
587
|
+
import { getProfileBalances, setApiKey } from "@zoralabs/coins-sdk";
|
|
512
588
|
|
|
513
589
|
// src/lib/render.tsx
|
|
514
|
-
import { renderToString } from "ink";
|
|
590
|
+
import { render, renderToString } from "ink";
|
|
515
591
|
var renderOnce = (element) => {
|
|
516
|
-
const
|
|
592
|
+
const columns = process.stdout.columns || 80;
|
|
593
|
+
const output = renderToString(element, { columns });
|
|
517
594
|
console.log(output);
|
|
518
595
|
};
|
|
596
|
+
var renderLive = async (element) => {
|
|
597
|
+
const instance = render(element);
|
|
598
|
+
await instance.waitUntilExit();
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/components/BalanceView.tsx
|
|
602
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef } from "react";
|
|
603
|
+
import { Box as Box2, Text as Text2, useInput, useApp } from "ink";
|
|
604
|
+
import Spinner from "ink-spinner";
|
|
519
605
|
|
|
520
606
|
// src/components/table.tsx
|
|
521
607
|
import { Box, Text } from "ink";
|
|
522
608
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
609
|
+
var PADDING_LEFT = 1;
|
|
523
610
|
var truncate = (str, max) => {
|
|
524
611
|
if (str.length <= max) return str;
|
|
525
612
|
return str.slice(0, max - 1) + "\u2026";
|
|
526
613
|
};
|
|
527
|
-
var
|
|
614
|
+
var computeColumnWidths = (columns, fullWidth) => {
|
|
615
|
+
const baseWidths = columns.map((col) => col.width);
|
|
616
|
+
const totalBase = baseWidths.reduce((sum, w) => sum + w, 0);
|
|
617
|
+
if (!fullWidth) return baseWidths;
|
|
618
|
+
const width = process.stdout.columns ?? 80;
|
|
619
|
+
const available = width - PADDING_LEFT;
|
|
620
|
+
if (available <= totalBase) return baseWidths;
|
|
621
|
+
const computed = baseWidths.map(
|
|
622
|
+
(w) => Math.max(1, Math.floor(w / totalBase * available))
|
|
623
|
+
);
|
|
624
|
+
let remainder = available - computed.reduce((sum, w) => sum + w, 0);
|
|
625
|
+
for (let i = 0; i < computed.length && remainder > 0; i++) {
|
|
626
|
+
computed[i]++;
|
|
627
|
+
remainder--;
|
|
628
|
+
}
|
|
629
|
+
return computed;
|
|
630
|
+
};
|
|
631
|
+
var Table = ({
|
|
528
632
|
columns,
|
|
529
633
|
data,
|
|
530
634
|
title,
|
|
531
|
-
subtitle
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
getNewTrends,
|
|
568
|
-
getTopVolumeTrends24h,
|
|
569
|
-
getTrendingAll,
|
|
570
|
-
getTrendingCreators,
|
|
571
|
-
getTrendingPosts,
|
|
572
|
-
getTrendingTrends
|
|
573
|
-
} from "@zoralabs/coins-sdk";
|
|
635
|
+
subtitle,
|
|
636
|
+
fullWidth = true,
|
|
637
|
+
footer,
|
|
638
|
+
selectedRow
|
|
639
|
+
}) => {
|
|
640
|
+
const widths = computeColumnWidths(columns, fullWidth);
|
|
641
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
|
|
642
|
+
title && /* @__PURE__ */ jsxs(Box, { paddingLeft: PADDING_LEFT, marginBottom: 1, children: [
|
|
643
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: title }),
|
|
644
|
+
subtitle && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
645
|
+
" ",
|
|
646
|
+
subtitle
|
|
647
|
+
] })
|
|
648
|
+
] }),
|
|
649
|
+
/* @__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)) }),
|
|
650
|
+
data.map((row, i) => {
|
|
651
|
+
const isSelected = selectedRow === i;
|
|
652
|
+
return /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, colIdx) => {
|
|
653
|
+
const colWidth = widths[colIdx];
|
|
654
|
+
const value = col.noTruncate ? col.accessor(row) : truncate(col.accessor(row), colWidth - 2);
|
|
655
|
+
const colorName = col.color?.(row);
|
|
656
|
+
return /* @__PURE__ */ jsx(Box, { width: colWidth, children: /* @__PURE__ */ jsx(
|
|
657
|
+
Text,
|
|
658
|
+
{
|
|
659
|
+
color: colorName,
|
|
660
|
+
bold: isSelected,
|
|
661
|
+
inverse: isSelected,
|
|
662
|
+
wrap: col.noTruncate ? "wrap" : "truncate",
|
|
663
|
+
children: value
|
|
664
|
+
}
|
|
665
|
+
) }, col.header);
|
|
666
|
+
}) }, i);
|
|
667
|
+
}),
|
|
668
|
+
footer && /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: footer }) })
|
|
669
|
+
] });
|
|
670
|
+
};
|
|
574
671
|
|
|
575
672
|
// src/lib/format.ts
|
|
576
673
|
import { format, formatDistanceStrict } from "date-fns";
|
|
577
674
|
import { formatEther } from "viem";
|
|
578
|
-
|
|
579
|
-
dim: ["\x1B[2m", "\x1B[22m"],
|
|
580
|
-
bold: ["\x1B[1m", "\x1B[22m"]
|
|
581
|
-
};
|
|
582
|
-
function styledText(text, style) {
|
|
583
|
-
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
584
|
-
if (!useColor) return text;
|
|
585
|
-
const [open, close] = ANSI_CODES[style];
|
|
586
|
-
return `${open}${text}${close}`;
|
|
587
|
-
}
|
|
588
|
-
function formatCurrency(value) {
|
|
675
|
+
function formatCompactUsd(value) {
|
|
589
676
|
if (!value || Number(value) === 0) return "$0";
|
|
590
677
|
return new Intl.NumberFormat("en-US", {
|
|
591
678
|
style: "currency",
|
|
@@ -640,247 +727,14 @@ var formatEthDisplay = (wei) => {
|
|
|
640
727
|
const trimmed = parts[1].replace(/0+$/, "") || "0";
|
|
641
728
|
return `${parts[0]}.${trimmed}`;
|
|
642
729
|
};
|
|
730
|
+
var truncateAddress = (address) => `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
643
731
|
var formatCoinsDisplay = (coinsOut) => new Intl.NumberFormat("en-US", {
|
|
644
732
|
maximumFractionDigits: 2
|
|
645
733
|
}).format(Number(coinsOut));
|
|
646
734
|
|
|
647
|
-
// src/lib/types.ts
|
|
648
|
-
var SORT_LABELS = {
|
|
649
|
-
mcap: "Top by Market Cap",
|
|
650
|
-
volume: "Top by 24h Volume",
|
|
651
|
-
new: "New",
|
|
652
|
-
gainers: "Top Gainers (24h)",
|
|
653
|
-
"last-traded": "Last Traded",
|
|
654
|
-
"last-traded-unique": "Last Traded (Unique)",
|
|
655
|
-
trending: "Trending",
|
|
656
|
-
featured: "Featured"
|
|
657
|
-
};
|
|
658
|
-
var TYPE_LABELS = {
|
|
659
|
-
all: "all",
|
|
660
|
-
trend: "trends",
|
|
661
|
-
"creator-coin": "creator coins",
|
|
662
|
-
post: "posts"
|
|
663
|
-
};
|
|
664
|
-
var COIN_TYPE_DISPLAY = {
|
|
665
|
-
CONTENT: "post",
|
|
666
|
-
CREATOR: "creator-coin",
|
|
667
|
-
TREND: "trend"
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
// src/commands/explore.tsx
|
|
671
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
672
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
673
|
-
var QUERY_MAP = {
|
|
674
|
-
mcap: {
|
|
675
|
-
all: getMostValuableAll,
|
|
676
|
-
trend: getMostValuableTrends,
|
|
677
|
-
"creator-coin": getMostValuableCreatorCoins,
|
|
678
|
-
post: getCoinsMostValuable
|
|
679
|
-
},
|
|
680
|
-
volume: {
|
|
681
|
-
all: getExploreTopVolumeAll24h,
|
|
682
|
-
trend: getTopVolumeTrends24h,
|
|
683
|
-
"creator-coin": getExploreTopVolumeCreators24h,
|
|
684
|
-
post: getCoinsTopVolume24h
|
|
685
|
-
},
|
|
686
|
-
new: {
|
|
687
|
-
all: getExploreNewAll,
|
|
688
|
-
trend: getNewTrends,
|
|
689
|
-
"creator-coin": getCreatorCoins,
|
|
690
|
-
post: getCoinsNew
|
|
691
|
-
},
|
|
692
|
-
gainers: {
|
|
693
|
-
post: getCoinsTopGainers
|
|
694
|
-
},
|
|
695
|
-
"last-traded": {
|
|
696
|
-
post: getCoinsLastTraded
|
|
697
|
-
},
|
|
698
|
-
"last-traded-unique": {
|
|
699
|
-
post: getCoinsLastTradedUnique
|
|
700
|
-
},
|
|
701
|
-
trending: {
|
|
702
|
-
all: getTrendingAll,
|
|
703
|
-
trend: getTrendingTrends,
|
|
704
|
-
"creator-coin": getTrendingCreators,
|
|
705
|
-
post: getTrendingPosts
|
|
706
|
-
},
|
|
707
|
-
featured: {
|
|
708
|
-
"creator-coin": getExploreFeaturedCreators,
|
|
709
|
-
post: getExploreFeaturedVideos
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
var formatCompactCurrency = (value) => {
|
|
713
|
-
if (!value) return "$0";
|
|
714
|
-
return new Intl.NumberFormat("en-US", {
|
|
715
|
-
style: "currency",
|
|
716
|
-
currency: "USD",
|
|
717
|
-
notation: "compact",
|
|
718
|
-
maximumFractionDigits: 1
|
|
719
|
-
}).format(Number(value));
|
|
720
|
-
};
|
|
721
|
-
var formatChange = (marketCap, delta) => {
|
|
722
|
-
if (!delta || !marketCap) return "-";
|
|
723
|
-
const cap = Number(marketCap);
|
|
724
|
-
const d = Number(delta);
|
|
725
|
-
if (cap === 0) return "-";
|
|
726
|
-
const prevCap = cap - d;
|
|
727
|
-
if (prevCap === 0) return "-";
|
|
728
|
-
const pct = d / prevCap * 100;
|
|
729
|
-
const sign = pct >= 0 ? "+" : "";
|
|
730
|
-
return `${sign}${pct.toFixed(1)}%`;
|
|
731
|
-
};
|
|
732
|
-
var changeColor = (row) => {
|
|
733
|
-
if (!row.marketCapDelta24h || !row.marketCap) return void 0;
|
|
734
|
-
const cap = Number(row.marketCap);
|
|
735
|
-
const d = Number(row.marketCapDelta24h);
|
|
736
|
-
if (cap === 0 || cap - d === 0) return void 0;
|
|
737
|
-
const pct = d / (cap - d) * 100;
|
|
738
|
-
if (pct > 0) return "green";
|
|
739
|
-
if (pct < 0) return "red";
|
|
740
|
-
return void 0;
|
|
741
|
-
};
|
|
742
|
-
var SORT_OPTIONS = Object.keys(SORT_LABELS).join(", ");
|
|
743
|
-
var rankColumn = {
|
|
744
|
-
header: "#",
|
|
745
|
-
width: 5,
|
|
746
|
-
accessor: (r) => String(r.rank)
|
|
747
|
-
};
|
|
748
|
-
var exploreColumns = [
|
|
749
|
-
{ header: "Name", width: 27, accessor: (r) => r.name ?? "Unknown" },
|
|
750
|
-
{ header: "Address", width: 44, accessor: (r) => r.address ?? "" },
|
|
751
|
-
{
|
|
752
|
-
header: "Type",
|
|
753
|
-
width: 16,
|
|
754
|
-
accessor: (r) => COIN_TYPE_DISPLAY[r.coinType ?? ""] ?? r.coinType ?? ""
|
|
755
|
-
},
|
|
756
|
-
{
|
|
757
|
-
header: "Market Cap",
|
|
758
|
-
width: 14,
|
|
759
|
-
accessor: (r) => formatCompactCurrency(r.marketCap)
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
header: "24h Vol",
|
|
763
|
-
width: 14,
|
|
764
|
-
accessor: (r) => formatCompactCurrency(r.volume24h)
|
|
765
|
-
},
|
|
766
|
-
{
|
|
767
|
-
header: "24h Change",
|
|
768
|
-
width: 12,
|
|
769
|
-
accessor: (r) => formatChange(r.marketCap, r.marketCapDelta24h),
|
|
770
|
-
color: changeColor
|
|
771
|
-
}
|
|
772
|
-
];
|
|
773
|
-
var exploreCommand = new Command2("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "mcap").option(
|
|
774
|
-
"--type <type>",
|
|
775
|
-
"Filter by type: all, trend, creator-coin, post (availability varies by sort)",
|
|
776
|
-
"post"
|
|
777
|
-
).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
|
|
778
|
-
const json = getJson(this);
|
|
779
|
-
const sort = opts.sort;
|
|
780
|
-
const type = opts.type;
|
|
781
|
-
const limit = parseInt(opts.limit, 10);
|
|
782
|
-
const after = opts.after;
|
|
783
|
-
if (isNaN(limit) || limit <= 0 || limit > 20) {
|
|
784
|
-
outputErrorAndExit(
|
|
785
|
-
json,
|
|
786
|
-
`Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
|
|
787
|
-
"Usage: zora explore --limit 10"
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
if (!QUERY_MAP[sort]) {
|
|
791
|
-
outputErrorAndExit(
|
|
792
|
-
json,
|
|
793
|
-
`Invalid --sort value: ${sort}.`,
|
|
794
|
-
`Supported: ${SORT_OPTIONS}`
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
if (!QUERY_MAP[sort][type]) {
|
|
798
|
-
const supported = Object.keys(QUERY_MAP[sort]);
|
|
799
|
-
outputErrorAndExit(
|
|
800
|
-
json,
|
|
801
|
-
`Invalid --type for --sort ${sort}.`,
|
|
802
|
-
`Supported: ${supported.join(", ")}`
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
const apiKey = getApiKey();
|
|
806
|
-
if (apiKey) {
|
|
807
|
-
setApiKey(apiKey);
|
|
808
|
-
}
|
|
809
|
-
const queryFn = QUERY_MAP[sort][type];
|
|
810
|
-
let response;
|
|
811
|
-
try {
|
|
812
|
-
response = await queryFn({ count: limit, after });
|
|
813
|
-
} catch (err) {
|
|
814
|
-
outputErrorAndExit(
|
|
815
|
-
json,
|
|
816
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
if (response.error) {
|
|
820
|
-
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
821
|
-
outputErrorAndExit(json, `API error: ${msg}`);
|
|
822
|
-
}
|
|
823
|
-
const edges = response.data?.exploreList?.edges ?? [];
|
|
824
|
-
const coins = edges.map((e) => e.node);
|
|
825
|
-
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
826
|
-
if (coins.length === 0) {
|
|
827
|
-
outputData(json, {
|
|
828
|
-
json: { coins: [], pageInfo: pageInfo ?? null },
|
|
829
|
-
table: () => {
|
|
830
|
-
renderOnce(
|
|
831
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 1, marginTop: 1, children: [
|
|
832
|
-
/* @__PURE__ */ jsx2(Text2, { children: "No coins found." }),
|
|
833
|
-
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
834
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Try a different sort or type (defaults to posts):" }),
|
|
835
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " zora explore --sort volume --type all" }),
|
|
836
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " zora explore --sort new --type all" })
|
|
837
|
-
] })
|
|
838
|
-
] })
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
});
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
const rankedCoins = coins.map((c, i) => ({ ...c, rank: i + 1 }));
|
|
845
|
-
const columns = after ? exploreColumns : [rankColumn, ...exploreColumns];
|
|
846
|
-
const title = type !== "all" ? `${SORT_LABELS[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS[sort];
|
|
847
|
-
const subtitle = `${coins.length} result${coins.length !== 1 ? "s" : ""}`;
|
|
848
|
-
outputData(json, {
|
|
849
|
-
json: { coins, pageInfo: pageInfo ?? null },
|
|
850
|
-
table: () => {
|
|
851
|
-
renderOnce(
|
|
852
|
-
/* @__PURE__ */ jsx2(
|
|
853
|
-
TableComponent,
|
|
854
|
-
{
|
|
855
|
-
columns,
|
|
856
|
-
data: rankedCoins,
|
|
857
|
-
title,
|
|
858
|
-
subtitle
|
|
859
|
-
}
|
|
860
|
-
)
|
|
861
|
-
);
|
|
862
|
-
if (pageInfo?.hasNextPage && pageInfo.endCursor) {
|
|
863
|
-
console.log(
|
|
864
|
-
`
|
|
865
|
-
${styledText(`Next page: zora explore --sort ${sort} --type ${type} --limit ${limit} --after ${pageInfo.endCursor}`, "dim")}`
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
track("cli_explore", {
|
|
871
|
-
sort,
|
|
872
|
-
type,
|
|
873
|
-
limit,
|
|
874
|
-
paginated: after !== void 0,
|
|
875
|
-
result_count: coins.length,
|
|
876
|
-
has_next_page: pageInfo?.hasNextPage ?? false,
|
|
877
|
-
output_format: json ? "json" : "text"
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
|
|
881
735
|
// src/lib/balance-format.ts
|
|
882
736
|
var COIN_DECIMALS = 18;
|
|
883
|
-
var
|
|
737
|
+
var parseRawBalance = (rawBalance) => Number(normalizeTokenAmount(rawBalance));
|
|
884
738
|
var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
|
|
885
739
|
try {
|
|
886
740
|
const value = BigInt(rawBalance);
|
|
@@ -895,20 +749,20 @@ var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
|
|
|
895
749
|
return rawBalance;
|
|
896
750
|
}
|
|
897
751
|
};
|
|
898
|
-
var
|
|
752
|
+
var formatBalanceAsUsd = (balance, priceInUsdc) => {
|
|
899
753
|
if (!priceInUsdc) return "-";
|
|
900
|
-
const value =
|
|
754
|
+
const value = parseRawBalance(balance) * Number(priceInUsdc);
|
|
901
755
|
if (value < 0.01) return "<$0.01";
|
|
902
756
|
return formatUsd(value);
|
|
903
757
|
};
|
|
904
758
|
var formatBalance = (balance) => {
|
|
905
|
-
const n =
|
|
759
|
+
const n = parseRawBalance(balance);
|
|
906
760
|
if (n === 0) return "0";
|
|
907
761
|
if (n < 1e-3) return "<0.001";
|
|
908
762
|
if (n < 1) return n.toFixed(4);
|
|
909
763
|
return new Intl.NumberFormat("en-US", {
|
|
910
764
|
notation: "compact",
|
|
911
|
-
compactDisplay: "
|
|
765
|
+
compactDisplay: "short",
|
|
912
766
|
maximumFractionDigits: 1
|
|
913
767
|
}).format(n);
|
|
914
768
|
};
|
|
@@ -918,27 +772,267 @@ var trimTrailingZeros = (value) => {
|
|
|
918
772
|
return trimmed || "0";
|
|
919
773
|
};
|
|
920
774
|
|
|
921
|
-
// src/lib/
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
var TRACKED_TOKENS = [
|
|
775
|
+
// src/lib/balance-columns.tsx
|
|
776
|
+
var SORT_LABELS = {
|
|
777
|
+
"usd-value": "USD Value",
|
|
778
|
+
balance: "Balance",
|
|
779
|
+
"market-cap": "Market Cap",
|
|
780
|
+
"price-change": "Price Change"
|
|
781
|
+
};
|
|
782
|
+
var walletColumns = [
|
|
783
|
+
{ header: "Name", width: 14, accessor: (row) => row.name },
|
|
931
784
|
{
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
priceAddress: WETH_ADDRESS,
|
|
937
|
-
isNative: true
|
|
785
|
+
header: "Symbol",
|
|
786
|
+
width: 10,
|
|
787
|
+
noTruncate: true,
|
|
788
|
+
accessor: (row) => row.symbol
|
|
938
789
|
},
|
|
939
|
-
{
|
|
940
|
-
|
|
941
|
-
|
|
790
|
+
{ header: "Balance", width: 20, accessor: (row) => row.balance },
|
|
791
|
+
{ header: "USD Value", width: 16, accessor: (row) => row.usdValue }
|
|
792
|
+
];
|
|
793
|
+
var balanceColumns = [
|
|
794
|
+
{ header: "#", width: 5, accessor: (row) => String(row.rank) },
|
|
795
|
+
{
|
|
796
|
+
header: "Name",
|
|
797
|
+
width: 24,
|
|
798
|
+
accessor: (row) => row.coin?.name ?? "Unknown"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
header: "Symbol",
|
|
802
|
+
width: 12,
|
|
803
|
+
noTruncate: true,
|
|
804
|
+
accessor: (row) => row.coin?.symbol ?? ""
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
header: "Balance",
|
|
808
|
+
width: 14,
|
|
809
|
+
accessor: (row) => formatBalance(row.balance)
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
header: "USD Value",
|
|
813
|
+
width: 14,
|
|
814
|
+
accessor: (row) => formatBalanceAsUsd(row.balance, row.coin?.tokenPrice?.priceInUsdc)
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
header: "Market Cap",
|
|
818
|
+
width: 14,
|
|
819
|
+
accessor: (row) => formatCompactUsd(row.coin?.marketCap)
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
header: "24h Change",
|
|
823
|
+
width: 12,
|
|
824
|
+
accessor: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).text,
|
|
825
|
+
color: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).color
|
|
826
|
+
}
|
|
827
|
+
];
|
|
828
|
+
|
|
829
|
+
// src/hooks/use-auto-refresh.ts
|
|
830
|
+
import { useState, useEffect, useCallback } from "react";
|
|
831
|
+
var useAutoRefresh = (intervalSeconds, enabled) => {
|
|
832
|
+
const [refreshCount, setRefreshCount] = useState(0);
|
|
833
|
+
const [secondsUntilRefresh, setSecondsUntilRefresh] = useState(intervalSeconds);
|
|
834
|
+
const [resetCount, setResetCount] = useState(0);
|
|
835
|
+
const triggerManualRefresh = useCallback(() => {
|
|
836
|
+
if (!enabled) return;
|
|
837
|
+
setRefreshCount((c) => c + 1);
|
|
838
|
+
setSecondsUntilRefresh(intervalSeconds);
|
|
839
|
+
setResetCount((c) => c + 1);
|
|
840
|
+
}, [enabled, intervalSeconds]);
|
|
841
|
+
useEffect(() => {
|
|
842
|
+
if (!enabled) return;
|
|
843
|
+
setSecondsUntilRefresh(intervalSeconds);
|
|
844
|
+
const ticker = setInterval(() => {
|
|
845
|
+
setSecondsUntilRefresh((prev) => {
|
|
846
|
+
if (prev <= 1) {
|
|
847
|
+
setRefreshCount((c) => c + 1);
|
|
848
|
+
return intervalSeconds;
|
|
849
|
+
}
|
|
850
|
+
return prev - 1;
|
|
851
|
+
});
|
|
852
|
+
}, 1e3);
|
|
853
|
+
return () => clearInterval(ticker);
|
|
854
|
+
}, [enabled, intervalSeconds, resetCount]);
|
|
855
|
+
if (!enabled) {
|
|
856
|
+
return { refreshCount: 0, secondsUntilRefresh: 0, triggerManualRefresh };
|
|
857
|
+
}
|
|
858
|
+
return { refreshCount, secondsUntilRefresh, triggerManualRefresh };
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// src/components/BalanceView.tsx
|
|
862
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
863
|
+
var BalanceView = ({
|
|
864
|
+
fetchData,
|
|
865
|
+
sort,
|
|
866
|
+
mode = "full",
|
|
867
|
+
initialCursor,
|
|
868
|
+
autoRefresh = false,
|
|
869
|
+
intervalSeconds = 30
|
|
870
|
+
}) => {
|
|
871
|
+
const { exit } = useApp();
|
|
872
|
+
const [loading, setLoading] = useState2(true);
|
|
873
|
+
const [isRefreshing, setIsRefreshing] = useState2(false);
|
|
874
|
+
const [error, setError] = useState2(null);
|
|
875
|
+
const [data, setData] = useState2(null);
|
|
876
|
+
const paginated = mode === "coins";
|
|
877
|
+
const [page, setPage] = useState2(1);
|
|
878
|
+
const [cursorHistory, setCursorHistory] = useState2(
|
|
879
|
+
[]
|
|
880
|
+
);
|
|
881
|
+
const [currentCursor, setCurrentCursor] = useState2(
|
|
882
|
+
initialCursor
|
|
883
|
+
);
|
|
884
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
885
|
+
const [manualRefreshCount, setManualRefreshCount] = useState2(0);
|
|
886
|
+
const hasLoadedOnce = useRef(false);
|
|
887
|
+
const load = useCallback2(
|
|
888
|
+
async (cursor) => {
|
|
889
|
+
if (hasLoadedOnce.current) {
|
|
890
|
+
setIsRefreshing(true);
|
|
891
|
+
} else {
|
|
892
|
+
setLoading(true);
|
|
893
|
+
}
|
|
894
|
+
setError(null);
|
|
895
|
+
try {
|
|
896
|
+
const result = await fetchData(cursor);
|
|
897
|
+
setData(result);
|
|
898
|
+
hasLoadedOnce.current = true;
|
|
899
|
+
} catch (err) {
|
|
900
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
901
|
+
}
|
|
902
|
+
setLoading(false);
|
|
903
|
+
setIsRefreshing(false);
|
|
904
|
+
},
|
|
905
|
+
[fetchData]
|
|
906
|
+
);
|
|
907
|
+
useEffect2(() => {
|
|
908
|
+
load(currentCursor);
|
|
909
|
+
}, [load, refreshCount, manualRefreshCount, currentCursor]);
|
|
910
|
+
useInput((input, key) => {
|
|
911
|
+
if (input === "q" || key.escape) {
|
|
912
|
+
exit();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (loading) return;
|
|
916
|
+
if (input === "r") {
|
|
917
|
+
triggerManualRefresh();
|
|
918
|
+
setManualRefreshCount((c) => c + 1);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (!paginated) return;
|
|
922
|
+
const canGoNext = data?.pageInfo?.hasNextPage && data.pageInfo.endCursor;
|
|
923
|
+
const canGoPrev = cursorHistory.length > 0;
|
|
924
|
+
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
925
|
+
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
926
|
+
setCurrentCursor(data.pageInfo.endCursor);
|
|
927
|
+
setPage((p) => p + 1);
|
|
928
|
+
}
|
|
929
|
+
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
930
|
+
const prev = cursorHistory[cursorHistory.length - 1];
|
|
931
|
+
setCursorHistory((h) => h.slice(0, -1));
|
|
932
|
+
setCurrentCursor(prev);
|
|
933
|
+
setPage((p) => p - 1);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
if (error && !data) {
|
|
937
|
+
return /* @__PURE__ */ jsxs2(
|
|
938
|
+
Box2,
|
|
939
|
+
{
|
|
940
|
+
flexDirection: "column",
|
|
941
|
+
paddingLeft: 1,
|
|
942
|
+
paddingTop: 1,
|
|
943
|
+
paddingBottom: 1,
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
946
|
+
"Error: ",
|
|
947
|
+
error
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Press q to exit" }) })
|
|
950
|
+
]
|
|
951
|
+
}
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
if (loading && !data) {
|
|
955
|
+
return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
956
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
957
|
+
" Loading\u2026"
|
|
958
|
+
] }) });
|
|
959
|
+
}
|
|
960
|
+
if (!data) return null;
|
|
961
|
+
const hints = [];
|
|
962
|
+
if (paginated && cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
963
|
+
if (paginated && data?.pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
964
|
+
hints.push("r refresh");
|
|
965
|
+
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
966
|
+
hints.push("q quit");
|
|
967
|
+
const footer = hints.join(" \xB7 ");
|
|
968
|
+
const showWallet = mode === "full" || mode === "wallet";
|
|
969
|
+
const showCoins = mode === "full" || mode === "coins";
|
|
970
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
971
|
+
isRefreshing && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
972
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
973
|
+
" Refreshing\u2026"
|
|
974
|
+
] }) }),
|
|
975
|
+
showWallet && /* @__PURE__ */ jsx2(
|
|
976
|
+
Table,
|
|
977
|
+
{
|
|
978
|
+
columns: walletColumns,
|
|
979
|
+
data: data.walletBalances,
|
|
980
|
+
title: "Wallet"
|
|
981
|
+
}
|
|
982
|
+
),
|
|
983
|
+
showCoins && data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs2(
|
|
984
|
+
Box2,
|
|
985
|
+
{
|
|
986
|
+
flexDirection: "column",
|
|
987
|
+
paddingLeft: 1,
|
|
988
|
+
paddingTop: 1,
|
|
989
|
+
paddingBottom: 1,
|
|
990
|
+
children: [
|
|
991
|
+
/* @__PURE__ */ jsx2(Text2, { children: "No coin balances found." }),
|
|
992
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
993
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
994
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
995
|
+
" zora buy ",
|
|
996
|
+
"<address>",
|
|
997
|
+
" --eth 0.001"
|
|
998
|
+
] })
|
|
999
|
+
] })
|
|
1000
|
+
]
|
|
1001
|
+
}
|
|
1002
|
+
) : showCoins ? /* @__PURE__ */ jsx2(
|
|
1003
|
+
Table,
|
|
1004
|
+
{
|
|
1005
|
+
columns: balanceColumns,
|
|
1006
|
+
data: data.rankedBalances,
|
|
1007
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1008
|
+
subtitle: paginated ? `Page ${page} \xB7 ${data.rankedBalances.length} result${data.rankedBalances.length !== 1 ? "s" : ""}` : `${data.rankedBalances.length} of ${data.total}`
|
|
1009
|
+
}
|
|
1010
|
+
) : null,
|
|
1011
|
+
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: footer }) })
|
|
1012
|
+
] });
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
// src/lib/wallet-balances.ts
|
|
1016
|
+
import { getTokenInfo } from "@zoralabs/coins-sdk";
|
|
1017
|
+
import {
|
|
1018
|
+
createPublicClient as createPublicClient2,
|
|
1019
|
+
erc20Abi,
|
|
1020
|
+
formatUnits,
|
|
1021
|
+
http
|
|
1022
|
+
} from "viem";
|
|
1023
|
+
import { base as base2 } from "viem/chains";
|
|
1024
|
+
var TRACKED_TOKENS = [
|
|
1025
|
+
{
|
|
1026
|
+
name: "Ether",
|
|
1027
|
+
symbol: "ETH",
|
|
1028
|
+
address: WETH_ADDRESS,
|
|
1029
|
+
decimals: 18,
|
|
1030
|
+
priceAddress: WETH_ADDRESS,
|
|
1031
|
+
isNative: true
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "USD Coin",
|
|
1035
|
+
symbol: "USDC",
|
|
942
1036
|
address: USDC_ADDRESS,
|
|
943
1037
|
decimals: USDC_DECIMALS,
|
|
944
1038
|
priceAddress: USDC_ADDRESS,
|
|
@@ -958,7 +1052,7 @@ var fetchTokenPriceUsd = async (address, chainId = BASE_CHAIN_ID) => {
|
|
|
958
1052
|
return res.data?.erc20Token?.currency?.priceUsd ? Number(res.data.erc20Token.currency.priceUsd) : null;
|
|
959
1053
|
} catch (err) {
|
|
960
1054
|
console.warn(
|
|
961
|
-
`Warning: failed to fetch price for ${address}: ${
|
|
1055
|
+
`Warning: failed to fetch price for ${address}: ${formatError(err)}`
|
|
962
1056
|
);
|
|
963
1057
|
return null;
|
|
964
1058
|
}
|
|
@@ -1033,78 +1127,21 @@ var fetchWalletBalances = async (walletAddress) => {
|
|
|
1033
1127
|
return { walletBalances, walletBalancesJson };
|
|
1034
1128
|
};
|
|
1035
1129
|
|
|
1036
|
-
// src/commands/balance.
|
|
1130
|
+
// src/commands/balance.tsx
|
|
1131
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1037
1132
|
var SORT_MAP = {
|
|
1038
1133
|
"usd-value": "USD_VALUE",
|
|
1039
1134
|
balance: "BALANCE",
|
|
1040
1135
|
"market-cap": "MARKET_CAP",
|
|
1041
1136
|
"price-change": "PRICE_CHANGE"
|
|
1042
1137
|
};
|
|
1043
|
-
var
|
|
1044
|
-
"usd-value": "USD Value",
|
|
1045
|
-
balance: "Balance",
|
|
1046
|
-
"market-cap": "Market Cap",
|
|
1047
|
-
"price-change": "Price Change"
|
|
1048
|
-
};
|
|
1049
|
-
var SORT_OPTIONS2 = Object.keys(SORT_MAP).join(", ");
|
|
1138
|
+
var SORT_OPTIONS = Object.keys(SORT_LABELS).join(", ");
|
|
1050
1139
|
var extractErrorMessage = (error) => {
|
|
1051
1140
|
if (typeof error === "object" && error !== null && "error" in error) {
|
|
1052
1141
|
return String(error.error);
|
|
1053
1142
|
}
|
|
1054
1143
|
return JSON.stringify(error);
|
|
1055
1144
|
};
|
|
1056
|
-
var changeColor2 = (row) => {
|
|
1057
|
-
if (!row.coin?.marketCap || !row.coin.marketCapDelta24h) return void 0;
|
|
1058
|
-
const cap = Number(row.coin.marketCap);
|
|
1059
|
-
const d = Number(row.coin.marketCapDelta24h);
|
|
1060
|
-
if (cap === 0 || cap - d === 0) return void 0;
|
|
1061
|
-
const pct = d / (cap - d) * 100;
|
|
1062
|
-
if (pct > 0) return "green";
|
|
1063
|
-
if (pct < 0) return "red";
|
|
1064
|
-
return void 0;
|
|
1065
|
-
};
|
|
1066
|
-
var walletColumns = [
|
|
1067
|
-
{ header: "Name", width: 14, accessor: (row) => row.name },
|
|
1068
|
-
{
|
|
1069
|
-
header: "Symbol",
|
|
1070
|
-
width: 10,
|
|
1071
|
-
noTruncate: true,
|
|
1072
|
-
accessor: (row) => row.symbol
|
|
1073
|
-
},
|
|
1074
|
-
{ header: "Balance", width: 20, accessor: (row) => row.balance },
|
|
1075
|
-
{ header: "USD Value", width: 16, accessor: (row) => row.usdValue }
|
|
1076
|
-
];
|
|
1077
|
-
var balanceColumns = [
|
|
1078
|
-
{ header: "#", width: 5, accessor: (row) => String(row.rank) },
|
|
1079
|
-
{ header: "Name", width: 24, accessor: (row) => row.coin?.name ?? "Unknown" },
|
|
1080
|
-
{
|
|
1081
|
-
header: "Symbol",
|
|
1082
|
-
width: 12,
|
|
1083
|
-
noTruncate: true,
|
|
1084
|
-
accessor: (row) => row.coin?.symbol ?? ""
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
header: "Balance",
|
|
1088
|
-
width: 14,
|
|
1089
|
-
accessor: (row) => formatBalance(row.balance)
|
|
1090
|
-
},
|
|
1091
|
-
{
|
|
1092
|
-
header: "USD Value",
|
|
1093
|
-
width: 14,
|
|
1094
|
-
accessor: (row) => formatUsdValue(row.balance, row.coin?.tokenPrice?.priceInUsdc)
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
header: "Market Cap",
|
|
1098
|
-
width: 14,
|
|
1099
|
-
accessor: (row) => formatCompactCurrency(row.coin?.marketCap)
|
|
1100
|
-
},
|
|
1101
|
-
{
|
|
1102
|
-
header: "24h Change",
|
|
1103
|
-
width: 12,
|
|
1104
|
-
accessor: (row) => formatChange(row.coin?.marketCap, row.coin?.marketCapDelta24h),
|
|
1105
|
-
color: changeColor2
|
|
1106
|
-
}
|
|
1107
|
-
];
|
|
1108
1145
|
var formatBalanceJson = (balance, rank) => {
|
|
1109
1146
|
const priceUsd = balance.coin?.tokenPrice?.priceInUsdc;
|
|
1110
1147
|
const marketCap = balance.coin?.marketCap ? Number(balance.coin.marketCap) : null;
|
|
@@ -1112,7 +1149,7 @@ var formatBalanceJson = (balance, rank) => {
|
|
|
1112
1149
|
const volume24h = balance.coin?.volume24h ? Number(balance.coin.volume24h) : null;
|
|
1113
1150
|
const totalVolume = balance.coin?.totalVolume ? Number(balance.coin.totalVolume) : null;
|
|
1114
1151
|
const priceUsdValue = priceUsd ? Number(priceUsd) : null;
|
|
1115
|
-
const usdValue = priceUsdValue !== null ? Number((
|
|
1152
|
+
const usdValue = priceUsdValue !== null ? Number((parseRawBalance(balance.balance) * priceUsdValue).toFixed(6)) : null;
|
|
1116
1153
|
const marketCapChange24h = marketCap !== null && marketCapDelta24h !== null && marketCap - marketCapDelta24h !== 0 ? Number(
|
|
1117
1154
|
(marketCapDelta24h / (marketCap - marketCapDelta24h) * 100).toFixed(
|
|
1118
1155
|
4
|
|
@@ -1146,24 +1183,27 @@ function resolveContext(json) {
|
|
|
1146
1183
|
"Not authenticated. Run 'zora auth configure' to set your API key."
|
|
1147
1184
|
);
|
|
1148
1185
|
}
|
|
1149
|
-
|
|
1186
|
+
setApiKey(apiKey);
|
|
1150
1187
|
return account;
|
|
1151
1188
|
}
|
|
1152
1189
|
function renderWallet(json, walletResult) {
|
|
1153
1190
|
outputData(json, {
|
|
1154
1191
|
json: { wallet: walletResult.walletBalancesJson },
|
|
1155
|
-
|
|
1192
|
+
render: () => {
|
|
1156
1193
|
renderOnce(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1194
|
+
/* @__PURE__ */ jsx3(
|
|
1195
|
+
Table,
|
|
1196
|
+
{
|
|
1197
|
+
columns: walletColumns,
|
|
1198
|
+
data: walletResult.walletBalances,
|
|
1199
|
+
title: "Wallet"
|
|
1200
|
+
}
|
|
1201
|
+
)
|
|
1162
1202
|
);
|
|
1163
1203
|
}
|
|
1164
1204
|
});
|
|
1165
1205
|
}
|
|
1166
|
-
function renderCoins(json, balances, total, sort) {
|
|
1206
|
+
function renderCoins(json, balances, total, sort, limit, pageInfo) {
|
|
1167
1207
|
const rankedBalances = balances.map((balance, index) => ({
|
|
1168
1208
|
...balance,
|
|
1169
1209
|
rank: index + 1
|
|
@@ -1172,39 +1212,43 @@ function renderCoins(json, balances, total, sort) {
|
|
|
1172
1212
|
json: {
|
|
1173
1213
|
coins: rankedBalances.map(
|
|
1174
1214
|
(balance) => formatBalanceJson(balance, balance.rank)
|
|
1175
|
-
)
|
|
1215
|
+
),
|
|
1216
|
+
pageInfo: pageInfo ?? null
|
|
1176
1217
|
},
|
|
1177
|
-
|
|
1218
|
+
render: () => {
|
|
1178
1219
|
if (balances.length === 0) {
|
|
1179
1220
|
console.log("\n No coin balances found.\n");
|
|
1180
1221
|
console.log(" Buy coins to see them here:");
|
|
1181
1222
|
console.log(" zora buy <address> --eth 0.001\n");
|
|
1182
1223
|
} else {
|
|
1224
|
+
const footer = pageInfo?.hasNextPage && pageInfo.endCursor ? `Next page: zora balance coins --sort ${sort} --limit ${limit} --after ${pageInfo.endCursor}` : void 0;
|
|
1183
1225
|
renderOnce(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1226
|
+
/* @__PURE__ */ jsx3(
|
|
1227
|
+
Table,
|
|
1228
|
+
{
|
|
1229
|
+
columns: balanceColumns,
|
|
1230
|
+
data: rankedBalances,
|
|
1231
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1232
|
+
subtitle: `${balances.length} of ${total}`,
|
|
1233
|
+
footer
|
|
1234
|
+
}
|
|
1235
|
+
)
|
|
1190
1236
|
);
|
|
1191
1237
|
}
|
|
1192
1238
|
}
|
|
1193
1239
|
});
|
|
1194
1240
|
}
|
|
1195
|
-
async function fetchCoins(json, address, sort, limit) {
|
|
1241
|
+
async function fetchCoins(json, address, sort, limit, after) {
|
|
1196
1242
|
let response;
|
|
1197
1243
|
try {
|
|
1198
1244
|
response = await getProfileBalances({
|
|
1199
1245
|
identifier: address,
|
|
1200
1246
|
count: limit,
|
|
1201
|
-
sortOption: SORT_MAP[sort]
|
|
1247
|
+
sortOption: SORT_MAP[sort],
|
|
1248
|
+
after
|
|
1202
1249
|
});
|
|
1203
1250
|
} catch (err) {
|
|
1204
|
-
outputErrorAndExit(
|
|
1205
|
-
json,
|
|
1206
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1207
|
-
);
|
|
1251
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
1208
1252
|
}
|
|
1209
1253
|
if (response.error) {
|
|
1210
1254
|
outputErrorAndExit(
|
|
@@ -1217,14 +1261,15 @@ async function fetchCoins(json, address, sort, limit) {
|
|
|
1217
1261
|
(e) => e.node
|
|
1218
1262
|
);
|
|
1219
1263
|
const total = response.data?.profile?.coinBalances?.count ?? balances.length;
|
|
1220
|
-
|
|
1264
|
+
const pageInfo = response.data?.profile?.coinBalances?.pageInfo;
|
|
1265
|
+
return { balances, total, pageInfo };
|
|
1221
1266
|
}
|
|
1222
1267
|
function validateCoinOpts(json, sort, limitStr) {
|
|
1223
1268
|
if (!SORT_MAP[sort]) {
|
|
1224
1269
|
outputErrorAndExit(
|
|
1225
1270
|
json,
|
|
1226
1271
|
`Invalid --sort value: ${sort}.`,
|
|
1227
|
-
`Supported: ${
|
|
1272
|
+
`Supported: ${SORT_OPTIONS}`
|
|
1228
1273
|
);
|
|
1229
1274
|
}
|
|
1230
1275
|
const limit = parseInt(limitStr, 10);
|
|
@@ -1236,100 +1281,255 @@ function validateCoinOpts(json, sort, limitStr) {
|
|
|
1236
1281
|
}
|
|
1237
1282
|
return { sort, limit };
|
|
1238
1283
|
}
|
|
1239
|
-
var balanceCommand = new
|
|
1240
|
-
|
|
1284
|
+
var balanceCommand = new Command2("balance").description("Show balances in your wallet").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
1285
|
+
"--refresh <seconds>",
|
|
1286
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1287
|
+
"30"
|
|
1288
|
+
).action(async function() {
|
|
1289
|
+
const output = getOutputMode(this, "live");
|
|
1290
|
+
const json = output === "json";
|
|
1241
1291
|
const account = resolveContext(json);
|
|
1292
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1242
1293
|
const sort = "usd-value";
|
|
1243
1294
|
const limit = 10;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
try {
|
|
1247
|
-
[walletResult, coinsResult] = await Promise.all([
|
|
1295
|
+
const fetchBalanceData = async () => {
|
|
1296
|
+
const [walletResult, coinsResult] = await Promise.allSettled([
|
|
1248
1297
|
fetchWalletBalances(account.address),
|
|
1249
1298
|
fetchCoins(json, account.address, sort, limit)
|
|
1250
1299
|
]);
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1300
|
+
if (walletResult.status === "rejected" || coinsResult.status === "rejected") {
|
|
1301
|
+
const err = walletResult.status === "rejected" ? walletResult.reason : coinsResult.reason;
|
|
1302
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
1303
|
+
}
|
|
1304
|
+
const rankedBalances = coinsResult.value.balances.map(
|
|
1305
|
+
(balance, index) => ({
|
|
1306
|
+
...balance,
|
|
1307
|
+
rank: index + 1
|
|
1308
|
+
})
|
|
1255
1309
|
);
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1310
|
+
return {
|
|
1311
|
+
walletBalances: walletResult.value.walletBalances,
|
|
1312
|
+
walletBalancesJson: walletResult.value.walletBalancesJson,
|
|
1313
|
+
rankedBalances,
|
|
1314
|
+
total: coinsResult.value.total
|
|
1315
|
+
};
|
|
1316
|
+
};
|
|
1317
|
+
if (json) {
|
|
1318
|
+
const data = await fetchBalanceData().catch(
|
|
1319
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1320
|
+
);
|
|
1321
|
+
outputData(json, {
|
|
1322
|
+
json: {
|
|
1323
|
+
wallet: data.walletBalancesJson,
|
|
1324
|
+
coins: data.rankedBalances.map(
|
|
1325
|
+
(balance) => formatBalanceJson(balance, balance.rank)
|
|
1326
|
+
)
|
|
1327
|
+
},
|
|
1328
|
+
render: () => {
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
track("cli_balances", {
|
|
1332
|
+
sort,
|
|
1333
|
+
limit,
|
|
1334
|
+
live: false,
|
|
1335
|
+
result_count: data.rankedBalances.length,
|
|
1336
|
+
total_count: data.total,
|
|
1337
|
+
output_format: "json"
|
|
1338
|
+
});
|
|
1339
|
+
} else if (live) {
|
|
1340
|
+
await renderLive(
|
|
1341
|
+
/* @__PURE__ */ jsx3(
|
|
1342
|
+
BalanceView,
|
|
1343
|
+
{
|
|
1344
|
+
fetchData: fetchBalanceData,
|
|
1345
|
+
sort,
|
|
1346
|
+
mode: "full",
|
|
1347
|
+
autoRefresh: live,
|
|
1348
|
+
intervalSeconds
|
|
1349
|
+
}
|
|
1266
1350
|
)
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1351
|
+
);
|
|
1352
|
+
track("cli_balances", {
|
|
1353
|
+
sort,
|
|
1354
|
+
limit,
|
|
1355
|
+
live,
|
|
1356
|
+
interval: intervalSeconds,
|
|
1357
|
+
output_format: "live"
|
|
1358
|
+
});
|
|
1359
|
+
} else {
|
|
1360
|
+
const data = await fetchBalanceData().catch(
|
|
1361
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1362
|
+
);
|
|
1363
|
+
renderOnce(
|
|
1364
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1365
|
+
/* @__PURE__ */ jsx3(
|
|
1366
|
+
Table,
|
|
1367
|
+
{
|
|
1368
|
+
columns: walletColumns,
|
|
1369
|
+
data: data.walletBalances,
|
|
1370
|
+
title: "Wallet"
|
|
1371
|
+
}
|
|
1372
|
+
),
|
|
1373
|
+
data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs3(
|
|
1374
|
+
Box3,
|
|
1375
|
+
{
|
|
1376
|
+
flexDirection: "column",
|
|
1377
|
+
paddingLeft: 1,
|
|
1378
|
+
paddingTop: 1,
|
|
1379
|
+
paddingBottom: 1,
|
|
1380
|
+
children: [
|
|
1381
|
+
/* @__PURE__ */ jsx3(Text3, { children: "No coin balances found." }),
|
|
1382
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1383
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
1384
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1385
|
+
" zora buy ",
|
|
1386
|
+
"<address>",
|
|
1387
|
+
" --eth 0.001"
|
|
1388
|
+
] })
|
|
1389
|
+
] })
|
|
1390
|
+
]
|
|
1391
|
+
}
|
|
1392
|
+
) : /* @__PURE__ */ jsx3(
|
|
1393
|
+
Table,
|
|
1394
|
+
{
|
|
1283
1395
|
columns: balanceColumns,
|
|
1284
|
-
data: rankedBalances,
|
|
1285
|
-
title: `Coins \xB7 sorted by ${
|
|
1286
|
-
subtitle: `${
|
|
1287
|
-
}
|
|
1288
|
-
)
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1396
|
+
data: data.rankedBalances,
|
|
1397
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1398
|
+
subtitle: `${data.rankedBalances.length} of ${data.total}`
|
|
1399
|
+
}
|
|
1400
|
+
)
|
|
1401
|
+
] })
|
|
1402
|
+
);
|
|
1403
|
+
track("cli_balances", {
|
|
1404
|
+
sort,
|
|
1405
|
+
limit,
|
|
1406
|
+
live: false,
|
|
1407
|
+
result_count: data.rankedBalances.length,
|
|
1408
|
+
total_count: data.total,
|
|
1409
|
+
output_format: "static"
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1299
1412
|
});
|
|
1300
|
-
balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").
|
|
1301
|
-
|
|
1413
|
+
balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
1414
|
+
"--refresh <seconds>",
|
|
1415
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1416
|
+
"30"
|
|
1417
|
+
).action(async function() {
|
|
1418
|
+
const output = getOutputMode(this, "live");
|
|
1419
|
+
const json = output === "json";
|
|
1302
1420
|
const account = resolveContext(json);
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
walletResult = await fetchWalletBalances(account.address);
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1421
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1422
|
+
const fetchSpendableData = async () => {
|
|
1423
|
+
const walletResult = await fetchWalletBalances(account.address);
|
|
1424
|
+
return {
|
|
1425
|
+
walletBalances: walletResult.walletBalances,
|
|
1426
|
+
walletBalancesJson: walletResult.walletBalancesJson,
|
|
1427
|
+
rankedBalances: [],
|
|
1428
|
+
total: 0
|
|
1429
|
+
};
|
|
1430
|
+
};
|
|
1431
|
+
if (json) {
|
|
1432
|
+
const data = await fetchSpendableData().catch(
|
|
1433
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1434
|
+
);
|
|
1435
|
+
outputData(json, {
|
|
1436
|
+
json: { wallet: data.walletBalancesJson },
|
|
1437
|
+
render: () => {
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
} else if (live) {
|
|
1441
|
+
await renderLive(
|
|
1442
|
+
/* @__PURE__ */ jsx3(
|
|
1443
|
+
BalanceView,
|
|
1444
|
+
{
|
|
1445
|
+
fetchData: fetchSpendableData,
|
|
1446
|
+
sort: "usd-value",
|
|
1447
|
+
mode: "wallet",
|
|
1448
|
+
autoRefresh: live,
|
|
1449
|
+
intervalSeconds
|
|
1450
|
+
}
|
|
1451
|
+
)
|
|
1452
|
+
);
|
|
1453
|
+
} else {
|
|
1454
|
+
const walletResult = await fetchWalletBalances(account.address).catch(
|
|
1455
|
+
(err) => outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`)
|
|
1310
1456
|
);
|
|
1457
|
+
renderWallet(json, walletResult);
|
|
1311
1458
|
}
|
|
1312
|
-
renderWallet(json, walletResult);
|
|
1313
1459
|
});
|
|
1314
|
-
balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${
|
|
1315
|
-
|
|
1460
|
+
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(
|
|
1461
|
+
"--refresh <seconds>",
|
|
1462
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
1463
|
+
"30"
|
|
1464
|
+
).option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
|
|
1465
|
+
const output = getOutputMode(this, "live");
|
|
1466
|
+
const json = output === "json";
|
|
1316
1467
|
const { sort, limit } = validateCoinOpts(json, opts.sort, opts.limit);
|
|
1468
|
+
const after = opts.after;
|
|
1317
1469
|
const account = resolveContext(json);
|
|
1318
|
-
const {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1470
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
1471
|
+
const fetchCoinsPage = async (cursor) => {
|
|
1472
|
+
const { balances, total, pageInfo } = await fetchCoins(
|
|
1473
|
+
json,
|
|
1474
|
+
account.address,
|
|
1475
|
+
sort,
|
|
1476
|
+
limit,
|
|
1477
|
+
cursor
|
|
1478
|
+
);
|
|
1479
|
+
const rankedBalances = balances.map((balance, index) => ({
|
|
1480
|
+
...balance,
|
|
1481
|
+
rank: index + 1
|
|
1482
|
+
}));
|
|
1483
|
+
return {
|
|
1484
|
+
walletBalances: [],
|
|
1485
|
+
walletBalancesJson: [],
|
|
1486
|
+
rankedBalances,
|
|
1487
|
+
total,
|
|
1488
|
+
pageInfo
|
|
1489
|
+
};
|
|
1490
|
+
};
|
|
1491
|
+
if (json) {
|
|
1492
|
+
const data = await fetchCoinsPage(after);
|
|
1493
|
+
renderCoins(
|
|
1494
|
+
json,
|
|
1495
|
+
data.rankedBalances,
|
|
1496
|
+
data.total,
|
|
1497
|
+
sort,
|
|
1498
|
+
limit,
|
|
1499
|
+
data.pageInfo
|
|
1500
|
+
);
|
|
1501
|
+
} else if (live) {
|
|
1502
|
+
await renderLive(
|
|
1503
|
+
/* @__PURE__ */ jsx3(
|
|
1504
|
+
BalanceView,
|
|
1505
|
+
{
|
|
1506
|
+
fetchData: fetchCoinsPage,
|
|
1507
|
+
sort,
|
|
1508
|
+
mode: "coins",
|
|
1509
|
+
initialCursor: after,
|
|
1510
|
+
autoRefresh: live,
|
|
1511
|
+
intervalSeconds
|
|
1512
|
+
}
|
|
1513
|
+
)
|
|
1514
|
+
);
|
|
1515
|
+
} else {
|
|
1516
|
+
const { balances, total, pageInfo } = await fetchCoins(
|
|
1517
|
+
json,
|
|
1518
|
+
account.address,
|
|
1519
|
+
sort,
|
|
1520
|
+
limit,
|
|
1521
|
+
after
|
|
1522
|
+
);
|
|
1523
|
+
renderCoins(json, balances, total, sort, limit, pageInfo);
|
|
1524
|
+
}
|
|
1325
1525
|
});
|
|
1326
1526
|
|
|
1327
1527
|
// src/commands/buy.ts
|
|
1328
|
-
import { Command as
|
|
1528
|
+
import { Command as Command3 } from "commander";
|
|
1329
1529
|
import confirm2 from "@inquirer/confirm";
|
|
1330
1530
|
import { parseUnits, formatUnits as formatUnits3, isAddress } from "viem";
|
|
1331
1531
|
import {
|
|
1332
|
-
setApiKey as
|
|
1532
|
+
setApiKey as setApiKey2,
|
|
1333
1533
|
getCoin,
|
|
1334
1534
|
tradeCoin,
|
|
1335
1535
|
createTradeCall
|
|
@@ -1343,7 +1543,7 @@ import {
|
|
|
1343
1543
|
parseEventLogs,
|
|
1344
1544
|
erc20Abi as erc20Abi2
|
|
1345
1545
|
} from "viem";
|
|
1346
|
-
var GAS_RESERVE = parseEther("0.
|
|
1546
|
+
var GAS_RESERVE = parseEther("0.00001");
|
|
1347
1547
|
var BUY_AMOUNT_CHECKS = {
|
|
1348
1548
|
eth: (opts) => opts.eth !== void 0,
|
|
1349
1549
|
usd: (opts) => opts.usd !== void 0,
|
|
@@ -1409,13 +1609,16 @@ var getReceivedAmountFromReceipt = ({
|
|
|
1409
1609
|
if (matchingTransfers.length === 0) {
|
|
1410
1610
|
throw new Error("No matching Transfer event found in receipt.");
|
|
1411
1611
|
}
|
|
1412
|
-
|
|
1612
|
+
const amount = matchingTransfers.reduce((total, transfer) => {
|
|
1413
1613
|
const value = transfer.args?.value;
|
|
1414
1614
|
if (value === void 0) {
|
|
1415
1615
|
throw new Error("Transfer event missing amount.");
|
|
1416
1616
|
}
|
|
1417
1617
|
return total + value;
|
|
1418
1618
|
}, 0n);
|
|
1619
|
+
const lastTransfer = matchingTransfers[matchingTransfers.length - 1];
|
|
1620
|
+
const logIndex = lastTransfer?.logIndex ?? null;
|
|
1621
|
+
return { amount, logIndex };
|
|
1419
1622
|
};
|
|
1420
1623
|
var printDebugRequest = (label, tradeParameters) => {
|
|
1421
1624
|
if (process.env.ZORA_API_TARGET) {
|
|
@@ -1505,19 +1708,12 @@ var printTradeResult = (json, info) => {
|
|
|
1505
1708
|
};
|
|
1506
1709
|
|
|
1507
1710
|
// src/commands/buy.ts
|
|
1508
|
-
var buyCommand = new
|
|
1509
|
-
const json =
|
|
1711
|
+
var buyCommand = new Command3("buy").description("Buy a coin").argument("[address]", "Coin contract address (0x\u2026)").option("--eth <value>", "Buy with ETH amount").option("--usd <value>", "Buy with USD equivalent (use with --token)").option("--token <asset>", "Token to spend: eth, usdc, zora", "eth").option("--percent <value>", "Buy with percentage of ETH balance").option("--all", "Swap all ETH for coin").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(coinAddress, opts) {
|
|
1712
|
+
const json = getJson(this);
|
|
1510
1713
|
const debug = opts.debug === true;
|
|
1511
1714
|
if (!isAddress(coinAddress)) {
|
|
1512
1715
|
outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
|
|
1513
1716
|
}
|
|
1514
|
-
const output = opts.output;
|
|
1515
|
-
if (output !== "table" && output !== "json") {
|
|
1516
|
-
outputErrorAndExit(
|
|
1517
|
-
false,
|
|
1518
|
-
`Invalid --output value: ${output}. Use: table, json`
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
1717
|
const tokenKey = opts.token.toLowerCase();
|
|
1522
1718
|
if (!(tokenKey in BASE_TRADE_TOKENS)) {
|
|
1523
1719
|
outputErrorAndExit(
|
|
@@ -1542,7 +1738,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1542
1738
|
const slippage = slippagePct / 100;
|
|
1543
1739
|
const apiKey = getApiKey();
|
|
1544
1740
|
if (apiKey) {
|
|
1545
|
-
|
|
1741
|
+
setApiKey2(apiKey);
|
|
1546
1742
|
}
|
|
1547
1743
|
const account = resolveAccount(json);
|
|
1548
1744
|
const { publicClient, walletClient } = createClients(account);
|
|
@@ -1551,10 +1747,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1551
1747
|
const response = await getCoin({ address: coinAddress });
|
|
1552
1748
|
token = response.data?.zora20Token;
|
|
1553
1749
|
} catch (err) {
|
|
1554
|
-
outputErrorAndExit(
|
|
1555
|
-
json,
|
|
1556
|
-
`Failed to fetch coin: ${err instanceof Error ? err.message : String(err)}`
|
|
1557
|
-
);
|
|
1750
|
+
outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
|
|
1558
1751
|
}
|
|
1559
1752
|
if (!token) {
|
|
1560
1753
|
outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
|
|
@@ -1648,7 +1841,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1648
1841
|
if (isEth && balance <= gasReserve) {
|
|
1649
1842
|
outputErrorAndExit(
|
|
1650
1843
|
json,
|
|
1651
|
-
`Balance too low (${formatEthDisplay(balance)} ETH). Need
|
|
1844
|
+
`Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
|
|
1652
1845
|
);
|
|
1653
1846
|
}
|
|
1654
1847
|
const spendableBalance = balance - gasReserve;
|
|
@@ -1662,7 +1855,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1662
1855
|
"Invalid --percent value. Must be between 0 and 100."
|
|
1663
1856
|
);
|
|
1664
1857
|
}
|
|
1665
|
-
amountIn = pct === 100 ? spendableBalance :
|
|
1858
|
+
amountIn = pct === 100 ? spendableBalance : spendableBalance * BigInt(Math.round(pct * 100)) / 10000n;
|
|
1666
1859
|
if (amountIn === 0n) {
|
|
1667
1860
|
outputErrorAndExit(
|
|
1668
1861
|
json,
|
|
@@ -1671,6 +1864,17 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1671
1864
|
}
|
|
1672
1865
|
}
|
|
1673
1866
|
}
|
|
1867
|
+
let swapAmountUsd;
|
|
1868
|
+
if (amountMode === "usd") {
|
|
1869
|
+
swapAmountUsd = parsePercentageLikeValue(opts.usd);
|
|
1870
|
+
} else {
|
|
1871
|
+
const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
|
|
1872
|
+
if (priceUsd != null) {
|
|
1873
|
+
swapAmountUsd = Number(
|
|
1874
|
+
(Number(formatUnits3(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1674
1878
|
const tradeParameters = {
|
|
1675
1879
|
sell: inputToken.trade,
|
|
1676
1880
|
buy: { type: "erc20", address: coinAddress },
|
|
@@ -1718,7 +1922,7 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1718
1922
|
}
|
|
1719
1923
|
outputErrorAndExit(
|
|
1720
1924
|
json,
|
|
1721
|
-
`Quote failed: ${
|
|
1925
|
+
`Quote failed: ${apiErrorMessage(err)}`,
|
|
1722
1926
|
"Check the coin address is valid and try again. Use --debug for full error details."
|
|
1723
1927
|
);
|
|
1724
1928
|
}
|
|
@@ -1747,8 +1951,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1747
1951
|
coin_name: coinName,
|
|
1748
1952
|
coin_symbol: coinSymbol,
|
|
1749
1953
|
amount_mode: amountMode,
|
|
1954
|
+
swap_amount_usd: swapAmountUsd,
|
|
1955
|
+
valueUsd: swapAmountUsd,
|
|
1956
|
+
swapCoinType: token.coinType ?? null,
|
|
1750
1957
|
slippage: slippagePct,
|
|
1751
|
-
output_format:
|
|
1958
|
+
output_format: json ? "json" : "static"
|
|
1752
1959
|
});
|
|
1753
1960
|
return;
|
|
1754
1961
|
}
|
|
@@ -1776,6 +1983,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1776
1983
|
let receipt;
|
|
1777
1984
|
let txHash;
|
|
1778
1985
|
let receivedAmountOut = BigInt(amountOut);
|
|
1986
|
+
let swapLogIndex = null;
|
|
1987
|
+
const swapCoinType = token.coinType ?? null;
|
|
1779
1988
|
try {
|
|
1780
1989
|
receipt = await tradeCoin({
|
|
1781
1990
|
tradeParameters,
|
|
@@ -1790,24 +1999,26 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1790
1999
|
coin_name: coinName,
|
|
1791
2000
|
coin_symbol: coinSymbol,
|
|
1792
2001
|
amount_mode: amountMode,
|
|
2002
|
+
swap_amount_usd: swapAmountUsd,
|
|
2003
|
+
valueUsd: swapAmountUsd,
|
|
2004
|
+
swapCoinType,
|
|
1793
2005
|
slippage: slippagePct,
|
|
1794
|
-
output_format:
|
|
2006
|
+
output_format: json ? "json" : "static",
|
|
1795
2007
|
success: false,
|
|
1796
2008
|
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
1797
2009
|
});
|
|
1798
2010
|
await shutdownAnalytics();
|
|
1799
|
-
outputErrorAndExit(
|
|
1800
|
-
json,
|
|
1801
|
-
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1802
|
-
);
|
|
2011
|
+
outputErrorAndExit(json, tradeErrorMessage(err));
|
|
1803
2012
|
}
|
|
1804
2013
|
txHash = receipt.transactionHash;
|
|
1805
2014
|
try {
|
|
1806
|
-
|
|
2015
|
+
const result = getReceivedAmountFromReceipt({
|
|
1807
2016
|
receipt,
|
|
1808
2017
|
tokenAddress: coinAddress,
|
|
1809
2018
|
recipient: account.address
|
|
1810
2019
|
});
|
|
2020
|
+
receivedAmountOut = result.amount;
|
|
2021
|
+
swapLogIndex = result.logIndex;
|
|
1811
2022
|
} catch (err) {
|
|
1812
2023
|
console.warn(
|
|
1813
2024
|
`Warning: transaction succeeded but could not determine received amount: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1833,13 +2044,518 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1833
2044
|
amount_mode: amountMode,
|
|
1834
2045
|
input_amount: amountIn.toString(),
|
|
1835
2046
|
input_token_symbol: inputToken.symbol,
|
|
2047
|
+
swap_amount_usd: swapAmountUsd,
|
|
2048
|
+
valueUsd: swapAmountUsd,
|
|
2049
|
+
swapCoinType,
|
|
2050
|
+
transactionHash: txHash,
|
|
2051
|
+
logIndex: swapLogIndex,
|
|
1836
2052
|
slippage: slippagePct,
|
|
1837
|
-
output_format:
|
|
2053
|
+
output_format: json ? "json" : "static",
|
|
1838
2054
|
success: true,
|
|
1839
2055
|
tx_hash: txHash
|
|
1840
2056
|
});
|
|
1841
2057
|
});
|
|
1842
2058
|
|
|
2059
|
+
// src/commands/explore.tsx
|
|
2060
|
+
import { Command as Command4 } from "commander";
|
|
2061
|
+
import {
|
|
2062
|
+
setApiKey as setApiKey3,
|
|
2063
|
+
getCoinsTopVolume24h,
|
|
2064
|
+
getCoinsMostValuable,
|
|
2065
|
+
getCoinsNew,
|
|
2066
|
+
getCoinsTopGainers,
|
|
2067
|
+
getCoinsLastTraded,
|
|
2068
|
+
getCoinsLastTradedUnique,
|
|
2069
|
+
getExploreTopVolumeAll24h,
|
|
2070
|
+
getExploreTopVolumeCreators24h,
|
|
2071
|
+
getExploreNewAll,
|
|
2072
|
+
getExploreFeaturedCreators,
|
|
2073
|
+
getExploreFeaturedVideos,
|
|
2074
|
+
getCreatorCoins,
|
|
2075
|
+
getMostValuableCreatorCoins,
|
|
2076
|
+
getMostValuableAll,
|
|
2077
|
+
getMostValuableTrends,
|
|
2078
|
+
getNewTrends,
|
|
2079
|
+
getTopVolumeTrends24h,
|
|
2080
|
+
getTrendingAll,
|
|
2081
|
+
getTrendingCreators,
|
|
2082
|
+
getTrendingPosts,
|
|
2083
|
+
getTrendingTrends
|
|
2084
|
+
} from "@zoralabs/coins-sdk";
|
|
2085
|
+
|
|
2086
|
+
// src/lib/types.ts
|
|
2087
|
+
var SORT_LABELS2 = {
|
|
2088
|
+
mcap: "Top by Market Cap",
|
|
2089
|
+
volume: "Top by 24h Volume",
|
|
2090
|
+
new: "New",
|
|
2091
|
+
gainers: "Top Gainers (24h)",
|
|
2092
|
+
"last-traded": "Last Traded",
|
|
2093
|
+
"last-traded-unique": "Last Traded (Unique)",
|
|
2094
|
+
trending: "Trending",
|
|
2095
|
+
featured: "Featured"
|
|
2096
|
+
};
|
|
2097
|
+
var TYPE_LABELS = {
|
|
2098
|
+
all: "all",
|
|
2099
|
+
trend: "trends",
|
|
2100
|
+
"creator-coin": "creator coins",
|
|
2101
|
+
post: "posts"
|
|
2102
|
+
};
|
|
2103
|
+
var COIN_TYPE_DISPLAY = {
|
|
2104
|
+
CONTENT: "post",
|
|
2105
|
+
CREATOR: "creator-coin",
|
|
2106
|
+
TREND: "trend"
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// src/components/ExploreView.tsx
|
|
2110
|
+
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
2111
|
+
import { Box as Box4, Text as Text4, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
2112
|
+
import Spinner2 from "ink-spinner";
|
|
2113
|
+
|
|
2114
|
+
// src/lib/clipboard.ts
|
|
2115
|
+
import { execFileSync } from "child_process";
|
|
2116
|
+
import { platform as platform2 } from "os";
|
|
2117
|
+
var copyToClipboard = (text) => {
|
|
2118
|
+
const os = platform2();
|
|
2119
|
+
try {
|
|
2120
|
+
if (os === "darwin") {
|
|
2121
|
+
execFileSync("pbcopy", {
|
|
2122
|
+
input: text,
|
|
2123
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2124
|
+
});
|
|
2125
|
+
} else if (os === "linux") {
|
|
2126
|
+
execFileSync("xclip", ["-selection", "clipboard"], {
|
|
2127
|
+
input: text,
|
|
2128
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2129
|
+
});
|
|
2130
|
+
} else if (os === "win32") {
|
|
2131
|
+
execFileSync("clip", {
|
|
2132
|
+
input: text,
|
|
2133
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2134
|
+
});
|
|
2135
|
+
} else {
|
|
2136
|
+
return false;
|
|
2137
|
+
}
|
|
2138
|
+
return true;
|
|
2139
|
+
} catch {
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
// src/components/ExploreView.tsx
|
|
2145
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2146
|
+
var COLUMNS = [
|
|
2147
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2148
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
2149
|
+
{
|
|
2150
|
+
header: "Address",
|
|
2151
|
+
width: 14,
|
|
2152
|
+
accessor: (c) => c.address ? truncateAddress(c.address) : ""
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
header: "Type",
|
|
2156
|
+
width: 14,
|
|
2157
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
header: "Market Cap",
|
|
2161
|
+
width: 12,
|
|
2162
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
2163
|
+
},
|
|
2164
|
+
{
|
|
2165
|
+
header: "24h Vol",
|
|
2166
|
+
width: 12,
|
|
2167
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
header: "24h Change",
|
|
2171
|
+
width: 11,
|
|
2172
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
2173
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2174
|
+
}
|
|
2175
|
+
];
|
|
2176
|
+
var CACHE_TTL_MS = 6e4;
|
|
2177
|
+
var CACHE_KEY_FIRST = "__first__";
|
|
2178
|
+
var ExploreView = ({
|
|
2179
|
+
fetchPage,
|
|
2180
|
+
sort,
|
|
2181
|
+
type,
|
|
2182
|
+
limit,
|
|
2183
|
+
initialCursor,
|
|
2184
|
+
cacheTtlMs = CACHE_TTL_MS,
|
|
2185
|
+
autoRefresh = false,
|
|
2186
|
+
intervalSeconds = 30
|
|
2187
|
+
}) => {
|
|
2188
|
+
const { exit } = useApp2();
|
|
2189
|
+
const [loading, setLoading] = useState3(true);
|
|
2190
|
+
const [error, setError] = useState3(null);
|
|
2191
|
+
const [coins, setCoins] = useState3([]);
|
|
2192
|
+
const [pageInfo, setPageInfo] = useState3(null);
|
|
2193
|
+
const [page, setPage] = useState3(1);
|
|
2194
|
+
const [cursorHistory, setCursorHistory] = useState3(
|
|
2195
|
+
[]
|
|
2196
|
+
);
|
|
2197
|
+
const [currentCursor, setCurrentCursor] = useState3(
|
|
2198
|
+
initialCursor
|
|
2199
|
+
);
|
|
2200
|
+
const cache = useRef2(/* @__PURE__ */ new Map());
|
|
2201
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
2202
|
+
const [manualRefreshCount, setManualRefreshCount] = useState3(0);
|
|
2203
|
+
const [selectedRow, setSelectedRow] = useState3(0);
|
|
2204
|
+
const [copyFeedback, setCopyFeedback] = useState3(null);
|
|
2205
|
+
useEffect3(() => {
|
|
2206
|
+
setSelectedRow((r) => Math.min(r, Math.max(0, coins.length - 1)));
|
|
2207
|
+
}, [coins.length]);
|
|
2208
|
+
const loadPage = useCallback3(
|
|
2209
|
+
async (cursor) => {
|
|
2210
|
+
const cacheKey = cursor ?? CACHE_KEY_FIRST;
|
|
2211
|
+
const cached = cache.current.get(cacheKey);
|
|
2212
|
+
const isFresh = cached && Date.now() - cached.fetchedAt < cacheTtlMs;
|
|
2213
|
+
if (isFresh) {
|
|
2214
|
+
setCoins(cached.result.coins);
|
|
2215
|
+
setPageInfo(cached.result.pageInfo ?? null);
|
|
2216
|
+
setError(null);
|
|
2217
|
+
setLoading(false);
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
setLoading(true);
|
|
2221
|
+
setError(null);
|
|
2222
|
+
try {
|
|
2223
|
+
const result = await fetchPage(cursor);
|
|
2224
|
+
cache.current.set(cacheKey, { result, fetchedAt: Date.now() });
|
|
2225
|
+
setCoins(result.coins);
|
|
2226
|
+
setPageInfo(result.pageInfo ?? null);
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2229
|
+
}
|
|
2230
|
+
setLoading(false);
|
|
2231
|
+
},
|
|
2232
|
+
[fetchPage, cacheTtlMs]
|
|
2233
|
+
);
|
|
2234
|
+
useEffect3(() => {
|
|
2235
|
+
if (refreshCount === 0) return;
|
|
2236
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2237
|
+
cache.current.delete(cacheKey);
|
|
2238
|
+
}, [refreshCount, currentCursor]);
|
|
2239
|
+
useEffect3(() => {
|
|
2240
|
+
loadPage(currentCursor);
|
|
2241
|
+
}, [currentCursor, loadPage, refreshCount, manualRefreshCount]);
|
|
2242
|
+
useInput2((input, key) => {
|
|
2243
|
+
if (input === "q" || key.escape) {
|
|
2244
|
+
exit();
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (loading) return;
|
|
2248
|
+
if (key.upArrow || input === "k") {
|
|
2249
|
+
setSelectedRow((r) => Math.max(0, r - 1));
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (key.downArrow || input === "j") {
|
|
2253
|
+
setSelectedRow((r) => Math.min(coins.length - 1, r + 1));
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (input === "c") {
|
|
2257
|
+
const coin = coins[selectedRow];
|
|
2258
|
+
if (coin?.address) {
|
|
2259
|
+
const ok = copyToClipboard(coin.address);
|
|
2260
|
+
setCopyFeedback(ok ? "Copied!" : "Copy failed");
|
|
2261
|
+
setTimeout(() => setCopyFeedback(null), 1500);
|
|
2262
|
+
}
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
|
|
2266
|
+
const canGoPrev = cursorHistory.length > 0;
|
|
2267
|
+
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
2268
|
+
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
2269
|
+
setCurrentCursor(pageInfo.endCursor);
|
|
2270
|
+
setPage((p) => p + 1);
|
|
2271
|
+
setSelectedRow(0);
|
|
2272
|
+
}
|
|
2273
|
+
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
2274
|
+
const prev = cursorHistory[cursorHistory.length - 1];
|
|
2275
|
+
setCursorHistory((h) => h.slice(0, -1));
|
|
2276
|
+
setCurrentCursor(prev);
|
|
2277
|
+
setPage((p) => p - 1);
|
|
2278
|
+
setSelectedRow(0);
|
|
2279
|
+
}
|
|
2280
|
+
if (input === "r") {
|
|
2281
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2282
|
+
cache.current.delete(cacheKey);
|
|
2283
|
+
triggerManualRefresh();
|
|
2284
|
+
setManualRefreshCount((c) => c + 1);
|
|
2285
|
+
setSelectedRow(0);
|
|
2286
|
+
}
|
|
2287
|
+
});
|
|
2288
|
+
if (error) {
|
|
2289
|
+
return /* @__PURE__ */ jsxs4(
|
|
2290
|
+
Box4,
|
|
2291
|
+
{
|
|
2292
|
+
flexDirection: "column",
|
|
2293
|
+
paddingLeft: 1,
|
|
2294
|
+
paddingTop: 1,
|
|
2295
|
+
paddingBottom: 1,
|
|
2296
|
+
children: [
|
|
2297
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
2298
|
+
"Error: ",
|
|
2299
|
+
error
|
|
2300
|
+
] }),
|
|
2301
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2302
|
+
]
|
|
2303
|
+
}
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
if (loading) {
|
|
2307
|
+
return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
2308
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
2309
|
+
" Loading\u2026"
|
|
2310
|
+
] }) });
|
|
2311
|
+
}
|
|
2312
|
+
if (coins.length === 0) {
|
|
2313
|
+
return /* @__PURE__ */ jsxs4(
|
|
2314
|
+
Box4,
|
|
2315
|
+
{
|
|
2316
|
+
flexDirection: "column",
|
|
2317
|
+
paddingLeft: 1,
|
|
2318
|
+
paddingTop: 1,
|
|
2319
|
+
paddingBottom: 1,
|
|
2320
|
+
children: [
|
|
2321
|
+
/* @__PURE__ */ jsx4(Text4, { children: "No coins found." }),
|
|
2322
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2323
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Try a different sort or type:" }),
|
|
2324
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort volume --type all" }),
|
|
2325
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort new --type all" })
|
|
2326
|
+
] }),
|
|
2327
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2328
|
+
]
|
|
2329
|
+
}
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
|
|
2333
|
+
const subtitle = `Page ${page} \xB7 ${coins.length} result${coins.length !== 1 ? "s" : ""}`;
|
|
2334
|
+
const rankedCoins = coins.map((c, i) => ({
|
|
2335
|
+
...c,
|
|
2336
|
+
rank: (page - 1) * limit + i + 1
|
|
2337
|
+
}));
|
|
2338
|
+
const hints = [];
|
|
2339
|
+
hints.push("\u2191\u2193 select");
|
|
2340
|
+
hints.push("c copy address");
|
|
2341
|
+
if (cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
2342
|
+
if (pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
2343
|
+
hints.push("r refresh");
|
|
2344
|
+
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
2345
|
+
hints.push("q quit");
|
|
2346
|
+
const footer = hints.join(" \xB7 ") + (copyFeedback ? ` ${copyFeedback}` : "");
|
|
2347
|
+
return /* @__PURE__ */ jsx4(
|
|
2348
|
+
Table,
|
|
2349
|
+
{
|
|
2350
|
+
data: rankedCoins,
|
|
2351
|
+
columns: COLUMNS,
|
|
2352
|
+
title,
|
|
2353
|
+
subtitle,
|
|
2354
|
+
footer,
|
|
2355
|
+
selectedRow
|
|
2356
|
+
}
|
|
2357
|
+
);
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
// src/commands/explore.tsx
|
|
2361
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
2362
|
+
var QUERY_MAP = {
|
|
2363
|
+
mcap: {
|
|
2364
|
+
all: getMostValuableAll,
|
|
2365
|
+
trend: getMostValuableTrends,
|
|
2366
|
+
"creator-coin": getMostValuableCreatorCoins,
|
|
2367
|
+
post: getCoinsMostValuable
|
|
2368
|
+
},
|
|
2369
|
+
volume: {
|
|
2370
|
+
all: getExploreTopVolumeAll24h,
|
|
2371
|
+
trend: getTopVolumeTrends24h,
|
|
2372
|
+
"creator-coin": getExploreTopVolumeCreators24h,
|
|
2373
|
+
post: getCoinsTopVolume24h
|
|
2374
|
+
},
|
|
2375
|
+
new: {
|
|
2376
|
+
all: getExploreNewAll,
|
|
2377
|
+
trend: getNewTrends,
|
|
2378
|
+
"creator-coin": getCreatorCoins,
|
|
2379
|
+
post: getCoinsNew
|
|
2380
|
+
},
|
|
2381
|
+
gainers: {
|
|
2382
|
+
post: getCoinsTopGainers
|
|
2383
|
+
},
|
|
2384
|
+
"last-traded": {
|
|
2385
|
+
post: getCoinsLastTraded
|
|
2386
|
+
},
|
|
2387
|
+
"last-traded-unique": {
|
|
2388
|
+
post: getCoinsLastTradedUnique
|
|
2389
|
+
},
|
|
2390
|
+
trending: {
|
|
2391
|
+
all: getTrendingAll,
|
|
2392
|
+
trend: getTrendingTrends,
|
|
2393
|
+
"creator-coin": getTrendingCreators,
|
|
2394
|
+
post: getTrendingPosts
|
|
2395
|
+
},
|
|
2396
|
+
featured: {
|
|
2397
|
+
"creator-coin": getExploreFeaturedCreators,
|
|
2398
|
+
post: getExploreFeaturedVideos
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
var STATIC_COLUMNS = [
|
|
2402
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2403
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
2404
|
+
{ header: "Address", width: 48, accessor: (c) => c.address ?? "" },
|
|
2405
|
+
{
|
|
2406
|
+
header: "Type",
|
|
2407
|
+
width: 14,
|
|
2408
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
header: "Market Cap",
|
|
2412
|
+
width: 12,
|
|
2413
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
header: "24h Vol",
|
|
2417
|
+
width: 12,
|
|
2418
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
header: "24h Change",
|
|
2422
|
+
width: 11,
|
|
2423
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
2424
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2425
|
+
}
|
|
2426
|
+
];
|
|
2427
|
+
var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
|
|
2428
|
+
var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
|
|
2429
|
+
"--type <type>",
|
|
2430
|
+
"Filter by type: all, trend, creator-coin, post (availability varies by sort)",
|
|
2431
|
+
"post"
|
|
2432
|
+
).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
2433
|
+
"--refresh <seconds>",
|
|
2434
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
2435
|
+
"30"
|
|
2436
|
+
).action(async function(opts) {
|
|
2437
|
+
const output = getOutputMode(this, "live");
|
|
2438
|
+
const json = output === "json";
|
|
2439
|
+
const sort = opts.sort;
|
|
2440
|
+
const type = opts.type;
|
|
2441
|
+
const limit = parseInt(opts.limit, 10);
|
|
2442
|
+
const after = opts.after;
|
|
2443
|
+
if (isNaN(limit) || limit <= 0 || limit > 20) {
|
|
2444
|
+
outputErrorAndExit(
|
|
2445
|
+
json,
|
|
2446
|
+
`Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
|
|
2447
|
+
"Usage: zora explore --limit 10"
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
if (!QUERY_MAP[sort]) {
|
|
2451
|
+
outputErrorAndExit(
|
|
2452
|
+
json,
|
|
2453
|
+
`Invalid --sort value: ${sort}.`,
|
|
2454
|
+
`Supported: ${SORT_OPTIONS2}`
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
if (!QUERY_MAP[sort][type]) {
|
|
2458
|
+
const supported = Object.keys(QUERY_MAP[sort]);
|
|
2459
|
+
outputErrorAndExit(
|
|
2460
|
+
json,
|
|
2461
|
+
`Invalid --type for --sort ${sort}.`,
|
|
2462
|
+
`Supported: ${supported.join(", ")}`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
const apiKey = getApiKey();
|
|
2466
|
+
if (apiKey) {
|
|
2467
|
+
setApiKey3(apiKey);
|
|
2468
|
+
}
|
|
2469
|
+
const queryFn = QUERY_MAP[sort][type];
|
|
2470
|
+
if (json) {
|
|
2471
|
+
let response;
|
|
2472
|
+
try {
|
|
2473
|
+
response = await queryFn({ count: limit, after });
|
|
2474
|
+
} catch (err) {
|
|
2475
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
2476
|
+
}
|
|
2477
|
+
if (response.error) {
|
|
2478
|
+
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
2479
|
+
outputErrorAndExit(json, `API error: ${msg}`);
|
|
2480
|
+
}
|
|
2481
|
+
const edges = response.data?.exploreList?.edges ?? [];
|
|
2482
|
+
const coins = edges.map((e) => e.node);
|
|
2483
|
+
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2484
|
+
outputJson({ coins, pageInfo: pageInfo ?? null });
|
|
2485
|
+
track("cli_explore", {
|
|
2486
|
+
sort,
|
|
2487
|
+
type,
|
|
2488
|
+
limit,
|
|
2489
|
+
paginated: after !== void 0,
|
|
2490
|
+
result_count: coins.length,
|
|
2491
|
+
has_next_page: pageInfo?.hasNextPage ?? false,
|
|
2492
|
+
output_format: "json"
|
|
2493
|
+
});
|
|
2494
|
+
} else {
|
|
2495
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
2496
|
+
const fetchPage = async (cursor) => {
|
|
2497
|
+
const response = await queryFn({ count: limit, after: cursor });
|
|
2498
|
+
if (response.error) {
|
|
2499
|
+
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
2500
|
+
throw new Error(msg);
|
|
2501
|
+
}
|
|
2502
|
+
const edges = response.data?.exploreList?.edges ?? [];
|
|
2503
|
+
const coins = edges.map((e) => e.node);
|
|
2504
|
+
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2505
|
+
return { coins, pageInfo };
|
|
2506
|
+
};
|
|
2507
|
+
if (live) {
|
|
2508
|
+
await renderLive(
|
|
2509
|
+
/* @__PURE__ */ jsx5(
|
|
2510
|
+
ExploreView,
|
|
2511
|
+
{
|
|
2512
|
+
fetchPage,
|
|
2513
|
+
sort,
|
|
2514
|
+
type,
|
|
2515
|
+
limit,
|
|
2516
|
+
initialCursor: after,
|
|
2517
|
+
autoRefresh: live,
|
|
2518
|
+
intervalSeconds
|
|
2519
|
+
}
|
|
2520
|
+
)
|
|
2521
|
+
);
|
|
2522
|
+
track("cli_explore", {
|
|
2523
|
+
sort,
|
|
2524
|
+
type,
|
|
2525
|
+
limit,
|
|
2526
|
+
live,
|
|
2527
|
+
interval: intervalSeconds,
|
|
2528
|
+
paginated: after !== void 0,
|
|
2529
|
+
output_format: "live"
|
|
2530
|
+
});
|
|
2531
|
+
} else {
|
|
2532
|
+
const { coins } = await fetchPage(after).catch(
|
|
2533
|
+
(err) => outputErrorAndExit(
|
|
2534
|
+
false,
|
|
2535
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2536
|
+
)
|
|
2537
|
+
);
|
|
2538
|
+
const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
|
|
2539
|
+
const rankedCoins = coins.map((c, i) => ({
|
|
2540
|
+
...c,
|
|
2541
|
+
rank: i + 1
|
|
2542
|
+
}));
|
|
2543
|
+
renderOnce(
|
|
2544
|
+
/* @__PURE__ */ jsx5(Table, { columns: STATIC_COLUMNS, data: rankedCoins, title })
|
|
2545
|
+
);
|
|
2546
|
+
track("cli_explore", {
|
|
2547
|
+
sort,
|
|
2548
|
+
type,
|
|
2549
|
+
limit,
|
|
2550
|
+
live: false,
|
|
2551
|
+
paginated: after !== void 0,
|
|
2552
|
+
result_count: coins.length,
|
|
2553
|
+
output_format: "static"
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
});
|
|
2558
|
+
|
|
1843
2559
|
// src/commands/get.tsx
|
|
1844
2560
|
import { Command as Command5 } from "commander";
|
|
1845
2561
|
import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
|
|
@@ -1933,24 +2649,24 @@ async function resolveCoin(ref) {
|
|
|
1933
2649
|
}
|
|
1934
2650
|
|
|
1935
2651
|
// src/components/CoinDetail.tsx
|
|
1936
|
-
import { Box as
|
|
1937
|
-
import { jsx as
|
|
2652
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
2653
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1938
2654
|
var LABEL_WIDTH = 18;
|
|
1939
2655
|
function Row({
|
|
1940
2656
|
label,
|
|
1941
2657
|
children
|
|
1942
2658
|
}) {
|
|
1943
|
-
return /* @__PURE__ */
|
|
1944
|
-
/* @__PURE__ */
|
|
1945
|
-
/* @__PURE__ */
|
|
2659
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2660
|
+
/* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
|
|
2661
|
+
/* @__PURE__ */ jsx6(Text5, { children })
|
|
1946
2662
|
] });
|
|
1947
2663
|
}
|
|
1948
2664
|
function CoinDetail({ coin }) {
|
|
1949
2665
|
const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
|
|
1950
|
-
return /* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
1953
|
-
/* @__PURE__ */
|
|
2666
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2667
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
2668
|
+
/* @__PURE__ */ jsx6(Text5, { bold: true, children: coin.name }),
|
|
2669
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1954
2670
|
coin.coinType,
|
|
1955
2671
|
" ",
|
|
1956
2672
|
"\xB7",
|
|
@@ -1958,20 +2674,20 @@ function CoinDetail({ coin }) {
|
|
|
1958
2674
|
coin.address
|
|
1959
2675
|
] })
|
|
1960
2676
|
] }),
|
|
1961
|
-
/* @__PURE__ */
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
1964
|
-
/* @__PURE__ */
|
|
1965
|
-
/* @__PURE__ */
|
|
1966
|
-
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
2677
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
2678
|
+
/* @__PURE__ */ jsx6(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
|
|
2679
|
+
/* @__PURE__ */ jsx6(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
|
|
2680
|
+
/* @__PURE__ */ jsx6(Row, { label: "24h Change", children: /* @__PURE__ */ jsx6(Text5, { color: change.color, children: change.text }) }),
|
|
2681
|
+
/* @__PURE__ */ jsx6(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
|
|
2682
|
+
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx6(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
|
|
2683
|
+
/* @__PURE__ */ jsx6(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
|
|
1968
2684
|
] }),
|
|
1969
|
-
/* @__PURE__ */
|
|
2685
|
+
/* @__PURE__ */ jsx6(Box5, { marginBottom: 1 })
|
|
1970
2686
|
] });
|
|
1971
2687
|
}
|
|
1972
2688
|
|
|
1973
2689
|
// src/commands/get.tsx
|
|
1974
|
-
import { jsx as
|
|
2690
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1975
2691
|
function formatCoinJson(coin) {
|
|
1976
2692
|
return {
|
|
1977
2693
|
name: coin.name,
|
|
@@ -1987,7 +2703,7 @@ function formatCoinJson(coin) {
|
|
|
1987
2703
|
};
|
|
1988
2704
|
}
|
|
1989
2705
|
var VALID_TYPES = ["creator-coin", "post", "trend"];
|
|
1990
|
-
var getCommand = new Command5("get").description("Look up a coin by address or name").argument("
|
|
2706
|
+
var getCommand = new Command5("get").description("Look up a coin by address or name").argument("[identifier]", "Coin address (0x...) or creator name").option("--type <type>", "Coin type: creator-coin, post, trend").action(async function(identifier, opts) {
|
|
1991
2707
|
const json = getJson(this);
|
|
1992
2708
|
if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
|
|
1993
2709
|
outputErrorAndExit(
|
|
@@ -2013,10 +2729,7 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
|
|
|
2013
2729
|
try {
|
|
2014
2730
|
result = await resolveCoin(ref);
|
|
2015
2731
|
} catch (err) {
|
|
2016
|
-
outputErrorAndExit(
|
|
2017
|
-
json,
|
|
2018
|
-
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2019
|
-
);
|
|
2732
|
+
outputErrorAndExit(json, `Request failed: ${apiErrorMessage(err)}`);
|
|
2020
2733
|
return;
|
|
2021
2734
|
}
|
|
2022
2735
|
if (type && result.kind === "found" && result.coin.coinType !== type) {
|
|
@@ -2033,8 +2746,8 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
|
|
|
2033
2746
|
}
|
|
2034
2747
|
outputData(json, {
|
|
2035
2748
|
json: formatCoinJson(result.coin),
|
|
2036
|
-
|
|
2037
|
-
renderOnce(/* @__PURE__ */
|
|
2749
|
+
render: () => {
|
|
2750
|
+
renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin: result.coin }));
|
|
2038
2751
|
}
|
|
2039
2752
|
});
|
|
2040
2753
|
track("cli_get", {
|
|
@@ -2046,19 +2759,243 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
|
|
|
2046
2759
|
});
|
|
2047
2760
|
});
|
|
2048
2761
|
|
|
2049
|
-
// src/commands/
|
|
2762
|
+
// src/commands/price-history.tsx
|
|
2050
2763
|
import { Command as Command6 } from "commander";
|
|
2051
|
-
import
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2764
|
+
import { setApiKey as setApiKey5, apiGet } from "@zoralabs/coins-sdk";
|
|
2765
|
+
|
|
2766
|
+
// src/components/PriceHistory.tsx
|
|
2767
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
2768
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2769
|
+
var LABEL_WIDTH2 = 18;
|
|
2770
|
+
var Row2 = ({
|
|
2771
|
+
label,
|
|
2772
|
+
children
|
|
2773
|
+
}) => /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2774
|
+
/* @__PURE__ */ jsx8(Box6, { width: LABEL_WIDTH2, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: label }) }),
|
|
2775
|
+
/* @__PURE__ */ jsx8(Text6, { children })
|
|
2776
|
+
] });
|
|
2777
|
+
var PriceHistory = ({
|
|
2778
|
+
coin,
|
|
2779
|
+
coinType,
|
|
2780
|
+
interval,
|
|
2781
|
+
high,
|
|
2782
|
+
low,
|
|
2783
|
+
change,
|
|
2784
|
+
sparklineText
|
|
2785
|
+
}) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2786
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2787
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Coin", children: coin }),
|
|
2788
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Type", children: coinType }),
|
|
2789
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Interval", children: interval }),
|
|
2790
|
+
/* @__PURE__ */ jsx8(Row2, { label: "High", children: high }),
|
|
2791
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Low", children: low }),
|
|
2792
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Change", children: /* @__PURE__ */ jsx8(Text6, { color: change.color, children: change.text }) })
|
|
2793
|
+
] }),
|
|
2794
|
+
sparklineText.length > 0 && /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text6, { children: sparklineText }) }),
|
|
2795
|
+
/* @__PURE__ */ jsx8(Box6, { marginBottom: 1 })
|
|
2796
|
+
] });
|
|
2797
|
+
|
|
2798
|
+
// src/lib/sparkline.ts
|
|
2799
|
+
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
2800
|
+
var sparkline = (values) => {
|
|
2801
|
+
if (values.length <= 1) return values.length === 1 ? BLOCKS[4] : "";
|
|
2802
|
+
const min = Math.min(...values);
|
|
2803
|
+
const max = Math.max(...values);
|
|
2804
|
+
const range = max - min;
|
|
2805
|
+
if (range === 0) return BLOCKS[4].repeat(values.length);
|
|
2806
|
+
return values.map((v) => {
|
|
2807
|
+
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
2808
|
+
return BLOCKS[idx];
|
|
2809
|
+
}).join("");
|
|
2810
|
+
};
|
|
2811
|
+
var MAX_SPARKLINE_WIDTH = 50;
|
|
2812
|
+
var downsample = (values, maxWidth) => {
|
|
2813
|
+
if (values.length <= maxWidth) return values;
|
|
2814
|
+
const bucketSize = values.length / maxWidth;
|
|
2815
|
+
const result = [];
|
|
2816
|
+
for (let i = 0; i < maxWidth; i++) {
|
|
2817
|
+
const start = Math.floor(i * bucketSize);
|
|
2818
|
+
const end = Math.floor((i + 1) * bucketSize);
|
|
2819
|
+
let sum = 0;
|
|
2820
|
+
for (let j = start; j < end; j++) {
|
|
2821
|
+
sum += values[j];
|
|
2822
|
+
}
|
|
2823
|
+
result.push(sum / (end - start));
|
|
2824
|
+
}
|
|
2825
|
+
return result;
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
// src/commands/price-history.tsx
|
|
2829
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2830
|
+
var VALID_TYPES2 = ["creator-coin", "post", "trend"];
|
|
2831
|
+
var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
|
|
2832
|
+
var INTERVAL_TO_API_FIELD = {
|
|
2833
|
+
"1h": "oneHour",
|
|
2834
|
+
"24h": "oneDay",
|
|
2835
|
+
"1w": "oneWeek",
|
|
2836
|
+
"1m": "oneMonth",
|
|
2837
|
+
ALL: "all"
|
|
2838
|
+
};
|
|
2839
|
+
var formatPrice = (price) => {
|
|
2840
|
+
if (price >= 1) {
|
|
2841
|
+
return `$${price.toFixed(2)}`;
|
|
2842
|
+
}
|
|
2843
|
+
if (price >= 0.01) {
|
|
2844
|
+
return `$${price.toFixed(4)}`;
|
|
2845
|
+
}
|
|
2846
|
+
return `$${price.toPrecision(4)}`;
|
|
2847
|
+
};
|
|
2848
|
+
var formatChange = (first, last) => {
|
|
2849
|
+
if (first === 0) return { text: "-", color: void 0 };
|
|
2850
|
+
const pct = (last - first) / first * 100;
|
|
2851
|
+
const prefix = pct >= 0 ? "+" : "";
|
|
2852
|
+
const text = `${prefix}${pct.toFixed(1)}%`;
|
|
2853
|
+
const color = pct > 0 ? "green" : pct < 0 ? "red" : void 0;
|
|
2854
|
+
return { text, color };
|
|
2855
|
+
};
|
|
2856
|
+
var fetchPriceHistory = async (address, interval) => {
|
|
2857
|
+
const response = await apiGet("/coinPriceHistory", {
|
|
2858
|
+
address
|
|
2859
|
+
});
|
|
2860
|
+
const data = response.data;
|
|
2861
|
+
const token = data?.zora20Token;
|
|
2862
|
+
if (!token) return [];
|
|
2863
|
+
const field = INTERVAL_TO_API_FIELD[interval];
|
|
2864
|
+
const points = token[field];
|
|
2865
|
+
if (!points || points.length === 0) return [];
|
|
2866
|
+
return points.map((p) => ({
|
|
2867
|
+
timestamp: p.timestamp,
|
|
2868
|
+
price: Number(p.closePrice)
|
|
2869
|
+
}));
|
|
2870
|
+
};
|
|
2871
|
+
var priceHistoryCommand = new Command6("price-history").description("Display price history for a coin").argument("[identifier]", "Coin address (0x...) or name").option("--type <type>", "Coin type: creator-coin, post, trend").option(
|
|
2872
|
+
"--interval <interval>",
|
|
2873
|
+
`Time range: ${VALID_INTERVALS.join(", ")}`,
|
|
2874
|
+
"1w"
|
|
2875
|
+
).action(async function(identifier, opts) {
|
|
2876
|
+
const json = getJson(this);
|
|
2877
|
+
const interval = opts.interval ?? "1w";
|
|
2878
|
+
if (!VALID_INTERVALS.includes(interval)) {
|
|
2879
|
+
outputErrorAndExit(
|
|
2880
|
+
json,
|
|
2881
|
+
`Invalid --interval value: ${interval}.`,
|
|
2882
|
+
`Supported: ${VALID_INTERVALS.join(", ")}`
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
if (opts.type !== void 0 && !VALID_TYPES2.includes(opts.type)) {
|
|
2886
|
+
outputErrorAndExit(
|
|
2887
|
+
json,
|
|
2888
|
+
`Invalid --type value: ${opts.type}.`,
|
|
2889
|
+
`Supported: ${VALID_TYPES2.join(", ")}`
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
if (opts.type === "post" && !identifier.startsWith("0x")) {
|
|
2893
|
+
outputErrorAndExit(
|
|
2894
|
+
json,
|
|
2895
|
+
"Posts can only be looked up by address.",
|
|
2896
|
+
"Use: zora price-history 0x..."
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
const ref = parseCoinRef(identifier, opts.type);
|
|
2900
|
+
const apiKey = getApiKey();
|
|
2901
|
+
if (apiKey) {
|
|
2902
|
+
setApiKey5(apiKey);
|
|
2903
|
+
}
|
|
2904
|
+
let result;
|
|
2905
|
+
try {
|
|
2906
|
+
result = await resolveCoin(ref);
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
outputErrorAndExit(
|
|
2909
|
+
json,
|
|
2910
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2911
|
+
);
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
if (result.kind === "not-found") {
|
|
2915
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
const { coin } = result;
|
|
2919
|
+
let prices;
|
|
2920
|
+
try {
|
|
2921
|
+
prices = await fetchPriceHistory(coin.address, interval);
|
|
2922
|
+
} catch (err) {
|
|
2923
|
+
outputErrorAndExit(
|
|
2924
|
+
json,
|
|
2925
|
+
`Failed to fetch price data: ${err instanceof Error ? err.message : String(err)}`
|
|
2926
|
+
);
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
if (prices.length === 0) {
|
|
2930
|
+
outputErrorAndExit(
|
|
2931
|
+
json,
|
|
2932
|
+
`No price data found for ${coin.name} in the last ${interval}.`,
|
|
2933
|
+
"Try a longer interval with --interval"
|
|
2934
|
+
);
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const priceValues = prices.map((p) => p.price);
|
|
2938
|
+
const high = Math.max(...priceValues);
|
|
2939
|
+
const low = Math.min(...priceValues);
|
|
2940
|
+
const change = formatChange(
|
|
2941
|
+
priceValues[0],
|
|
2942
|
+
priceValues[priceValues.length - 1]
|
|
2943
|
+
);
|
|
2944
|
+
const sparklineText = sparkline(
|
|
2945
|
+
downsample(priceValues, MAX_SPARKLINE_WIDTH)
|
|
2946
|
+
);
|
|
2947
|
+
outputData(json, {
|
|
2948
|
+
json: {
|
|
2949
|
+
coin: coin.name,
|
|
2950
|
+
type: coin.coinType,
|
|
2951
|
+
interval,
|
|
2952
|
+
high,
|
|
2953
|
+
low,
|
|
2954
|
+
change: priceValues[0] === 0 ? null : (priceValues[priceValues.length - 1] - priceValues[0]) / priceValues[0],
|
|
2955
|
+
prices: prices.map((p) => ({
|
|
2956
|
+
timestamp: p.timestamp,
|
|
2957
|
+
price: p.price
|
|
2958
|
+
}))
|
|
2959
|
+
},
|
|
2960
|
+
render: () => {
|
|
2961
|
+
renderOnce(
|
|
2962
|
+
/* @__PURE__ */ jsx9(
|
|
2963
|
+
PriceHistory,
|
|
2964
|
+
{
|
|
2965
|
+
coin: coin.name,
|
|
2966
|
+
coinType: coin.coinType,
|
|
2967
|
+
interval,
|
|
2968
|
+
high: formatPrice(high),
|
|
2969
|
+
low: formatPrice(low),
|
|
2970
|
+
change,
|
|
2971
|
+
sparklineText
|
|
2972
|
+
}
|
|
2973
|
+
)
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
});
|
|
2977
|
+
track("cli_price_history", {
|
|
2978
|
+
lookup_type: identifier.startsWith("0x") ? "address" : "name",
|
|
2979
|
+
coin_type: coin.coinType,
|
|
2980
|
+
interval,
|
|
2981
|
+
data_points: prices.length,
|
|
2982
|
+
output_format: json ? "json" : "text"
|
|
2983
|
+
});
|
|
2984
|
+
});
|
|
2985
|
+
|
|
2986
|
+
// src/commands/sell.ts
|
|
2987
|
+
import { Command as Command7 } from "commander";
|
|
2988
|
+
import confirm3 from "@inquirer/confirm";
|
|
2989
|
+
import {
|
|
2990
|
+
erc20Abi as erc20Abi3,
|
|
2991
|
+
formatUnits as formatUnits4,
|
|
2055
2992
|
isAddress as isAddress2,
|
|
2056
2993
|
parseUnits as parseUnits2
|
|
2057
2994
|
} from "viem";
|
|
2058
2995
|
import {
|
|
2059
2996
|
createTradeCall as createTradeCall2,
|
|
2060
2997
|
getCoin as getCoin3,
|
|
2061
|
-
setApiKey as
|
|
2998
|
+
setApiKey as setApiKey6,
|
|
2062
2999
|
tradeCoin as tradeCoin2
|
|
2063
3000
|
} from "@zoralabs/coins-sdk";
|
|
2064
3001
|
function printSellQuote(output, info) {
|
|
@@ -2133,19 +3070,13 @@ function printSellResult(output, info) {
|
|
|
2133
3070
|
console.log(` Tx ${info.txHash}
|
|
2134
3071
|
`);
|
|
2135
3072
|
}
|
|
2136
|
-
var sellCommand = new
|
|
2137
|
-
const json =
|
|
3073
|
+
var sellCommand = new Command7("sell").description("Sell a coin").argument("[address]", "Coin contract address (0x\u2026)").option("--amount <value>", "Sell specific number of coins").option("--usd <value>", "Sell USD equivalent worth of coins").option("--percent <value>", "Sell percentage of coin balance").option("--all", "Sell entire coin balance").option("--to <asset>", "Receive asset: eth, usdc, zora", "eth").option("--token <asset>", "Receive asset: eth, usdc, zora (alias for --to)").option("--quote", "Print quote and exit without trading").option("--yes", "Skip confirmation and execute directly").option("--slippage <pct>", "Slippage tolerance percent", "1").option("--debug", "Print full quote request/response JSON").action(async function(coinAddress, opts) {
|
|
3074
|
+
const json = getJson(this);
|
|
2138
3075
|
const debug = opts.debug === true;
|
|
2139
3076
|
if (!isAddress2(coinAddress)) {
|
|
2140
3077
|
outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
|
|
2141
3078
|
}
|
|
2142
|
-
const output =
|
|
2143
|
-
if (output !== "table" && output !== "json") {
|
|
2144
|
-
outputErrorAndExit(
|
|
2145
|
-
false,
|
|
2146
|
-
`Invalid --output value: ${output}. Use: table, json`
|
|
2147
|
-
);
|
|
2148
|
-
}
|
|
3079
|
+
const output = json ? "json" : "static";
|
|
2149
3080
|
const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
|
|
2150
3081
|
if (!(outputAsset in BASE_TRADE_TOKENS)) {
|
|
2151
3082
|
outputErrorAndExit(
|
|
@@ -2170,7 +3101,7 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2170
3101
|
const slippage = slippagePct / 100;
|
|
2171
3102
|
const apiKey = getApiKey();
|
|
2172
3103
|
if (apiKey) {
|
|
2173
|
-
|
|
3104
|
+
setApiKey6(apiKey);
|
|
2174
3105
|
}
|
|
2175
3106
|
const account = resolveAccount(json);
|
|
2176
3107
|
const { publicClient, walletClient } = createClients(account);
|
|
@@ -2179,10 +3110,7 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2179
3110
|
const response = await getCoin3({ address: coinAddress });
|
|
2180
3111
|
token = response.data?.zora20Token;
|
|
2181
3112
|
} catch (err) {
|
|
2182
|
-
outputErrorAndExit(
|
|
2183
|
-
json,
|
|
2184
|
-
`Failed to fetch coin: ${err instanceof Error ? err.message : String(err)}`
|
|
2185
|
-
);
|
|
3113
|
+
outputErrorAndExit(json, `Failed to fetch coin: ${apiErrorMessage(err)}`);
|
|
2186
3114
|
}
|
|
2187
3115
|
if (!token) {
|
|
2188
3116
|
outputErrorAndExit(json, `Coin not found: ${coinAddress}`);
|
|
@@ -2200,52 +3128,995 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2200
3128
|
);
|
|
2201
3129
|
return;
|
|
2202
3130
|
}
|
|
2203
|
-
const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
|
|
2204
|
-
if (coinPriceUsd === null || coinPriceUsd <= 0) {
|
|
2205
|
-
outputErrorAndExit(
|
|
2206
|
-
json,
|
|
2207
|
-
`Failed to fetch ${coinSymbol} price for USD conversion.`
|
|
3131
|
+
const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
|
|
3132
|
+
if (coinPriceUsd === null || coinPriceUsd <= 0) {
|
|
3133
|
+
outputErrorAndExit(
|
|
3134
|
+
json,
|
|
3135
|
+
`Failed to fetch ${coinSymbol} price for USD conversion.`
|
|
3136
|
+
);
|
|
3137
|
+
return;
|
|
3138
|
+
}
|
|
3139
|
+
const coinAmount = usdVal / coinPriceUsd;
|
|
3140
|
+
amountIn = parseUnits2(coinAmount.toFixed(coinDecimals), coinDecimals);
|
|
3141
|
+
if (amountIn === 0n) {
|
|
3142
|
+
outputErrorAndExit(json, "Calculated amount is zero. USD too small.");
|
|
3143
|
+
}
|
|
3144
|
+
if (debug) {
|
|
3145
|
+
console.error(
|
|
3146
|
+
`[debug] $${usdVal} USD = ${formatUnits4(amountIn, coinDecimals)} ${coinSymbol} (coin price: $${coinPriceUsd})`
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
} else if (amountMode === "amount") {
|
|
3150
|
+
const val = parsePercentageLikeValue(opts.amount);
|
|
3151
|
+
if (val === void 0 || val <= 0) {
|
|
3152
|
+
outputErrorAndExit(
|
|
3153
|
+
json,
|
|
3154
|
+
"Invalid --amount value. Must be a positive number."
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
try {
|
|
3158
|
+
amountIn = parseUnits2(opts.amount, coinDecimals);
|
|
3159
|
+
} catch {
|
|
3160
|
+
outputErrorAndExit(json, "Invalid --amount value for token decimals.");
|
|
3161
|
+
}
|
|
3162
|
+
} else {
|
|
3163
|
+
const balance = await publicClient.readContract({
|
|
3164
|
+
abi: erc20Abi3,
|
|
3165
|
+
address: coinAddress,
|
|
3166
|
+
functionName: "balanceOf",
|
|
3167
|
+
args: [account.address]
|
|
3168
|
+
});
|
|
3169
|
+
if (balance === 0n) {
|
|
3170
|
+
outputErrorAndExit(
|
|
3171
|
+
json,
|
|
3172
|
+
`No ${coinSymbol} balance. Buy some first or pick a different wallet.`
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
if (amountMode === "all") {
|
|
3176
|
+
amountIn = balance;
|
|
3177
|
+
} else {
|
|
3178
|
+
const pct = parsePercentageLikeValue(opts.percent);
|
|
3179
|
+
if (pct === void 0 || pct <= 0 || pct > 100) {
|
|
3180
|
+
outputErrorAndExit(
|
|
3181
|
+
json,
|
|
3182
|
+
"Invalid --percent value. Must be between 0 and 100."
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
amountIn = pct === 100 ? balance : balance * BigInt(Math.round(pct * 100)) / 10000n;
|
|
3186
|
+
if (amountIn === 0n) {
|
|
3187
|
+
outputErrorAndExit(
|
|
3188
|
+
json,
|
|
3189
|
+
"Calculated amount is zero. Balance too low."
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
let swapAmountUsd;
|
|
3195
|
+
if (amountMode === "usd") {
|
|
3196
|
+
swapAmountUsd = parsePercentageLikeValue(opts.usd);
|
|
3197
|
+
} else {
|
|
3198
|
+
const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
|
|
3199
|
+
if (coinPriceUsd !== null && coinPriceUsd > 0) {
|
|
3200
|
+
swapAmountUsd = Number(
|
|
3201
|
+
(Number(formatUnits4(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
|
|
3202
|
+
2
|
|
3203
|
+
)
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
const tradeParameters = {
|
|
3208
|
+
sell: { type: "erc20", address: coinAddress },
|
|
3209
|
+
buy: outputToken.trade,
|
|
3210
|
+
amountIn,
|
|
3211
|
+
slippage,
|
|
3212
|
+
sender: account.address
|
|
3213
|
+
};
|
|
3214
|
+
if (debug) {
|
|
3215
|
+
printDebugRequest("sell", tradeParameters);
|
|
3216
|
+
}
|
|
3217
|
+
let quoteAmountOut;
|
|
3218
|
+
try {
|
|
3219
|
+
const quote = await createTradeCall2(tradeParameters);
|
|
3220
|
+
if (debug) {
|
|
3221
|
+
printDebugResponse("sell", quote);
|
|
3222
|
+
}
|
|
3223
|
+
if (!quote.quote?.amountOut || quote.quote.amountOut === "0") {
|
|
3224
|
+
outputErrorAndExit(
|
|
3225
|
+
json,
|
|
3226
|
+
"Quote returned zero output. Amount may be too small."
|
|
3227
|
+
);
|
|
3228
|
+
}
|
|
3229
|
+
quoteAmountOut = quote.quote.amountOut;
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
if (debug) {
|
|
3232
|
+
console.error(
|
|
3233
|
+
`
|
|
3234
|
+
[debug] sell \u2014 Quote Error:
|
|
3235
|
+
${err instanceof Error ? err.stack || err.message : String(err)}
|
|
3236
|
+
`
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3240
|
+
const errorType = err?.errorType;
|
|
3241
|
+
const errorBody = err?.errorBody;
|
|
3242
|
+
if (errorType === "LIQUIDITY" || msg.includes("Not enough liquidity")) {
|
|
3243
|
+
if (json) {
|
|
3244
|
+
outputJson({ error: errorBody ?? msg });
|
|
3245
|
+
process.exit(1);
|
|
3246
|
+
}
|
|
3247
|
+
outputErrorAndExit(
|
|
3248
|
+
json,
|
|
3249
|
+
"Not enough available liquidity for your swap. Please try swapping fewer tokens."
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
outputErrorAndExit(
|
|
3253
|
+
json,
|
|
3254
|
+
`Quote failed: ${apiErrorMessage(err)}`,
|
|
3255
|
+
"Check the coin address and amount, then try again. Use --debug for full error details."
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
const soldFormatted = formatAmountDisplay(amountIn, coinDecimals);
|
|
3259
|
+
const receivedFormatted = formatAmountDisplay(
|
|
3260
|
+
BigInt(quoteAmountOut),
|
|
3261
|
+
outputToken.decimals
|
|
3262
|
+
);
|
|
3263
|
+
if (opts.quote) {
|
|
3264
|
+
printSellQuote(output, {
|
|
3265
|
+
coinName,
|
|
3266
|
+
coinSymbol,
|
|
3267
|
+
address: coinAddress,
|
|
3268
|
+
soldFormatted,
|
|
3269
|
+
amountIn,
|
|
3270
|
+
coinDecimals,
|
|
3271
|
+
receivedFormatted,
|
|
3272
|
+
quoteAmountOut,
|
|
3273
|
+
outputSymbol: outputToken.symbol,
|
|
3274
|
+
outputDecimals: outputToken.decimals,
|
|
3275
|
+
slippagePct
|
|
3276
|
+
});
|
|
3277
|
+
track("cli_sell", {
|
|
3278
|
+
action: "quote",
|
|
3279
|
+
coin_address: coinAddress,
|
|
3280
|
+
coin_name: coinName,
|
|
3281
|
+
coin_symbol: coinSymbol,
|
|
3282
|
+
amount_mode: amountMode,
|
|
3283
|
+
swap_amount_usd: swapAmountUsd,
|
|
3284
|
+
valueUsd: swapAmountUsd,
|
|
3285
|
+
swapCoinType: token.coinType ?? null,
|
|
3286
|
+
output_asset: outputAsset,
|
|
3287
|
+
slippage: slippagePct,
|
|
3288
|
+
output_format: output
|
|
3289
|
+
});
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
if (!opts.yes) {
|
|
3293
|
+
printSellQuote("static", {
|
|
3294
|
+
coinName,
|
|
3295
|
+
coinSymbol,
|
|
3296
|
+
address: coinAddress,
|
|
3297
|
+
soldFormatted,
|
|
3298
|
+
amountIn,
|
|
3299
|
+
coinDecimals,
|
|
3300
|
+
receivedFormatted,
|
|
3301
|
+
quoteAmountOut,
|
|
3302
|
+
outputSymbol: outputToken.symbol,
|
|
3303
|
+
outputDecimals: outputToken.decimals,
|
|
3304
|
+
slippagePct
|
|
3305
|
+
});
|
|
3306
|
+
const ok = await confirm3({
|
|
3307
|
+
message: "Confirm?",
|
|
3308
|
+
default: false
|
|
3309
|
+
});
|
|
3310
|
+
if (!ok) {
|
|
3311
|
+
console.error("Aborted.");
|
|
3312
|
+
process.exit(0);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
let receipt;
|
|
3316
|
+
let txHash;
|
|
3317
|
+
let receivedAmountOut = BigInt(quoteAmountOut);
|
|
3318
|
+
let receivedSource = "quote";
|
|
3319
|
+
let swapLogIndex = null;
|
|
3320
|
+
const swapCoinType = token.coinType ?? null;
|
|
3321
|
+
try {
|
|
3322
|
+
receipt = await tradeCoin2({
|
|
3323
|
+
tradeParameters,
|
|
3324
|
+
walletClient,
|
|
3325
|
+
publicClient,
|
|
3326
|
+
account
|
|
3327
|
+
});
|
|
3328
|
+
} catch (err) {
|
|
3329
|
+
track("cli_sell", {
|
|
3330
|
+
action: "trade",
|
|
3331
|
+
coin_address: coinAddress,
|
|
3332
|
+
coin_name: coinName,
|
|
3333
|
+
coin_symbol: coinSymbol,
|
|
3334
|
+
amount_mode: amountMode,
|
|
3335
|
+
swap_amount_usd: swapAmountUsd,
|
|
3336
|
+
valueUsd: swapAmountUsd,
|
|
3337
|
+
swapCoinType,
|
|
3338
|
+
output_asset: outputAsset,
|
|
3339
|
+
slippage: slippagePct,
|
|
3340
|
+
output_format: output,
|
|
3341
|
+
success: false,
|
|
3342
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3343
|
+
});
|
|
3344
|
+
await shutdownAnalytics();
|
|
3345
|
+
outputErrorAndExit(json, tradeErrorMessage(err));
|
|
3346
|
+
}
|
|
3347
|
+
txHash = receipt.transactionHash;
|
|
3348
|
+
if (outputToken.trade.type === "erc20") {
|
|
3349
|
+
try {
|
|
3350
|
+
const result = getReceivedAmountFromReceipt({
|
|
3351
|
+
receipt,
|
|
3352
|
+
tokenAddress: outputToken.trade.address,
|
|
3353
|
+
recipient: account.address
|
|
3354
|
+
});
|
|
3355
|
+
receivedAmountOut = result.amount;
|
|
3356
|
+
swapLogIndex = result.logIndex;
|
|
3357
|
+
receivedSource = "receipt";
|
|
3358
|
+
} catch {
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
printSellResult(output, {
|
|
3362
|
+
coinName,
|
|
3363
|
+
coinSymbol,
|
|
3364
|
+
address: coinAddress,
|
|
3365
|
+
amountIn,
|
|
3366
|
+
coinDecimals,
|
|
3367
|
+
soldFormatted,
|
|
3368
|
+
receivedAmountOut,
|
|
3369
|
+
outputSymbol: outputToken.symbol,
|
|
3370
|
+
outputDecimals: outputToken.decimals,
|
|
3371
|
+
receivedSource,
|
|
3372
|
+
txHash
|
|
3373
|
+
});
|
|
3374
|
+
track("cli_sell", {
|
|
3375
|
+
action: "trade",
|
|
3376
|
+
coin_address: coinAddress,
|
|
3377
|
+
coin_name: coinName,
|
|
3378
|
+
coin_symbol: coinSymbol,
|
|
3379
|
+
amount_mode: amountMode,
|
|
3380
|
+
swap_amount_usd: swapAmountUsd,
|
|
3381
|
+
valueUsd: swapAmountUsd,
|
|
3382
|
+
swapCoinType,
|
|
3383
|
+
transactionHash: txHash,
|
|
3384
|
+
logIndex: swapLogIndex,
|
|
3385
|
+
output_asset: outputAsset,
|
|
3386
|
+
slippage: slippagePct,
|
|
3387
|
+
output_format: output,
|
|
3388
|
+
success: true,
|
|
3389
|
+
tx_hash: txHash
|
|
3390
|
+
});
|
|
3391
|
+
});
|
|
3392
|
+
|
|
3393
|
+
// src/commands/profile.tsx
|
|
3394
|
+
import { Command as Command8 } from "commander";
|
|
3395
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
3396
|
+
import {
|
|
3397
|
+
getProfileCoins,
|
|
3398
|
+
getProfileBalances as getProfileBalances2,
|
|
3399
|
+
setApiKey as setApiKey7
|
|
3400
|
+
} from "@zoralabs/coins-sdk";
|
|
3401
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3402
|
+
|
|
3403
|
+
// src/components/ProfileView.tsx
|
|
3404
|
+
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
3405
|
+
import { Box as Box7, Text as Text7, useInput as useInput3, useApp as useApp3 } from "ink";
|
|
3406
|
+
import Spinner3 from "ink-spinner";
|
|
3407
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3408
|
+
var TAB_NAMES = ["Posts", "Holdings"];
|
|
3409
|
+
var postColumns = [
|
|
3410
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
3411
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
3412
|
+
{
|
|
3413
|
+
header: "Type",
|
|
3414
|
+
width: 14,
|
|
3415
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
3416
|
+
},
|
|
3417
|
+
{
|
|
3418
|
+
header: "Market Cap",
|
|
3419
|
+
width: 12,
|
|
3420
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
header: "24h Vol",
|
|
3424
|
+
width: 12,
|
|
3425
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
3426
|
+
},
|
|
3427
|
+
{
|
|
3428
|
+
header: "24h Change",
|
|
3429
|
+
width: 11,
|
|
3430
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
3431
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
header: "Created",
|
|
3435
|
+
width: 16,
|
|
3436
|
+
accessor: (c) => {
|
|
3437
|
+
if (!c.createdAt) return "-";
|
|
3438
|
+
const date = new Date(c.createdAt);
|
|
3439
|
+
if (isNaN(date.getTime())) return "-";
|
|
3440
|
+
return formatRelativeTime(date);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
];
|
|
3444
|
+
var ProfileView = ({
|
|
3445
|
+
fetchData,
|
|
3446
|
+
identifier,
|
|
3447
|
+
autoRefresh = false,
|
|
3448
|
+
intervalSeconds = 30
|
|
3449
|
+
}) => {
|
|
3450
|
+
const { exit } = useApp3();
|
|
3451
|
+
const [activeTab, setActiveTab] = useState4(0);
|
|
3452
|
+
const [loading, setLoading] = useState4(true);
|
|
3453
|
+
const [isRefreshing, setIsRefreshing] = useState4(false);
|
|
3454
|
+
const [error, setError] = useState4(null);
|
|
3455
|
+
const [data, setData] = useState4(null);
|
|
3456
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
3457
|
+
const [manualRefreshCount, setManualRefreshCount] = useState4(0);
|
|
3458
|
+
const hasLoadedOnce = useRef3(false);
|
|
3459
|
+
const load = useCallback4(async () => {
|
|
3460
|
+
if (hasLoadedOnce.current) {
|
|
3461
|
+
setIsRefreshing(true);
|
|
3462
|
+
} else {
|
|
3463
|
+
setLoading(true);
|
|
3464
|
+
}
|
|
3465
|
+
setError(null);
|
|
3466
|
+
try {
|
|
3467
|
+
const result = await fetchData();
|
|
3468
|
+
setData(result);
|
|
3469
|
+
hasLoadedOnce.current = true;
|
|
3470
|
+
} catch (err) {
|
|
3471
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3472
|
+
}
|
|
3473
|
+
setLoading(false);
|
|
3474
|
+
setIsRefreshing(false);
|
|
3475
|
+
}, [fetchData]);
|
|
3476
|
+
useEffect4(() => {
|
|
3477
|
+
load();
|
|
3478
|
+
}, [load, refreshCount, manualRefreshCount]);
|
|
3479
|
+
useInput3((input, key) => {
|
|
3480
|
+
if (input === "q" || key.escape) {
|
|
3481
|
+
exit();
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
if (input === "r" && !loading) {
|
|
3485
|
+
triggerManualRefresh();
|
|
3486
|
+
setManualRefreshCount((c) => c + 1);
|
|
3487
|
+
}
|
|
3488
|
+
if (key.leftArrow || input === "1") {
|
|
3489
|
+
setActiveTab(0);
|
|
3490
|
+
}
|
|
3491
|
+
if (key.rightArrow || input === "2") {
|
|
3492
|
+
setActiveTab(1);
|
|
3493
|
+
}
|
|
3494
|
+
});
|
|
3495
|
+
if (error && !data) {
|
|
3496
|
+
return /* @__PURE__ */ jsxs7(
|
|
3497
|
+
Box7,
|
|
3498
|
+
{
|
|
3499
|
+
flexDirection: "column",
|
|
3500
|
+
paddingLeft: 1,
|
|
3501
|
+
paddingTop: 1,
|
|
3502
|
+
paddingBottom: 1,
|
|
3503
|
+
children: [
|
|
3504
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
3505
|
+
"Error: ",
|
|
3506
|
+
error
|
|
3507
|
+
] }),
|
|
3508
|
+
/* @__PURE__ */ jsx10(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: "Press q to exit" }) })
|
|
3509
|
+
]
|
|
3510
|
+
}
|
|
3511
|
+
);
|
|
3512
|
+
}
|
|
3513
|
+
if (loading && !data) {
|
|
3514
|
+
return /* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
3515
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3516
|
+
" Loading profile\u2026"
|
|
3517
|
+
] }) });
|
|
3518
|
+
}
|
|
3519
|
+
if (!data) return null;
|
|
3520
|
+
const hints = ["\u2190 \u2192 switch tab", "r refresh"];
|
|
3521
|
+
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
3522
|
+
hints.push("q quit");
|
|
3523
|
+
const footer = hints.join(" \xB7 ");
|
|
3524
|
+
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
3525
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
3526
|
+
isRefreshing && /* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
3527
|
+
/* @__PURE__ */ jsx10(Spinner3, { type: "dots" }),
|
|
3528
|
+
" Refreshing\u2026"
|
|
3529
|
+
] }) }),
|
|
3530
|
+
/* @__PURE__ */ jsxs7(Box7, { paddingLeft: 1, paddingTop: 1, gap: 2, children: [
|
|
3531
|
+
TAB_NAMES.map((name, i) => /* @__PURE__ */ jsx10(Text7, { bold: activeTab === i, dimColor: activeTab !== i, children: activeTab === i ? `[${name}]` : name }, name)),
|
|
3532
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
3533
|
+
" ",
|
|
3534
|
+
identifier
|
|
3535
|
+
] })
|
|
3536
|
+
] }),
|
|
3537
|
+
activeTab === 0 ? rankedPosts.length === 0 ? /* @__PURE__ */ jsx10(
|
|
3538
|
+
Box7,
|
|
3539
|
+
{
|
|
3540
|
+
flexDirection: "column",
|
|
3541
|
+
paddingLeft: 1,
|
|
3542
|
+
paddingTop: 1,
|
|
3543
|
+
paddingBottom: 1,
|
|
3544
|
+
children: /* @__PURE__ */ jsx10(Text7, { children: "No posts found for this profile." })
|
|
3545
|
+
}
|
|
3546
|
+
) : /* @__PURE__ */ jsx10(
|
|
3547
|
+
Table,
|
|
3548
|
+
{
|
|
3549
|
+
columns: postColumns,
|
|
3550
|
+
data: rankedPosts,
|
|
3551
|
+
title: "Posts",
|
|
3552
|
+
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
3553
|
+
}
|
|
3554
|
+
) : data.holdings.length === 0 ? /* @__PURE__ */ jsx10(
|
|
3555
|
+
Box7,
|
|
3556
|
+
{
|
|
3557
|
+
flexDirection: "column",
|
|
3558
|
+
paddingLeft: 1,
|
|
3559
|
+
paddingTop: 1,
|
|
3560
|
+
paddingBottom: 1,
|
|
3561
|
+
children: /* @__PURE__ */ jsx10(Text7, { children: "No holdings found for this profile." })
|
|
3562
|
+
}
|
|
3563
|
+
) : /* @__PURE__ */ jsx10(
|
|
3564
|
+
Table,
|
|
3565
|
+
{
|
|
3566
|
+
columns: balanceColumns,
|
|
3567
|
+
data: data.holdings,
|
|
3568
|
+
title: "Holdings",
|
|
3569
|
+
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
3570
|
+
}
|
|
3571
|
+
),
|
|
3572
|
+
/* @__PURE__ */ jsx10(Box7, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: footer }) })
|
|
3573
|
+
] });
|
|
3574
|
+
};
|
|
3575
|
+
|
|
3576
|
+
// src/commands/profile.tsx
|
|
3577
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3578
|
+
var extractErrorMessage2 = (error) => {
|
|
3579
|
+
if (typeof error === "object" && error !== null && "error" in error) {
|
|
3580
|
+
return String(error.error);
|
|
3581
|
+
}
|
|
3582
|
+
return JSON.stringify(error);
|
|
3583
|
+
};
|
|
3584
|
+
var resolveApiKey = (json) => {
|
|
3585
|
+
const apiKey = getApiKey();
|
|
3586
|
+
if (!apiKey) {
|
|
3587
|
+
outputErrorAndExit(
|
|
3588
|
+
json,
|
|
3589
|
+
"Not authenticated. Run 'zora auth configure' to set your API key."
|
|
3590
|
+
);
|
|
3591
|
+
}
|
|
3592
|
+
setApiKey7(apiKey);
|
|
3593
|
+
};
|
|
3594
|
+
var formatPostJson = (post, rank) => ({
|
|
3595
|
+
rank,
|
|
3596
|
+
name: post.name,
|
|
3597
|
+
symbol: post.symbol,
|
|
3598
|
+
coinType: COIN_TYPE_DISPLAY[post.coinType] ?? post.coinType,
|
|
3599
|
+
address: post.address,
|
|
3600
|
+
marketCap: post.marketCap ?? null,
|
|
3601
|
+
marketCapDelta24h: post.marketCapDelta24h ?? null,
|
|
3602
|
+
volume24h: post.volume24h ?? null,
|
|
3603
|
+
createdAt: post.createdAt ?? null
|
|
3604
|
+
});
|
|
3605
|
+
var formatHoldingJson = (balance) => {
|
|
3606
|
+
const priceUsd = balance.coin?.tokenPrice?.priceInUsdc ? Number(balance.coin.tokenPrice.priceInUsdc) : null;
|
|
3607
|
+
const usdValue = priceUsd !== null ? Number((parseRawBalance(balance.balance) * priceUsd).toFixed(6)) : null;
|
|
3608
|
+
return {
|
|
3609
|
+
rank: balance.rank,
|
|
3610
|
+
name: balance.coin?.name ?? null,
|
|
3611
|
+
symbol: balance.coin?.symbol ?? null,
|
|
3612
|
+
coinType: balance.coin?.coinType ?? null,
|
|
3613
|
+
address: balance.coin?.address ?? null,
|
|
3614
|
+
balance: normalizeTokenAmount(balance.balance),
|
|
3615
|
+
usdValue,
|
|
3616
|
+
priceUsd,
|
|
3617
|
+
marketCap: balance.coin?.marketCap ? Number(balance.coin.marketCap) : null
|
|
3618
|
+
};
|
|
3619
|
+
};
|
|
3620
|
+
var fetchProfileData = async (identifier) => {
|
|
3621
|
+
const [postsResult, holdingsResult] = await Promise.allSettled([
|
|
3622
|
+
getProfileCoins({ identifier, count: 20 }),
|
|
3623
|
+
getProfileBalances2({ identifier, count: 20, sortOption: "USD_VALUE" })
|
|
3624
|
+
]);
|
|
3625
|
+
if (postsResult.status === "rejected") {
|
|
3626
|
+
throw new Error(
|
|
3627
|
+
postsResult.reason instanceof Error ? postsResult.reason.message : String(postsResult.reason)
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
if (holdingsResult.status === "rejected") {
|
|
3631
|
+
throw new Error(
|
|
3632
|
+
holdingsResult.reason instanceof Error ? holdingsResult.reason.message : String(holdingsResult.reason)
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
if (postsResult.value.error) {
|
|
3636
|
+
throw new Error(
|
|
3637
|
+
`API error (posts): ${extractErrorMessage2(postsResult.value.error)}`
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
if (holdingsResult.value.error) {
|
|
3641
|
+
throw new Error(
|
|
3642
|
+
`API error (holdings): ${extractErrorMessage2(holdingsResult.value.error)}`
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
const postEdges = postsResult.value.data?.profile?.createdCoins?.edges ?? [];
|
|
3646
|
+
const posts = postEdges.map((e) => e.node);
|
|
3647
|
+
const postsCount = postsResult.value.data?.profile?.createdCoins?.count ?? posts.length;
|
|
3648
|
+
const holdingEdges = holdingsResult.value.data?.profile?.coinBalances?.edges ?? [];
|
|
3649
|
+
const holdings = holdingEdges.map(
|
|
3650
|
+
(e, i) => ({
|
|
3651
|
+
...e.node,
|
|
3652
|
+
rank: i + 1
|
|
3653
|
+
})
|
|
3654
|
+
);
|
|
3655
|
+
const holdingsCount = holdingsResult.value.data?.profile?.coinBalances?.count ?? holdings.length;
|
|
3656
|
+
return { posts, postsCount, holdings, holdingsCount };
|
|
3657
|
+
};
|
|
3658
|
+
var profileCommand = new Command8("profile").description("View profile activity (posts and holdings)").argument(
|
|
3659
|
+
"[identifier]",
|
|
3660
|
+
"Wallet address or profile handle (defaults to your wallet)"
|
|
3661
|
+
).option("--live", "Interactive live-updating display (default)").option("--static", "Static snapshot").option(
|
|
3662
|
+
"--refresh <seconds>",
|
|
3663
|
+
"Auto-refresh interval in seconds, requires --live (min 5)",
|
|
3664
|
+
"30"
|
|
3665
|
+
).action(async function(identifierArg) {
|
|
3666
|
+
const output = getOutputMode(this, "live");
|
|
3667
|
+
const json = output === "json";
|
|
3668
|
+
resolveApiKey(json);
|
|
3669
|
+
const { live, intervalSeconds } = getLiveConfig(this, output);
|
|
3670
|
+
let identifier = identifierArg;
|
|
3671
|
+
if (!identifier) {
|
|
3672
|
+
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
3673
|
+
const key = envKey || getPrivateKey();
|
|
3674
|
+
if (!key) {
|
|
3675
|
+
outputErrorAndExit(
|
|
3676
|
+
json,
|
|
3677
|
+
"No identifier provided and no wallet configured.",
|
|
3678
|
+
"Pass an address or handle, or run 'zora setup' first."
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3681
|
+
try {
|
|
3682
|
+
identifier = privateKeyToAccount3(normalizeKey(key)).address;
|
|
3683
|
+
} catch {
|
|
3684
|
+
outputErrorAndExit(
|
|
3685
|
+
json,
|
|
3686
|
+
"Invalid wallet key. Run 'zora setup --force' to replace it."
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
if (json) {
|
|
3691
|
+
const data = await fetchProfileData(identifier).catch(
|
|
3692
|
+
(err) => outputErrorAndExit(
|
|
3693
|
+
json,
|
|
3694
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3695
|
+
)
|
|
3696
|
+
);
|
|
3697
|
+
outputData(json, {
|
|
3698
|
+
json: {
|
|
3699
|
+
posts: data.posts.map((p, i) => formatPostJson(p, i + 1)),
|
|
3700
|
+
holdings: data.holdings.map(formatHoldingJson)
|
|
3701
|
+
},
|
|
3702
|
+
render: () => {
|
|
3703
|
+
}
|
|
3704
|
+
});
|
|
3705
|
+
track("cli_profile", {
|
|
3706
|
+
identifier,
|
|
3707
|
+
output_format: "json",
|
|
3708
|
+
posts_count: data.postsCount,
|
|
3709
|
+
holdings_count: data.holdingsCount
|
|
3710
|
+
});
|
|
3711
|
+
} else if (live) {
|
|
3712
|
+
const fetchData = () => fetchProfileData(identifier);
|
|
3713
|
+
await renderLive(
|
|
3714
|
+
/* @__PURE__ */ jsx11(
|
|
3715
|
+
ProfileView,
|
|
3716
|
+
{
|
|
3717
|
+
fetchData,
|
|
3718
|
+
identifier,
|
|
3719
|
+
autoRefresh: live,
|
|
3720
|
+
intervalSeconds
|
|
3721
|
+
}
|
|
3722
|
+
)
|
|
3723
|
+
);
|
|
3724
|
+
track("cli_profile", {
|
|
3725
|
+
identifier,
|
|
3726
|
+
output_format: "live",
|
|
3727
|
+
live,
|
|
3728
|
+
interval: intervalSeconds
|
|
3729
|
+
});
|
|
3730
|
+
} else {
|
|
3731
|
+
const data = await fetchProfileData(identifier).catch(
|
|
3732
|
+
(err) => outputErrorAndExit(
|
|
3733
|
+
json,
|
|
3734
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3735
|
+
)
|
|
3736
|
+
);
|
|
3737
|
+
const rankedPosts = data.posts.map((p, i) => ({ ...p, rank: i + 1 }));
|
|
3738
|
+
renderOnce(
|
|
3739
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
3740
|
+
rankedPosts.length === 0 ? /* @__PURE__ */ jsx11(
|
|
3741
|
+
Box8,
|
|
3742
|
+
{
|
|
3743
|
+
flexDirection: "column",
|
|
3744
|
+
paddingLeft: 1,
|
|
3745
|
+
paddingTop: 1,
|
|
3746
|
+
paddingBottom: 1,
|
|
3747
|
+
children: /* @__PURE__ */ jsx11(Box8, { children: /* @__PURE__ */ jsx11(Text8, { children: "No posts found for this profile." }) })
|
|
3748
|
+
}
|
|
3749
|
+
) : /* @__PURE__ */ jsx11(
|
|
3750
|
+
Table,
|
|
3751
|
+
{
|
|
3752
|
+
columns: postColumns,
|
|
3753
|
+
data: rankedPosts,
|
|
3754
|
+
title: "Posts",
|
|
3755
|
+
subtitle: `${rankedPosts.length} of ${data.postsCount}`
|
|
3756
|
+
}
|
|
3757
|
+
),
|
|
3758
|
+
data.holdings.length === 0 ? /* @__PURE__ */ jsx11(
|
|
3759
|
+
Box8,
|
|
3760
|
+
{
|
|
3761
|
+
flexDirection: "column",
|
|
3762
|
+
paddingLeft: 1,
|
|
3763
|
+
paddingTop: 1,
|
|
3764
|
+
paddingBottom: 1,
|
|
3765
|
+
children: /* @__PURE__ */ jsx11(Box8, { children: /* @__PURE__ */ jsx11(Text8, { children: "No holdings found for this profile." }) })
|
|
3766
|
+
}
|
|
3767
|
+
) : /* @__PURE__ */ jsx11(
|
|
3768
|
+
Table,
|
|
3769
|
+
{
|
|
3770
|
+
columns: balanceColumns,
|
|
3771
|
+
data: data.holdings,
|
|
3772
|
+
title: "Holdings",
|
|
3773
|
+
subtitle: `${data.holdings.length} of ${data.holdingsCount}`
|
|
3774
|
+
}
|
|
3775
|
+
)
|
|
3776
|
+
] })
|
|
3777
|
+
);
|
|
3778
|
+
track("cli_profile", {
|
|
3779
|
+
identifier,
|
|
3780
|
+
output_format: "static",
|
|
3781
|
+
posts_count: data.postsCount,
|
|
3782
|
+
holdings_count: data.holdingsCount
|
|
3783
|
+
});
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
|
|
3787
|
+
// src/commands/send.ts
|
|
3788
|
+
import { Command as Command9 } from "commander";
|
|
3789
|
+
import confirm4 from "@inquirer/confirm";
|
|
3790
|
+
import {
|
|
3791
|
+
erc20Abi as erc20Abi4,
|
|
3792
|
+
formatUnits as formatUnits5,
|
|
3793
|
+
isAddress as isAddress3,
|
|
3794
|
+
parseUnits as parseUnits3
|
|
3795
|
+
} from "viem";
|
|
3796
|
+
import { setApiKey as setApiKey8 } from "@zoralabs/coins-sdk";
|
|
3797
|
+
var SEND_AMOUNT_CHECKS = {
|
|
3798
|
+
amount: (opts) => opts.amount !== void 0,
|
|
3799
|
+
percent: (opts) => opts.percent !== void 0,
|
|
3800
|
+
all: (opts) => opts.all === true
|
|
3801
|
+
};
|
|
3802
|
+
var VALID_TYPES3 = ["creator-coin", "post", "trend"];
|
|
3803
|
+
function printSendPreview(info) {
|
|
3804
|
+
const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
|
|
3805
|
+
console.log(`
|
|
3806
|
+
Send ${info.name} (${info.symbol})
|
|
3807
|
+
`);
|
|
3808
|
+
console.log(
|
|
3809
|
+
` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
|
|
3810
|
+
);
|
|
3811
|
+
console.log(` To ${info.to}`);
|
|
3812
|
+
console.log(`
|
|
3813
|
+
Ensure receiving wallet can receive on Base.`);
|
|
3814
|
+
console.log("");
|
|
3815
|
+
}
|
|
3816
|
+
function printSendResult(json, info) {
|
|
3817
|
+
if (json) {
|
|
3818
|
+
outputJson({
|
|
3819
|
+
action: "send",
|
|
3820
|
+
coin: info.symbol,
|
|
3821
|
+
address: info.address,
|
|
3822
|
+
sent: {
|
|
3823
|
+
amount: formatUnits5(info.amount, info.decimals),
|
|
3824
|
+
raw: info.amount.toString(),
|
|
3825
|
+
symbol: info.symbol,
|
|
3826
|
+
amountUsd: info.amountUsd
|
|
3827
|
+
},
|
|
3828
|
+
to: info.to,
|
|
3829
|
+
tx: info.txHash
|
|
3830
|
+
});
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
|
|
3834
|
+
console.log(`
|
|
3835
|
+
Sent ${info.name}
|
|
3836
|
+
`);
|
|
3837
|
+
console.log(
|
|
3838
|
+
` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
|
|
3839
|
+
);
|
|
3840
|
+
console.log(` To ${info.to}`);
|
|
3841
|
+
console.log(` Tx ${info.txHash}
|
|
3842
|
+
`);
|
|
3843
|
+
}
|
|
3844
|
+
var sendCommand = new Command9("send").description("Send coins or ETH to an address").argument("[identifier]", "Coin address, name, or token (eth, usdc, zora)").option("--to <address>", "Recipient address (0x...)").option("--type <type>", "Coin type: creator-coin, post, trend").option("--amount <value>", "Send specific amount").option("--percent <value>", "Send percentage of balance (1-100)").option("--all", "Send entire balance").option("--yes", "Skip confirmation").action(async function(identifier, opts) {
|
|
3845
|
+
const json = getJson(this);
|
|
3846
|
+
if (!opts.to) {
|
|
3847
|
+
outputErrorAndExit(
|
|
3848
|
+
json,
|
|
3849
|
+
"Missing --to flag.",
|
|
3850
|
+
"Usage: zora send <identifier> --to <address>"
|
|
3851
|
+
);
|
|
3852
|
+
}
|
|
3853
|
+
if (!isAddress3(opts.to)) {
|
|
3854
|
+
outputErrorAndExit(
|
|
3855
|
+
json,
|
|
3856
|
+
`Invalid recipient address: ${opts.to}`,
|
|
3857
|
+
"Must be a valid 0x address."
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3860
|
+
const recipient = opts.to;
|
|
3861
|
+
if (opts.type !== void 0 && !VALID_TYPES3.includes(opts.type)) {
|
|
3862
|
+
outputErrorAndExit(
|
|
3863
|
+
json,
|
|
3864
|
+
`Invalid --type value: ${opts.type}.`,
|
|
3865
|
+
`Supported: ${VALID_TYPES3.join(", ")}`
|
|
3866
|
+
);
|
|
3867
|
+
}
|
|
3868
|
+
const amountMode = getAmountMode(
|
|
3869
|
+
json,
|
|
3870
|
+
opts,
|
|
3871
|
+
SEND_AMOUNT_CHECKS,
|
|
3872
|
+
"--amount, --percent, or --all"
|
|
3873
|
+
);
|
|
3874
|
+
const isEth = identifier.toLowerCase() === "eth";
|
|
3875
|
+
if (isEth) {
|
|
3876
|
+
if (opts.type) {
|
|
3877
|
+
outputErrorAndExit(json, "--type is not valid when sending ETH.");
|
|
3878
|
+
}
|
|
3879
|
+
const account = resolveAccount(json);
|
|
3880
|
+
const { publicClient, walletClient } = createClients(account);
|
|
3881
|
+
const balance = await publicClient.getBalance({
|
|
3882
|
+
address: account.address
|
|
3883
|
+
});
|
|
3884
|
+
if (balance === 0n) {
|
|
3885
|
+
outputErrorAndExit(
|
|
3886
|
+
json,
|
|
3887
|
+
`No ETH balance. Deposit ETH to ${account.address} on Base.`
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
let amount;
|
|
3891
|
+
if (amountMode === "amount") {
|
|
3892
|
+
const val = parsePercentageLikeValue(opts.amount);
|
|
3893
|
+
if (val === void 0 || val <= 0) {
|
|
3894
|
+
outputErrorAndExit(
|
|
3895
|
+
json,
|
|
3896
|
+
"Invalid --amount value. Must be a positive number."
|
|
3897
|
+
);
|
|
3898
|
+
}
|
|
3899
|
+
try {
|
|
3900
|
+
amount = parseUnits3(opts.amount, 18);
|
|
3901
|
+
} catch {
|
|
3902
|
+
outputErrorAndExit(
|
|
3903
|
+
json,
|
|
3904
|
+
"Invalid --amount value. Must be a valid ETH amount."
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
if (amount === 0n) {
|
|
3908
|
+
outputErrorAndExit(
|
|
3909
|
+
json,
|
|
3910
|
+
"Amount too small \u2014 rounds to zero at 18 decimal places."
|
|
3911
|
+
);
|
|
3912
|
+
}
|
|
3913
|
+
if (amount + GAS_RESERVE > balance) {
|
|
3914
|
+
outputErrorAndExit(
|
|
3915
|
+
json,
|
|
3916
|
+
`Insufficient balance. Have ${formatEthDisplay(balance)} ETH (need to reserve ~${formatEthDisplay(GAS_RESERVE)} ETH for gas).`
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
} else {
|
|
3920
|
+
if (balance <= GAS_RESERVE) {
|
|
3921
|
+
outputErrorAndExit(
|
|
3922
|
+
json,
|
|
3923
|
+
`Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
const spendable = balance - GAS_RESERVE;
|
|
3927
|
+
if (amountMode === "all") {
|
|
3928
|
+
amount = spendable;
|
|
3929
|
+
} else {
|
|
3930
|
+
const pct = parsePercentageLikeValue(opts.percent);
|
|
3931
|
+
if (pct === void 0 || pct <= 0 || pct > 100) {
|
|
3932
|
+
outputErrorAndExit(
|
|
3933
|
+
json,
|
|
3934
|
+
"Invalid --percent value. Must be between 0 and 100."
|
|
3935
|
+
);
|
|
3936
|
+
}
|
|
3937
|
+
amount = pct === 100 ? spendable : spendable * BigInt(Math.round(pct * 100)) / 10000n;
|
|
3938
|
+
if (amount === 0n) {
|
|
3939
|
+
outputErrorAndExit(
|
|
3940
|
+
json,
|
|
3941
|
+
"Calculated amount is zero. Balance too low."
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
const amountFormatted = formatEthDisplay(amount);
|
|
3947
|
+
let amountUsd = null;
|
|
3948
|
+
const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
|
|
3949
|
+
if (ethPriceUsd != null) {
|
|
3950
|
+
amountUsd = Number(
|
|
3951
|
+
(Number(formatUnits5(amount, 18)) * ethPriceUsd).toFixed(2)
|
|
2208
3952
|
);
|
|
2209
|
-
return;
|
|
2210
3953
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
3954
|
+
if (!opts.yes) {
|
|
3955
|
+
printSendPreview({
|
|
3956
|
+
name: "ETH",
|
|
3957
|
+
symbol: "ETH",
|
|
3958
|
+
amountFormatted,
|
|
3959
|
+
amountUsd,
|
|
3960
|
+
to: recipient
|
|
3961
|
+
});
|
|
3962
|
+
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
3963
|
+
if (!ok) {
|
|
3964
|
+
console.error("Aborted.");
|
|
3965
|
+
process.exit(0);
|
|
3966
|
+
}
|
|
2215
3967
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
3968
|
+
let txHash;
|
|
3969
|
+
try {
|
|
3970
|
+
txHash = await walletClient.sendTransaction({
|
|
3971
|
+
to: recipient,
|
|
3972
|
+
value: amount
|
|
3973
|
+
});
|
|
3974
|
+
} catch (err) {
|
|
3975
|
+
track("cli_send", {
|
|
3976
|
+
asset: "eth",
|
|
3977
|
+
output_format: json ? "json" : "static",
|
|
3978
|
+
success: false,
|
|
3979
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3980
|
+
});
|
|
3981
|
+
await shutdownAnalytics();
|
|
3982
|
+
outputErrorAndExit(
|
|
3983
|
+
json,
|
|
3984
|
+
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2219
3985
|
);
|
|
2220
3986
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
3987
|
+
await publicClient.waitForTransactionReceipt({
|
|
3988
|
+
hash: txHash
|
|
3989
|
+
});
|
|
3990
|
+
printSendResult(json, {
|
|
3991
|
+
name: "ETH",
|
|
3992
|
+
symbol: "ETH",
|
|
3993
|
+
address: null,
|
|
3994
|
+
amount,
|
|
3995
|
+
decimals: 18,
|
|
3996
|
+
amountFormatted,
|
|
3997
|
+
amountUsd,
|
|
3998
|
+
to: recipient,
|
|
3999
|
+
txHash
|
|
4000
|
+
});
|
|
4001
|
+
track("cli_send", {
|
|
4002
|
+
asset: "eth",
|
|
4003
|
+
amount_mode: amountMode,
|
|
4004
|
+
amount_usd: amountUsd,
|
|
4005
|
+
transactionHash: txHash,
|
|
4006
|
+
output_format: json ? "json" : "static",
|
|
4007
|
+
success: true,
|
|
4008
|
+
tx_hash: txHash
|
|
4009
|
+
});
|
|
4010
|
+
} else {
|
|
4011
|
+
const knownTokenKey = identifier.toLowerCase();
|
|
4012
|
+
const knownToken = knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
|
|
4013
|
+
if (knownToken && opts.type) {
|
|
2224
4014
|
outputErrorAndExit(
|
|
2225
4015
|
json,
|
|
2226
|
-
|
|
4016
|
+
`--type is not valid when sending ${knownToken.symbol}.`
|
|
2227
4017
|
);
|
|
2228
4018
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
4019
|
+
let tokenAddress;
|
|
4020
|
+
let tokenName;
|
|
4021
|
+
if (knownToken) {
|
|
4022
|
+
const trade = knownToken.trade;
|
|
4023
|
+
tokenAddress = trade.address;
|
|
4024
|
+
tokenName = knownToken.symbol;
|
|
4025
|
+
} else {
|
|
4026
|
+
const apiKey = getApiKey();
|
|
4027
|
+
if (apiKey) {
|
|
4028
|
+
setApiKey8(apiKey);
|
|
4029
|
+
}
|
|
4030
|
+
const ref = parseCoinRef(identifier, opts.type);
|
|
4031
|
+
let result;
|
|
4032
|
+
try {
|
|
4033
|
+
result = await resolveCoin(ref);
|
|
4034
|
+
} catch (err) {
|
|
4035
|
+
outputErrorAndExit(
|
|
4036
|
+
json,
|
|
4037
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
if (result.kind === "not-found") {
|
|
4041
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
4042
|
+
}
|
|
4043
|
+
tokenAddress = result.coin.address;
|
|
4044
|
+
tokenName = result.coin.name;
|
|
4045
|
+
}
|
|
4046
|
+
const account = resolveAccount(json);
|
|
4047
|
+
const { publicClient, walletClient } = createClients(account);
|
|
4048
|
+
let balance;
|
|
4049
|
+
let decimals;
|
|
4050
|
+
let symbol;
|
|
4051
|
+
if (knownToken) {
|
|
4052
|
+
balance = await publicClient.readContract({
|
|
4053
|
+
abi: erc20Abi4,
|
|
4054
|
+
address: tokenAddress,
|
|
4055
|
+
functionName: "balanceOf",
|
|
4056
|
+
args: [account.address]
|
|
4057
|
+
});
|
|
4058
|
+
decimals = knownToken.decimals;
|
|
4059
|
+
symbol = knownToken.symbol;
|
|
4060
|
+
} else {
|
|
4061
|
+
const results = await Promise.all([
|
|
4062
|
+
publicClient.readContract({
|
|
4063
|
+
abi: erc20Abi4,
|
|
4064
|
+
address: tokenAddress,
|
|
4065
|
+
functionName: "balanceOf",
|
|
4066
|
+
args: [account.address]
|
|
4067
|
+
}),
|
|
4068
|
+
publicClient.readContract({
|
|
4069
|
+
abi: erc20Abi4,
|
|
4070
|
+
address: tokenAddress,
|
|
4071
|
+
functionName: "decimals"
|
|
4072
|
+
}),
|
|
4073
|
+
publicClient.readContract({
|
|
4074
|
+
abi: erc20Abi4,
|
|
4075
|
+
address: tokenAddress,
|
|
4076
|
+
functionName: "symbol"
|
|
4077
|
+
})
|
|
4078
|
+
]);
|
|
4079
|
+
balance = results[0];
|
|
4080
|
+
decimals = results[1];
|
|
4081
|
+
symbol = results[2];
|
|
2233
4082
|
}
|
|
2234
|
-
} else {
|
|
2235
|
-
const balance = await publicClient.readContract({
|
|
2236
|
-
abi: erc20Abi3,
|
|
2237
|
-
address: coinAddress,
|
|
2238
|
-
functionName: "balanceOf",
|
|
2239
|
-
args: [account.address]
|
|
2240
|
-
});
|
|
2241
4083
|
if (balance === 0n) {
|
|
2242
4084
|
outputErrorAndExit(
|
|
2243
4085
|
json,
|
|
2244
|
-
`No ${
|
|
4086
|
+
`No ${symbol} balance. Buy some first or pick a different wallet.`
|
|
2245
4087
|
);
|
|
2246
4088
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
4089
|
+
let amount;
|
|
4090
|
+
if (amountMode === "amount") {
|
|
4091
|
+
const val = parsePercentageLikeValue(opts.amount);
|
|
4092
|
+
if (val === void 0 || val <= 0) {
|
|
4093
|
+
outputErrorAndExit(
|
|
4094
|
+
json,
|
|
4095
|
+
"Invalid --amount value. Must be a positive number."
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
try {
|
|
4099
|
+
amount = parseUnits3(opts.amount, decimals);
|
|
4100
|
+
} catch {
|
|
4101
|
+
outputErrorAndExit(
|
|
4102
|
+
json,
|
|
4103
|
+
"Invalid --amount value for token decimals."
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
if (amount === 0n) {
|
|
4107
|
+
outputErrorAndExit(
|
|
4108
|
+
json,
|
|
4109
|
+
`Amount too small \u2014 rounds to zero at ${decimals} decimal places.`
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
if (amount > balance) {
|
|
4113
|
+
outputErrorAndExit(
|
|
4114
|
+
json,
|
|
4115
|
+
`Insufficient balance. Have ${formatAmountDisplay(balance, decimals)} ${symbol}.`
|
|
4116
|
+
);
|
|
4117
|
+
}
|
|
4118
|
+
} else if (amountMode === "all") {
|
|
4119
|
+
amount = balance;
|
|
2249
4120
|
} else {
|
|
2250
4121
|
const pct = parsePercentageLikeValue(opts.percent);
|
|
2251
4122
|
if (pct === void 0 || pct <= 0 || pct > 100) {
|
|
@@ -2254,192 +4125,91 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2254
4125
|
"Invalid --percent value. Must be between 0 and 100."
|
|
2255
4126
|
);
|
|
2256
4127
|
}
|
|
2257
|
-
|
|
2258
|
-
if (
|
|
4128
|
+
amount = pct === 100 ? balance : balance * BigInt(Math.round(pct * 100)) / 10000n;
|
|
4129
|
+
if (amount === 0n) {
|
|
2259
4130
|
outputErrorAndExit(
|
|
2260
4131
|
json,
|
|
2261
4132
|
"Calculated amount is zero. Balance too low."
|
|
2262
4133
|
);
|
|
2263
4134
|
}
|
|
2264
4135
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
};
|
|
2273
|
-
if (debug) {
|
|
2274
|
-
printDebugRequest("sell", tradeParameters);
|
|
2275
|
-
}
|
|
2276
|
-
let quoteAmountOut;
|
|
2277
|
-
try {
|
|
2278
|
-
const quote = await createTradeCall2(tradeParameters);
|
|
2279
|
-
if (debug) {
|
|
2280
|
-
printDebugResponse("sell", quote);
|
|
2281
|
-
}
|
|
2282
|
-
if (!quote.quote?.amountOut || quote.quote.amountOut === "0") {
|
|
2283
|
-
outputErrorAndExit(
|
|
2284
|
-
json,
|
|
2285
|
-
"Quote returned zero output. Amount may be too small."
|
|
2286
|
-
);
|
|
2287
|
-
}
|
|
2288
|
-
quoteAmountOut = quote.quote.amountOut;
|
|
2289
|
-
} catch (err) {
|
|
2290
|
-
if (debug) {
|
|
2291
|
-
console.error(
|
|
2292
|
-
`
|
|
2293
|
-
[debug] sell \u2014 Quote Error:
|
|
2294
|
-
${err instanceof Error ? err.stack || err.message : String(err)}
|
|
2295
|
-
`
|
|
4136
|
+
const amountFormatted = formatAmountDisplay(amount, decimals);
|
|
4137
|
+
let amountUsd = null;
|
|
4138
|
+
const priceAddress = knownToken ? knownToken.priceAddress : tokenAddress;
|
|
4139
|
+
const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
|
|
4140
|
+
if (priceUsd != null) {
|
|
4141
|
+
amountUsd = Number(
|
|
4142
|
+
(Number(formatUnits5(amount, decimals)) * priceUsd).toFixed(2)
|
|
2296
4143
|
);
|
|
2297
4144
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
4145
|
+
if (!opts.yes) {
|
|
4146
|
+
printSendPreview({
|
|
4147
|
+
name: tokenName,
|
|
4148
|
+
symbol,
|
|
4149
|
+
amountFormatted,
|
|
4150
|
+
amountUsd,
|
|
4151
|
+
to: recipient
|
|
4152
|
+
});
|
|
4153
|
+
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
4154
|
+
if (!ok) {
|
|
4155
|
+
console.error("Aborted.");
|
|
4156
|
+
process.exit(0);
|
|
2305
4157
|
}
|
|
4158
|
+
}
|
|
4159
|
+
let txHash;
|
|
4160
|
+
try {
|
|
4161
|
+
txHash = await walletClient.writeContract({
|
|
4162
|
+
abi: erc20Abi4,
|
|
4163
|
+
address: tokenAddress,
|
|
4164
|
+
functionName: "transfer",
|
|
4165
|
+
args: [recipient, amount]
|
|
4166
|
+
});
|
|
4167
|
+
} catch (err) {
|
|
4168
|
+
track("cli_send", {
|
|
4169
|
+
asset: knownToken ? knownTokenKey : "coin",
|
|
4170
|
+
coin_address: tokenAddress,
|
|
4171
|
+
coin_name: tokenName,
|
|
4172
|
+
coin_symbol: symbol,
|
|
4173
|
+
output_format: json ? "json" : "static",
|
|
4174
|
+
success: false,
|
|
4175
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
4176
|
+
});
|
|
4177
|
+
await shutdownAnalytics();
|
|
2306
4178
|
outputErrorAndExit(
|
|
2307
4179
|
json,
|
|
2308
|
-
|
|
4180
|
+
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2309
4181
|
);
|
|
2310
4182
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
if (opts.quote) {
|
|
2323
|
-
printSellQuote(output, {
|
|
2324
|
-
coinName,
|
|
2325
|
-
coinSymbol,
|
|
2326
|
-
address: coinAddress,
|
|
2327
|
-
soldFormatted,
|
|
2328
|
-
amountIn,
|
|
2329
|
-
coinDecimals,
|
|
2330
|
-
receivedFormatted,
|
|
2331
|
-
quoteAmountOut,
|
|
2332
|
-
outputSymbol: outputToken.symbol,
|
|
2333
|
-
outputDecimals: outputToken.decimals,
|
|
2334
|
-
slippagePct
|
|
2335
|
-
});
|
|
2336
|
-
track("cli_sell", {
|
|
2337
|
-
action: "quote",
|
|
2338
|
-
coin_address: coinAddress,
|
|
2339
|
-
coin_name: coinName,
|
|
2340
|
-
coin_symbol: coinSymbol,
|
|
2341
|
-
amount_mode: amountMode,
|
|
2342
|
-
output_asset: outputAsset,
|
|
2343
|
-
slippage: slippagePct,
|
|
2344
|
-
output_format: output
|
|
2345
|
-
});
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
if (!opts.yes) {
|
|
2349
|
-
printSellQuote("table", {
|
|
2350
|
-
coinName,
|
|
2351
|
-
coinSymbol,
|
|
2352
|
-
address: coinAddress,
|
|
2353
|
-
soldFormatted,
|
|
2354
|
-
amountIn,
|
|
2355
|
-
coinDecimals,
|
|
2356
|
-
receivedFormatted,
|
|
2357
|
-
quoteAmountOut,
|
|
2358
|
-
outputSymbol: outputToken.symbol,
|
|
2359
|
-
outputDecimals: outputToken.decimals,
|
|
2360
|
-
slippagePct
|
|
2361
|
-
});
|
|
2362
|
-
const ok = await confirm3({
|
|
2363
|
-
message: "Confirm?",
|
|
2364
|
-
default: false
|
|
2365
|
-
});
|
|
2366
|
-
if (!ok) {
|
|
2367
|
-
console.error("Aborted.");
|
|
2368
|
-
process.exit(0);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
let receipt;
|
|
2372
|
-
let txHash;
|
|
2373
|
-
let receivedAmountOut = BigInt(quoteAmountOut);
|
|
2374
|
-
let receivedSource = "quote";
|
|
2375
|
-
try {
|
|
2376
|
-
receipt = await tradeCoin2({
|
|
2377
|
-
tradeParameters,
|
|
2378
|
-
walletClient,
|
|
2379
|
-
publicClient,
|
|
2380
|
-
account
|
|
4183
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
4184
|
+
printSendResult(json, {
|
|
4185
|
+
name: tokenName,
|
|
4186
|
+
symbol,
|
|
4187
|
+
address: tokenAddress,
|
|
4188
|
+
amount,
|
|
4189
|
+
decimals,
|
|
4190
|
+
amountFormatted,
|
|
4191
|
+
amountUsd,
|
|
4192
|
+
to: recipient,
|
|
4193
|
+
txHash
|
|
2381
4194
|
});
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
coin_symbol: coinSymbol,
|
|
4195
|
+
track("cli_send", {
|
|
4196
|
+
asset: knownToken ? knownTokenKey : "coin",
|
|
4197
|
+
coin_address: tokenAddress,
|
|
4198
|
+
coin_name: tokenName,
|
|
4199
|
+
coin_symbol: symbol,
|
|
2388
4200
|
amount_mode: amountMode,
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
output_format:
|
|
2392
|
-
success:
|
|
2393
|
-
|
|
4201
|
+
amount_usd: amountUsd,
|
|
4202
|
+
transactionHash: txHash,
|
|
4203
|
+
output_format: json ? "json" : "static",
|
|
4204
|
+
success: true,
|
|
4205
|
+
tx_hash: txHash
|
|
2394
4206
|
});
|
|
2395
|
-
await shutdownAnalytics();
|
|
2396
|
-
outputErrorAndExit(
|
|
2397
|
-
json,
|
|
2398
|
-
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
txHash = receipt.transactionHash;
|
|
2402
|
-
if (outputToken.trade.type === "erc20") {
|
|
2403
|
-
try {
|
|
2404
|
-
receivedAmountOut = getReceivedAmountFromReceipt({
|
|
2405
|
-
receipt,
|
|
2406
|
-
tokenAddress: outputToken.trade.address,
|
|
2407
|
-
recipient: account.address
|
|
2408
|
-
});
|
|
2409
|
-
receivedSource = "receipt";
|
|
2410
|
-
} catch {
|
|
2411
|
-
}
|
|
2412
4207
|
}
|
|
2413
|
-
printSellResult(output, {
|
|
2414
|
-
coinName,
|
|
2415
|
-
coinSymbol,
|
|
2416
|
-
address: coinAddress,
|
|
2417
|
-
amountIn,
|
|
2418
|
-
coinDecimals,
|
|
2419
|
-
soldFormatted,
|
|
2420
|
-
receivedAmountOut,
|
|
2421
|
-
outputSymbol: outputToken.symbol,
|
|
2422
|
-
outputDecimals: outputToken.decimals,
|
|
2423
|
-
receivedSource,
|
|
2424
|
-
txHash
|
|
2425
|
-
});
|
|
2426
|
-
track("cli_sell", {
|
|
2427
|
-
action: "trade",
|
|
2428
|
-
coin_address: coinAddress,
|
|
2429
|
-
coin_name: coinName,
|
|
2430
|
-
coin_symbol: coinSymbol,
|
|
2431
|
-
amount_mode: amountMode,
|
|
2432
|
-
output_asset: outputAsset,
|
|
2433
|
-
slippage: slippagePct,
|
|
2434
|
-
output_format: output,
|
|
2435
|
-
success: true,
|
|
2436
|
-
tx_hash: txHash
|
|
2437
|
-
});
|
|
2438
4208
|
});
|
|
2439
4209
|
|
|
2440
4210
|
// src/commands/setup.ts
|
|
2441
|
-
import { Command as
|
|
2442
|
-
import { generatePrivateKey, privateKeyToAccount as
|
|
4211
|
+
import { Command as Command10 } from "commander";
|
|
4212
|
+
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2443
4213
|
|
|
2444
4214
|
// src/lib/strings.ts
|
|
2445
4215
|
var DEPOSIT_INSTRUCTIONS = "Deposit ETH or USDC to this address on Base to start trading.\n\n You can do this from:\n - Coinbase \u2014 withdraw directly to Base\n - Another wallet (MetaMask, Rainbow, etc.) \u2014 send on Base network\n - Bridge from other chains \u2014 use https://superbridge.app/base";
|
|
@@ -2452,7 +4222,7 @@ var BACKUP_WARNING = "Back up this file \u2014 it's the only copy of your key.";
|
|
|
2452
4222
|
var isValidPrivateKey = (key) => /^(0x)?[0-9a-fA-F]{64}$/.test(key);
|
|
2453
4223
|
var toAccount = (json, key, errorPrefix) => {
|
|
2454
4224
|
try {
|
|
2455
|
-
return
|
|
4225
|
+
return privateKeyToAccount4(normalizeKey(key));
|
|
2456
4226
|
} catch {
|
|
2457
4227
|
outputErrorAndExit(
|
|
2458
4228
|
json,
|
|
@@ -2460,7 +4230,7 @@ var toAccount = (json, key, errorPrefix) => {
|
|
|
2460
4230
|
);
|
|
2461
4231
|
}
|
|
2462
4232
|
};
|
|
2463
|
-
var setupCommand = new
|
|
4233
|
+
var setupCommand = new Command10("setup").description("Set up your Zora wallet").option("--create", "Create a new wallet without prompting").option("--force", "Overwrite existing wallet without prompting").option("--yes", "Skip interactive prompt and execute directly").action(async function(options) {
|
|
2464
4234
|
const json = getJson(this);
|
|
2465
4235
|
const nonInteractive = getYes(this);
|
|
2466
4236
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -2475,7 +4245,7 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2475
4245
|
const account = toAccount(json, envKey, "ZORA_PRIVATE_KEY");
|
|
2476
4246
|
outputData(json, {
|
|
2477
4247
|
json: { source: "env", address: account.address },
|
|
2478
|
-
|
|
4248
|
+
render: () => {
|
|
2479
4249
|
console.log(" Using wallet from ZORA_PRIVATE_KEY.\n");
|
|
2480
4250
|
console.log(` Address: ${account.address}
|
|
2481
4251
|
`);
|
|
@@ -2496,14 +4266,14 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2496
4266
|
} catch (err) {
|
|
2497
4267
|
outputErrorAndExit(
|
|
2498
4268
|
json,
|
|
2499
|
-
`\u2717 Could not read wallet: ${err
|
|
4269
|
+
`\u2717 Could not read wallet: ${formatError(err)}`,
|
|
2500
4270
|
"Run 'zora setup --force' to overwrite it."
|
|
2501
4271
|
);
|
|
2502
4272
|
}
|
|
2503
4273
|
}
|
|
2504
4274
|
if (existing) {
|
|
2505
4275
|
const account = toAccount(json, existing, "Stored private key");
|
|
2506
|
-
const truncated =
|
|
4276
|
+
const truncated = truncateAddress(account.address);
|
|
2507
4277
|
console.log(` Wallet already configured: ${truncated}
|
|
2508
4278
|
`);
|
|
2509
4279
|
if (!options.force) {
|
|
@@ -2565,7 +4335,7 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2565
4335
|
address: account.address,
|
|
2566
4336
|
path: getWalletPath()
|
|
2567
4337
|
},
|
|
2568
|
-
|
|
4338
|
+
render: () => {
|
|
2569
4339
|
console.log("\n\u2713 Wallet imported\n");
|
|
2570
4340
|
console.log(` Address: ${account.address}`);
|
|
2571
4341
|
console.log(` Private key: saved to ${getWalletPath()}
|
|
@@ -2600,7 +4370,7 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2600
4370
|
address: account.address,
|
|
2601
4371
|
path: getWalletPath()
|
|
2602
4372
|
},
|
|
2603
|
-
|
|
4373
|
+
render: () => {
|
|
2604
4374
|
console.log("\n\u2713 Wallet created\n");
|
|
2605
4375
|
console.log(` Address: ${account.address}`);
|
|
2606
4376
|
console.log(` Private key: saved to ${getWalletPath()}
|
|
@@ -2619,8 +4389,8 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2619
4389
|
});
|
|
2620
4390
|
|
|
2621
4391
|
// src/commands/wallet.ts
|
|
2622
|
-
import { Command as
|
|
2623
|
-
import { privateKeyToAccount as
|
|
4392
|
+
import { Command as Command11 } from "commander";
|
|
4393
|
+
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
2624
4394
|
var resolvePrivateKey = () => {
|
|
2625
4395
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
2626
4396
|
if (envKey) {
|
|
@@ -2632,7 +4402,7 @@ var resolvePrivateKey = () => {
|
|
|
2632
4402
|
}
|
|
2633
4403
|
return void 0;
|
|
2634
4404
|
};
|
|
2635
|
-
var walletCommand = new
|
|
4405
|
+
var walletCommand = new Command11("wallet").description(
|
|
2636
4406
|
"Manage your Zora wallet"
|
|
2637
4407
|
);
|
|
2638
4408
|
walletCommand.command("info").description("Show wallet address and storage location").action(function() {
|
|
@@ -2643,7 +4413,7 @@ walletCommand.command("info").description("Show wallet address and storage locat
|
|
|
2643
4413
|
}
|
|
2644
4414
|
let account;
|
|
2645
4415
|
try {
|
|
2646
|
-
account =
|
|
4416
|
+
account = privateKeyToAccount5(normalizeKey(resolved.key));
|
|
2647
4417
|
} catch {
|
|
2648
4418
|
const msg = resolved.source === "env" ? "ZORA_PRIVATE_KEY is not a valid private key." : "Stored private key is invalid.";
|
|
2649
4419
|
const suggestion = resolved.source === "env" ? void 0 : "Run 'zora setup --force' to replace it.";
|
|
@@ -2652,7 +4422,7 @@ walletCommand.command("info").description("Show wallet address and storage locat
|
|
|
2652
4422
|
const source = resolved.source === "env" ? "env (ZORA_PRIVATE_KEY)" : getWalletPath();
|
|
2653
4423
|
outputData(json, {
|
|
2654
4424
|
json: { address: account.address, source },
|
|
2655
|
-
|
|
4425
|
+
render: () => {
|
|
2656
4426
|
console.log(` Address: ${account.address}`);
|
|
2657
4427
|
console.log(` Source: ${source}`);
|
|
2658
4428
|
}
|
|
@@ -2692,7 +4462,7 @@ walletCommand.command("export").description("Print the raw private key to stdout
|
|
|
2692
4462
|
});
|
|
2693
4463
|
|
|
2694
4464
|
// src/components/Zorb.tsx
|
|
2695
|
-
import { Text as
|
|
4465
|
+
import { Text as Text9, Box as Box9 } from "ink";
|
|
2696
4466
|
|
|
2697
4467
|
// src/lib/zorb-pixels.ts
|
|
2698
4468
|
function supportsTruecolor() {
|
|
@@ -2837,7 +4607,7 @@ function generateZorbPixels(size) {
|
|
|
2837
4607
|
}
|
|
2838
4608
|
|
|
2839
4609
|
// src/components/Zorb.tsx
|
|
2840
|
-
import { jsx as
|
|
4610
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2841
4611
|
var LOWER_HALF_BLOCK = "\u2584";
|
|
2842
4612
|
var UPPER_HALF_BLOCK = "\u2580";
|
|
2843
4613
|
function rgbString([r, g, b]) {
|
|
@@ -2860,19 +4630,19 @@ function Zorb({ size = 20 }) {
|
|
|
2860
4630
|
const topIsBlack = isBlack(top);
|
|
2861
4631
|
const bottomIsBlack = isBlack(bottom);
|
|
2862
4632
|
if (topIsBlack && bottomIsBlack) {
|
|
2863
|
-
cells.push(/* @__PURE__ */
|
|
4633
|
+
cells.push(/* @__PURE__ */ jsx12(Text9, { children: " " }, x));
|
|
2864
4634
|
} else if (topIsBlack) {
|
|
2865
4635
|
cells.push(
|
|
2866
|
-
/* @__PURE__ */
|
|
4636
|
+
/* @__PURE__ */ jsx12(Text9, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
|
|
2867
4637
|
);
|
|
2868
4638
|
} else if (bottomIsBlack) {
|
|
2869
4639
|
cells.push(
|
|
2870
|
-
/* @__PURE__ */
|
|
4640
|
+
/* @__PURE__ */ jsx12(Text9, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
|
|
2871
4641
|
);
|
|
2872
4642
|
} else {
|
|
2873
4643
|
cells.push(
|
|
2874
|
-
/* @__PURE__ */
|
|
2875
|
-
|
|
4644
|
+
/* @__PURE__ */ jsx12(
|
|
4645
|
+
Text9,
|
|
2876
4646
|
{
|
|
2877
4647
|
backgroundColor: rgbString(top),
|
|
2878
4648
|
color: rgbString(bottom),
|
|
@@ -2883,41 +4653,54 @@ function Zorb({ size = 20 }) {
|
|
|
2883
4653
|
);
|
|
2884
4654
|
}
|
|
2885
4655
|
}
|
|
2886
|
-
rows.push(/* @__PURE__ */
|
|
4656
|
+
rows.push(/* @__PURE__ */ jsx12(Text9, { children: cells }, y));
|
|
2887
4657
|
}
|
|
2888
|
-
return /* @__PURE__ */
|
|
2889
|
-
/* @__PURE__ */
|
|
4658
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
4659
|
+
/* @__PURE__ */ jsx12(Text9, { children: " " }),
|
|
2890
4660
|
rows,
|
|
2891
|
-
/* @__PURE__ */
|
|
4661
|
+
/* @__PURE__ */ jsx12(Text9, { children: " " })
|
|
2892
4662
|
] });
|
|
2893
4663
|
}
|
|
2894
4664
|
|
|
2895
4665
|
// src/index.tsx
|
|
2896
|
-
import { jsx as
|
|
4666
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2897
4667
|
if (process.env.ZORA_API_TARGET) {
|
|
2898
4668
|
setApiBaseUrl(process.env.ZORA_API_TARGET);
|
|
2899
4669
|
}
|
|
2900
|
-
var version = true ? "0.
|
|
4670
|
+
var version = true ? "0.3.0" : JSON.parse(
|
|
2901
4671
|
readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
|
|
2902
4672
|
).version;
|
|
2903
4673
|
var buildProgram = () => {
|
|
2904
|
-
const program2 = new
|
|
4674
|
+
const program2 = new Command12().name("zora").description("Zora CLI").version(version).option("--json", "Output as JSON (for scripts and automation)", false);
|
|
2905
4675
|
program2.addCommand(authCommand);
|
|
2906
4676
|
program2.addCommand(balanceCommand);
|
|
2907
4677
|
program2.addCommand(buyCommand);
|
|
2908
4678
|
program2.addCommand(exploreCommand);
|
|
2909
4679
|
program2.addCommand(getCommand);
|
|
4680
|
+
program2.addCommand(priceHistoryCommand);
|
|
4681
|
+
program2.addCommand(profileCommand);
|
|
2910
4682
|
program2.addCommand(setupCommand);
|
|
2911
4683
|
program2.addCommand(walletCommand);
|
|
2912
4684
|
program2.addCommand(sellCommand);
|
|
4685
|
+
program2.addCommand(sendCommand);
|
|
4686
|
+
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
4687
|
+
const expected = actionCommand.registeredArguments.length;
|
|
4688
|
+
if (expected > 0 && actionCommand.args.length < expected) {
|
|
4689
|
+
actionCommand.outputHelp();
|
|
4690
|
+
process.exit(1);
|
|
4691
|
+
}
|
|
4692
|
+
});
|
|
2913
4693
|
return program2;
|
|
2914
4694
|
};
|
|
2915
4695
|
var program = buildProgram();
|
|
2916
4696
|
if (!process.env.VITEST) {
|
|
2917
4697
|
const showingHelp = process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h");
|
|
2918
4698
|
if (showingHelp && !process.argv.includes("--json") && supportsTruecolor()) {
|
|
2919
|
-
renderOnce(/* @__PURE__ */
|
|
4699
|
+
renderOnce(/* @__PURE__ */ jsx13(Zorb, { size: 20 }));
|
|
2920
4700
|
}
|
|
4701
|
+
console.warn(
|
|
4702
|
+
"\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
|
|
4703
|
+
);
|
|
2921
4704
|
identify();
|
|
2922
4705
|
try {
|
|
2923
4706
|
await program.parseAsync();
|