@zoralabs/cli 0.2.3 → 0.2.4
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/dist/index.js +1729 -559
- 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 Command11 } from "commander";
|
|
5
5
|
import { ExitPromptError } from "@inquirer/core";
|
|
6
6
|
import "fs";
|
|
7
7
|
import { setApiBaseUrl } from "@zoralabs/coins-sdk";
|
|
@@ -155,7 +155,18 @@ function maskKey(key) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// src/lib/output.ts
|
|
158
|
-
var
|
|
158
|
+
var VALID_OUTPUT_MODES = ["table", "json", "live"];
|
|
159
|
+
var getOutputMode = (cmd, defaultMode) => {
|
|
160
|
+
const raw = cmd.optsWithGlobals().output;
|
|
161
|
+
if (!raw) return defaultMode;
|
|
162
|
+
if (VALID_OUTPUT_MODES.includes(raw)) return raw;
|
|
163
|
+
return outputErrorAndExit(
|
|
164
|
+
false,
|
|
165
|
+
`Invalid --output value: ${raw}.`,
|
|
166
|
+
`Supported: ${VALID_OUTPUT_MODES.join(", ")}`
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
var getJson = (cmd) => getOutputMode(cmd, "table") === "json";
|
|
159
170
|
var getYes = (cmd) => cmd.optsWithGlobals().yes ?? false;
|
|
160
171
|
var outputJson = (data) => {
|
|
161
172
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -180,6 +191,13 @@ var outputData = (json, opts) => {
|
|
|
180
191
|
opts.table();
|
|
181
192
|
}
|
|
182
193
|
};
|
|
194
|
+
var getLiveConfig = (cmd, defaultMode) => {
|
|
195
|
+
const mode = getOutputMode(cmd, defaultMode);
|
|
196
|
+
const live = mode === "live";
|
|
197
|
+
const intervalRaw = parseInt(cmd.optsWithGlobals().interval, 10);
|
|
198
|
+
const intervalSeconds = isNaN(intervalRaw) || intervalRaw < 5 ? 30 : intervalRaw;
|
|
199
|
+
return { live, intervalSeconds };
|
|
200
|
+
};
|
|
183
201
|
|
|
184
202
|
// src/lib/prompt.ts
|
|
185
203
|
import confirm from "@inquirer/confirm";
|
|
@@ -356,7 +374,7 @@ var getClient = () => {
|
|
|
356
374
|
return client;
|
|
357
375
|
};
|
|
358
376
|
var commonProperties = () => ({
|
|
359
|
-
cli_version: true ? "0.2.
|
|
377
|
+
cli_version: true ? "0.2.4" : "development",
|
|
360
378
|
os: process.platform,
|
|
361
379
|
arch: process.arch,
|
|
362
380
|
node_version: process.version
|
|
@@ -506,86 +524,92 @@ authCommand.command("status").description("Check authentication status").action(
|
|
|
506
524
|
});
|
|
507
525
|
});
|
|
508
526
|
|
|
509
|
-
// src/commands/balance.
|
|
510
|
-
import { Command as
|
|
511
|
-
import {
|
|
527
|
+
// src/commands/balance.tsx
|
|
528
|
+
import { Command as Command2 } from "commander";
|
|
529
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
530
|
+
import { getProfileBalances, setApiKey } from "@zoralabs/coins-sdk";
|
|
512
531
|
|
|
513
532
|
// src/lib/render.tsx
|
|
514
|
-
import { renderToString } from "ink";
|
|
533
|
+
import { render, renderToString } from "ink";
|
|
515
534
|
var renderOnce = (element) => {
|
|
516
|
-
const
|
|
535
|
+
const columns = process.stdout.columns || 80;
|
|
536
|
+
const output = renderToString(element, { columns });
|
|
517
537
|
console.log(output);
|
|
518
538
|
};
|
|
539
|
+
var renderLive = async (element) => {
|
|
540
|
+
const instance = render(element);
|
|
541
|
+
await instance.waitUntilExit();
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/components/BalanceView.tsx
|
|
545
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef } from "react";
|
|
546
|
+
import { Box as Box2, Text as Text2, useInput, useApp } from "ink";
|
|
547
|
+
import Spinner from "ink-spinner";
|
|
519
548
|
|
|
520
549
|
// src/components/table.tsx
|
|
521
550
|
import { Box, Text } from "ink";
|
|
522
551
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
552
|
+
var PADDING_LEFT = 1;
|
|
523
553
|
var truncate = (str, max) => {
|
|
524
554
|
if (str.length <= max) return str;
|
|
525
555
|
return str.slice(0, max - 1) + "\u2026";
|
|
526
556
|
};
|
|
527
|
-
var
|
|
557
|
+
var computeColumnWidths = (columns, fullWidth) => {
|
|
558
|
+
const baseWidths = columns.map((col) => col.width);
|
|
559
|
+
const totalBase = baseWidths.reduce((sum, w) => sum + w, 0);
|
|
560
|
+
if (!fullWidth) return baseWidths;
|
|
561
|
+
const width = process.stdout.columns ?? 80;
|
|
562
|
+
const available = width - PADDING_LEFT;
|
|
563
|
+
if (available <= totalBase) return baseWidths;
|
|
564
|
+
const computed = baseWidths.map(
|
|
565
|
+
(w) => Math.max(1, Math.floor(w / totalBase * available))
|
|
566
|
+
);
|
|
567
|
+
let remainder = available - computed.reduce((sum, w) => sum + w, 0);
|
|
568
|
+
for (let i = 0; i < computed.length && remainder > 0; i++) {
|
|
569
|
+
computed[i]++;
|
|
570
|
+
remainder--;
|
|
571
|
+
}
|
|
572
|
+
return computed;
|
|
573
|
+
};
|
|
574
|
+
var Table = ({
|
|
528
575
|
columns,
|
|
529
576
|
data,
|
|
530
577
|
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
|
-
getExploreFeaturedCreators,
|
|
562
|
-
getExploreFeaturedVideos,
|
|
563
|
-
getCreatorCoins,
|
|
564
|
-
getMostValuableCreatorCoins,
|
|
565
|
-
getMostValuableAll,
|
|
566
|
-
getMostValuableTrends,
|
|
567
|
-
getNewTrends,
|
|
568
|
-
getTopVolumeTrends24h,
|
|
569
|
-
getTrendingAll,
|
|
570
|
-
getTrendingCreators,
|
|
571
|
-
getTrendingPosts,
|
|
572
|
-
getTrendingTrends
|
|
573
|
-
} from "@zoralabs/coins-sdk";
|
|
578
|
+
subtitle,
|
|
579
|
+
fullWidth = true,
|
|
580
|
+
footer
|
|
581
|
+
}) => {
|
|
582
|
+
const widths = computeColumnWidths(columns, fullWidth);
|
|
583
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
|
|
584
|
+
title && /* @__PURE__ */ jsxs(Box, { paddingLeft: PADDING_LEFT, marginBottom: 1, children: [
|
|
585
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: title }),
|
|
586
|
+
subtitle && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
587
|
+
" ",
|
|
588
|
+
subtitle
|
|
589
|
+
] })
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, i) => /* @__PURE__ */ jsx(Box, { width: widths[i], children: /* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, wrap: "truncate", children: col.header }) }, col.header)) }),
|
|
592
|
+
data.map((row, i) => /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, children: columns.map((col, colIdx) => {
|
|
593
|
+
const colWidth = widths[colIdx];
|
|
594
|
+
const value = col.noTruncate ? col.accessor(row) : truncate(col.accessor(row), colWidth - 2);
|
|
595
|
+
const colorName = col.color?.(row);
|
|
596
|
+
return /* @__PURE__ */ jsx(Box, { width: colWidth, children: /* @__PURE__ */ jsx(
|
|
597
|
+
Text,
|
|
598
|
+
{
|
|
599
|
+
color: colorName,
|
|
600
|
+
wrap: col.noTruncate ? "wrap" : "truncate",
|
|
601
|
+
children: value
|
|
602
|
+
}
|
|
603
|
+
) }, col.header);
|
|
604
|
+
}) }, i)),
|
|
605
|
+
footer && /* @__PURE__ */ jsx(Box, { paddingLeft: PADDING_LEFT, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: footer }) })
|
|
606
|
+
] });
|
|
607
|
+
};
|
|
574
608
|
|
|
575
609
|
// src/lib/format.ts
|
|
576
610
|
import { format, formatDistanceStrict } from "date-fns";
|
|
577
611
|
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) {
|
|
612
|
+
function formatCompactUsd(value) {
|
|
589
613
|
if (!value || Number(value) === 0) return "$0";
|
|
590
614
|
return new Intl.NumberFormat("en-US", {
|
|
591
615
|
style: "currency",
|
|
@@ -644,243 +668,9 @@ var formatCoinsDisplay = (coinsOut) => new Intl.NumberFormat("en-US", {
|
|
|
644
668
|
maximumFractionDigits: 2
|
|
645
669
|
}).format(Number(coinsOut));
|
|
646
670
|
|
|
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
671
|
// src/lib/balance-format.ts
|
|
882
672
|
var COIN_DECIMALS = 18;
|
|
883
|
-
var
|
|
673
|
+
var parseRawBalance = (rawBalance) => Number(normalizeTokenAmount(rawBalance));
|
|
884
674
|
var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
|
|
885
675
|
try {
|
|
886
676
|
const value = BigInt(rawBalance);
|
|
@@ -895,20 +685,20 @@ var normalizeTokenAmount = (rawBalance, decimals = COIN_DECIMALS) => {
|
|
|
895
685
|
return rawBalance;
|
|
896
686
|
}
|
|
897
687
|
};
|
|
898
|
-
var
|
|
688
|
+
var formatBalanceAsUsd = (balance, priceInUsdc) => {
|
|
899
689
|
if (!priceInUsdc) return "-";
|
|
900
|
-
const value =
|
|
690
|
+
const value = parseRawBalance(balance) * Number(priceInUsdc);
|
|
901
691
|
if (value < 0.01) return "<$0.01";
|
|
902
692
|
return formatUsd(value);
|
|
903
693
|
};
|
|
904
694
|
var formatBalance = (balance) => {
|
|
905
|
-
const n =
|
|
695
|
+
const n = parseRawBalance(balance);
|
|
906
696
|
if (n === 0) return "0";
|
|
907
697
|
if (n < 1e-3) return "<0.001";
|
|
908
698
|
if (n < 1) return n.toFixed(4);
|
|
909
699
|
return new Intl.NumberFormat("en-US", {
|
|
910
700
|
notation: "compact",
|
|
911
|
-
compactDisplay: "
|
|
701
|
+
compactDisplay: "short",
|
|
912
702
|
maximumFractionDigits: 1
|
|
913
703
|
}).format(n);
|
|
914
704
|
};
|
|
@@ -918,37 +708,246 @@ var trimTrailingZeros = (value) => {
|
|
|
918
708
|
return trimmed || "0";
|
|
919
709
|
};
|
|
920
710
|
|
|
921
|
-
// src/lib/
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
var TRACKED_TOKENS = [
|
|
711
|
+
// src/lib/balance-columns.tsx
|
|
712
|
+
var SORT_LABELS = {
|
|
713
|
+
"usd-value": "USD Value",
|
|
714
|
+
balance: "Balance",
|
|
715
|
+
"market-cap": "Market Cap",
|
|
716
|
+
"price-change": "Price Change"
|
|
717
|
+
};
|
|
718
|
+
var walletColumns = [
|
|
719
|
+
{ header: "Name", width: 14, accessor: (row) => row.name },
|
|
931
720
|
{
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
priceAddress: WETH_ADDRESS,
|
|
937
|
-
isNative: true
|
|
721
|
+
header: "Symbol",
|
|
722
|
+
width: 10,
|
|
723
|
+
noTruncate: true,
|
|
724
|
+
accessor: (row) => row.symbol
|
|
938
725
|
},
|
|
726
|
+
{ header: "Balance", width: 20, accessor: (row) => row.balance },
|
|
727
|
+
{ header: "USD Value", width: 16, accessor: (row) => row.usdValue }
|
|
728
|
+
];
|
|
729
|
+
var balanceColumns = [
|
|
730
|
+
{ header: "#", width: 5, accessor: (row) => String(row.rank) },
|
|
939
731
|
{
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
decimals: USDC_DECIMALS,
|
|
944
|
-
priceAddress: USDC_ADDRESS,
|
|
945
|
-
fixedPriceUsd: 1
|
|
732
|
+
header: "Name",
|
|
733
|
+
width: 24,
|
|
734
|
+
accessor: (row) => row.coin?.name ?? "Unknown"
|
|
946
735
|
},
|
|
947
736
|
{
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
737
|
+
header: "Symbol",
|
|
738
|
+
width: 12,
|
|
739
|
+
noTruncate: true,
|
|
740
|
+
accessor: (row) => row.coin?.symbol ?? ""
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
header: "Balance",
|
|
744
|
+
width: 14,
|
|
745
|
+
accessor: (row) => formatBalance(row.balance)
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
header: "USD Value",
|
|
749
|
+
width: 14,
|
|
750
|
+
accessor: (row) => formatBalanceAsUsd(row.balance, row.coin?.tokenPrice?.priceInUsdc)
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
header: "Market Cap",
|
|
754
|
+
width: 14,
|
|
755
|
+
accessor: (row) => formatCompactUsd(row.coin?.marketCap)
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
header: "24h Change",
|
|
759
|
+
width: 12,
|
|
760
|
+
accessor: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).text,
|
|
761
|
+
color: (row) => formatMcapChange(row.coin?.marketCap, row.coin?.marketCapDelta24h).color
|
|
762
|
+
}
|
|
763
|
+
];
|
|
764
|
+
|
|
765
|
+
// src/hooks/use-auto-refresh.ts
|
|
766
|
+
import { useState, useEffect, useCallback } from "react";
|
|
767
|
+
var useAutoRefresh = (intervalSeconds, enabled) => {
|
|
768
|
+
const [refreshCount, setRefreshCount] = useState(0);
|
|
769
|
+
const [secondsUntilRefresh, setSecondsUntilRefresh] = useState(intervalSeconds);
|
|
770
|
+
const [resetCount, setResetCount] = useState(0);
|
|
771
|
+
const triggerManualRefresh = useCallback(() => {
|
|
772
|
+
if (!enabled) return;
|
|
773
|
+
setRefreshCount((c) => c + 1);
|
|
774
|
+
setSecondsUntilRefresh(intervalSeconds);
|
|
775
|
+
setResetCount((c) => c + 1);
|
|
776
|
+
}, [enabled, intervalSeconds]);
|
|
777
|
+
useEffect(() => {
|
|
778
|
+
if (!enabled) return;
|
|
779
|
+
setSecondsUntilRefresh(intervalSeconds);
|
|
780
|
+
const ticker = setInterval(() => {
|
|
781
|
+
setSecondsUntilRefresh((prev) => {
|
|
782
|
+
if (prev <= 1) {
|
|
783
|
+
setRefreshCount((c) => c + 1);
|
|
784
|
+
return intervalSeconds;
|
|
785
|
+
}
|
|
786
|
+
return prev - 1;
|
|
787
|
+
});
|
|
788
|
+
}, 1e3);
|
|
789
|
+
return () => clearInterval(ticker);
|
|
790
|
+
}, [enabled, intervalSeconds, resetCount]);
|
|
791
|
+
if (!enabled) {
|
|
792
|
+
return { refreshCount: 0, secondsUntilRefresh: 0, triggerManualRefresh };
|
|
793
|
+
}
|
|
794
|
+
return { refreshCount, secondsUntilRefresh, triggerManualRefresh };
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/components/BalanceView.tsx
|
|
798
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
799
|
+
var BalanceView = ({
|
|
800
|
+
fetchData,
|
|
801
|
+
sort,
|
|
802
|
+
mode = "full",
|
|
803
|
+
autoRefresh = false,
|
|
804
|
+
intervalSeconds = 30
|
|
805
|
+
}) => {
|
|
806
|
+
const { exit } = useApp();
|
|
807
|
+
const [loading, setLoading] = useState2(true);
|
|
808
|
+
const [isRefreshing, setIsRefreshing] = useState2(false);
|
|
809
|
+
const [error, setError] = useState2(null);
|
|
810
|
+
const [data, setData] = useState2(null);
|
|
811
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
812
|
+
const [manualRefreshCount, setManualRefreshCount] = useState2(0);
|
|
813
|
+
const hasLoadedOnce = useRef(false);
|
|
814
|
+
const load = useCallback2(async () => {
|
|
815
|
+
if (hasLoadedOnce.current) {
|
|
816
|
+
setIsRefreshing(true);
|
|
817
|
+
} else {
|
|
818
|
+
setLoading(true);
|
|
819
|
+
}
|
|
820
|
+
setError(null);
|
|
821
|
+
try {
|
|
822
|
+
const result = await fetchData();
|
|
823
|
+
setData(result);
|
|
824
|
+
hasLoadedOnce.current = true;
|
|
825
|
+
} catch (err) {
|
|
826
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
827
|
+
}
|
|
828
|
+
setLoading(false);
|
|
829
|
+
setIsRefreshing(false);
|
|
830
|
+
}, [fetchData]);
|
|
831
|
+
useEffect2(() => {
|
|
832
|
+
load();
|
|
833
|
+
}, [refreshCount, manualRefreshCount]);
|
|
834
|
+
useInput((input, key) => {
|
|
835
|
+
if (input === "q" || key.escape) {
|
|
836
|
+
exit();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (input === "r" && !loading) {
|
|
840
|
+
triggerManualRefresh();
|
|
841
|
+
setManualRefreshCount((c) => c + 1);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
if (error && !data) {
|
|
845
|
+
return /* @__PURE__ */ jsxs2(
|
|
846
|
+
Box2,
|
|
847
|
+
{
|
|
848
|
+
flexDirection: "column",
|
|
849
|
+
paddingLeft: 1,
|
|
850
|
+
paddingTop: 1,
|
|
851
|
+
paddingBottom: 1,
|
|
852
|
+
children: [
|
|
853
|
+
/* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
854
|
+
"Error: ",
|
|
855
|
+
error
|
|
856
|
+
] }),
|
|
857
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Press q to exit" }) })
|
|
858
|
+
]
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (loading && !data) {
|
|
863
|
+
return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
864
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
865
|
+
" Loading\u2026"
|
|
866
|
+
] }) });
|
|
867
|
+
}
|
|
868
|
+
if (!data) return null;
|
|
869
|
+
const hints = ["r refresh"];
|
|
870
|
+
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
871
|
+
hints.push("q quit");
|
|
872
|
+
const footer = hints.join(" \xB7 ");
|
|
873
|
+
const showWallet = mode === "full" || mode === "wallet";
|
|
874
|
+
const showCoins = mode === "full" || mode === "coins";
|
|
875
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
876
|
+
isRefreshing && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
877
|
+
/* @__PURE__ */ jsx2(Spinner, { type: "dots" }),
|
|
878
|
+
" Refreshing\u2026"
|
|
879
|
+
] }) }),
|
|
880
|
+
showWallet && /* @__PURE__ */ jsx2(
|
|
881
|
+
Table,
|
|
882
|
+
{
|
|
883
|
+
columns: walletColumns,
|
|
884
|
+
data: data.walletBalances,
|
|
885
|
+
title: "Wallet"
|
|
886
|
+
}
|
|
887
|
+
),
|
|
888
|
+
showCoins && data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs2(
|
|
889
|
+
Box2,
|
|
890
|
+
{
|
|
891
|
+
flexDirection: "column",
|
|
892
|
+
paddingLeft: 1,
|
|
893
|
+
paddingTop: 1,
|
|
894
|
+
paddingBottom: 1,
|
|
895
|
+
children: [
|
|
896
|
+
/* @__PURE__ */ jsx2(Text2, { children: "No coin balances found." }),
|
|
897
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
898
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
899
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
900
|
+
" zora buy ",
|
|
901
|
+
"<address>",
|
|
902
|
+
" --eth 0.001"
|
|
903
|
+
] })
|
|
904
|
+
] })
|
|
905
|
+
]
|
|
906
|
+
}
|
|
907
|
+
) : showCoins ? /* @__PURE__ */ jsx2(
|
|
908
|
+
Table,
|
|
909
|
+
{
|
|
910
|
+
columns: balanceColumns,
|
|
911
|
+
data: data.rankedBalances,
|
|
912
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
913
|
+
subtitle: `${data.rankedBalances.length} of ${data.total}`
|
|
914
|
+
}
|
|
915
|
+
) : null,
|
|
916
|
+
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: footer }) })
|
|
917
|
+
] });
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/lib/wallet-balances.ts
|
|
921
|
+
import { getTokenInfo } from "@zoralabs/coins-sdk";
|
|
922
|
+
import {
|
|
923
|
+
createPublicClient as createPublicClient2,
|
|
924
|
+
erc20Abi,
|
|
925
|
+
formatUnits,
|
|
926
|
+
http
|
|
927
|
+
} from "viem";
|
|
928
|
+
import { base as base2 } from "viem/chains";
|
|
929
|
+
var TRACKED_TOKENS = [
|
|
930
|
+
{
|
|
931
|
+
name: "Ether",
|
|
932
|
+
symbol: "ETH",
|
|
933
|
+
address: WETH_ADDRESS,
|
|
934
|
+
decimals: 18,
|
|
935
|
+
priceAddress: WETH_ADDRESS,
|
|
936
|
+
isNative: true
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: "USD Coin",
|
|
940
|
+
symbol: "USDC",
|
|
941
|
+
address: USDC_ADDRESS,
|
|
942
|
+
decimals: USDC_DECIMALS,
|
|
943
|
+
priceAddress: USDC_ADDRESS,
|
|
944
|
+
fixedPriceUsd: 1
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "ZORA",
|
|
948
|
+
symbol: "ZORA",
|
|
949
|
+
address: ZORA_ADDRESS,
|
|
950
|
+
decimals: 18,
|
|
952
951
|
priceAddress: ZORA_ADDRESS
|
|
953
952
|
}
|
|
954
953
|
];
|
|
@@ -1033,78 +1032,21 @@ var fetchWalletBalances = async (walletAddress) => {
|
|
|
1033
1032
|
return { walletBalances, walletBalancesJson };
|
|
1034
1033
|
};
|
|
1035
1034
|
|
|
1036
|
-
// src/commands/balance.
|
|
1035
|
+
// src/commands/balance.tsx
|
|
1036
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1037
1037
|
var SORT_MAP = {
|
|
1038
1038
|
"usd-value": "USD_VALUE",
|
|
1039
1039
|
balance: "BALANCE",
|
|
1040
1040
|
"market-cap": "MARKET_CAP",
|
|
1041
1041
|
"price-change": "PRICE_CHANGE"
|
|
1042
1042
|
};
|
|
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(", ");
|
|
1043
|
+
var SORT_OPTIONS = Object.keys(SORT_LABELS).join(", ");
|
|
1050
1044
|
var extractErrorMessage = (error) => {
|
|
1051
1045
|
if (typeof error === "object" && error !== null && "error" in error) {
|
|
1052
1046
|
return String(error.error);
|
|
1053
1047
|
}
|
|
1054
1048
|
return JSON.stringify(error);
|
|
1055
1049
|
};
|
|
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
1050
|
var formatBalanceJson = (balance, rank) => {
|
|
1109
1051
|
const priceUsd = balance.coin?.tokenPrice?.priceInUsdc;
|
|
1110
1052
|
const marketCap = balance.coin?.marketCap ? Number(balance.coin.marketCap) : null;
|
|
@@ -1112,7 +1054,7 @@ var formatBalanceJson = (balance, rank) => {
|
|
|
1112
1054
|
const volume24h = balance.coin?.volume24h ? Number(balance.coin.volume24h) : null;
|
|
1113
1055
|
const totalVolume = balance.coin?.totalVolume ? Number(balance.coin.totalVolume) : null;
|
|
1114
1056
|
const priceUsdValue = priceUsd ? Number(priceUsd) : null;
|
|
1115
|
-
const usdValue = priceUsdValue !== null ? Number((
|
|
1057
|
+
const usdValue = priceUsdValue !== null ? Number((parseRawBalance(balance.balance) * priceUsdValue).toFixed(6)) : null;
|
|
1116
1058
|
const marketCapChange24h = marketCap !== null && marketCapDelta24h !== null && marketCap - marketCapDelta24h !== 0 ? Number(
|
|
1117
1059
|
(marketCapDelta24h / (marketCap - marketCapDelta24h) * 100).toFixed(
|
|
1118
1060
|
4
|
|
@@ -1146,7 +1088,7 @@ function resolveContext(json) {
|
|
|
1146
1088
|
"Not authenticated. Run 'zora auth configure' to set your API key."
|
|
1147
1089
|
);
|
|
1148
1090
|
}
|
|
1149
|
-
|
|
1091
|
+
setApiKey(apiKey);
|
|
1150
1092
|
return account;
|
|
1151
1093
|
}
|
|
1152
1094
|
function renderWallet(json, walletResult) {
|
|
@@ -1154,11 +1096,14 @@ function renderWallet(json, walletResult) {
|
|
|
1154
1096
|
json: { wallet: walletResult.walletBalancesJson },
|
|
1155
1097
|
table: () => {
|
|
1156
1098
|
renderOnce(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1099
|
+
/* @__PURE__ */ jsx3(
|
|
1100
|
+
Table,
|
|
1101
|
+
{
|
|
1102
|
+
columns: walletColumns,
|
|
1103
|
+
data: walletResult.walletBalances,
|
|
1104
|
+
title: "Wallet"
|
|
1105
|
+
}
|
|
1106
|
+
)
|
|
1162
1107
|
);
|
|
1163
1108
|
}
|
|
1164
1109
|
});
|
|
@@ -1181,12 +1126,15 @@ function renderCoins(json, balances, total, sort) {
|
|
|
1181
1126
|
console.log(" zora buy <address> --eth 0.001\n");
|
|
1182
1127
|
} else {
|
|
1183
1128
|
renderOnce(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1129
|
+
/* @__PURE__ */ jsx3(
|
|
1130
|
+
Table,
|
|
1131
|
+
{
|
|
1132
|
+
columns: balanceColumns,
|
|
1133
|
+
data: rankedBalances,
|
|
1134
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1135
|
+
subtitle: `${balances.length} of ${total}`
|
|
1136
|
+
}
|
|
1137
|
+
)
|
|
1190
1138
|
);
|
|
1191
1139
|
}
|
|
1192
1140
|
}
|
|
@@ -1224,7 +1172,7 @@ function validateCoinOpts(json, sort, limitStr) {
|
|
|
1224
1172
|
outputErrorAndExit(
|
|
1225
1173
|
json,
|
|
1226
1174
|
`Invalid --sort value: ${sort}.`,
|
|
1227
|
-
`Supported: ${
|
|
1175
|
+
`Supported: ${SORT_OPTIONS}`
|
|
1228
1176
|
);
|
|
1229
1177
|
}
|
|
1230
1178
|
const limit = parseInt(limitStr, 10);
|
|
@@ -1236,100 +1184,243 @@ function validateCoinOpts(json, sort, limitStr) {
|
|
|
1236
1184
|
}
|
|
1237
1185
|
return { sort, limit };
|
|
1238
1186
|
}
|
|
1239
|
-
var balanceCommand = new
|
|
1240
|
-
const
|
|
1187
|
+
var balanceCommand = new Command2("balance").description("Show balances in your wallet").action(async function() {
|
|
1188
|
+
const output = getOutputMode(this, "live");
|
|
1189
|
+
const json = output === "json";
|
|
1241
1190
|
const account = resolveContext(json);
|
|
1191
|
+
const { live, intervalSeconds } = getLiveConfig(this, "live");
|
|
1242
1192
|
const sort = "usd-value";
|
|
1243
1193
|
const limit = 10;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
try {
|
|
1247
|
-
[walletResult, coinsResult] = await Promise.all([
|
|
1194
|
+
const fetchBalanceData = async () => {
|
|
1195
|
+
const [walletResult, coinsResult] = await Promise.allSettled([
|
|
1248
1196
|
fetchWalletBalances(account.address),
|
|
1249
1197
|
fetchCoins(json, account.address, sort, limit)
|
|
1250
1198
|
]);
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1199
|
+
if (walletResult.status === "rejected" || coinsResult.status === "rejected") {
|
|
1200
|
+
const err = walletResult.status === "rejected" ? walletResult.reason : coinsResult.reason;
|
|
1201
|
+
throw new Error(err instanceof Error ? err.message : String(err));
|
|
1202
|
+
}
|
|
1203
|
+
const rankedBalances = coinsResult.value.balances.map(
|
|
1204
|
+
(balance, index) => ({
|
|
1205
|
+
...balance,
|
|
1206
|
+
rank: index + 1
|
|
1207
|
+
})
|
|
1255
1208
|
);
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1209
|
+
return {
|
|
1210
|
+
walletBalances: walletResult.value.walletBalances,
|
|
1211
|
+
walletBalancesJson: walletResult.value.walletBalancesJson,
|
|
1212
|
+
rankedBalances,
|
|
1213
|
+
total: coinsResult.value.total
|
|
1214
|
+
};
|
|
1215
|
+
};
|
|
1216
|
+
if (json) {
|
|
1217
|
+
const data = await fetchBalanceData().catch(
|
|
1218
|
+
(err) => outputErrorAndExit(
|
|
1219
|
+
json,
|
|
1220
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1266
1221
|
)
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
)
|
|
1276
|
-
if (coinsResult.balances.length === 0) {
|
|
1277
|
-
console.log("\n No coin balances found.\n");
|
|
1278
|
-
console.log(" Buy coins to see them here:");
|
|
1279
|
-
console.log(" zora buy <address> --eth 0.001\n");
|
|
1280
|
-
} else {
|
|
1281
|
-
renderOnce(
|
|
1282
|
-
TableComponent({
|
|
1283
|
-
columns: balanceColumns,
|
|
1284
|
-
data: rankedBalances,
|
|
1285
|
-
title: `Coins \xB7 sorted by ${SORT_LABELS2[sort]}`,
|
|
1286
|
-
subtitle: `${coinsResult.balances.length} of ${coinsResult.total}`
|
|
1287
|
-
})
|
|
1288
|
-
);
|
|
1222
|
+
);
|
|
1223
|
+
outputData(json, {
|
|
1224
|
+
json: {
|
|
1225
|
+
wallet: data.walletBalancesJson,
|
|
1226
|
+
coins: data.rankedBalances.map(
|
|
1227
|
+
(balance) => formatBalanceJson(balance, balance.rank)
|
|
1228
|
+
)
|
|
1229
|
+
},
|
|
1230
|
+
table: () => {
|
|
1289
1231
|
}
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1232
|
+
});
|
|
1233
|
+
track("cli_balances", {
|
|
1234
|
+
sort,
|
|
1235
|
+
limit,
|
|
1236
|
+
live: false,
|
|
1237
|
+
result_count: data.rankedBalances.length,
|
|
1238
|
+
total_count: data.total,
|
|
1239
|
+
output_format: "json"
|
|
1240
|
+
});
|
|
1241
|
+
} else if (live) {
|
|
1242
|
+
await renderLive(
|
|
1243
|
+
/* @__PURE__ */ jsx3(
|
|
1244
|
+
BalanceView,
|
|
1245
|
+
{
|
|
1246
|
+
fetchData: fetchBalanceData,
|
|
1247
|
+
sort,
|
|
1248
|
+
mode: "full",
|
|
1249
|
+
autoRefresh: live,
|
|
1250
|
+
intervalSeconds
|
|
1251
|
+
}
|
|
1252
|
+
)
|
|
1253
|
+
);
|
|
1254
|
+
track("cli_balances", {
|
|
1255
|
+
sort,
|
|
1256
|
+
limit,
|
|
1257
|
+
live,
|
|
1258
|
+
interval: intervalSeconds,
|
|
1259
|
+
output_format: "live"
|
|
1260
|
+
});
|
|
1261
|
+
} else {
|
|
1262
|
+
const data = await fetchBalanceData().catch(
|
|
1263
|
+
(err) => outputErrorAndExit(
|
|
1264
|
+
json,
|
|
1265
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1266
|
+
)
|
|
1267
|
+
);
|
|
1268
|
+
renderOnce(
|
|
1269
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1270
|
+
/* @__PURE__ */ jsx3(
|
|
1271
|
+
Table,
|
|
1272
|
+
{
|
|
1273
|
+
columns: walletColumns,
|
|
1274
|
+
data: data.walletBalances,
|
|
1275
|
+
title: "Wallet"
|
|
1276
|
+
}
|
|
1277
|
+
),
|
|
1278
|
+
data.rankedBalances.length === 0 ? /* @__PURE__ */ jsxs3(
|
|
1279
|
+
Box3,
|
|
1280
|
+
{
|
|
1281
|
+
flexDirection: "column",
|
|
1282
|
+
paddingLeft: 1,
|
|
1283
|
+
paddingTop: 1,
|
|
1284
|
+
paddingBottom: 1,
|
|
1285
|
+
children: [
|
|
1286
|
+
/* @__PURE__ */ jsx3(Text3, { children: "No coin balances found." }),
|
|
1287
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1288
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Buy coins to see them here:" }),
|
|
1289
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1290
|
+
" zora buy ",
|
|
1291
|
+
"<address>",
|
|
1292
|
+
" --eth 0.001"
|
|
1293
|
+
] })
|
|
1294
|
+
] })
|
|
1295
|
+
]
|
|
1296
|
+
}
|
|
1297
|
+
) : /* @__PURE__ */ jsx3(
|
|
1298
|
+
Table,
|
|
1299
|
+
{
|
|
1300
|
+
columns: balanceColumns,
|
|
1301
|
+
data: data.rankedBalances,
|
|
1302
|
+
title: `Coins \xB7 sorted by ${SORT_LABELS[sort]}`,
|
|
1303
|
+
subtitle: `${data.rankedBalances.length} of ${data.total}`
|
|
1304
|
+
}
|
|
1305
|
+
)
|
|
1306
|
+
] })
|
|
1307
|
+
);
|
|
1308
|
+
track("cli_balances", {
|
|
1309
|
+
sort,
|
|
1310
|
+
limit,
|
|
1311
|
+
live: false,
|
|
1312
|
+
result_count: data.rankedBalances.length,
|
|
1313
|
+
total_count: data.total,
|
|
1314
|
+
output_format: "text"
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1299
1317
|
});
|
|
1300
1318
|
balanceCommand.command("spendable").description("Show wallet token balances (ETH, USDC, ZORA)").action(async function() {
|
|
1301
|
-
const
|
|
1319
|
+
const output = getOutputMode(this, "live");
|
|
1320
|
+
const json = output === "json";
|
|
1302
1321
|
const account = resolveContext(json);
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
walletResult = await fetchWalletBalances(account.address);
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1322
|
+
const { live, intervalSeconds } = getLiveConfig(this, "live");
|
|
1323
|
+
const fetchSpendableData = async () => {
|
|
1324
|
+
const walletResult = await fetchWalletBalances(account.address);
|
|
1325
|
+
return {
|
|
1326
|
+
walletBalances: walletResult.walletBalances,
|
|
1327
|
+
walletBalancesJson: walletResult.walletBalancesJson,
|
|
1328
|
+
rankedBalances: [],
|
|
1329
|
+
total: 0
|
|
1330
|
+
};
|
|
1331
|
+
};
|
|
1332
|
+
if (json) {
|
|
1333
|
+
const data = await fetchSpendableData().catch(
|
|
1334
|
+
(err) => outputErrorAndExit(
|
|
1335
|
+
json,
|
|
1336
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1337
|
+
)
|
|
1310
1338
|
);
|
|
1339
|
+
outputData(json, {
|
|
1340
|
+
json: { wallet: data.walletBalancesJson },
|
|
1341
|
+
table: () => {
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
} else if (live) {
|
|
1345
|
+
await renderLive(
|
|
1346
|
+
/* @__PURE__ */ jsx3(
|
|
1347
|
+
BalanceView,
|
|
1348
|
+
{
|
|
1349
|
+
fetchData: fetchSpendableData,
|
|
1350
|
+
sort: "usd-value",
|
|
1351
|
+
mode: "wallet",
|
|
1352
|
+
autoRefresh: live,
|
|
1353
|
+
intervalSeconds
|
|
1354
|
+
}
|
|
1355
|
+
)
|
|
1356
|
+
);
|
|
1357
|
+
} else {
|
|
1358
|
+
const walletResult = await fetchWalletBalances(account.address).catch(
|
|
1359
|
+
(err) => outputErrorAndExit(
|
|
1360
|
+
json,
|
|
1361
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
renderWallet(json, walletResult);
|
|
1311
1365
|
}
|
|
1312
|
-
renderWallet(json, walletResult);
|
|
1313
1366
|
});
|
|
1314
|
-
balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${
|
|
1315
|
-
const
|
|
1367
|
+
balanceCommand.command("coins").description("Show coin positions").option("--sort <sort>", `Sort by: ${SORT_OPTIONS}`, "usd-value").option("--limit <n>", "Number of results (max 20)", "10").action(async function(opts) {
|
|
1368
|
+
const output = getOutputMode(this, "live");
|
|
1369
|
+
const json = output === "json";
|
|
1316
1370
|
const { sort, limit } = validateCoinOpts(json, opts.sort, opts.limit);
|
|
1317
1371
|
const account = resolveContext(json);
|
|
1318
|
-
const {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1372
|
+
const { live, intervalSeconds } = getLiveConfig(this, "live");
|
|
1373
|
+
const fetchCoinsData = async () => {
|
|
1374
|
+
const { balances, total } = await fetchCoins(
|
|
1375
|
+
json,
|
|
1376
|
+
account.address,
|
|
1377
|
+
sort,
|
|
1378
|
+
limit
|
|
1379
|
+
);
|
|
1380
|
+
const rankedBalances = balances.map((balance, index) => ({
|
|
1381
|
+
...balance,
|
|
1382
|
+
rank: index + 1
|
|
1383
|
+
}));
|
|
1384
|
+
return {
|
|
1385
|
+
walletBalances: [],
|
|
1386
|
+
walletBalancesJson: [],
|
|
1387
|
+
rankedBalances,
|
|
1388
|
+
total
|
|
1389
|
+
};
|
|
1390
|
+
};
|
|
1391
|
+
if (json) {
|
|
1392
|
+
const data = await fetchCoinsData();
|
|
1393
|
+
renderCoins(json, data.rankedBalances, data.total, sort);
|
|
1394
|
+
} else if (live) {
|
|
1395
|
+
await renderLive(
|
|
1396
|
+
/* @__PURE__ */ jsx3(
|
|
1397
|
+
BalanceView,
|
|
1398
|
+
{
|
|
1399
|
+
fetchData: fetchCoinsData,
|
|
1400
|
+
sort,
|
|
1401
|
+
mode: "coins",
|
|
1402
|
+
autoRefresh: live,
|
|
1403
|
+
intervalSeconds
|
|
1404
|
+
}
|
|
1405
|
+
)
|
|
1406
|
+
);
|
|
1407
|
+
} else {
|
|
1408
|
+
const { balances, total } = await fetchCoins(
|
|
1409
|
+
json,
|
|
1410
|
+
account.address,
|
|
1411
|
+
sort,
|
|
1412
|
+
limit
|
|
1413
|
+
);
|
|
1414
|
+
renderCoins(json, balances, total, sort);
|
|
1415
|
+
}
|
|
1325
1416
|
});
|
|
1326
1417
|
|
|
1327
1418
|
// src/commands/buy.ts
|
|
1328
|
-
import { Command as
|
|
1419
|
+
import { Command as Command3 } from "commander";
|
|
1329
1420
|
import confirm2 from "@inquirer/confirm";
|
|
1330
1421
|
import { parseUnits, formatUnits as formatUnits3, isAddress } from "viem";
|
|
1331
1422
|
import {
|
|
1332
|
-
setApiKey as
|
|
1423
|
+
setApiKey as setApiKey2,
|
|
1333
1424
|
getCoin,
|
|
1334
1425
|
tradeCoin,
|
|
1335
1426
|
createTradeCall
|
|
@@ -1343,7 +1434,7 @@ import {
|
|
|
1343
1434
|
parseEventLogs,
|
|
1344
1435
|
erc20Abi as erc20Abi2
|
|
1345
1436
|
} from "viem";
|
|
1346
|
-
var GAS_RESERVE = parseEther("0.
|
|
1437
|
+
var GAS_RESERVE = parseEther("0.00001");
|
|
1347
1438
|
var BUY_AMOUNT_CHECKS = {
|
|
1348
1439
|
eth: (opts) => opts.eth !== void 0,
|
|
1349
1440
|
usd: (opts) => opts.usd !== void 0,
|
|
@@ -1409,13 +1500,16 @@ var getReceivedAmountFromReceipt = ({
|
|
|
1409
1500
|
if (matchingTransfers.length === 0) {
|
|
1410
1501
|
throw new Error("No matching Transfer event found in receipt.");
|
|
1411
1502
|
}
|
|
1412
|
-
|
|
1503
|
+
const amount = matchingTransfers.reduce((total, transfer) => {
|
|
1413
1504
|
const value = transfer.args?.value;
|
|
1414
1505
|
if (value === void 0) {
|
|
1415
1506
|
throw new Error("Transfer event missing amount.");
|
|
1416
1507
|
}
|
|
1417
1508
|
return total + value;
|
|
1418
1509
|
}, 0n);
|
|
1510
|
+
const lastTransfer = matchingTransfers[matchingTransfers.length - 1];
|
|
1511
|
+
const logIndex = lastTransfer?.logIndex ?? null;
|
|
1512
|
+
return { amount, logIndex };
|
|
1419
1513
|
};
|
|
1420
1514
|
var printDebugRequest = (label, tradeParameters) => {
|
|
1421
1515
|
if (process.env.ZORA_API_TARGET) {
|
|
@@ -1505,19 +1599,12 @@ var printTradeResult = (json, info) => {
|
|
|
1505
1599
|
};
|
|
1506
1600
|
|
|
1507
1601
|
// src/commands/buy.ts
|
|
1508
|
-
var buyCommand = new
|
|
1509
|
-
const json =
|
|
1602
|
+
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) {
|
|
1603
|
+
const json = getJson(this);
|
|
1510
1604
|
const debug = opts.debug === true;
|
|
1511
1605
|
if (!isAddress(coinAddress)) {
|
|
1512
1606
|
outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
|
|
1513
1607
|
}
|
|
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
1608
|
const tokenKey = opts.token.toLowerCase();
|
|
1522
1609
|
if (!(tokenKey in BASE_TRADE_TOKENS)) {
|
|
1523
1610
|
outputErrorAndExit(
|
|
@@ -1542,7 +1629,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1542
1629
|
const slippage = slippagePct / 100;
|
|
1543
1630
|
const apiKey = getApiKey();
|
|
1544
1631
|
if (apiKey) {
|
|
1545
|
-
|
|
1632
|
+
setApiKey2(apiKey);
|
|
1546
1633
|
}
|
|
1547
1634
|
const account = resolveAccount(json);
|
|
1548
1635
|
const { publicClient, walletClient } = createClients(account);
|
|
@@ -1648,7 +1735,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1648
1735
|
if (isEth && balance <= gasReserve) {
|
|
1649
1736
|
outputErrorAndExit(
|
|
1650
1737
|
json,
|
|
1651
|
-
`Balance too low (${formatEthDisplay(balance)} ETH). Need
|
|
1738
|
+
`Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
|
|
1652
1739
|
);
|
|
1653
1740
|
}
|
|
1654
1741
|
const spendableBalance = balance - gasReserve;
|
|
@@ -1662,7 +1749,7 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1662
1749
|
"Invalid --percent value. Must be between 0 and 100."
|
|
1663
1750
|
);
|
|
1664
1751
|
}
|
|
1665
|
-
amountIn = pct === 100 ? spendableBalance :
|
|
1752
|
+
amountIn = pct === 100 ? spendableBalance : spendableBalance * BigInt(Math.round(pct * 100)) / 10000n;
|
|
1666
1753
|
if (amountIn === 0n) {
|
|
1667
1754
|
outputErrorAndExit(
|
|
1668
1755
|
json,
|
|
@@ -1671,6 +1758,17 @@ var buyCommand = new Command4("buy").description("Buy a coin").argument("<addres
|
|
|
1671
1758
|
}
|
|
1672
1759
|
}
|
|
1673
1760
|
}
|
|
1761
|
+
let swapAmountUsd;
|
|
1762
|
+
if (amountMode === "usd") {
|
|
1763
|
+
swapAmountUsd = parsePercentageLikeValue(opts.usd);
|
|
1764
|
+
} else {
|
|
1765
|
+
const priceUsd = inputToken.fixedPriceUsd ?? await fetchTokenPriceUsd(inputToken.priceAddress);
|
|
1766
|
+
if (priceUsd != null) {
|
|
1767
|
+
swapAmountUsd = Number(
|
|
1768
|
+
(Number(formatUnits3(amountIn, inputToken.decimals)) * priceUsd).toFixed(2)
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1674
1772
|
const tradeParameters = {
|
|
1675
1773
|
sell: inputToken.trade,
|
|
1676
1774
|
buy: { type: "erc20", address: coinAddress },
|
|
@@ -1747,8 +1845,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1747
1845
|
coin_name: coinName,
|
|
1748
1846
|
coin_symbol: coinSymbol,
|
|
1749
1847
|
amount_mode: amountMode,
|
|
1848
|
+
swap_amount_usd: swapAmountUsd,
|
|
1849
|
+
valueUsd: swapAmountUsd,
|
|
1850
|
+
swapCoinType: token.coinType ?? null,
|
|
1750
1851
|
slippage: slippagePct,
|
|
1751
|
-
output_format:
|
|
1852
|
+
output_format: json ? "json" : "table"
|
|
1752
1853
|
});
|
|
1753
1854
|
return;
|
|
1754
1855
|
}
|
|
@@ -1776,6 +1877,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1776
1877
|
let receipt;
|
|
1777
1878
|
let txHash;
|
|
1778
1879
|
let receivedAmountOut = BigInt(amountOut);
|
|
1880
|
+
let swapLogIndex = null;
|
|
1881
|
+
const swapCoinType = token.coinType ?? null;
|
|
1779
1882
|
try {
|
|
1780
1883
|
receipt = await tradeCoin({
|
|
1781
1884
|
tradeParameters,
|
|
@@ -1790,8 +1893,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1790
1893
|
coin_name: coinName,
|
|
1791
1894
|
coin_symbol: coinSymbol,
|
|
1792
1895
|
amount_mode: amountMode,
|
|
1896
|
+
swap_amount_usd: swapAmountUsd,
|
|
1897
|
+
valueUsd: swapAmountUsd,
|
|
1898
|
+
swapCoinType,
|
|
1793
1899
|
slippage: slippagePct,
|
|
1794
|
-
output_format:
|
|
1900
|
+
output_format: json ? "json" : "table",
|
|
1795
1901
|
success: false,
|
|
1796
1902
|
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
1797
1903
|
});
|
|
@@ -1803,11 +1909,13 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1803
1909
|
}
|
|
1804
1910
|
txHash = receipt.transactionHash;
|
|
1805
1911
|
try {
|
|
1806
|
-
|
|
1912
|
+
const result = getReceivedAmountFromReceipt({
|
|
1807
1913
|
receipt,
|
|
1808
1914
|
tokenAddress: coinAddress,
|
|
1809
1915
|
recipient: account.address
|
|
1810
1916
|
});
|
|
1917
|
+
receivedAmountOut = result.amount;
|
|
1918
|
+
swapLogIndex = result.logIndex;
|
|
1811
1919
|
} catch (err) {
|
|
1812
1920
|
console.warn(
|
|
1813
1921
|
`Warning: transaction succeeded but could not determine received amount: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1833,13 +1941,401 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
1833
1941
|
amount_mode: amountMode,
|
|
1834
1942
|
input_amount: amountIn.toString(),
|
|
1835
1943
|
input_token_symbol: inputToken.symbol,
|
|
1944
|
+
swap_amount_usd: swapAmountUsd,
|
|
1945
|
+
valueUsd: swapAmountUsd,
|
|
1946
|
+
swapCoinType,
|
|
1947
|
+
transactionHash: txHash,
|
|
1948
|
+
logIndex: swapLogIndex,
|
|
1836
1949
|
slippage: slippagePct,
|
|
1837
|
-
output_format:
|
|
1950
|
+
output_format: json ? "json" : "table",
|
|
1838
1951
|
success: true,
|
|
1839
1952
|
tx_hash: txHash
|
|
1840
1953
|
});
|
|
1841
1954
|
});
|
|
1842
1955
|
|
|
1956
|
+
// src/commands/explore.tsx
|
|
1957
|
+
import { Command as Command4 } from "commander";
|
|
1958
|
+
import {
|
|
1959
|
+
setApiKey as setApiKey3,
|
|
1960
|
+
getCoinsTopVolume24h,
|
|
1961
|
+
getCoinsMostValuable,
|
|
1962
|
+
getCoinsNew,
|
|
1963
|
+
getCoinsTopGainers,
|
|
1964
|
+
getCoinsLastTraded,
|
|
1965
|
+
getCoinsLastTradedUnique,
|
|
1966
|
+
getExploreTopVolumeAll24h,
|
|
1967
|
+
getExploreTopVolumeCreators24h,
|
|
1968
|
+
getExploreNewAll,
|
|
1969
|
+
getExploreFeaturedCreators,
|
|
1970
|
+
getExploreFeaturedVideos,
|
|
1971
|
+
getCreatorCoins,
|
|
1972
|
+
getMostValuableCreatorCoins,
|
|
1973
|
+
getMostValuableAll,
|
|
1974
|
+
getMostValuableTrends,
|
|
1975
|
+
getNewTrends,
|
|
1976
|
+
getTopVolumeTrends24h,
|
|
1977
|
+
getTrendingAll,
|
|
1978
|
+
getTrendingCreators,
|
|
1979
|
+
getTrendingPosts,
|
|
1980
|
+
getTrendingTrends
|
|
1981
|
+
} from "@zoralabs/coins-sdk";
|
|
1982
|
+
|
|
1983
|
+
// src/lib/types.ts
|
|
1984
|
+
var SORT_LABELS2 = {
|
|
1985
|
+
mcap: "Top by Market Cap",
|
|
1986
|
+
volume: "Top by 24h Volume",
|
|
1987
|
+
new: "New",
|
|
1988
|
+
gainers: "Top Gainers (24h)",
|
|
1989
|
+
"last-traded": "Last Traded",
|
|
1990
|
+
"last-traded-unique": "Last Traded (Unique)",
|
|
1991
|
+
trending: "Trending",
|
|
1992
|
+
featured: "Featured"
|
|
1993
|
+
};
|
|
1994
|
+
var TYPE_LABELS = {
|
|
1995
|
+
all: "all",
|
|
1996
|
+
trend: "trends",
|
|
1997
|
+
"creator-coin": "creator coins",
|
|
1998
|
+
post: "posts"
|
|
1999
|
+
};
|
|
2000
|
+
var COIN_TYPE_DISPLAY = {
|
|
2001
|
+
CONTENT: "post",
|
|
2002
|
+
CREATOR: "creator-coin",
|
|
2003
|
+
TREND: "trend"
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// src/components/ExploreView.tsx
|
|
2007
|
+
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2 } from "react";
|
|
2008
|
+
import { Box as Box4, Text as Text4, useInput as useInput2, useApp as useApp2 } from "ink";
|
|
2009
|
+
import Spinner2 from "ink-spinner";
|
|
2010
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2011
|
+
var COLUMNS = [
|
|
2012
|
+
{ header: "#", width: 4, accessor: (c) => String(c.rank) },
|
|
2013
|
+
{ header: "Name", width: 20, accessor: (c) => c.name ?? "Unknown" },
|
|
2014
|
+
{ header: "Address", width: 44, accessor: (c) => c.address ?? "" },
|
|
2015
|
+
{
|
|
2016
|
+
header: "Type",
|
|
2017
|
+
width: 14,
|
|
2018
|
+
accessor: (c) => COIN_TYPE_DISPLAY[c.coinType ?? ""] ?? c.coinType ?? ""
|
|
2019
|
+
},
|
|
2020
|
+
{
|
|
2021
|
+
header: "Market Cap",
|
|
2022
|
+
width: 12,
|
|
2023
|
+
accessor: (c) => formatCompactUsd(c.marketCap)
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
header: "24h Vol",
|
|
2027
|
+
width: 12,
|
|
2028
|
+
accessor: (c) => formatCompactUsd(c.volume24h)
|
|
2029
|
+
},
|
|
2030
|
+
{
|
|
2031
|
+
header: "24h Change",
|
|
2032
|
+
width: 11,
|
|
2033
|
+
accessor: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).text,
|
|
2034
|
+
color: (c) => formatMcapChange(c.marketCap, c.marketCapDelta24h).color
|
|
2035
|
+
}
|
|
2036
|
+
];
|
|
2037
|
+
var CACHE_TTL_MS = 6e4;
|
|
2038
|
+
var CACHE_KEY_FIRST = "__first__";
|
|
2039
|
+
var ExploreView = ({
|
|
2040
|
+
fetchPage,
|
|
2041
|
+
sort,
|
|
2042
|
+
type,
|
|
2043
|
+
limit,
|
|
2044
|
+
initialCursor,
|
|
2045
|
+
cacheTtlMs = CACHE_TTL_MS,
|
|
2046
|
+
autoRefresh = false,
|
|
2047
|
+
intervalSeconds = 30
|
|
2048
|
+
}) => {
|
|
2049
|
+
const { exit } = useApp2();
|
|
2050
|
+
const [loading, setLoading] = useState3(true);
|
|
2051
|
+
const [error, setError] = useState3(null);
|
|
2052
|
+
const [coins, setCoins] = useState3([]);
|
|
2053
|
+
const [pageInfo, setPageInfo] = useState3(null);
|
|
2054
|
+
const [page, setPage] = useState3(1);
|
|
2055
|
+
const [cursorHistory, setCursorHistory] = useState3(
|
|
2056
|
+
[]
|
|
2057
|
+
);
|
|
2058
|
+
const [currentCursor, setCurrentCursor] = useState3(
|
|
2059
|
+
initialCursor
|
|
2060
|
+
);
|
|
2061
|
+
const cache = useRef2(/* @__PURE__ */ new Map());
|
|
2062
|
+
const { refreshCount, secondsUntilRefresh, triggerManualRefresh } = useAutoRefresh(intervalSeconds, autoRefresh);
|
|
2063
|
+
const [manualRefreshCount, setManualRefreshCount] = useState3(0);
|
|
2064
|
+
const loadPage = useCallback3(
|
|
2065
|
+
async (cursor) => {
|
|
2066
|
+
const cacheKey = cursor ?? CACHE_KEY_FIRST;
|
|
2067
|
+
const cached = cache.current.get(cacheKey);
|
|
2068
|
+
const isFresh = cached && Date.now() - cached.fetchedAt < cacheTtlMs;
|
|
2069
|
+
if (isFresh) {
|
|
2070
|
+
setCoins(cached.result.coins);
|
|
2071
|
+
setPageInfo(cached.result.pageInfo ?? null);
|
|
2072
|
+
setError(null);
|
|
2073
|
+
setLoading(false);
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
setLoading(true);
|
|
2077
|
+
setError(null);
|
|
2078
|
+
try {
|
|
2079
|
+
const result = await fetchPage(cursor);
|
|
2080
|
+
cache.current.set(cacheKey, { result, fetchedAt: Date.now() });
|
|
2081
|
+
setCoins(result.coins);
|
|
2082
|
+
setPageInfo(result.pageInfo ?? null);
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2085
|
+
}
|
|
2086
|
+
setLoading(false);
|
|
2087
|
+
},
|
|
2088
|
+
[fetchPage, cacheTtlMs]
|
|
2089
|
+
);
|
|
2090
|
+
useEffect3(() => {
|
|
2091
|
+
if (refreshCount === 0) return;
|
|
2092
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2093
|
+
cache.current.delete(cacheKey);
|
|
2094
|
+
}, [refreshCount, currentCursor]);
|
|
2095
|
+
useEffect3(() => {
|
|
2096
|
+
loadPage(currentCursor);
|
|
2097
|
+
}, [currentCursor, loadPage, refreshCount, manualRefreshCount]);
|
|
2098
|
+
useInput2((input, key) => {
|
|
2099
|
+
if (input === "q" || key.escape) {
|
|
2100
|
+
exit();
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (loading) return;
|
|
2104
|
+
const canGoNext = pageInfo?.hasNextPage && pageInfo.endCursor;
|
|
2105
|
+
const canGoPrev = cursorHistory.length > 0;
|
|
2106
|
+
if ((input === "n" || key.rightArrow) && canGoNext) {
|
|
2107
|
+
setCursorHistory((prev) => [...prev, currentCursor]);
|
|
2108
|
+
setCurrentCursor(pageInfo.endCursor);
|
|
2109
|
+
setPage((p) => p + 1);
|
|
2110
|
+
}
|
|
2111
|
+
if ((input === "p" || key.leftArrow) && canGoPrev) {
|
|
2112
|
+
const prev = cursorHistory[cursorHistory.length - 1];
|
|
2113
|
+
setCursorHistory((h) => h.slice(0, -1));
|
|
2114
|
+
setCurrentCursor(prev);
|
|
2115
|
+
setPage((p) => p - 1);
|
|
2116
|
+
}
|
|
2117
|
+
if (input === "r") {
|
|
2118
|
+
const cacheKey = currentCursor ?? CACHE_KEY_FIRST;
|
|
2119
|
+
cache.current.delete(cacheKey);
|
|
2120
|
+
triggerManualRefresh();
|
|
2121
|
+
setManualRefreshCount((c) => c + 1);
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
if (error) {
|
|
2125
|
+
return /* @__PURE__ */ jsxs4(
|
|
2126
|
+
Box4,
|
|
2127
|
+
{
|
|
2128
|
+
flexDirection: "column",
|
|
2129
|
+
paddingLeft: 1,
|
|
2130
|
+
paddingTop: 1,
|
|
2131
|
+
paddingBottom: 1,
|
|
2132
|
+
children: [
|
|
2133
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
2134
|
+
"Error: ",
|
|
2135
|
+
error
|
|
2136
|
+
] }),
|
|
2137
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2138
|
+
]
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
if (loading) {
|
|
2143
|
+
return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
2144
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
2145
|
+
" Loading\u2026"
|
|
2146
|
+
] }) });
|
|
2147
|
+
}
|
|
2148
|
+
if (coins.length === 0) {
|
|
2149
|
+
return /* @__PURE__ */ jsxs4(
|
|
2150
|
+
Box4,
|
|
2151
|
+
{
|
|
2152
|
+
flexDirection: "column",
|
|
2153
|
+
paddingLeft: 1,
|
|
2154
|
+
paddingTop: 1,
|
|
2155
|
+
paddingBottom: 1,
|
|
2156
|
+
children: [
|
|
2157
|
+
/* @__PURE__ */ jsx4(Text4, { children: "No coins found." }),
|
|
2158
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2159
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Try a different sort or type:" }),
|
|
2160
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort volume --type all" }),
|
|
2161
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " zora explore --sort new --type all" })
|
|
2162
|
+
] }),
|
|
2163
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press q to exit" }) })
|
|
2164
|
+
]
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
const title = type !== "all" ? `${SORT_LABELS2[sort]} \xB7 ${TYPE_LABELS[type]}` : SORT_LABELS2[sort];
|
|
2169
|
+
const subtitle = `Page ${page} \xB7 ${coins.length} result${coins.length !== 1 ? "s" : ""}`;
|
|
2170
|
+
const rankedCoins = coins.map((c, i) => ({
|
|
2171
|
+
...c,
|
|
2172
|
+
rank: (page - 1) * limit + i + 1
|
|
2173
|
+
}));
|
|
2174
|
+
const hints = [];
|
|
2175
|
+
if (cursorHistory.length > 0) hints.push("\u2190 prev");
|
|
2176
|
+
if (pageInfo?.hasNextPage) hints.push("\u2192 next");
|
|
2177
|
+
hints.push("r refresh");
|
|
2178
|
+
if (autoRefresh) hints.push(`auto: ${secondsUntilRefresh}s`);
|
|
2179
|
+
hints.push("q quit");
|
|
2180
|
+
const footer = hints.join(" \xB7 ");
|
|
2181
|
+
return /* @__PURE__ */ jsx4(
|
|
2182
|
+
Table,
|
|
2183
|
+
{
|
|
2184
|
+
data: rankedCoins,
|
|
2185
|
+
columns: COLUMNS,
|
|
2186
|
+
title,
|
|
2187
|
+
subtitle,
|
|
2188
|
+
footer
|
|
2189
|
+
}
|
|
2190
|
+
);
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
// src/commands/explore.tsx
|
|
2194
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
2195
|
+
var QUERY_MAP = {
|
|
2196
|
+
mcap: {
|
|
2197
|
+
all: getMostValuableAll,
|
|
2198
|
+
trend: getMostValuableTrends,
|
|
2199
|
+
"creator-coin": getMostValuableCreatorCoins,
|
|
2200
|
+
post: getCoinsMostValuable
|
|
2201
|
+
},
|
|
2202
|
+
volume: {
|
|
2203
|
+
all: getExploreTopVolumeAll24h,
|
|
2204
|
+
trend: getTopVolumeTrends24h,
|
|
2205
|
+
"creator-coin": getExploreTopVolumeCreators24h,
|
|
2206
|
+
post: getCoinsTopVolume24h
|
|
2207
|
+
},
|
|
2208
|
+
new: {
|
|
2209
|
+
all: getExploreNewAll,
|
|
2210
|
+
trend: getNewTrends,
|
|
2211
|
+
"creator-coin": getCreatorCoins,
|
|
2212
|
+
post: getCoinsNew
|
|
2213
|
+
},
|
|
2214
|
+
gainers: {
|
|
2215
|
+
post: getCoinsTopGainers
|
|
2216
|
+
},
|
|
2217
|
+
"last-traded": {
|
|
2218
|
+
post: getCoinsLastTraded
|
|
2219
|
+
},
|
|
2220
|
+
"last-traded-unique": {
|
|
2221
|
+
post: getCoinsLastTradedUnique
|
|
2222
|
+
},
|
|
2223
|
+
trending: {
|
|
2224
|
+
all: getTrendingAll,
|
|
2225
|
+
trend: getTrendingTrends,
|
|
2226
|
+
"creator-coin": getTrendingCreators,
|
|
2227
|
+
post: getTrendingPosts
|
|
2228
|
+
},
|
|
2229
|
+
featured: {
|
|
2230
|
+
"creator-coin": getExploreFeaturedCreators,
|
|
2231
|
+
post: getExploreFeaturedVideos
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
var SORT_OPTIONS2 = Object.keys(SORT_LABELS2).join(", ");
|
|
2235
|
+
var exploreCommand = new Command4("explore").description("Browse top, new, and highest volume coins").option("--sort <sort>", `Sort by: ${SORT_OPTIONS2}`, "mcap").option(
|
|
2236
|
+
"--type <type>",
|
|
2237
|
+
"Filter by type: all, trend, creator-coin, post (availability varies by sort)",
|
|
2238
|
+
"post"
|
|
2239
|
+
).option("--limit <n>", "Number of results (max 20)", "10").option("--after <cursor>", "Pagination cursor from a previous result").action(async function(opts) {
|
|
2240
|
+
const output = getOutputMode(this, "live");
|
|
2241
|
+
const json = output === "json";
|
|
2242
|
+
const sort = opts.sort;
|
|
2243
|
+
const type = opts.type;
|
|
2244
|
+
const limit = parseInt(opts.limit, 10);
|
|
2245
|
+
const after = opts.after;
|
|
2246
|
+
if (isNaN(limit) || limit <= 0 || limit > 20) {
|
|
2247
|
+
outputErrorAndExit(
|
|
2248
|
+
json,
|
|
2249
|
+
`Invalid --limit value: ${opts.limit}. Must be an integer between 1 and 20.`,
|
|
2250
|
+
"Usage: zora explore --limit 10"
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
if (!QUERY_MAP[sort]) {
|
|
2254
|
+
outputErrorAndExit(
|
|
2255
|
+
json,
|
|
2256
|
+
`Invalid --sort value: ${sort}.`,
|
|
2257
|
+
`Supported: ${SORT_OPTIONS2}`
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
if (!QUERY_MAP[sort][type]) {
|
|
2261
|
+
const supported = Object.keys(QUERY_MAP[sort]);
|
|
2262
|
+
outputErrorAndExit(
|
|
2263
|
+
json,
|
|
2264
|
+
`Invalid --type for --sort ${sort}.`,
|
|
2265
|
+
`Supported: ${supported.join(", ")}`
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
const apiKey = getApiKey();
|
|
2269
|
+
if (apiKey) {
|
|
2270
|
+
setApiKey3(apiKey);
|
|
2271
|
+
}
|
|
2272
|
+
const queryFn = QUERY_MAP[sort][type];
|
|
2273
|
+
if (json) {
|
|
2274
|
+
let response;
|
|
2275
|
+
try {
|
|
2276
|
+
response = await queryFn({ count: limit, after });
|
|
2277
|
+
} catch (err) {
|
|
2278
|
+
outputErrorAndExit(
|
|
2279
|
+
json,
|
|
2280
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
if (response.error) {
|
|
2284
|
+
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
2285
|
+
outputErrorAndExit(json, `API error: ${msg}`);
|
|
2286
|
+
}
|
|
2287
|
+
const edges = response.data?.exploreList?.edges ?? [];
|
|
2288
|
+
const coins = edges.map((e) => e.node);
|
|
2289
|
+
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2290
|
+
outputJson({ coins, pageInfo: pageInfo ?? null });
|
|
2291
|
+
track("cli_explore", {
|
|
2292
|
+
sort,
|
|
2293
|
+
type,
|
|
2294
|
+
limit,
|
|
2295
|
+
paginated: after !== void 0,
|
|
2296
|
+
result_count: coins.length,
|
|
2297
|
+
has_next_page: pageInfo?.hasNextPage ?? false,
|
|
2298
|
+
output_format: "json"
|
|
2299
|
+
});
|
|
2300
|
+
} else {
|
|
2301
|
+
const { live, intervalSeconds } = getLiveConfig(this, "live");
|
|
2302
|
+
const fetchPage = async (cursor) => {
|
|
2303
|
+
const response = await queryFn({ count: limit, after: cursor });
|
|
2304
|
+
if (response.error) {
|
|
2305
|
+
const msg = typeof response.error === "object" && response.error.error ? response.error.error : JSON.stringify(response.error);
|
|
2306
|
+
throw new Error(msg);
|
|
2307
|
+
}
|
|
2308
|
+
const edges = response.data?.exploreList?.edges ?? [];
|
|
2309
|
+
const coins = edges.map((e) => e.node);
|
|
2310
|
+
const pageInfo = response.data?.exploreList?.pageInfo;
|
|
2311
|
+
return { coins, pageInfo };
|
|
2312
|
+
};
|
|
2313
|
+
await renderLive(
|
|
2314
|
+
/* @__PURE__ */ jsx5(
|
|
2315
|
+
ExploreView,
|
|
2316
|
+
{
|
|
2317
|
+
fetchPage,
|
|
2318
|
+
sort,
|
|
2319
|
+
type,
|
|
2320
|
+
limit,
|
|
2321
|
+
initialCursor: after,
|
|
2322
|
+
autoRefresh: live,
|
|
2323
|
+
intervalSeconds
|
|
2324
|
+
}
|
|
2325
|
+
)
|
|
2326
|
+
);
|
|
2327
|
+
track("cli_explore", {
|
|
2328
|
+
sort,
|
|
2329
|
+
type,
|
|
2330
|
+
limit,
|
|
2331
|
+
live,
|
|
2332
|
+
interval: intervalSeconds,
|
|
2333
|
+
paginated: after !== void 0,
|
|
2334
|
+
output_format: "text"
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
|
|
1843
2339
|
// src/commands/get.tsx
|
|
1844
2340
|
import { Command as Command5 } from "commander";
|
|
1845
2341
|
import { setApiKey as setApiKey4 } from "@zoralabs/coins-sdk";
|
|
@@ -1933,24 +2429,24 @@ async function resolveCoin(ref) {
|
|
|
1933
2429
|
}
|
|
1934
2430
|
|
|
1935
2431
|
// src/components/CoinDetail.tsx
|
|
1936
|
-
import { Box as
|
|
1937
|
-
import { jsx as
|
|
2432
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
2433
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1938
2434
|
var LABEL_WIDTH = 18;
|
|
1939
2435
|
function Row({
|
|
1940
2436
|
label,
|
|
1941
2437
|
children
|
|
1942
2438
|
}) {
|
|
1943
|
-
return /* @__PURE__ */
|
|
1944
|
-
/* @__PURE__ */
|
|
1945
|
-
/* @__PURE__ */
|
|
2439
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
2440
|
+
/* @__PURE__ */ jsx6(Box5, { width: LABEL_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: label }) }),
|
|
2441
|
+
/* @__PURE__ */ jsx6(Text5, { children })
|
|
1946
2442
|
] });
|
|
1947
2443
|
}
|
|
1948
2444
|
function CoinDetail({ coin }) {
|
|
1949
2445
|
const change = formatMcapChange(coin.marketCap, coin.marketCapDelta24h);
|
|
1950
|
-
return /* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
1953
|
-
/* @__PURE__ */
|
|
2446
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2447
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
2448
|
+
/* @__PURE__ */ jsx6(Text5, { bold: true, children: coin.name }),
|
|
2449
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1954
2450
|
coin.coinType,
|
|
1955
2451
|
" ",
|
|
1956
2452
|
"\xB7",
|
|
@@ -1958,20 +2454,20 @@ function CoinDetail({ coin }) {
|
|
|
1958
2454
|
coin.address
|
|
1959
2455
|
] })
|
|
1960
2456
|
] }),
|
|
1961
|
-
/* @__PURE__ */
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
1964
|
-
/* @__PURE__ */
|
|
1965
|
-
/* @__PURE__ */
|
|
1966
|
-
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
2457
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
2458
|
+
/* @__PURE__ */ jsx6(Row, { label: "Market Cap", children: formatCompactUsd(coin.marketCap) }),
|
|
2459
|
+
/* @__PURE__ */ jsx6(Row, { label: "24h Volume", children: formatCompactUsd(coin.volume24h) }),
|
|
2460
|
+
/* @__PURE__ */ jsx6(Row, { label: "24h Change", children: /* @__PURE__ */ jsx6(Text5, { color: change.color, children: change.text }) }),
|
|
2461
|
+
/* @__PURE__ */ jsx6(Row, { label: "Holders", children: formatHolders(coin.uniqueHolders) }),
|
|
2462
|
+
coin.coinType === "post" && (coin.creatorHandle ?? coin.creatorAddress) && /* @__PURE__ */ jsx6(Row, { label: "Creator", children: coin.creatorHandle ?? coin.creatorAddress }),
|
|
2463
|
+
/* @__PURE__ */ jsx6(Row, { label: "Created", children: formatCreatedAt(coin.createdAt) })
|
|
1968
2464
|
] }),
|
|
1969
|
-
/* @__PURE__ */
|
|
2465
|
+
/* @__PURE__ */ jsx6(Box5, { marginBottom: 1 })
|
|
1970
2466
|
] });
|
|
1971
2467
|
}
|
|
1972
2468
|
|
|
1973
2469
|
// src/commands/get.tsx
|
|
1974
|
-
import { jsx as
|
|
2470
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1975
2471
|
function formatCoinJson(coin) {
|
|
1976
2472
|
return {
|
|
1977
2473
|
name: coin.name,
|
|
@@ -1989,25 +2485,204 @@ function formatCoinJson(coin) {
|
|
|
1989
2485
|
var VALID_TYPES = ["creator-coin", "post", "trend"];
|
|
1990
2486
|
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
2487
|
const json = getJson(this);
|
|
1992
|
-
if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
|
|
2488
|
+
if (opts.type !== void 0 && !VALID_TYPES.includes(opts.type)) {
|
|
2489
|
+
outputErrorAndExit(
|
|
2490
|
+
json,
|
|
2491
|
+
`Invalid --type value: ${opts.type}.`,
|
|
2492
|
+
`Supported: ${VALID_TYPES.join(", ")}`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
const type = opts.type;
|
|
2496
|
+
if (type === "post" && !identifier.startsWith("0x")) {
|
|
2497
|
+
outputErrorAndExit(
|
|
2498
|
+
json,
|
|
2499
|
+
"Posts can only be looked up by address.",
|
|
2500
|
+
"Use: zora get 0x..."
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
const ref = parseCoinRef(identifier, opts.type);
|
|
2504
|
+
const apiKey = getApiKey();
|
|
2505
|
+
if (apiKey) {
|
|
2506
|
+
setApiKey4(apiKey);
|
|
2507
|
+
}
|
|
2508
|
+
let result;
|
|
2509
|
+
try {
|
|
2510
|
+
result = await resolveCoin(ref);
|
|
2511
|
+
} catch (err) {
|
|
2512
|
+
outputErrorAndExit(
|
|
2513
|
+
json,
|
|
2514
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2515
|
+
);
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
if (type && result.kind === "found" && result.coin.coinType !== type) {
|
|
2519
|
+
outputErrorAndExit(
|
|
2520
|
+
json,
|
|
2521
|
+
`Coin at ${result.coin.address} is a ${result.coin.coinType}, not a ${type}.`,
|
|
2522
|
+
`Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
|
|
2523
|
+
);
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (result.kind === "not-found") {
|
|
2527
|
+
outputErrorAndExit(json, result.message);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
outputData(json, {
|
|
2531
|
+
json: formatCoinJson(result.coin),
|
|
2532
|
+
table: () => {
|
|
2533
|
+
renderOnce(/* @__PURE__ */ jsx7(CoinDetail, { coin: result.coin }));
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
track("cli_get", {
|
|
2537
|
+
lookup_type: identifier.startsWith("0x") ? "address" : "name",
|
|
2538
|
+
coin_type_filter: type ?? null,
|
|
2539
|
+
found: result.kind === "found",
|
|
2540
|
+
coin_type: result.kind === "found" ? result.coin.coinType : null,
|
|
2541
|
+
output_format: json ? "json" : "text"
|
|
2542
|
+
});
|
|
2543
|
+
});
|
|
2544
|
+
|
|
2545
|
+
// src/commands/price-history.tsx
|
|
2546
|
+
import { Command as Command6 } from "commander";
|
|
2547
|
+
import { setApiKey as setApiKey5, apiGet } from "@zoralabs/coins-sdk";
|
|
2548
|
+
|
|
2549
|
+
// src/components/PriceHistory.tsx
|
|
2550
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
2551
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2552
|
+
var LABEL_WIDTH2 = 18;
|
|
2553
|
+
var Row2 = ({
|
|
2554
|
+
label,
|
|
2555
|
+
children
|
|
2556
|
+
}) => /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2557
|
+
/* @__PURE__ */ jsx8(Box6, { width: LABEL_WIDTH2, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: label }) }),
|
|
2558
|
+
/* @__PURE__ */ jsx8(Text6, { children })
|
|
2559
|
+
] });
|
|
2560
|
+
var PriceHistory = ({
|
|
2561
|
+
coin,
|
|
2562
|
+
coinType,
|
|
2563
|
+
interval,
|
|
2564
|
+
high,
|
|
2565
|
+
low,
|
|
2566
|
+
change,
|
|
2567
|
+
sparklineText
|
|
2568
|
+
}) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2569
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2570
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Coin", children: coin }),
|
|
2571
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Type", children: coinType }),
|
|
2572
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Interval", children: interval }),
|
|
2573
|
+
/* @__PURE__ */ jsx8(Row2, { label: "High", children: high }),
|
|
2574
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Low", children: low }),
|
|
2575
|
+
/* @__PURE__ */ jsx8(Row2, { label: "Change", children: /* @__PURE__ */ jsx8(Text6, { color: change.color, children: change.text }) })
|
|
2576
|
+
] }),
|
|
2577
|
+
sparklineText.length > 0 && /* @__PURE__ */ jsx8(Box6, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text6, { children: sparklineText }) }),
|
|
2578
|
+
/* @__PURE__ */ jsx8(Box6, { marginBottom: 1 })
|
|
2579
|
+
] });
|
|
2580
|
+
|
|
2581
|
+
// src/lib/sparkline.ts
|
|
2582
|
+
var BLOCKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
2583
|
+
var sparkline = (values) => {
|
|
2584
|
+
if (values.length <= 1) return values.length === 1 ? BLOCKS[4] : "";
|
|
2585
|
+
const min = Math.min(...values);
|
|
2586
|
+
const max = Math.max(...values);
|
|
2587
|
+
const range = max - min;
|
|
2588
|
+
if (range === 0) return BLOCKS[4].repeat(values.length);
|
|
2589
|
+
return values.map((v) => {
|
|
2590
|
+
const idx = Math.round((v - min) / range * (BLOCKS.length - 1));
|
|
2591
|
+
return BLOCKS[idx];
|
|
2592
|
+
}).join("");
|
|
2593
|
+
};
|
|
2594
|
+
var MAX_SPARKLINE_WIDTH = 50;
|
|
2595
|
+
var downsample = (values, maxWidth) => {
|
|
2596
|
+
if (values.length <= maxWidth) return values;
|
|
2597
|
+
const bucketSize = values.length / maxWidth;
|
|
2598
|
+
const result = [];
|
|
2599
|
+
for (let i = 0; i < maxWidth; i++) {
|
|
2600
|
+
const start = Math.floor(i * bucketSize);
|
|
2601
|
+
const end = Math.floor((i + 1) * bucketSize);
|
|
2602
|
+
let sum = 0;
|
|
2603
|
+
for (let j = start; j < end; j++) {
|
|
2604
|
+
sum += values[j];
|
|
2605
|
+
}
|
|
2606
|
+
result.push(sum / (end - start));
|
|
2607
|
+
}
|
|
2608
|
+
return result;
|
|
2609
|
+
};
|
|
2610
|
+
|
|
2611
|
+
// src/commands/price-history.tsx
|
|
2612
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2613
|
+
var VALID_TYPES2 = ["creator-coin", "post", "trend"];
|
|
2614
|
+
var VALID_INTERVALS = ["1h", "24h", "1w", "1m", "ALL"];
|
|
2615
|
+
var INTERVAL_TO_API_FIELD = {
|
|
2616
|
+
"1h": "oneHour",
|
|
2617
|
+
"24h": "oneDay",
|
|
2618
|
+
"1w": "oneWeek",
|
|
2619
|
+
"1m": "oneMonth",
|
|
2620
|
+
ALL: "all"
|
|
2621
|
+
};
|
|
2622
|
+
var formatPrice = (price) => {
|
|
2623
|
+
if (price >= 1) {
|
|
2624
|
+
return `$${price.toFixed(2)}`;
|
|
2625
|
+
}
|
|
2626
|
+
if (price >= 0.01) {
|
|
2627
|
+
return `$${price.toFixed(4)}`;
|
|
2628
|
+
}
|
|
2629
|
+
return `$${price.toPrecision(4)}`;
|
|
2630
|
+
};
|
|
2631
|
+
var formatChange = (first, last) => {
|
|
2632
|
+
if (first === 0) return { text: "-", color: void 0 };
|
|
2633
|
+
const pct = (last - first) / first * 100;
|
|
2634
|
+
const prefix = pct >= 0 ? "+" : "";
|
|
2635
|
+
const text = `${prefix}${pct.toFixed(1)}%`;
|
|
2636
|
+
const color = pct > 0 ? "green" : pct < 0 ? "red" : void 0;
|
|
2637
|
+
return { text, color };
|
|
2638
|
+
};
|
|
2639
|
+
var fetchPriceHistory = async (address, interval) => {
|
|
2640
|
+
const response = await apiGet("/coinPriceHistory", {
|
|
2641
|
+
address
|
|
2642
|
+
});
|
|
2643
|
+
const data = response.data;
|
|
2644
|
+
const token = data?.zora20Token;
|
|
2645
|
+
if (!token) return [];
|
|
2646
|
+
const field = INTERVAL_TO_API_FIELD[interval];
|
|
2647
|
+
const points = token[field];
|
|
2648
|
+
if (!points || points.length === 0) return [];
|
|
2649
|
+
return points.map((p) => ({
|
|
2650
|
+
timestamp: p.timestamp,
|
|
2651
|
+
price: Number(p.closePrice)
|
|
2652
|
+
}));
|
|
2653
|
+
};
|
|
2654
|
+
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(
|
|
2655
|
+
"--interval <interval>",
|
|
2656
|
+
`Time range: ${VALID_INTERVALS.join(", ")}`,
|
|
2657
|
+
"1w"
|
|
2658
|
+
).action(async function(identifier, opts) {
|
|
2659
|
+
const json = getJson(this);
|
|
2660
|
+
const interval = opts.interval ?? "1w";
|
|
2661
|
+
if (!VALID_INTERVALS.includes(interval)) {
|
|
2662
|
+
outputErrorAndExit(
|
|
2663
|
+
json,
|
|
2664
|
+
`Invalid --interval value: ${interval}.`,
|
|
2665
|
+
`Supported: ${VALID_INTERVALS.join(", ")}`
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
if (opts.type !== void 0 && !VALID_TYPES2.includes(opts.type)) {
|
|
1993
2669
|
outputErrorAndExit(
|
|
1994
2670
|
json,
|
|
1995
2671
|
`Invalid --type value: ${opts.type}.`,
|
|
1996
|
-
`Supported: ${
|
|
2672
|
+
`Supported: ${VALID_TYPES2.join(", ")}`
|
|
1997
2673
|
);
|
|
1998
2674
|
}
|
|
1999
|
-
|
|
2000
|
-
if (type === "post" && !identifier.startsWith("0x")) {
|
|
2675
|
+
if (opts.type === "post" && !identifier.startsWith("0x")) {
|
|
2001
2676
|
outputErrorAndExit(
|
|
2002
2677
|
json,
|
|
2003
2678
|
"Posts can only be looked up by address.",
|
|
2004
|
-
"Use: zora
|
|
2679
|
+
"Use: zora price-history 0x..."
|
|
2005
2680
|
);
|
|
2006
2681
|
}
|
|
2007
2682
|
const ref = parseCoinRef(identifier, opts.type);
|
|
2008
2683
|
const apiKey = getApiKey();
|
|
2009
2684
|
if (apiKey) {
|
|
2010
|
-
|
|
2685
|
+
setApiKey5(apiKey);
|
|
2011
2686
|
}
|
|
2012
2687
|
let result;
|
|
2013
2688
|
try {
|
|
@@ -2019,35 +2694,80 @@ var getCommand = new Command5("get").description("Look up a coin by address or n
|
|
|
2019
2694
|
);
|
|
2020
2695
|
return;
|
|
2021
2696
|
}
|
|
2022
|
-
if (
|
|
2697
|
+
if (result.kind === "not-found") {
|
|
2698
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const { coin } = result;
|
|
2702
|
+
let prices;
|
|
2703
|
+
try {
|
|
2704
|
+
prices = await fetchPriceHistory(coin.address, interval);
|
|
2705
|
+
} catch (err) {
|
|
2023
2706
|
outputErrorAndExit(
|
|
2024
2707
|
json,
|
|
2025
|
-
`
|
|
2026
|
-
`Use: zora get ${result.coin.address} --type ${result.coin.coinType}`
|
|
2708
|
+
`Failed to fetch price data: ${err instanceof Error ? err.message : String(err)}`
|
|
2027
2709
|
);
|
|
2028
2710
|
return;
|
|
2029
2711
|
}
|
|
2030
|
-
if (
|
|
2031
|
-
outputErrorAndExit(
|
|
2712
|
+
if (prices.length === 0) {
|
|
2713
|
+
outputErrorAndExit(
|
|
2714
|
+
json,
|
|
2715
|
+
`No price data found for ${coin.name} in the last ${interval}.`,
|
|
2716
|
+
"Try a longer interval with --interval"
|
|
2717
|
+
);
|
|
2032
2718
|
return;
|
|
2033
2719
|
}
|
|
2720
|
+
const priceValues = prices.map((p) => p.price);
|
|
2721
|
+
const high = Math.max(...priceValues);
|
|
2722
|
+
const low = Math.min(...priceValues);
|
|
2723
|
+
const change = formatChange(
|
|
2724
|
+
priceValues[0],
|
|
2725
|
+
priceValues[priceValues.length - 1]
|
|
2726
|
+
);
|
|
2727
|
+
const sparklineText = sparkline(
|
|
2728
|
+
downsample(priceValues, MAX_SPARKLINE_WIDTH)
|
|
2729
|
+
);
|
|
2034
2730
|
outputData(json, {
|
|
2035
|
-
json:
|
|
2731
|
+
json: {
|
|
2732
|
+
coin: coin.name,
|
|
2733
|
+
type: coin.coinType,
|
|
2734
|
+
interval,
|
|
2735
|
+
high,
|
|
2736
|
+
low,
|
|
2737
|
+
change: priceValues[0] === 0 ? null : (priceValues[priceValues.length - 1] - priceValues[0]) / priceValues[0],
|
|
2738
|
+
prices: prices.map((p) => ({
|
|
2739
|
+
timestamp: p.timestamp,
|
|
2740
|
+
price: p.price
|
|
2741
|
+
}))
|
|
2742
|
+
},
|
|
2036
2743
|
table: () => {
|
|
2037
|
-
renderOnce(
|
|
2744
|
+
renderOnce(
|
|
2745
|
+
/* @__PURE__ */ jsx9(
|
|
2746
|
+
PriceHistory,
|
|
2747
|
+
{
|
|
2748
|
+
coin: coin.name,
|
|
2749
|
+
coinType: coin.coinType,
|
|
2750
|
+
interval,
|
|
2751
|
+
high: formatPrice(high),
|
|
2752
|
+
low: formatPrice(low),
|
|
2753
|
+
change,
|
|
2754
|
+
sparklineText
|
|
2755
|
+
}
|
|
2756
|
+
)
|
|
2757
|
+
);
|
|
2038
2758
|
}
|
|
2039
2759
|
});
|
|
2040
|
-
track("
|
|
2760
|
+
track("cli_price_history", {
|
|
2041
2761
|
lookup_type: identifier.startsWith("0x") ? "address" : "name",
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2762
|
+
coin_type: coin.coinType,
|
|
2763
|
+
interval,
|
|
2764
|
+
data_points: prices.length,
|
|
2045
2765
|
output_format: json ? "json" : "text"
|
|
2046
2766
|
});
|
|
2047
2767
|
});
|
|
2048
2768
|
|
|
2049
2769
|
// src/commands/sell.ts
|
|
2050
|
-
import { Command as
|
|
2770
|
+
import { Command as Command7 } from "commander";
|
|
2051
2771
|
import confirm3 from "@inquirer/confirm";
|
|
2052
2772
|
import {
|
|
2053
2773
|
erc20Abi as erc20Abi3,
|
|
@@ -2058,7 +2778,7 @@ import {
|
|
|
2058
2778
|
import {
|
|
2059
2779
|
createTradeCall as createTradeCall2,
|
|
2060
2780
|
getCoin as getCoin3,
|
|
2061
|
-
setApiKey as
|
|
2781
|
+
setApiKey as setApiKey6,
|
|
2062
2782
|
tradeCoin as tradeCoin2
|
|
2063
2783
|
} from "@zoralabs/coins-sdk";
|
|
2064
2784
|
function printSellQuote(output, info) {
|
|
@@ -2133,19 +2853,13 @@ function printSellResult(output, info) {
|
|
|
2133
2853
|
console.log(` Tx ${info.txHash}
|
|
2134
2854
|
`);
|
|
2135
2855
|
}
|
|
2136
|
-
var sellCommand = new
|
|
2137
|
-
const json =
|
|
2856
|
+
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) {
|
|
2857
|
+
const json = getJson(this);
|
|
2138
2858
|
const debug = opts.debug === true;
|
|
2139
2859
|
if (!isAddress2(coinAddress)) {
|
|
2140
2860
|
outputErrorAndExit(json, `Invalid address: ${coinAddress}`);
|
|
2141
2861
|
}
|
|
2142
|
-
const output =
|
|
2143
|
-
if (output !== "table" && output !== "json") {
|
|
2144
|
-
outputErrorAndExit(
|
|
2145
|
-
false,
|
|
2146
|
-
`Invalid --output value: ${output}. Use: table, json`
|
|
2147
|
-
);
|
|
2148
|
-
}
|
|
2862
|
+
const output = json ? "json" : "table";
|
|
2149
2863
|
const outputAsset = opts.token ? opts.token.toLowerCase() : opts.to;
|
|
2150
2864
|
if (!(outputAsset in BASE_TRADE_TOKENS)) {
|
|
2151
2865
|
outputErrorAndExit(
|
|
@@ -2170,7 +2884,7 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2170
2884
|
const slippage = slippagePct / 100;
|
|
2171
2885
|
const apiKey = getApiKey();
|
|
2172
2886
|
if (apiKey) {
|
|
2173
|
-
|
|
2887
|
+
setApiKey6(apiKey);
|
|
2174
2888
|
}
|
|
2175
2889
|
const account = resolveAccount(json);
|
|
2176
2890
|
const { publicClient, walletClient } = createClients(account);
|
|
@@ -2263,6 +2977,19 @@ var sellCommand = new Command6("sell").description("Sell a coin").argument("<add
|
|
|
2263
2977
|
}
|
|
2264
2978
|
}
|
|
2265
2979
|
}
|
|
2980
|
+
let swapAmountUsd;
|
|
2981
|
+
if (amountMode === "usd") {
|
|
2982
|
+
swapAmountUsd = parsePercentageLikeValue(opts.usd);
|
|
2983
|
+
} else {
|
|
2984
|
+
const coinPriceUsd = await fetchTokenPriceUsd(coinAddress);
|
|
2985
|
+
if (coinPriceUsd !== null && coinPriceUsd > 0) {
|
|
2986
|
+
swapAmountUsd = Number(
|
|
2987
|
+
(Number(formatUnits4(amountIn, coinDecimals)) * coinPriceUsd).toFixed(
|
|
2988
|
+
2
|
|
2989
|
+
)
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2266
2993
|
const tradeParameters = {
|
|
2267
2994
|
sell: { type: "erc20", address: coinAddress },
|
|
2268
2995
|
buy: outputToken.trade,
|
|
@@ -2339,6 +3066,9 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2339
3066
|
coin_name: coinName,
|
|
2340
3067
|
coin_symbol: coinSymbol,
|
|
2341
3068
|
amount_mode: amountMode,
|
|
3069
|
+
swap_amount_usd: swapAmountUsd,
|
|
3070
|
+
valueUsd: swapAmountUsd,
|
|
3071
|
+
swapCoinType: token.coinType ?? null,
|
|
2342
3072
|
output_asset: outputAsset,
|
|
2343
3073
|
slippage: slippagePct,
|
|
2344
3074
|
output_format: output
|
|
@@ -2372,6 +3102,8 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2372
3102
|
let txHash;
|
|
2373
3103
|
let receivedAmountOut = BigInt(quoteAmountOut);
|
|
2374
3104
|
let receivedSource = "quote";
|
|
3105
|
+
let swapLogIndex = null;
|
|
3106
|
+
const swapCoinType = token.coinType ?? null;
|
|
2375
3107
|
try {
|
|
2376
3108
|
receipt = await tradeCoin2({
|
|
2377
3109
|
tradeParameters,
|
|
@@ -2386,6 +3118,9 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2386
3118
|
coin_name: coinName,
|
|
2387
3119
|
coin_symbol: coinSymbol,
|
|
2388
3120
|
amount_mode: amountMode,
|
|
3121
|
+
swap_amount_usd: swapAmountUsd,
|
|
3122
|
+
valueUsd: swapAmountUsd,
|
|
3123
|
+
swapCoinType,
|
|
2389
3124
|
output_asset: outputAsset,
|
|
2390
3125
|
slippage: slippagePct,
|
|
2391
3126
|
output_format: output,
|
|
@@ -2401,11 +3136,13 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2401
3136
|
txHash = receipt.transactionHash;
|
|
2402
3137
|
if (outputToken.trade.type === "erc20") {
|
|
2403
3138
|
try {
|
|
2404
|
-
|
|
3139
|
+
const result = getReceivedAmountFromReceipt({
|
|
2405
3140
|
receipt,
|
|
2406
3141
|
tokenAddress: outputToken.trade.address,
|
|
2407
3142
|
recipient: account.address
|
|
2408
3143
|
});
|
|
3144
|
+
receivedAmountOut = result.amount;
|
|
3145
|
+
swapLogIndex = result.logIndex;
|
|
2409
3146
|
receivedSource = "receipt";
|
|
2410
3147
|
} catch {
|
|
2411
3148
|
}
|
|
@@ -2429,6 +3166,11 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2429
3166
|
coin_name: coinName,
|
|
2430
3167
|
coin_symbol: coinSymbol,
|
|
2431
3168
|
amount_mode: amountMode,
|
|
3169
|
+
swap_amount_usd: swapAmountUsd,
|
|
3170
|
+
valueUsd: swapAmountUsd,
|
|
3171
|
+
swapCoinType,
|
|
3172
|
+
transactionHash: txHash,
|
|
3173
|
+
logIndex: swapLogIndex,
|
|
2432
3174
|
output_asset: outputAsset,
|
|
2433
3175
|
slippage: slippagePct,
|
|
2434
3176
|
output_format: output,
|
|
@@ -2437,8 +3179,424 @@ ${err instanceof Error ? err.stack || err.message : String(err)}
|
|
|
2437
3179
|
});
|
|
2438
3180
|
});
|
|
2439
3181
|
|
|
3182
|
+
// src/commands/send.ts
|
|
3183
|
+
import { Command as Command8 } from "commander";
|
|
3184
|
+
import confirm4 from "@inquirer/confirm";
|
|
3185
|
+
import {
|
|
3186
|
+
erc20Abi as erc20Abi4,
|
|
3187
|
+
formatUnits as formatUnits5,
|
|
3188
|
+
isAddress as isAddress3,
|
|
3189
|
+
parseUnits as parseUnits3
|
|
3190
|
+
} from "viem";
|
|
3191
|
+
import { setApiKey as setApiKey7 } from "@zoralabs/coins-sdk";
|
|
3192
|
+
var SEND_AMOUNT_CHECKS = {
|
|
3193
|
+
amount: (opts) => opts.amount !== void 0,
|
|
3194
|
+
percent: (opts) => opts.percent !== void 0,
|
|
3195
|
+
all: (opts) => opts.all === true
|
|
3196
|
+
};
|
|
3197
|
+
var VALID_TYPES3 = ["creator-coin", "post", "trend"];
|
|
3198
|
+
function printSendPreview(info) {
|
|
3199
|
+
const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
|
|
3200
|
+
console.log(`
|
|
3201
|
+
Send ${info.name} (${info.symbol})
|
|
3202
|
+
`);
|
|
3203
|
+
console.log(
|
|
3204
|
+
` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
|
|
3205
|
+
);
|
|
3206
|
+
console.log(` To ${info.to}`);
|
|
3207
|
+
console.log(`
|
|
3208
|
+
Ensure receiving wallet can receive on Base.`);
|
|
3209
|
+
console.log("");
|
|
3210
|
+
}
|
|
3211
|
+
function printSendResult(json, info) {
|
|
3212
|
+
if (json) {
|
|
3213
|
+
outputJson({
|
|
3214
|
+
action: "send",
|
|
3215
|
+
coin: info.symbol,
|
|
3216
|
+
address: info.address,
|
|
3217
|
+
sent: {
|
|
3218
|
+
amount: formatUnits5(info.amount, info.decimals),
|
|
3219
|
+
raw: info.amount.toString(),
|
|
3220
|
+
symbol: info.symbol,
|
|
3221
|
+
amountUsd: info.amountUsd
|
|
3222
|
+
},
|
|
3223
|
+
to: info.to,
|
|
3224
|
+
tx: info.txHash
|
|
3225
|
+
});
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
const usdStr = info.amountUsd != null ? ` ($${info.amountUsd.toFixed(2)})` : "";
|
|
3229
|
+
console.log(`
|
|
3230
|
+
Sent ${info.name}
|
|
3231
|
+
`);
|
|
3232
|
+
console.log(
|
|
3233
|
+
` Amount ${info.amountFormatted} ${info.symbol}${usdStr}`
|
|
3234
|
+
);
|
|
3235
|
+
console.log(` To ${info.to}`);
|
|
3236
|
+
console.log(` Tx ${info.txHash}
|
|
3237
|
+
`);
|
|
3238
|
+
}
|
|
3239
|
+
var sendCommand = new Command8("send").description("Send coins or ETH to an address").argument("<identifier>", "Coin address, name, or token (eth, usdc, zora)").requiredOption("--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) {
|
|
3240
|
+
const json = getJson(this);
|
|
3241
|
+
if (!isAddress3(opts.to)) {
|
|
3242
|
+
outputErrorAndExit(
|
|
3243
|
+
json,
|
|
3244
|
+
`Invalid recipient address: ${opts.to}`,
|
|
3245
|
+
"Must be a valid 0x address."
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
const recipient = opts.to;
|
|
3249
|
+
if (opts.type !== void 0 && !VALID_TYPES3.includes(opts.type)) {
|
|
3250
|
+
outputErrorAndExit(
|
|
3251
|
+
json,
|
|
3252
|
+
`Invalid --type value: ${opts.type}.`,
|
|
3253
|
+
`Supported: ${VALID_TYPES3.join(", ")}`
|
|
3254
|
+
);
|
|
3255
|
+
}
|
|
3256
|
+
const amountMode = getAmountMode(
|
|
3257
|
+
json,
|
|
3258
|
+
opts,
|
|
3259
|
+
SEND_AMOUNT_CHECKS,
|
|
3260
|
+
"--amount, --percent, or --all"
|
|
3261
|
+
);
|
|
3262
|
+
const isEth = identifier.toLowerCase() === "eth";
|
|
3263
|
+
if (isEth) {
|
|
3264
|
+
if (opts.type) {
|
|
3265
|
+
outputErrorAndExit(json, "--type is not valid when sending ETH.");
|
|
3266
|
+
}
|
|
3267
|
+
const account = resolveAccount(json);
|
|
3268
|
+
const { publicClient, walletClient } = createClients(account);
|
|
3269
|
+
const balance = await publicClient.getBalance({
|
|
3270
|
+
address: account.address
|
|
3271
|
+
});
|
|
3272
|
+
if (balance === 0n) {
|
|
3273
|
+
outputErrorAndExit(
|
|
3274
|
+
json,
|
|
3275
|
+
`No ETH balance. Deposit ETH to ${account.address} on Base.`
|
|
3276
|
+
);
|
|
3277
|
+
}
|
|
3278
|
+
let amount;
|
|
3279
|
+
if (amountMode === "amount") {
|
|
3280
|
+
const val = parsePercentageLikeValue(opts.amount);
|
|
3281
|
+
if (val === void 0 || val <= 0) {
|
|
3282
|
+
outputErrorAndExit(
|
|
3283
|
+
json,
|
|
3284
|
+
"Invalid --amount value. Must be a positive number."
|
|
3285
|
+
);
|
|
3286
|
+
}
|
|
3287
|
+
try {
|
|
3288
|
+
amount = parseUnits3(opts.amount, 18);
|
|
3289
|
+
} catch {
|
|
3290
|
+
outputErrorAndExit(
|
|
3291
|
+
json,
|
|
3292
|
+
"Invalid --amount value. Must be a valid ETH amount."
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
if (amount === 0n) {
|
|
3296
|
+
outputErrorAndExit(
|
|
3297
|
+
json,
|
|
3298
|
+
"Amount too small \u2014 rounds to zero at 18 decimal places."
|
|
3299
|
+
);
|
|
3300
|
+
}
|
|
3301
|
+
if (amount + GAS_RESERVE > balance) {
|
|
3302
|
+
outputErrorAndExit(
|
|
3303
|
+
json,
|
|
3304
|
+
`Insufficient balance. Have ${formatEthDisplay(balance)} ETH (need to reserve ~${formatEthDisplay(GAS_RESERVE)} ETH for gas).`
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3307
|
+
} else {
|
|
3308
|
+
if (balance <= GAS_RESERVE) {
|
|
3309
|
+
outputErrorAndExit(
|
|
3310
|
+
json,
|
|
3311
|
+
`Balance too low (${formatEthDisplay(balance)} ETH). Need >${formatEthDisplay(GAS_RESERVE)} ETH for gas.`
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
const spendable = balance - GAS_RESERVE;
|
|
3315
|
+
if (amountMode === "all") {
|
|
3316
|
+
amount = spendable;
|
|
3317
|
+
} else {
|
|
3318
|
+
const pct = parsePercentageLikeValue(opts.percent);
|
|
3319
|
+
if (pct === void 0 || pct <= 0 || pct > 100) {
|
|
3320
|
+
outputErrorAndExit(
|
|
3321
|
+
json,
|
|
3322
|
+
"Invalid --percent value. Must be between 0 and 100."
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3325
|
+
amount = pct === 100 ? spendable : spendable * BigInt(Math.round(pct * 100)) / 10000n;
|
|
3326
|
+
if (amount === 0n) {
|
|
3327
|
+
outputErrorAndExit(
|
|
3328
|
+
json,
|
|
3329
|
+
"Calculated amount is zero. Balance too low."
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
const amountFormatted = formatEthDisplay(amount);
|
|
3335
|
+
let amountUsd = null;
|
|
3336
|
+
const ethPriceUsd = await fetchTokenPriceUsd(WETH_ADDRESS);
|
|
3337
|
+
if (ethPriceUsd != null) {
|
|
3338
|
+
amountUsd = Number(
|
|
3339
|
+
(Number(formatUnits5(amount, 18)) * ethPriceUsd).toFixed(2)
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
if (!opts.yes) {
|
|
3343
|
+
printSendPreview({
|
|
3344
|
+
name: "ETH",
|
|
3345
|
+
symbol: "ETH",
|
|
3346
|
+
amountFormatted,
|
|
3347
|
+
amountUsd,
|
|
3348
|
+
to: recipient
|
|
3349
|
+
});
|
|
3350
|
+
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
3351
|
+
if (!ok) {
|
|
3352
|
+
console.error("Aborted.");
|
|
3353
|
+
process.exit(0);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
let txHash;
|
|
3357
|
+
try {
|
|
3358
|
+
txHash = await walletClient.sendTransaction({
|
|
3359
|
+
to: recipient,
|
|
3360
|
+
value: amount
|
|
3361
|
+
});
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
track("cli_send", {
|
|
3364
|
+
asset: "eth",
|
|
3365
|
+
output_format: json ? "json" : "table",
|
|
3366
|
+
success: false,
|
|
3367
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3368
|
+
});
|
|
3369
|
+
await shutdownAnalytics();
|
|
3370
|
+
outputErrorAndExit(
|
|
3371
|
+
json,
|
|
3372
|
+
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3373
|
+
);
|
|
3374
|
+
}
|
|
3375
|
+
await publicClient.waitForTransactionReceipt({
|
|
3376
|
+
hash: txHash
|
|
3377
|
+
});
|
|
3378
|
+
printSendResult(json, {
|
|
3379
|
+
name: "ETH",
|
|
3380
|
+
symbol: "ETH",
|
|
3381
|
+
address: null,
|
|
3382
|
+
amount,
|
|
3383
|
+
decimals: 18,
|
|
3384
|
+
amountFormatted,
|
|
3385
|
+
amountUsd,
|
|
3386
|
+
to: recipient,
|
|
3387
|
+
txHash
|
|
3388
|
+
});
|
|
3389
|
+
track("cli_send", {
|
|
3390
|
+
asset: "eth",
|
|
3391
|
+
amount_mode: amountMode,
|
|
3392
|
+
amount_usd: amountUsd,
|
|
3393
|
+
transactionHash: txHash,
|
|
3394
|
+
output_format: json ? "json" : "table",
|
|
3395
|
+
success: true,
|
|
3396
|
+
tx_hash: txHash
|
|
3397
|
+
});
|
|
3398
|
+
} else {
|
|
3399
|
+
const knownTokenKey = identifier.toLowerCase();
|
|
3400
|
+
const knownToken = knownTokenKey !== "eth" && knownTokenKey in BASE_TRADE_TOKENS ? BASE_TRADE_TOKENS[knownTokenKey] : void 0;
|
|
3401
|
+
if (knownToken && opts.type) {
|
|
3402
|
+
outputErrorAndExit(
|
|
3403
|
+
json,
|
|
3404
|
+
`--type is not valid when sending ${knownToken.symbol}.`
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
let tokenAddress;
|
|
3408
|
+
let tokenName;
|
|
3409
|
+
if (knownToken) {
|
|
3410
|
+
const trade = knownToken.trade;
|
|
3411
|
+
tokenAddress = trade.address;
|
|
3412
|
+
tokenName = knownToken.symbol;
|
|
3413
|
+
} else {
|
|
3414
|
+
const apiKey = getApiKey();
|
|
3415
|
+
if (apiKey) {
|
|
3416
|
+
setApiKey7(apiKey);
|
|
3417
|
+
}
|
|
3418
|
+
const ref = parseCoinRef(identifier, opts.type);
|
|
3419
|
+
let result;
|
|
3420
|
+
try {
|
|
3421
|
+
result = await resolveCoin(ref);
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
outputErrorAndExit(
|
|
3424
|
+
json,
|
|
3425
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
if (result.kind === "not-found") {
|
|
3429
|
+
outputErrorAndExit(json, result.message, result.suggestion);
|
|
3430
|
+
}
|
|
3431
|
+
tokenAddress = result.coin.address;
|
|
3432
|
+
tokenName = result.coin.name;
|
|
3433
|
+
}
|
|
3434
|
+
const account = resolveAccount(json);
|
|
3435
|
+
const { publicClient, walletClient } = createClients(account);
|
|
3436
|
+
let balance;
|
|
3437
|
+
let decimals;
|
|
3438
|
+
let symbol;
|
|
3439
|
+
if (knownToken) {
|
|
3440
|
+
balance = await publicClient.readContract({
|
|
3441
|
+
abi: erc20Abi4,
|
|
3442
|
+
address: tokenAddress,
|
|
3443
|
+
functionName: "balanceOf",
|
|
3444
|
+
args: [account.address]
|
|
3445
|
+
});
|
|
3446
|
+
decimals = knownToken.decimals;
|
|
3447
|
+
symbol = knownToken.symbol;
|
|
3448
|
+
} else {
|
|
3449
|
+
const results = await Promise.all([
|
|
3450
|
+
publicClient.readContract({
|
|
3451
|
+
abi: erc20Abi4,
|
|
3452
|
+
address: tokenAddress,
|
|
3453
|
+
functionName: "balanceOf",
|
|
3454
|
+
args: [account.address]
|
|
3455
|
+
}),
|
|
3456
|
+
publicClient.readContract({
|
|
3457
|
+
abi: erc20Abi4,
|
|
3458
|
+
address: tokenAddress,
|
|
3459
|
+
functionName: "decimals"
|
|
3460
|
+
}),
|
|
3461
|
+
publicClient.readContract({
|
|
3462
|
+
abi: erc20Abi4,
|
|
3463
|
+
address: tokenAddress,
|
|
3464
|
+
functionName: "symbol"
|
|
3465
|
+
})
|
|
3466
|
+
]);
|
|
3467
|
+
balance = results[0];
|
|
3468
|
+
decimals = results[1];
|
|
3469
|
+
symbol = results[2];
|
|
3470
|
+
}
|
|
3471
|
+
if (balance === 0n) {
|
|
3472
|
+
outputErrorAndExit(
|
|
3473
|
+
json,
|
|
3474
|
+
`No ${symbol} balance. Buy some first or pick a different wallet.`
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
let amount;
|
|
3478
|
+
if (amountMode === "amount") {
|
|
3479
|
+
const val = parsePercentageLikeValue(opts.amount);
|
|
3480
|
+
if (val === void 0 || val <= 0) {
|
|
3481
|
+
outputErrorAndExit(
|
|
3482
|
+
json,
|
|
3483
|
+
"Invalid --amount value. Must be a positive number."
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
try {
|
|
3487
|
+
amount = parseUnits3(opts.amount, decimals);
|
|
3488
|
+
} catch {
|
|
3489
|
+
outputErrorAndExit(
|
|
3490
|
+
json,
|
|
3491
|
+
"Invalid --amount value for token decimals."
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
if (amount === 0n) {
|
|
3495
|
+
outputErrorAndExit(
|
|
3496
|
+
json,
|
|
3497
|
+
`Amount too small \u2014 rounds to zero at ${decimals} decimal places.`
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
if (amount > balance) {
|
|
3501
|
+
outputErrorAndExit(
|
|
3502
|
+
json,
|
|
3503
|
+
`Insufficient balance. Have ${formatAmountDisplay(balance, decimals)} ${symbol}.`
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
} else if (amountMode === "all") {
|
|
3507
|
+
amount = balance;
|
|
3508
|
+
} else {
|
|
3509
|
+
const pct = parsePercentageLikeValue(opts.percent);
|
|
3510
|
+
if (pct === void 0 || pct <= 0 || pct > 100) {
|
|
3511
|
+
outputErrorAndExit(
|
|
3512
|
+
json,
|
|
3513
|
+
"Invalid --percent value. Must be between 0 and 100."
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
amount = pct === 100 ? balance : balance * BigInt(Math.round(pct * 100)) / 10000n;
|
|
3517
|
+
if (amount === 0n) {
|
|
3518
|
+
outputErrorAndExit(
|
|
3519
|
+
json,
|
|
3520
|
+
"Calculated amount is zero. Balance too low."
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
const amountFormatted = formatAmountDisplay(amount, decimals);
|
|
3525
|
+
let amountUsd = null;
|
|
3526
|
+
const priceAddress = knownToken ? knownToken.priceAddress : tokenAddress;
|
|
3527
|
+
const priceUsd = knownToken?.fixedPriceUsd ?? await fetchTokenPriceUsd(priceAddress);
|
|
3528
|
+
if (priceUsd != null) {
|
|
3529
|
+
amountUsd = Number(
|
|
3530
|
+
(Number(formatUnits5(amount, decimals)) * priceUsd).toFixed(2)
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
if (!opts.yes) {
|
|
3534
|
+
printSendPreview({
|
|
3535
|
+
name: tokenName,
|
|
3536
|
+
symbol,
|
|
3537
|
+
amountFormatted,
|
|
3538
|
+
amountUsd,
|
|
3539
|
+
to: recipient
|
|
3540
|
+
});
|
|
3541
|
+
const ok = await confirm4({ message: "Confirm?", default: false });
|
|
3542
|
+
if (!ok) {
|
|
3543
|
+
console.error("Aborted.");
|
|
3544
|
+
process.exit(0);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
let txHash;
|
|
3548
|
+
try {
|
|
3549
|
+
txHash = await walletClient.writeContract({
|
|
3550
|
+
abi: erc20Abi4,
|
|
3551
|
+
address: tokenAddress,
|
|
3552
|
+
functionName: "transfer",
|
|
3553
|
+
args: [recipient, amount]
|
|
3554
|
+
});
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
track("cli_send", {
|
|
3557
|
+
asset: knownToken ? knownTokenKey : "coin",
|
|
3558
|
+
coin_address: tokenAddress,
|
|
3559
|
+
coin_name: tokenName,
|
|
3560
|
+
coin_symbol: symbol,
|
|
3561
|
+
output_format: json ? "json" : "table",
|
|
3562
|
+
success: false,
|
|
3563
|
+
error_type: err instanceof Error ? err.constructor.name : "unknown"
|
|
3564
|
+
});
|
|
3565
|
+
await shutdownAnalytics();
|
|
3566
|
+
outputErrorAndExit(
|
|
3567
|
+
json,
|
|
3568
|
+
`Transaction failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3572
|
+
printSendResult(json, {
|
|
3573
|
+
name: tokenName,
|
|
3574
|
+
symbol,
|
|
3575
|
+
address: tokenAddress,
|
|
3576
|
+
amount,
|
|
3577
|
+
decimals,
|
|
3578
|
+
amountFormatted,
|
|
3579
|
+
amountUsd,
|
|
3580
|
+
to: recipient,
|
|
3581
|
+
txHash
|
|
3582
|
+
});
|
|
3583
|
+
track("cli_send", {
|
|
3584
|
+
asset: knownToken ? knownTokenKey : "coin",
|
|
3585
|
+
coin_address: tokenAddress,
|
|
3586
|
+
coin_name: tokenName,
|
|
3587
|
+
coin_symbol: symbol,
|
|
3588
|
+
amount_mode: amountMode,
|
|
3589
|
+
amount_usd: amountUsd,
|
|
3590
|
+
transactionHash: txHash,
|
|
3591
|
+
output_format: json ? "json" : "table",
|
|
3592
|
+
success: true,
|
|
3593
|
+
tx_hash: txHash
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
|
|
2440
3598
|
// src/commands/setup.ts
|
|
2441
|
-
import { Command as
|
|
3599
|
+
import { Command as Command9 } from "commander";
|
|
2442
3600
|
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
2443
3601
|
|
|
2444
3602
|
// src/lib/strings.ts
|
|
@@ -2460,7 +3618,7 @@ var toAccount = (json, key, errorPrefix) => {
|
|
|
2460
3618
|
);
|
|
2461
3619
|
}
|
|
2462
3620
|
};
|
|
2463
|
-
var setupCommand = new
|
|
3621
|
+
var setupCommand = new Command9("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
3622
|
const json = getJson(this);
|
|
2465
3623
|
const nonInteractive = getYes(this);
|
|
2466
3624
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -2619,7 +3777,7 @@ var setupCommand = new Command7("setup").description("Set up your Zora wallet").
|
|
|
2619
3777
|
});
|
|
2620
3778
|
|
|
2621
3779
|
// src/commands/wallet.ts
|
|
2622
|
-
import { Command as
|
|
3780
|
+
import { Command as Command10 } from "commander";
|
|
2623
3781
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2624
3782
|
var resolvePrivateKey = () => {
|
|
2625
3783
|
const envKey = process.env.ZORA_PRIVATE_KEY;
|
|
@@ -2632,7 +3790,7 @@ var resolvePrivateKey = () => {
|
|
|
2632
3790
|
}
|
|
2633
3791
|
return void 0;
|
|
2634
3792
|
};
|
|
2635
|
-
var walletCommand = new
|
|
3793
|
+
var walletCommand = new Command10("wallet").description(
|
|
2636
3794
|
"Manage your Zora wallet"
|
|
2637
3795
|
);
|
|
2638
3796
|
walletCommand.command("info").description("Show wallet address and storage location").action(function() {
|
|
@@ -2692,7 +3850,7 @@ walletCommand.command("export").description("Print the raw private key to stdout
|
|
|
2692
3850
|
});
|
|
2693
3851
|
|
|
2694
3852
|
// src/components/Zorb.tsx
|
|
2695
|
-
import { Text as
|
|
3853
|
+
import { Text as Text7, Box as Box7 } from "ink";
|
|
2696
3854
|
|
|
2697
3855
|
// src/lib/zorb-pixels.ts
|
|
2698
3856
|
function supportsTruecolor() {
|
|
@@ -2837,7 +3995,7 @@ function generateZorbPixels(size) {
|
|
|
2837
3995
|
}
|
|
2838
3996
|
|
|
2839
3997
|
// src/components/Zorb.tsx
|
|
2840
|
-
import { jsx as
|
|
3998
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2841
3999
|
var LOWER_HALF_BLOCK = "\u2584";
|
|
2842
4000
|
var UPPER_HALF_BLOCK = "\u2580";
|
|
2843
4001
|
function rgbString([r, g, b]) {
|
|
@@ -2860,19 +4018,19 @@ function Zorb({ size = 20 }) {
|
|
|
2860
4018
|
const topIsBlack = isBlack(top);
|
|
2861
4019
|
const bottomIsBlack = isBlack(bottom);
|
|
2862
4020
|
if (topIsBlack && bottomIsBlack) {
|
|
2863
|
-
cells.push(/* @__PURE__ */
|
|
4021
|
+
cells.push(/* @__PURE__ */ jsx10(Text7, { children: " " }, x));
|
|
2864
4022
|
} else if (topIsBlack) {
|
|
2865
4023
|
cells.push(
|
|
2866
|
-
/* @__PURE__ */
|
|
4024
|
+
/* @__PURE__ */ jsx10(Text7, { color: rgbString(bottom), children: LOWER_HALF_BLOCK }, x)
|
|
2867
4025
|
);
|
|
2868
4026
|
} else if (bottomIsBlack) {
|
|
2869
4027
|
cells.push(
|
|
2870
|
-
/* @__PURE__ */
|
|
4028
|
+
/* @__PURE__ */ jsx10(Text7, { color: rgbString(top), children: UPPER_HALF_BLOCK }, x)
|
|
2871
4029
|
);
|
|
2872
4030
|
} else {
|
|
2873
4031
|
cells.push(
|
|
2874
|
-
/* @__PURE__ */
|
|
2875
|
-
|
|
4032
|
+
/* @__PURE__ */ jsx10(
|
|
4033
|
+
Text7,
|
|
2876
4034
|
{
|
|
2877
4035
|
backgroundColor: rgbString(top),
|
|
2878
4036
|
color: rgbString(bottom),
|
|
@@ -2883,41 +4041,53 @@ function Zorb({ size = 20 }) {
|
|
|
2883
4041
|
);
|
|
2884
4042
|
}
|
|
2885
4043
|
}
|
|
2886
|
-
rows.push(/* @__PURE__ */
|
|
4044
|
+
rows.push(/* @__PURE__ */ jsx10(Text7, { children: cells }, y));
|
|
2887
4045
|
}
|
|
2888
|
-
return /* @__PURE__ */
|
|
2889
|
-
/* @__PURE__ */
|
|
4046
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
4047
|
+
/* @__PURE__ */ jsx10(Text7, { children: " " }),
|
|
2890
4048
|
rows,
|
|
2891
|
-
/* @__PURE__ */
|
|
4049
|
+
/* @__PURE__ */ jsx10(Text7, { children: " " })
|
|
2892
4050
|
] });
|
|
2893
4051
|
}
|
|
2894
4052
|
|
|
2895
4053
|
// src/index.tsx
|
|
2896
|
-
import { jsx as
|
|
4054
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2897
4055
|
if (process.env.ZORA_API_TARGET) {
|
|
2898
4056
|
setApiBaseUrl(process.env.ZORA_API_TARGET);
|
|
2899
4057
|
}
|
|
2900
|
-
var version = true ? "0.2.
|
|
4058
|
+
var version = true ? "0.2.4" : JSON.parse(
|
|
2901
4059
|
readFileSync2(new URL("../package.json", import.meta.url), "utf-8")
|
|
2902
4060
|
).version;
|
|
2903
4061
|
var buildProgram = () => {
|
|
2904
|
-
const program2 = new
|
|
4062
|
+
const program2 = new Command11().name("zora").description("Zora CLI").version(version).option(
|
|
4063
|
+
"--output <format>",
|
|
4064
|
+
"Output format: table, json, live (default varies by command)"
|
|
4065
|
+
).option(
|
|
4066
|
+
"--interval <seconds>",
|
|
4067
|
+
"Auto-refresh interval in seconds (min 5)",
|
|
4068
|
+
"30"
|
|
4069
|
+
);
|
|
2905
4070
|
program2.addCommand(authCommand);
|
|
2906
4071
|
program2.addCommand(balanceCommand);
|
|
2907
4072
|
program2.addCommand(buyCommand);
|
|
2908
4073
|
program2.addCommand(exploreCommand);
|
|
2909
4074
|
program2.addCommand(getCommand);
|
|
4075
|
+
program2.addCommand(priceHistoryCommand);
|
|
2910
4076
|
program2.addCommand(setupCommand);
|
|
2911
4077
|
program2.addCommand(walletCommand);
|
|
2912
4078
|
program2.addCommand(sellCommand);
|
|
4079
|
+
program2.addCommand(sendCommand);
|
|
2913
4080
|
return program2;
|
|
2914
4081
|
};
|
|
2915
4082
|
var program = buildProgram();
|
|
2916
4083
|
if (!process.env.VITEST) {
|
|
2917
4084
|
const showingHelp = process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h");
|
|
2918
|
-
if (showingHelp && !process.argv.includes("--
|
|
2919
|
-
renderOnce(/* @__PURE__ */
|
|
4085
|
+
if (showingHelp && !process.argv.includes("--output") && supportsTruecolor()) {
|
|
4086
|
+
renderOnce(/* @__PURE__ */ jsx11(Zorb, { size: 20 }));
|
|
2920
4087
|
}
|
|
4088
|
+
console.warn(
|
|
4089
|
+
"\x1B[33m\u26A0 Beta:\x1B[0m This CLI is in beta and should be used with caution."
|
|
4090
|
+
);
|
|
2921
4091
|
identify();
|
|
2922
4092
|
try {
|
|
2923
4093
|
await program.parseAsync();
|