@bankr/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +379 -133
- package/dist/commands/balances.d.ts +1 -0
- package/dist/commands/balances.js +7 -32
- package/dist/commands/llm.js +191 -22
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +139 -33
- package/dist/commands/portfolio.d.ts +9 -0
- package/dist/commands/portfolio.js +134 -0
- package/dist/commands/tokens.d.ts +7 -0
- package/dist/commands/tokens.js +60 -0
- package/dist/commands/transfer.d.ts +8 -0
- package/dist/commands/transfer.js +80 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +116 -0
- package/dist/lib/api.d.ts +59 -2
- package/dist/lib/api.js +34 -8
- package/dist/lib/chains.d.ts +5 -0
- package/dist/lib/chains.js +35 -0
- package/dist/lib/output.d.ts +2 -0
- package/dist/lib/output.js +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getPortfolio, } from "../lib/api.js";
|
|
3
|
+
import * as output from "../lib/output.js";
|
|
4
|
+
import { formatUsd, formatBalance } from "../lib/output.js";
|
|
5
|
+
import { CHAIN_LABELS, VALID_CHAINS } from "../lib/chains.js";
|
|
6
|
+
const BRAND = chalk.hex("#FF613D");
|
|
7
|
+
const DIM = chalk.dim;
|
|
8
|
+
const BOLD = chalk.bold;
|
|
9
|
+
const GREEN = chalk.greenBright;
|
|
10
|
+
const RED = chalk.redBright;
|
|
11
|
+
const WHITE = chalk.whiteBright;
|
|
12
|
+
function formatPnl(value) {
|
|
13
|
+
if (value === 0)
|
|
14
|
+
return DIM("$0.00");
|
|
15
|
+
const formatted = `$${Math.abs(value).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
16
|
+
return value > 0 ? GREEN(`+${formatted}`) : RED(`-${formatted}`);
|
|
17
|
+
}
|
|
18
|
+
function printChainBalances(chain, data, showPnl) {
|
|
19
|
+
const label = CHAIN_LABELS[chain] || chain;
|
|
20
|
+
const total = parseFloat(data.total);
|
|
21
|
+
const totalStr = total > 0 ? GREEN(formatUsd(total)) : DIM(formatUsd(total));
|
|
22
|
+
console.log();
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(` ${BRAND.bold(label)} ${totalStr}`);
|
|
25
|
+
console.log(` ${DIM("─".repeat(showPnl ? 74 : 58))}`);
|
|
26
|
+
// Native balance
|
|
27
|
+
const nativeUsd = parseFloat(data.nativeUsd);
|
|
28
|
+
if (nativeUsd > 0 || parseFloat(data.nativeBalance) > 0) {
|
|
29
|
+
const nativeSymbol = chain === "solana" ? "SOL" : chain === "polygon" ? "POL" : "ETH";
|
|
30
|
+
console.log(` ${BOLD(WHITE(nativeSymbol.padEnd(12)))} ${formatBalance(data.nativeBalance).padStart(18)} ${DIM(formatUsd(nativeUsd))}`);
|
|
31
|
+
}
|
|
32
|
+
// Token balances sorted by USD value descending
|
|
33
|
+
const sorted = [...data.tokenBalances].sort((a, b) => b.token.balanceUSD - a.token.balanceUSD);
|
|
34
|
+
for (const tb of sorted) {
|
|
35
|
+
const symbol = tb.token.baseToken.symbol || "???";
|
|
36
|
+
const bal = formatBalance(tb.token.balance);
|
|
37
|
+
const usd = formatUsd(tb.token.balanceUSD);
|
|
38
|
+
let line = ` ${WHITE(symbol.padEnd(12))} ${bal.padStart(18)} ${DIM(usd)}`;
|
|
39
|
+
if (showPnl && tb.token.pnl) {
|
|
40
|
+
line += ` ${formatPnl(tb.token.pnl.totalPnl)}`;
|
|
41
|
+
}
|
|
42
|
+
console.log(line);
|
|
43
|
+
}
|
|
44
|
+
if (sorted.length === 0 && (nativeUsd === 0 || isNaN(nativeUsd))) {
|
|
45
|
+
console.log(DIM(" No token balances"));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function portfolioCommand(opts) {
|
|
49
|
+
// Validate chain option
|
|
50
|
+
const chains = opts.chain
|
|
51
|
+
? opts.chain.split(",").map((c) => c.trim().toLowerCase())
|
|
52
|
+
: undefined;
|
|
53
|
+
if (chains) {
|
|
54
|
+
const invalid = chains.filter((c) => !VALID_CHAINS.has(c));
|
|
55
|
+
if (invalid.length > 0) {
|
|
56
|
+
output.error(`Invalid chain${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid chains: ${[...VALID_CHAINS].join(", ")}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const includePnl = opts.pnl || opts.all;
|
|
61
|
+
const includeNfts = opts.nfts || opts.all;
|
|
62
|
+
const include = [];
|
|
63
|
+
if (includePnl)
|
|
64
|
+
include.push("pnl");
|
|
65
|
+
if (includeNfts)
|
|
66
|
+
include.push("nfts");
|
|
67
|
+
const spinText = include.length > 0
|
|
68
|
+
? `Fetching portfolio (${include.join(", ")})...`
|
|
69
|
+
: "Fetching portfolio...";
|
|
70
|
+
const spin = output.spinner(spinText);
|
|
71
|
+
let data;
|
|
72
|
+
try {
|
|
73
|
+
data = await getPortfolio({
|
|
74
|
+
chains,
|
|
75
|
+
showLowValueTokens: opts.lowValue,
|
|
76
|
+
include: include.length > 0 ? include : undefined,
|
|
77
|
+
});
|
|
78
|
+
spin.succeed("Portfolio loaded");
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
spin.fail(`Failed to fetch portfolio: ${err.message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// JSON output mode
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
console.log(JSON.stringify(data, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Calculate grand total
|
|
90
|
+
let grandTotal = 0;
|
|
91
|
+
const chainEntries = Object.entries(data.balances);
|
|
92
|
+
for (const [, chainData] of chainEntries) {
|
|
93
|
+
grandTotal += parseFloat(chainData.total) || 0;
|
|
94
|
+
}
|
|
95
|
+
console.log();
|
|
96
|
+
output.label("Wallet", "");
|
|
97
|
+
console.log(` ${"EVM".padEnd(8)} ${data.evmAddress}`);
|
|
98
|
+
if (data.solAddress) {
|
|
99
|
+
console.log(` ${"SOLANA".padEnd(8)} ${data.solAddress}`);
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
output.label("Total", GREEN(formatUsd(grandTotal)));
|
|
103
|
+
if (opts.lowValue) {
|
|
104
|
+
console.log();
|
|
105
|
+
output.info("Including low-value tokens");
|
|
106
|
+
}
|
|
107
|
+
// Print each chain
|
|
108
|
+
for (const [chain, chainData] of chainEntries) {
|
|
109
|
+
printChainBalances(chain, chainData, !!includePnl);
|
|
110
|
+
}
|
|
111
|
+
// Print NFTs
|
|
112
|
+
if (includeNfts && data.nfts && data.nfts.length > 0) {
|
|
113
|
+
console.log();
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(` ${BRAND.bold("NFTs")} ${DIM(`${data.nfts.length} item(s)`)}`);
|
|
116
|
+
console.log(` ${DIM("─".repeat(58))}`);
|
|
117
|
+
for (const nft of data.nfts) {
|
|
118
|
+
const name = nft.name || DIM("Unnamed");
|
|
119
|
+
const collection = nft.collection?.name
|
|
120
|
+
? DIM(` (${nft.collection.name})`)
|
|
121
|
+
: "";
|
|
122
|
+
const address = nft.collection?.address || DIM("unknown");
|
|
123
|
+
console.log(` ${WHITE(name)}${collection}`);
|
|
124
|
+
console.log(` ${DIM(address)} #${nft.tokenId} ${DIM(nft.chainName || nft.chain)}`);
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (includeNfts) {
|
|
129
|
+
console.log();
|
|
130
|
+
output.info("No NFTs found");
|
|
131
|
+
}
|
|
132
|
+
console.log();
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=portfolio.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { searchTokens } from "../lib/api.js";
|
|
2
|
+
import * as output from "../lib/output.js";
|
|
3
|
+
export async function tokensSearchCommand(query, opts) {
|
|
4
|
+
const spin = output.spinner(`Searching tokens for "${query}"...`);
|
|
5
|
+
try {
|
|
6
|
+
const chainId = opts.chain ? Number(opts.chain) : undefined;
|
|
7
|
+
const res = await searchTokens(query, chainId);
|
|
8
|
+
spin.succeed(`Found ${res.tokens.length} result(s)`);
|
|
9
|
+
if (res.tokens.length === 0) {
|
|
10
|
+
output.info("No tokens found. Try a different search query.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
console.log();
|
|
14
|
+
for (const token of res.tokens) {
|
|
15
|
+
const price = token.priceUsd
|
|
16
|
+
? `$${token.priceUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })}`
|
|
17
|
+
: output.fmt.dim("n/a");
|
|
18
|
+
console.log(` ${output.fmt.brandBold(`${token.symbol}`)} ${output.fmt.dim(token.name)}`);
|
|
19
|
+
console.log(` Address: ${token.address}`);
|
|
20
|
+
console.log(` Price: ${price}`);
|
|
21
|
+
console.log();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
spin.fail(`Search failed: ${err.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function tokensInfoCommand(address, opts) {
|
|
30
|
+
const spin = output.spinner(`Fetching token info for ${address}...`);
|
|
31
|
+
try {
|
|
32
|
+
const chainId = opts.chain ? Number(opts.chain) : undefined;
|
|
33
|
+
const res = await searchTokens(address, chainId);
|
|
34
|
+
spin.succeed("Token info loaded");
|
|
35
|
+
// Match by the requested address (search returns all tokens from matching pools)
|
|
36
|
+
const addressLower = address.toLowerCase();
|
|
37
|
+
const token = res.tokens.find((t) => t.address.toLowerCase() === addressLower) ??
|
|
38
|
+
res.tokens[0];
|
|
39
|
+
if (!token) {
|
|
40
|
+
output.error("Token not found. Check the address and try again.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
output.label("Name", token.name);
|
|
45
|
+
output.label("Symbol", token.symbol);
|
|
46
|
+
output.label("Address", token.address);
|
|
47
|
+
output.label("Decimals", String(token.decimals));
|
|
48
|
+
if (token.priceUsd) {
|
|
49
|
+
output.label("Price", `$${token.priceUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 })}`);
|
|
50
|
+
}
|
|
51
|
+
if (token.logoURI) {
|
|
52
|
+
output.label("Logo", token.logoURI);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
spin.fail(`Failed to fetch token info: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { transfer, searchTokens } from "../lib/api.js";
|
|
2
|
+
import * as output from "../lib/output.js";
|
|
3
|
+
import { NATIVE_SYMBOLS, CHAIN_IDS } from "../lib/chains.js";
|
|
4
|
+
import { isAddress } from "viem";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a token symbol or address to a contract address.
|
|
7
|
+
* If it's already a valid address, return as-is. Otherwise search by symbol.
|
|
8
|
+
*/
|
|
9
|
+
async function resolveToken(tokenInput, chain) {
|
|
10
|
+
if (isAddress(tokenInput)) {
|
|
11
|
+
return { address: tokenInput, symbol: tokenInput };
|
|
12
|
+
}
|
|
13
|
+
const chainId = CHAIN_IDS[chain];
|
|
14
|
+
const res = await searchTokens(tokenInput, chainId);
|
|
15
|
+
// Exact symbol match (case-insensitive) to avoid resolving to wrong token
|
|
16
|
+
const inputUpper = tokenInput.toUpperCase();
|
|
17
|
+
const exactMatch = res.tokens.find((t) => t.symbol.toUpperCase() === inputUpper);
|
|
18
|
+
if (!exactMatch) {
|
|
19
|
+
const available = res.tokens
|
|
20
|
+
.slice(0, 5)
|
|
21
|
+
.map((t) => `${t.symbol} (${t.address})`)
|
|
22
|
+
.join(", ");
|
|
23
|
+
throw new Error(`No exact match for "${tokenInput}" on ${chain}. Did you mean: ${available || "no results"}`);
|
|
24
|
+
}
|
|
25
|
+
return { address: exactMatch.address, symbol: exactMatch.symbol };
|
|
26
|
+
}
|
|
27
|
+
export async function transferCommand(opts) {
|
|
28
|
+
const isNative = opts.native ?? false;
|
|
29
|
+
const chain = opts.chain ?? "base";
|
|
30
|
+
if (!isNative && !opts.token) {
|
|
31
|
+
output.error("Token required for ERC20 transfers. Use --token <symbol or address> or --native.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
let tokenAddress;
|
|
35
|
+
let tokenLabel;
|
|
36
|
+
if (isNative) {
|
|
37
|
+
tokenAddress = "0x0000000000000000000000000000000000000000";
|
|
38
|
+
tokenLabel = NATIVE_SYMBOLS[chain] ?? "ETH";
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const spin = output.spinner(`Resolving token "${opts.token}"...`);
|
|
42
|
+
try {
|
|
43
|
+
const resolved = await resolveToken(opts.token, chain);
|
|
44
|
+
tokenAddress = resolved.address;
|
|
45
|
+
tokenLabel = resolved.symbol;
|
|
46
|
+
if (tokenAddress !== opts.token) {
|
|
47
|
+
spin.succeed(`Resolved ${opts.token} → ${tokenLabel} (${tokenAddress})`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
spin.stop();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
spin.fail(err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const chainLabel = chain !== "base" ? ` on ${chain}` : "";
|
|
59
|
+
const spin = output.spinner(`Transferring ${opts.amount} ${tokenLabel} to ${opts.to}${chainLabel}...`);
|
|
60
|
+
try {
|
|
61
|
+
const result = await transfer({
|
|
62
|
+
tokenAddress,
|
|
63
|
+
recipientAddress: opts.to,
|
|
64
|
+
amount: opts.amount,
|
|
65
|
+
isNativeToken: isNative,
|
|
66
|
+
chain,
|
|
67
|
+
});
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
spin.fail(`Transfer failed: ${result.error}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
spin.succeed(`Transfer successful`);
|
|
73
|
+
output.label("Tx Hash", result.txHash ?? "unknown");
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
spin.fail(`Transfer failed: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=transfer.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import * as output from "../lib/output.js";
|
|
4
|
+
const PACKAGE_NAME = "@bankr/cli";
|
|
5
|
+
/**
|
|
6
|
+
* Compare two semver version strings.
|
|
7
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
8
|
+
*/
|
|
9
|
+
function compareSemver(a, b) {
|
|
10
|
+
const partsA = a.split(".").map(Number);
|
|
11
|
+
const partsB = b.split(".").map(Number);
|
|
12
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
13
|
+
const numA = partsA[i] ?? 0;
|
|
14
|
+
const numB = partsB[i] ?? 0;
|
|
15
|
+
if (numA < numB)
|
|
16
|
+
return -1;
|
|
17
|
+
if (numA > numB)
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
async function fetchLatestVersion() {
|
|
23
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
24
|
+
signal: AbortSignal.timeout(10000),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new Error(`Failed to check for updates (HTTP ${res.status})`);
|
|
28
|
+
}
|
|
29
|
+
const data = (await res.json());
|
|
30
|
+
return data.version;
|
|
31
|
+
}
|
|
32
|
+
function getCurrentVersion() {
|
|
33
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
|
|
34
|
+
return pkg.version;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detect which package manager installed the CLI globally.
|
|
38
|
+
* Falls back to npm if we can't determine.
|
|
39
|
+
*/
|
|
40
|
+
function detectPackageManager() {
|
|
41
|
+
try {
|
|
42
|
+
const execPath = process.argv[1] ?? "";
|
|
43
|
+
// Check if running under bun
|
|
44
|
+
if (execPath.includes("/.bun/") || "bun" in process.versions) {
|
|
45
|
+
return "bun";
|
|
46
|
+
}
|
|
47
|
+
// Check if running under pnpm
|
|
48
|
+
if (execPath.includes("/pnpm/") || execPath.includes("/.pnpm/")) {
|
|
49
|
+
return "pnpm";
|
|
50
|
+
}
|
|
51
|
+
// Check if running under yarn
|
|
52
|
+
if (execPath.includes("/yarn/") || execPath.includes("/.yarn/")) {
|
|
53
|
+
return "yarn";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore detection errors
|
|
58
|
+
}
|
|
59
|
+
return "npm";
|
|
60
|
+
}
|
|
61
|
+
function getInstallCommand(pm) {
|
|
62
|
+
switch (pm) {
|
|
63
|
+
case "bun":
|
|
64
|
+
return `bun install -g ${PACKAGE_NAME}@latest`;
|
|
65
|
+
case "pnpm":
|
|
66
|
+
return `pnpm add -g ${PACKAGE_NAME}@latest`;
|
|
67
|
+
case "yarn":
|
|
68
|
+
return `yarn global add ${PACKAGE_NAME}@latest`;
|
|
69
|
+
default:
|
|
70
|
+
return `npm install -g ${PACKAGE_NAME}@latest`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function updateCommand(opts) {
|
|
74
|
+
const current = getCurrentVersion();
|
|
75
|
+
const spin = output.spinner("Checking for updates...");
|
|
76
|
+
let latest;
|
|
77
|
+
try {
|
|
78
|
+
latest = await fetchLatestVersion();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
spin.fail();
|
|
82
|
+
output.error(err.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const cmp = compareSemver(current, latest);
|
|
86
|
+
if (cmp === 0) {
|
|
87
|
+
spin.succeed(`Already on the latest version (${current})`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (cmp > 0) {
|
|
91
|
+
spin.succeed(`You're ahead of latest (${current} > ${latest}). No update needed.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
spin.succeed(`Update available: ${current} → ${latest}`);
|
|
95
|
+
if (opts.check) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const pm = detectPackageManager();
|
|
99
|
+
const cmd = getInstallCommand(pm);
|
|
100
|
+
output.blank();
|
|
101
|
+
output.info(`Updating via ${pm}...`);
|
|
102
|
+
output.dim(` $ ${cmd}`);
|
|
103
|
+
output.blank();
|
|
104
|
+
try {
|
|
105
|
+
execSync(cmd, { stdio: "inherit" });
|
|
106
|
+
output.blank();
|
|
107
|
+
output.success(`Updated to ${PACKAGE_NAME}@${latest}`);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
output.blank();
|
|
111
|
+
output.error("Update failed. Try running manually:");
|
|
112
|
+
output.dim(` $ ${cmd}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=update.js.map
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -61,7 +61,29 @@ export interface TokenBalanceInfo {
|
|
|
61
61
|
imgUrl: string;
|
|
62
62
|
decimals: number;
|
|
63
63
|
};
|
|
64
|
+
pnl?: {
|
|
65
|
+
realizedPnl: number;
|
|
66
|
+
unrealizedPnl: number;
|
|
67
|
+
totalPnl: number;
|
|
68
|
+
averageEntryPrice: number;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface NftInfo {
|
|
73
|
+
name?: string;
|
|
74
|
+
tokenId: string;
|
|
75
|
+
collection?: {
|
|
76
|
+
name: string;
|
|
77
|
+
address: string;
|
|
78
|
+
nftStandard?: string;
|
|
64
79
|
};
|
|
80
|
+
chain: string;
|
|
81
|
+
chainName?: string;
|
|
82
|
+
image?: {
|
|
83
|
+
thumbnail?: string;
|
|
84
|
+
original?: string;
|
|
85
|
+
};
|
|
86
|
+
openSeaUrl?: string;
|
|
65
87
|
}
|
|
66
88
|
export interface ChainBalanceInfo {
|
|
67
89
|
nativeBalance: string;
|
|
@@ -69,13 +91,23 @@ export interface ChainBalanceInfo {
|
|
|
69
91
|
tokenBalances: TokenBalanceInfo[];
|
|
70
92
|
total: string;
|
|
71
93
|
}
|
|
72
|
-
export interface
|
|
94
|
+
export interface PortfolioResponse {
|
|
73
95
|
success: boolean;
|
|
74
96
|
evmAddress: string;
|
|
75
97
|
solAddress?: string;
|
|
76
98
|
balances: Record<string, ChainBalanceInfo>;
|
|
99
|
+
nfts?: NftInfo[];
|
|
100
|
+
}
|
|
101
|
+
export interface PortfolioOptions {
|
|
102
|
+
chains?: string[];
|
|
103
|
+
showLowValueTokens?: boolean;
|
|
104
|
+
include?: string[];
|
|
77
105
|
}
|
|
78
|
-
export declare function
|
|
106
|
+
export declare function getPortfolio(opts?: PortfolioOptions): Promise<PortfolioResponse>;
|
|
107
|
+
/** @deprecated Use PortfolioResponse instead */
|
|
108
|
+
export type BalancesResponse = PortfolioResponse;
|
|
109
|
+
/** @deprecated Use getPortfolio instead */
|
|
110
|
+
export declare const getBalances: (chains?: string[], showLowValueTokens?: boolean) => Promise<PortfolioResponse>;
|
|
79
111
|
export declare function pollJob(jobId: string, opts?: {
|
|
80
112
|
interval?: number;
|
|
81
113
|
maxAttempts?: number;
|
|
@@ -324,5 +356,30 @@ export declare function addProjectUpdate(data: {
|
|
|
324
356
|
title: string;
|
|
325
357
|
content: string;
|
|
326
358
|
}): Promise<AgentProfileResponse>;
|
|
359
|
+
export interface TokenSearchResult {
|
|
360
|
+
address: string;
|
|
361
|
+
symbol: string;
|
|
362
|
+
name: string;
|
|
363
|
+
decimals: number;
|
|
364
|
+
logoURI?: string;
|
|
365
|
+
priceUsd?: number;
|
|
366
|
+
}
|
|
367
|
+
export interface TokenSearchResponse {
|
|
368
|
+
tokens: TokenSearchResult[];
|
|
369
|
+
}
|
|
370
|
+
export declare function searchTokens(query: string, chainId?: number): Promise<TokenSearchResponse>;
|
|
371
|
+
export interface TransferRequest {
|
|
372
|
+
tokenAddress: string;
|
|
373
|
+
recipientAddress: string;
|
|
374
|
+
amount: string;
|
|
375
|
+
isNativeToken: boolean;
|
|
376
|
+
chain?: string;
|
|
377
|
+
}
|
|
378
|
+
export interface TransferResponse {
|
|
379
|
+
success: boolean;
|
|
380
|
+
txHash?: string;
|
|
381
|
+
error?: string;
|
|
382
|
+
}
|
|
383
|
+
export declare function transfer(request: TransferRequest): Promise<TransferResponse>;
|
|
327
384
|
export declare function buildPublicClaimTxs(beneficiaryAddress: string, tokenAddresses: string[]): Promise<BuildClaimResponse>;
|
|
328
385
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/lib/api.js
CHANGED
|
@@ -62,18 +62,25 @@ export async function validateApiKey() {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
export async function getUserInfo() {
|
|
65
|
-
const res = await fetch(`${getApiUrl()}/
|
|
65
|
+
const res = await fetch(`${getApiUrl()}/wallet/me`, {
|
|
66
66
|
headers: authHeaders(),
|
|
67
67
|
});
|
|
68
68
|
return handleResponse(res);
|
|
69
69
|
}
|
|
70
|
-
export async function
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
export async function getPortfolio(opts = {}) {
|
|
71
|
+
const query = new URLSearchParams();
|
|
72
|
+
if (opts.chains?.length)
|
|
73
|
+
query.set("chains", opts.chains.join(","));
|
|
74
|
+
if (opts.showLowValueTokens)
|
|
75
|
+
query.set("showLowValueTokens", "true");
|
|
76
|
+
if (opts.include?.length)
|
|
77
|
+
query.set("include", opts.include.join(","));
|
|
78
|
+
const qs = query.toString();
|
|
79
|
+
const res = await fetch(`${getApiUrl()}/wallet/portfolio${qs ? `?${qs}` : ""}`, { headers: authHeaders() });
|
|
75
80
|
return handleResponse(res);
|
|
76
81
|
}
|
|
82
|
+
/** @deprecated Use getPortfolio instead */
|
|
83
|
+
export const getBalances = (chains, showLowValueTokens) => getPortfolio({ chains, showLowValueTokens });
|
|
77
84
|
export async function pollJob(jobId, opts = {}) {
|
|
78
85
|
const { interval = 2000, maxAttempts = 150, onStatus } = opts;
|
|
79
86
|
let attempts = 0;
|
|
@@ -89,7 +96,7 @@ export async function pollJob(jobId, opts = {}) {
|
|
|
89
96
|
throw new Error(`Polling timed out after ${maxAttempts} attempts`);
|
|
90
97
|
}
|
|
91
98
|
export async function sign(request) {
|
|
92
|
-
const res = await fetch(`${getApiUrl()}/
|
|
99
|
+
const res = await fetch(`${getApiUrl()}/wallet/sign`, {
|
|
93
100
|
method: "POST",
|
|
94
101
|
headers: authHeaders(),
|
|
95
102
|
body: JSON.stringify(request),
|
|
@@ -97,7 +104,7 @@ export async function sign(request) {
|
|
|
97
104
|
return handleResponse(res);
|
|
98
105
|
}
|
|
99
106
|
export async function submit(request) {
|
|
100
|
-
const res = await fetch(`${getApiUrl()}/
|
|
107
|
+
const res = await fetch(`${getApiUrl()}/wallet/submit`, {
|
|
101
108
|
method: "POST",
|
|
102
109
|
headers: authHeaders(),
|
|
103
110
|
body: JSON.stringify(request),
|
|
@@ -254,6 +261,25 @@ export async function addProjectUpdate(data) {
|
|
|
254
261
|
});
|
|
255
262
|
return handleResponse(res);
|
|
256
263
|
}
|
|
264
|
+
export async function searchTokens(query, chainId) {
|
|
265
|
+
const params = new URLSearchParams({ query });
|
|
266
|
+
if (chainId)
|
|
267
|
+
params.set("chainId", String(chainId));
|
|
268
|
+
const res = await fetch(`${getApiUrl()}/tokens/search?${params}`, {
|
|
269
|
+
headers: {
|
|
270
|
+
"User-Agent": CLI_USER_AGENT,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
return handleResponse(res);
|
|
274
|
+
}
|
|
275
|
+
export async function transfer(request) {
|
|
276
|
+
const res = await fetch(`${getApiUrl()}/wallet/transfer`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: authHeaders(),
|
|
279
|
+
body: JSON.stringify(request),
|
|
280
|
+
});
|
|
281
|
+
return handleResponse(res);
|
|
282
|
+
}
|
|
257
283
|
export async function buildPublicClaimTxs(beneficiaryAddress, tokenAddresses) {
|
|
258
284
|
const res = await fetch(`${getApiUrl()}/public/doppler/build-claim`, {
|
|
259
285
|
method: "POST",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const CHAIN_LABELS = {
|
|
2
|
+
base: "Base",
|
|
3
|
+
polygon: "Polygon",
|
|
4
|
+
mainnet: "Ethereum",
|
|
5
|
+
unichain: "Unichain",
|
|
6
|
+
worldchain: "World Chain",
|
|
7
|
+
arbitrum: "Arbitrum",
|
|
8
|
+
solana: "Solana",
|
|
9
|
+
};
|
|
10
|
+
export const VALID_CHAINS = new Set([
|
|
11
|
+
"base",
|
|
12
|
+
"polygon",
|
|
13
|
+
"mainnet",
|
|
14
|
+
"unichain",
|
|
15
|
+
"worldchain",
|
|
16
|
+
"arbitrum",
|
|
17
|
+
"solana",
|
|
18
|
+
]);
|
|
19
|
+
export const CHAIN_IDS = {
|
|
20
|
+
base: 8453,
|
|
21
|
+
mainnet: 1,
|
|
22
|
+
polygon: 137,
|
|
23
|
+
unichain: 130,
|
|
24
|
+
worldchain: 480,
|
|
25
|
+
arbitrum: 42161,
|
|
26
|
+
};
|
|
27
|
+
export const NATIVE_SYMBOLS = {
|
|
28
|
+
base: "ETH",
|
|
29
|
+
mainnet: "ETH",
|
|
30
|
+
unichain: "ETH",
|
|
31
|
+
worldchain: "ETH",
|
|
32
|
+
arbitrum: "ETH",
|
|
33
|
+
polygon: "POL",
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chains.js.map
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -29,5 +29,7 @@ export declare function keyValue(key: string, value: string): void;
|
|
|
29
29
|
export declare function spinner(text: string): Ora;
|
|
30
30
|
export declare function maskApiKey(key: string): string;
|
|
31
31
|
export declare function formatDuration(ms: number): string;
|
|
32
|
+
export declare function formatUsd(value: string | number): string;
|
|
33
|
+
export declare function formatBalance(value: string | number, decimals?: number): string;
|
|
32
34
|
export declare function formatStatus(status: string): string;
|
|
33
35
|
//# sourceMappingURL=output.d.ts.map
|
package/dist/lib/output.js
CHANGED
|
@@ -66,6 +66,23 @@ export function formatDuration(ms) {
|
|
|
66
66
|
return `${ms}ms`;
|
|
67
67
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
68
68
|
}
|
|
69
|
+
export function formatUsd(value) {
|
|
70
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
71
|
+
if (isNaN(num) || num === 0)
|
|
72
|
+
return chalk.dim("$0.00");
|
|
73
|
+
return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
74
|
+
}
|
|
75
|
+
export function formatBalance(value, decimals = 6) {
|
|
76
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
77
|
+
if (isNaN(num) || num === 0)
|
|
78
|
+
return "0";
|
|
79
|
+
if (num < 0.000001)
|
|
80
|
+
return "<0.000001";
|
|
81
|
+
return num.toLocaleString("en-US", {
|
|
82
|
+
minimumFractionDigits: 0,
|
|
83
|
+
maximumFractionDigits: decimals,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
69
86
|
export function formatStatus(status) {
|
|
70
87
|
switch (status) {
|
|
71
88
|
case "completed":
|