@bankr/cli 0.1.3 → 0.2.2
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 +439 -133
- package/dist/commands/balances.d.ts +1 -0
- package/dist/commands/balances.js +7 -32
- package/dist/commands/llm.js +74 -18
- 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/commands/x402.d.ts +69 -0
- package/dist/commands/x402.js +636 -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 +39 -0
- package/dist/lib/output.d.ts +4 -0
- package/dist/lib/output.js +19 -0
- package/package.json +1 -1
|
@@ -1,42 +1,13 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { getBalances, } from "../lib/api.js";
|
|
3
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";
|
|
4
6
|
const BRAND = chalk.hex("#FF613D");
|
|
5
7
|
const DIM = chalk.dim;
|
|
6
8
|
const BOLD = chalk.bold;
|
|
7
9
|
const GREEN = chalk.greenBright;
|
|
8
10
|
const WHITE = chalk.whiteBright;
|
|
9
|
-
const CHAIN_LABELS = {
|
|
10
|
-
base: "Base",
|
|
11
|
-
polygon: "Polygon",
|
|
12
|
-
mainnet: "Ethereum",
|
|
13
|
-
unichain: "Unichain",
|
|
14
|
-
solana: "Solana",
|
|
15
|
-
};
|
|
16
|
-
const VALID_CHAINS = new Set([
|
|
17
|
-
"base",
|
|
18
|
-
"polygon",
|
|
19
|
-
"mainnet",
|
|
20
|
-
"unichain",
|
|
21
|
-
"solana",
|
|
22
|
-
]);
|
|
23
|
-
function formatUsd(value) {
|
|
24
|
-
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
25
|
-
if (isNaN(num) || num === 0)
|
|
26
|
-
return DIM("$0.00");
|
|
27
|
-
return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
28
|
-
}
|
|
29
|
-
function formatBalance(value, decimals = 6) {
|
|
30
|
-
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
31
|
-
if (isNaN(num) || num === 0)
|
|
32
|
-
return "0";
|
|
33
|
-
if (num < 0.000001)
|
|
34
|
-
return "<0.000001";
|
|
35
|
-
return num.toLocaleString("en-US", {
|
|
36
|
-
minimumFractionDigits: 0,
|
|
37
|
-
maximumFractionDigits: decimals,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
11
|
function printChainBalances(chain, data) {
|
|
41
12
|
const label = CHAIN_LABELS[chain] || chain;
|
|
42
13
|
const total = parseFloat(data.total);
|
|
@@ -78,7 +49,7 @@ export async function balancesCommand(opts) {
|
|
|
78
49
|
const spin = output.spinner("Fetching balances...");
|
|
79
50
|
let data;
|
|
80
51
|
try {
|
|
81
|
-
data = await getBalances(chains);
|
|
52
|
+
data = await getBalances(chains, opts.showLowValueTokens);
|
|
82
53
|
spin.succeed("Balances loaded");
|
|
83
54
|
}
|
|
84
55
|
catch (err) {
|
|
@@ -104,6 +75,10 @@ export async function balancesCommand(opts) {
|
|
|
104
75
|
}
|
|
105
76
|
console.log();
|
|
106
77
|
output.label("Total", GREEN(formatUsd(grandTotal)));
|
|
78
|
+
if (opts.showLowValueTokens) {
|
|
79
|
+
console.log();
|
|
80
|
+
output.info("Including low-value tokens");
|
|
81
|
+
}
|
|
107
82
|
// Print each chain
|
|
108
83
|
for (const [chain, chainData] of chainEntries) {
|
|
109
84
|
printChainBalances(chain, chainData);
|
package/dist/commands/llm.js
CHANGED
|
@@ -79,15 +79,6 @@ const GATEWAY_MODELS = [
|
|
|
79
79
|
input: IMAGE_INPUT,
|
|
80
80
|
cost: { input: 0.25, output: 1.5, cacheRead: 0.025, cacheWrite: 0.08333 },
|
|
81
81
|
},
|
|
82
|
-
{
|
|
83
|
-
id: "gemini-3-pro",
|
|
84
|
-
name: "Gemini 3 Pro",
|
|
85
|
-
owned_by: "google",
|
|
86
|
-
contextWindow: 1048576,
|
|
87
|
-
maxTokens: 65536,
|
|
88
|
-
input: IMAGE_INPUT,
|
|
89
|
-
cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0.375 },
|
|
90
|
-
},
|
|
91
82
|
{
|
|
92
83
|
id: "gemini-3-flash",
|
|
93
84
|
name: "Gemini 3 Flash",
|
|
@@ -125,6 +116,24 @@ const GATEWAY_MODELS = [
|
|
|
125
116
|
input: IMAGE_INPUT,
|
|
126
117
|
cost: { input: 2.5, output: 15.0, cacheRead: 0.25, cacheWrite: 0 },
|
|
127
118
|
},
|
|
119
|
+
{
|
|
120
|
+
id: "gpt-5.4-mini",
|
|
121
|
+
name: "GPT 5.4 Mini",
|
|
122
|
+
owned_by: "openai",
|
|
123
|
+
contextWindow: 400000,
|
|
124
|
+
maxTokens: 128000,
|
|
125
|
+
input: IMAGE_INPUT,
|
|
126
|
+
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "gpt-5.4-nano",
|
|
130
|
+
name: "GPT 5.4 Nano",
|
|
131
|
+
owned_by: "openai",
|
|
132
|
+
contextWindow: 400000,
|
|
133
|
+
maxTokens: 128000,
|
|
134
|
+
input: IMAGE_INPUT,
|
|
135
|
+
cost: { input: 0.2, output: 1.25, cacheRead: 0.02, cacheWrite: 0 },
|
|
136
|
+
},
|
|
128
137
|
{
|
|
129
138
|
id: "gpt-5.2",
|
|
130
139
|
name: "GPT 5.2",
|
|
@@ -220,10 +229,46 @@ const GATEWAY_MODELS = [
|
|
|
220
229
|
id: "minimax-m2.5",
|
|
221
230
|
name: "MiniMax M2.5",
|
|
222
231
|
owned_by: "minimax",
|
|
223
|
-
contextWindow:
|
|
232
|
+
contextWindow: 204800,
|
|
224
233
|
maxTokens: 196608,
|
|
225
234
|
input: TEXT_INPUT,
|
|
226
|
-
cost: { input: 0.
|
|
235
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "minimax-m2.7",
|
|
239
|
+
name: "MiniMax M2.7",
|
|
240
|
+
owned_by: "minimax",
|
|
241
|
+
contextWindow: 204800,
|
|
242
|
+
maxTokens: 196608,
|
|
243
|
+
input: TEXT_INPUT,
|
|
244
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "minimax-m2.7-highspeed",
|
|
248
|
+
name: "MiniMax M2.7 Highspeed",
|
|
249
|
+
owned_by: "minimax",
|
|
250
|
+
contextWindow: 204800,
|
|
251
|
+
maxTokens: 196608,
|
|
252
|
+
input: TEXT_INPUT,
|
|
253
|
+
cost: { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0 },
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "glm-5",
|
|
257
|
+
name: "GLM-5",
|
|
258
|
+
owned_by: "z-ai",
|
|
259
|
+
contextWindow: 202752,
|
|
260
|
+
maxTokens: 131072,
|
|
261
|
+
input: TEXT_INPUT,
|
|
262
|
+
cost: { input: 0.72, output: 2.3, cacheRead: 0, cacheWrite: 0 },
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "glm-5-turbo",
|
|
266
|
+
name: "GLM-5 Turbo",
|
|
267
|
+
owned_by: "z-ai",
|
|
268
|
+
contextWindow: 202752,
|
|
269
|
+
maxTokens: 131072,
|
|
270
|
+
input: TEXT_INPUT,
|
|
271
|
+
cost: { input: 1.2, output: 4.0, cacheRead: 0.24, cacheWrite: 0 },
|
|
227
272
|
},
|
|
228
273
|
];
|
|
229
274
|
/** Fetch live model list from the gateway; falls back to hardcoded catalog. */
|
|
@@ -261,6 +306,7 @@ async function resolveModels() {
|
|
|
261
306
|
cacheRead: 0,
|
|
262
307
|
cacheWrite: 0,
|
|
263
308
|
}),
|
|
309
|
+
...(m.deprecation && { deprecation: m.deprecation }),
|
|
264
310
|
};
|
|
265
311
|
});
|
|
266
312
|
return { models, live: true };
|
|
@@ -306,10 +352,17 @@ export async function modelsCommand() {
|
|
|
306
352
|
output.brandBold("Bankr LLM Gateway — Available Models");
|
|
307
353
|
console.log();
|
|
308
354
|
const COL = { id: 24, name: 24, provider: 12 };
|
|
309
|
-
|
|
310
|
-
console.log(output.fmt.
|
|
355
|
+
const hasDeprecated = models.some((m) => m.deprecation);
|
|
356
|
+
console.log(` ${output.fmt.brandBold("Model ID".padEnd(COL.id))} ${output.fmt.brandBold("Name".padEnd(COL.name))} ${output.fmt.brandBold("Provider".padEnd(COL.provider))}${hasDeprecated ? ` ${output.fmt.brandBold("Notes")}` : ""}`);
|
|
357
|
+
console.log(output.fmt.dim(` ${"─".repeat(COL.id + COL.name + COL.provider + (hasDeprecated ? 24 : 2))}`));
|
|
311
358
|
for (const m of models) {
|
|
312
|
-
|
|
359
|
+
let notes = "";
|
|
360
|
+
if (m.deprecation) {
|
|
361
|
+
notes = m.deprecation.replaced_by
|
|
362
|
+
? `deprecated → ${m.deprecation.replaced_by}`
|
|
363
|
+
: "deprecated";
|
|
364
|
+
}
|
|
365
|
+
console.log(` ${output.fmt.brand(m.id.padEnd(COL.id))} ${m.name.padEnd(COL.name)} ${output.fmt.dim(m.owned_by.padEnd(COL.provider))} ${notes ? output.fmt.dim(notes) : ""}`);
|
|
313
366
|
}
|
|
314
367
|
console.log();
|
|
315
368
|
output.dim(` Gateway: ${getLlmUrl()}`);
|
|
@@ -671,11 +724,12 @@ export async function setupOpenclawCommand(opts) {
|
|
|
671
724
|
const llmKey = getLlmKey();
|
|
672
725
|
const llmUrl = getLlmUrl();
|
|
673
726
|
const { models } = await resolveModels();
|
|
727
|
+
const activeModels = models.filter((m) => !m.deprecation);
|
|
674
728
|
const providerConfig = {
|
|
675
729
|
baseUrl: llmUrl,
|
|
676
730
|
apiKey: llmKey ?? "${BANKR_API_KEY}",
|
|
677
731
|
api: "openai-completions",
|
|
678
|
-
models:
|
|
732
|
+
models: activeModels.map((m) => ({
|
|
679
733
|
id: m.id,
|
|
680
734
|
name: m.name,
|
|
681
735
|
...(m.owned_by === "anthropic" ? { api: "anthropic-messages" } : {}),
|
|
@@ -709,8 +763,9 @@ export async function setupOpenCodeCommand(opts) {
|
|
|
709
763
|
const llmKey = getLlmKey();
|
|
710
764
|
const llmUrl = getLlmUrl();
|
|
711
765
|
const { models } = await resolveModels();
|
|
766
|
+
const activeModels = models.filter((m) => !m.deprecation);
|
|
712
767
|
const modelsObj = {};
|
|
713
|
-
for (const m of
|
|
768
|
+
for (const m of activeModels) {
|
|
714
769
|
modelsObj[m.id] = { name: m.name };
|
|
715
770
|
}
|
|
716
771
|
const bankrProvider = {
|
|
@@ -752,9 +807,10 @@ export async function setupCursorCommand() {
|
|
|
752
807
|
const llmUrl = getLlmUrl();
|
|
753
808
|
const token = llmKey ?? "<your-bankr-api-key>";
|
|
754
809
|
const { models } = await resolveModels();
|
|
810
|
+
const activeModels = models.filter((m) => !m.deprecation);
|
|
755
811
|
// Pick one model per provider as recommended examples
|
|
756
|
-
const recommendedIds = ["claude-opus-4.6", "gemini-3-pro", "gpt-5.2"];
|
|
757
|
-
const recommended = recommendedIds.filter((id) =>
|
|
812
|
+
const recommendedIds = ["claude-opus-4.6", "gemini-3.1-pro", "gpt-5.2"];
|
|
813
|
+
const recommended = recommendedIds.filter((id) => activeModels.some((m) => m.id === id));
|
|
758
814
|
output.brandBold("Cursor — Bankr LLM Gateway");
|
|
759
815
|
console.log();
|
|
760
816
|
output.info("In Cursor, go to Settings > Models and configure:");
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -6,11 +6,16 @@ export declare function loginCommand(opts: {
|
|
|
6
6
|
acceptTerms?: boolean;
|
|
7
7
|
keyName?: string;
|
|
8
8
|
readWrite?: boolean;
|
|
9
|
+
walletApi?: boolean;
|
|
10
|
+
agentApi?: boolean;
|
|
11
|
+
tokenLaunch?: boolean;
|
|
9
12
|
llm?: boolean;
|
|
10
13
|
llmKey?: string;
|
|
11
14
|
url?: boolean;
|
|
12
15
|
siwe?: boolean;
|
|
13
16
|
privateKey?: string;
|
|
14
17
|
partnerKey?: string;
|
|
18
|
+
allowedIps?: string;
|
|
19
|
+
allowedRecipients?: string;
|
|
15
20
|
}): Promise<void>;
|
|
16
21
|
//# sourceMappingURL=login.d.ts.map
|
package/dist/commands/login.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { confirm, input, select } from "@inquirer/prompts";
|
|
2
|
+
import { isIP } from "node:net";
|
|
2
3
|
import open from "open";
|
|
4
|
+
import { isAddress } from "viem";
|
|
3
5
|
import { CLI_USER_AGENT, DEFAULT_API_URL, getApiUrl, getConfigPath, readConfig, writeConfig, } from "../lib/config.js";
|
|
4
6
|
import * as output from "../lib/output.js";
|
|
5
7
|
const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api";
|
|
@@ -218,17 +220,21 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
218
220
|
if (wallet.solAddress) {
|
|
219
221
|
output.dim(` SOL: ${wallet.solAddress}`);
|
|
220
222
|
}
|
|
223
|
+
// Headless mode: when --code is provided, skip all interactive prompts
|
|
224
|
+
const headless = !!opts.code;
|
|
221
225
|
// Step 3: Accept terms if needed
|
|
222
226
|
if (!wallet.hasAcceptedTerms) {
|
|
223
227
|
output.blank();
|
|
224
228
|
output.dim(" Please review our Terms of Service: https://bankr.bot/terms");
|
|
225
229
|
output.blank();
|
|
226
230
|
const accepted = opts.acceptTerms ??
|
|
227
|
-
(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
(headless
|
|
232
|
+
? false
|
|
233
|
+
: await confirm({
|
|
234
|
+
message: "Do you accept the Terms of Service?",
|
|
235
|
+
default: false,
|
|
236
|
+
theme: output.bankrTheme,
|
|
237
|
+
}));
|
|
232
238
|
if (!accepted) {
|
|
233
239
|
fatal("Terms must be accepted to continue.");
|
|
234
240
|
}
|
|
@@ -246,37 +252,49 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
246
252
|
output.blank();
|
|
247
253
|
const defaultKeyName = `CLI-${new Date().toISOString().slice(0, 10)}`;
|
|
248
254
|
const keyName = opts.keyName ??
|
|
249
|
-
(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
(headless
|
|
256
|
+
? defaultKeyName
|
|
257
|
+
: await input({
|
|
258
|
+
message: "Key name:",
|
|
259
|
+
default: defaultKeyName,
|
|
260
|
+
theme: output.bankrTheme,
|
|
261
|
+
validate: (v) => v.trim().length >= 3 ? true : "Name must be at least 3 characters.",
|
|
262
|
+
}));
|
|
263
|
+
// Fine-grained API flags. walletApi and tokenLaunch default to enabled.
|
|
264
|
+
// --read-write only controls readOnly, not which APIs are enabled.
|
|
265
|
+
const enableWallet = opts.walletApi ?? true;
|
|
266
|
+
const enableAgent = opts.agentApi ??
|
|
267
|
+
(headless
|
|
268
|
+
? false
|
|
269
|
+
: await confirm({
|
|
270
|
+
message: "Enable Agent API? (AI prompts)",
|
|
271
|
+
default: false,
|
|
272
|
+
theme: output.bankrTheme,
|
|
273
|
+
}));
|
|
274
|
+
const enableTokenLaunch = opts.tokenLaunch ?? true;
|
|
267
275
|
const enableLlm = opts.llm ??
|
|
268
|
-
(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
276
|
+
(headless
|
|
277
|
+
? false
|
|
278
|
+
: await confirm({
|
|
279
|
+
message: "Enable LLM gateway?",
|
|
280
|
+
default: false,
|
|
281
|
+
theme: output.bankrTheme,
|
|
282
|
+
}));
|
|
283
|
+
if (opts.readWrite && !enableWallet && !enableAgent) {
|
|
284
|
+
output.warn("--read-write has no effect when both Wallet API and Agent API are disabled");
|
|
285
|
+
}
|
|
273
286
|
const keySpin = output.spinner("Generating API key...");
|
|
274
287
|
let apiKeyResult;
|
|
275
288
|
try {
|
|
276
289
|
apiKeyResult = await callGenerateApiKey(apiUrl, identityToken, {
|
|
277
290
|
name: keyName.trim(),
|
|
278
|
-
|
|
291
|
+
walletApiEnabled: enableWallet,
|
|
292
|
+
agentApiEnabled: enableAgent,
|
|
293
|
+
readOnly: opts.readWrite ? false : undefined,
|
|
294
|
+
tokenLaunchApiEnabled: enableTokenLaunch,
|
|
279
295
|
llmGatewayEnabled: enableLlm,
|
|
296
|
+
allowedIps: opts.allowedIps,
|
|
297
|
+
allowedRecipients: opts.allowedRecipients,
|
|
280
298
|
});
|
|
281
299
|
keySpin.stop();
|
|
282
300
|
}
|
|
@@ -286,10 +304,25 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
286
304
|
}
|
|
287
305
|
// Show result
|
|
288
306
|
output.blank();
|
|
289
|
-
const
|
|
307
|
+
const features = [
|
|
308
|
+
enableWallet && "Wallet",
|
|
309
|
+
enableAgent && "Agent",
|
|
310
|
+
enableTokenLaunch && "Token Launch",
|
|
311
|
+
enableLlm && "LLM",
|
|
312
|
+
].filter(Boolean);
|
|
290
313
|
output.success(`API key "${apiKeyResult.name}" saved`);
|
|
291
|
-
output.dim(`
|
|
314
|
+
output.dim(` Features: ${features.length ? features.join(", ") : "none"}`);
|
|
292
315
|
output.dim(` LLM: ${apiKeyResult.llmGatewayEnabled ? "enabled" : "disabled"}`);
|
|
316
|
+
if (opts.allowedIps) {
|
|
317
|
+
output.dim(` IPs: ${opts.allowedIps.join(", ")}`);
|
|
318
|
+
}
|
|
319
|
+
if (opts.allowedRecipients) {
|
|
320
|
+
const parts = [
|
|
321
|
+
...(opts.allowedRecipients.evm ?? []),
|
|
322
|
+
...(opts.allowedRecipients.solana ?? []),
|
|
323
|
+
];
|
|
324
|
+
output.dim(` Recipients: ${parts.join(", ")}`);
|
|
325
|
+
}
|
|
293
326
|
output.dim(" Manage keys at bankr.bot/api");
|
|
294
327
|
return apiKeyResult.apiKey;
|
|
295
328
|
}
|
|
@@ -340,6 +373,11 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
340
373
|
partnerApiKey: opts.partnerKey,
|
|
341
374
|
keyName: opts.keyName ?? `SIWE-${new Date().toISOString().slice(0, 10)}`,
|
|
342
375
|
readOnly: opts.readOnly ?? false,
|
|
376
|
+
walletApiEnabled: opts.walletApi ?? true,
|
|
377
|
+
agentApiEnabled: opts.agentApi ?? false,
|
|
378
|
+
tokenLaunchApiEnabled: opts.tokenLaunch ?? true,
|
|
379
|
+
allowedIps: opts.allowedIps,
|
|
380
|
+
allowedRecipients: opts.allowedRecipients,
|
|
343
381
|
}),
|
|
344
382
|
});
|
|
345
383
|
if (!verifyRes.ok) {
|
|
@@ -397,6 +435,49 @@ async function validateApiKey(apiUrl, apiKey) {
|
|
|
397
435
|
}
|
|
398
436
|
}
|
|
399
437
|
// ── Main login command ──────────────────────────────────────────────
|
|
438
|
+
function parseAndValidateIps(raw) {
|
|
439
|
+
const ips = raw
|
|
440
|
+
.split(",")
|
|
441
|
+
.map((s) => s.trim())
|
|
442
|
+
.filter(Boolean);
|
|
443
|
+
const invalid = ips.filter((ip) => isIP(ip) === 0);
|
|
444
|
+
if (invalid.length > 0) {
|
|
445
|
+
fatal(`Invalid IP address(es): ${invalid.join(", ")}`);
|
|
446
|
+
}
|
|
447
|
+
return ips;
|
|
448
|
+
}
|
|
449
|
+
function parseAndValidateRecipients(raw) {
|
|
450
|
+
const addrs = raw
|
|
451
|
+
.split(",")
|
|
452
|
+
.map((s) => s.trim())
|
|
453
|
+
.filter(Boolean);
|
|
454
|
+
const evm = [];
|
|
455
|
+
const solana = [];
|
|
456
|
+
const invalid = [];
|
|
457
|
+
for (const addr of addrs) {
|
|
458
|
+
if (addr.startsWith("0x")) {
|
|
459
|
+
if (isAddress(addr)) {
|
|
460
|
+
evm.push(addr);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
invalid.push(addr);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// Solana: base58, 32-44 chars
|
|
468
|
+
if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addr)) {
|
|
469
|
+
solana.push(addr);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
invalid.push(addr);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (invalid.length > 0) {
|
|
477
|
+
fatal(`Invalid address(es): ${invalid.join(", ")}`);
|
|
478
|
+
}
|
|
479
|
+
return { evm, solana };
|
|
480
|
+
}
|
|
400
481
|
export async function loginCommand(opts) {
|
|
401
482
|
// Fail fast if flags were passed with empty values
|
|
402
483
|
if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
|
|
@@ -444,6 +525,17 @@ export async function loginCommand(opts) {
|
|
|
444
525
|
output.success(`Credentials saved to ${getConfigPath()}`);
|
|
445
526
|
output.dim(`Bankr API URL: ${apiUrl}`);
|
|
446
527
|
}
|
|
528
|
+
// Lazily parse allowedIps / allowedRecipients only in flows that use them
|
|
529
|
+
function getParsedRestrictions() {
|
|
530
|
+
return {
|
|
531
|
+
allowedIps: opts.allowedIps
|
|
532
|
+
? parseAndValidateIps(opts.allowedIps)
|
|
533
|
+
: undefined,
|
|
534
|
+
allowedRecipients: opts.allowedRecipients
|
|
535
|
+
? parseAndValidateRecipients(opts.allowedRecipients)
|
|
536
|
+
: undefined,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
447
539
|
// --siwe: SIWE login flow (headless agent onboarding)
|
|
448
540
|
if (opts.siwe) {
|
|
449
541
|
if (!opts.privateKey) {
|
|
@@ -454,6 +546,10 @@ export async function loginCommand(opts) {
|
|
|
454
546
|
partnerKey: opts.partnerKey,
|
|
455
547
|
keyName: opts.keyName,
|
|
456
548
|
readOnly: !opts.readWrite,
|
|
549
|
+
walletApi: opts.walletApi,
|
|
550
|
+
agentApi: opts.agentApi,
|
|
551
|
+
tokenLaunch: opts.tokenLaunch,
|
|
552
|
+
...getParsedRestrictions(),
|
|
457
553
|
});
|
|
458
554
|
config.apiKey = apiKey;
|
|
459
555
|
if (opts.partnerKey) {
|
|
@@ -492,10 +588,20 @@ export async function loginCommand(opts) {
|
|
|
492
588
|
const privyConfig = await fetchPrivyConfigOrFail(apiUrl);
|
|
493
589
|
await sendOtpWithSpinner(privyConfig, opts.email);
|
|
494
590
|
output.blank();
|
|
495
|
-
|
|
591
|
+
// Build the hint command, including any flags the user passed so they
|
|
592
|
+
// remember to re-supply them in step 2
|
|
593
|
+
let hint = `bankr login email ${opts.email} --code <code>`;
|
|
594
|
+
if (opts.allowedIps)
|
|
595
|
+
hint += ` --allowed-ips ${opts.allowedIps}`;
|
|
596
|
+
if (opts.allowedRecipients)
|
|
597
|
+
hint += ` --allowed-recipients ${opts.allowedRecipients}`;
|
|
598
|
+
output.info(`Complete login with: ${hint}`);
|
|
496
599
|
return;
|
|
497
600
|
}
|
|
498
|
-
const apiKey = await emailLoginFlow(apiUrl,
|
|
601
|
+
const apiKey = await emailLoginFlow(apiUrl, {
|
|
602
|
+
...opts,
|
|
603
|
+
...getParsedRestrictions(),
|
|
604
|
+
});
|
|
499
605
|
output.blank();
|
|
500
606
|
saveCredentials(apiKey, opts.llmKey);
|
|
501
607
|
return;
|
|
@@ -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
|