@bankr/cli 0.3.1 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +128 -80
- package/dist/commands/club.d.ts +1 -0
- package/dist/commands/club.js +47 -5
- package/dist/commands/fees.js +10 -2
- package/dist/commands/launch.js +16 -23
- package/dist/commands/llm.js +167 -41
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +28 -23
- package/dist/commands/prompt.js +5 -0
- package/dist/commands/swap.d.ts +8 -0
- package/dist/commands/swap.js +107 -0
- package/dist/commands/transfer.js +38 -25
- package/dist/commands/webhooks.js +2 -7
- package/dist/commands/x402.js +57 -41
- package/dist/lib/api.d.ts +43 -0
- package/dist/lib/api.js +27 -7
- package/dist/lib/config.d.ts +0 -1
- package/dist/lib/output.d.ts +1 -0
- package/dist/lib/output.js +3 -0
- package/dist/lib/tokens.d.ts +21 -0
- package/dist/lib/tokens.js +40 -0
- package/package.json +6 -6
package/dist/commands/llm.js
CHANGED
|
@@ -5,8 +5,7 @@ import { spawn } from "child_process";
|
|
|
5
5
|
import { CLI_USER_AGENT, getApiUrl, getLlmKey, getLlmUrl, requireApiKey, } from "../lib/config.js";
|
|
6
6
|
import { getBalances } from "../lib/api.js";
|
|
7
7
|
import * as output from "../lib/output.js";
|
|
8
|
-
|
|
9
|
-
const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api";
|
|
8
|
+
const ERR_LLM_NOT_ENABLED = "LLM Gateway not enabled on this API key. Enable at bankr.bot/api-keys";
|
|
10
9
|
/**
|
|
11
10
|
* Fallback model catalog — used when the gateway is unreachable or unauthenticated.
|
|
12
11
|
* resolveModels() fetches live data from GET /v1/models; this list is the offline safety net.
|
|
@@ -171,6 +170,15 @@ const GATEWAY_MODELS = [
|
|
|
171
170
|
cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
|
|
172
171
|
},
|
|
173
172
|
// Other providers
|
|
173
|
+
{
|
|
174
|
+
id: "grok-4.3",
|
|
175
|
+
name: "Grok 4.3",
|
|
176
|
+
owned_by: "x-ai",
|
|
177
|
+
contextWindow: 1000000,
|
|
178
|
+
maxTokens: 1000000,
|
|
179
|
+
input: IMAGE_INPUT,
|
|
180
|
+
cost: { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 },
|
|
181
|
+
},
|
|
174
182
|
{
|
|
175
183
|
id: "grok-4.1-fast",
|
|
176
184
|
name: "Grok 4.1 Fast",
|
|
@@ -198,6 +206,24 @@ const GATEWAY_MODELS = [
|
|
|
198
206
|
input: TEXT_INPUT,
|
|
199
207
|
cost: { input: 0.12, output: 0.75, cacheRead: 0.06, cacheWrite: 0 },
|
|
200
208
|
},
|
|
209
|
+
{
|
|
210
|
+
id: "qwen3.7-plus",
|
|
211
|
+
name: "Qwen3.7 Plus",
|
|
212
|
+
owned_by: "qwen",
|
|
213
|
+
contextWindow: 1000000,
|
|
214
|
+
maxTokens: 65536,
|
|
215
|
+
input: TEXT_INPUT,
|
|
216
|
+
cost: { input: 0.4, output: 1.6, cacheRead: 0.04, cacheWrite: 0 },
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "qwen3.6-flash",
|
|
220
|
+
name: "Qwen3.6 Flash",
|
|
221
|
+
owned_by: "qwen",
|
|
222
|
+
contextWindow: 1000000,
|
|
223
|
+
maxTokens: 65536,
|
|
224
|
+
input: TEXT_INPUT,
|
|
225
|
+
cost: { input: 0.25, output: 1.5, cacheRead: 0, cacheWrite: 0 },
|
|
226
|
+
},
|
|
201
227
|
{
|
|
202
228
|
id: "qwen3.5-plus",
|
|
203
229
|
name: "Qwen3.5 Plus",
|
|
@@ -216,6 +242,15 @@ const GATEWAY_MODELS = [
|
|
|
216
242
|
input: TEXT_INPUT,
|
|
217
243
|
cost: { input: 0.1, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
|
218
244
|
},
|
|
245
|
+
{
|
|
246
|
+
id: "kimi-k2.7-code",
|
|
247
|
+
name: "Kimi K2.7 Code",
|
|
248
|
+
owned_by: "moonshotai",
|
|
249
|
+
contextWindow: 262144,
|
|
250
|
+
maxTokens: 262144,
|
|
251
|
+
input: TEXT_INPUT,
|
|
252
|
+
cost: { input: 0.95, output: 4.0, cacheRead: 0.19, cacheWrite: 0 },
|
|
253
|
+
},
|
|
219
254
|
{
|
|
220
255
|
id: "kimi-k2.6",
|
|
221
256
|
name: "Kimi K2.6",
|
|
@@ -422,6 +457,89 @@ export async function creditsCommand() {
|
|
|
422
457
|
}
|
|
423
458
|
}
|
|
424
459
|
/* ─────────────────────── bankr llm credits add ──────────────────────────── */
|
|
460
|
+
// Keep in sync with packages/shared/src/chain-config-base/chainConfigBase.ts.
|
|
461
|
+
// CLI is standalone-published and cannot depend on @bankr/shared.
|
|
462
|
+
const TOPUP_CHAINS = ["base", "polygon", "mainnet", "arbitrum", "bnb"];
|
|
463
|
+
const CHAIN_DISPLAY_NAME = {
|
|
464
|
+
base: "Base",
|
|
465
|
+
polygon: "Polygon",
|
|
466
|
+
mainnet: "Ethereum",
|
|
467
|
+
arbitrum: "Arbitrum",
|
|
468
|
+
bnb: "BNB Chain",
|
|
469
|
+
};
|
|
470
|
+
// Polygon's native POL is at a precompile, not the 0xeee placeholder.
|
|
471
|
+
const NATIVE_TOKEN_ADDRESS_BY_CHAIN = {
|
|
472
|
+
base: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
473
|
+
polygon: "0x0000000000000000000000000000000000001010",
|
|
474
|
+
mainnet: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
475
|
+
arbitrum: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
476
|
+
bnb: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
477
|
+
};
|
|
478
|
+
const BLOCK_EXPLORER_URL = {
|
|
479
|
+
base: "https://basescan.org",
|
|
480
|
+
polygon: "https://polygonscan.com",
|
|
481
|
+
mainnet: "https://etherscan.io",
|
|
482
|
+
arbitrum: "https://arbiscan.io",
|
|
483
|
+
bnb: "https://bscscan.com",
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Resolve user input (symbol or 0x address) to the (chain, token) it should
|
|
487
|
+
* pay with. Scans every supported chain and picks the highest USD balance,
|
|
488
|
+
* so the user never has to think about chains. Returns null if nothing matches.
|
|
489
|
+
*/
|
|
490
|
+
function resolveTokenAcrossChains(input, balances) {
|
|
491
|
+
const isAddress = input.startsWith("0x");
|
|
492
|
+
const sym = isAddress ? null : input.toUpperCase();
|
|
493
|
+
const candidates = [];
|
|
494
|
+
for (const chain of TOPUP_CHAINS) {
|
|
495
|
+
const chainBal = balances[chain];
|
|
496
|
+
if (!chainBal)
|
|
497
|
+
continue;
|
|
498
|
+
if (!isAddress) {
|
|
499
|
+
const nativeUsd = parseFloat(chainBal.nativeUsd || "0");
|
|
500
|
+
const nativeSym = nativeSymbolFor(chain);
|
|
501
|
+
if (sym === nativeSym && nativeUsd > 0) {
|
|
502
|
+
candidates.push({
|
|
503
|
+
chain,
|
|
504
|
+
address: NATIVE_TOKEN_ADDRESS_BY_CHAIN[chain],
|
|
505
|
+
symbol: nativeSym,
|
|
506
|
+
name: nativeSym,
|
|
507
|
+
decimals: 18,
|
|
508
|
+
balance: parseFloat(chainBal.nativeBalance || "0"),
|
|
509
|
+
balanceUsd: nativeUsd,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
for (const tb of chainBal.tokenBalances) {
|
|
514
|
+
const matchesSym = sym != null && tb.token.baseToken.symbol.toUpperCase() === sym;
|
|
515
|
+
const matchesAddr = isAddress &&
|
|
516
|
+
tb.token.baseToken.address.toLowerCase() === input.toLowerCase();
|
|
517
|
+
if (!matchesSym && !matchesAddr)
|
|
518
|
+
continue;
|
|
519
|
+
candidates.push({
|
|
520
|
+
chain,
|
|
521
|
+
address: tb.token.baseToken.address,
|
|
522
|
+
symbol: tb.token.baseToken.symbol,
|
|
523
|
+
name: tb.token.baseToken.name,
|
|
524
|
+
decimals: tb.token.baseToken.decimals,
|
|
525
|
+
balance: tb.token.balance,
|
|
526
|
+
balanceUsd: tb.token.balanceUSD,
|
|
527
|
+
imageUrl: tb.token.baseToken.imgUrl,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (candidates.length === 0)
|
|
532
|
+
return null;
|
|
533
|
+
candidates.sort((a, b) => b.balanceUsd - a.balanceUsd);
|
|
534
|
+
return candidates[0];
|
|
535
|
+
}
|
|
536
|
+
function nativeSymbolFor(chain) {
|
|
537
|
+
if (chain === "polygon")
|
|
538
|
+
return "POL";
|
|
539
|
+
if (chain === "bnb")
|
|
540
|
+
return "BNB";
|
|
541
|
+
return "ETH";
|
|
542
|
+
}
|
|
425
543
|
export async function creditsAddCommand(amount, opts) {
|
|
426
544
|
const apiKey = requireApiKey();
|
|
427
545
|
const apiUrl = getApiUrl();
|
|
@@ -433,9 +551,9 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
433
551
|
output.error("Amount must be between $1 and $1,000");
|
|
434
552
|
process.exit(1);
|
|
435
553
|
}
|
|
436
|
-
// Fetch current balance for display (skip when --yes since user won't see it)
|
|
554
|
+
// Fetch current balance for display (skip when --yes/--not-interactive since user won't see it)
|
|
437
555
|
let currentBalance = "unknown";
|
|
438
|
-
if (llmKey && !opts.yes) {
|
|
556
|
+
if (llmKey && !opts.yes && !output.isNonInteractive()) {
|
|
439
557
|
const balSpin = output.spinner("Fetching balance…");
|
|
440
558
|
try {
|
|
441
559
|
const balRes = await fetch(`${llmUrl}/v1/credits`, {
|
|
@@ -457,18 +575,41 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
457
575
|
output.dim(` (Could not fetch balance: ${err.message})`);
|
|
458
576
|
}
|
|
459
577
|
}
|
|
578
|
+
// When --token is passed, the chain holding the highest USD balance of
|
|
579
|
+
// that symbol/address wins. Default (no --token) is Base USDC.
|
|
580
|
+
let resolved = null;
|
|
581
|
+
if (opts.token) {
|
|
582
|
+
const resolveSpin = output.spinner("Resolving token…");
|
|
583
|
+
try {
|
|
584
|
+
const balData = await getBalances([...TOPUP_CHAINS]);
|
|
585
|
+
resolved = resolveTokenAcrossChains(opts.token, balData.balances);
|
|
586
|
+
resolveSpin.stop();
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
resolveSpin.stop();
|
|
590
|
+
output.warn(`Could not fetch balances: ${err.message}`);
|
|
591
|
+
}
|
|
592
|
+
if (!resolved) {
|
|
593
|
+
output.error(`Token "${opts.token}" not found in your wallet on any supported chain.`);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const targetChain = resolved?.chain ?? "base";
|
|
598
|
+
const sourceTokenLabel = resolved
|
|
599
|
+
? `${resolved.symbol} on ${CHAIN_DISPLAY_NAME[targetChain]}`
|
|
600
|
+
: `USDC on ${CHAIN_DISPLAY_NAME.base} (default)`;
|
|
460
601
|
// Show confirmation
|
|
461
602
|
console.log();
|
|
462
603
|
output.label("Add Credits", `$${amountUsd.toFixed(2)}`);
|
|
463
|
-
output.keyValue("
|
|
604
|
+
output.keyValue("Pay with", sourceTokenLabel);
|
|
464
605
|
output.keyValue("Current balance", currentBalance);
|
|
465
606
|
console.log();
|
|
466
|
-
if (!opts.yes) {
|
|
607
|
+
if (!opts.yes && !output.isNonInteractive()) {
|
|
467
608
|
const { confirm } = await import("@inquirer/prompts");
|
|
468
609
|
const proceed = await confirm({
|
|
469
610
|
message: "Proceed?",
|
|
470
611
|
default: true,
|
|
471
|
-
theme: bankrTheme,
|
|
612
|
+
theme: output.bankrTheme,
|
|
472
613
|
});
|
|
473
614
|
if (!proceed) {
|
|
474
615
|
output.dim("Cancelled.");
|
|
@@ -477,9 +618,12 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
477
618
|
}
|
|
478
619
|
const spin = output.spinner("Adding credits…");
|
|
479
620
|
try {
|
|
480
|
-
const body = {
|
|
481
|
-
|
|
482
|
-
|
|
621
|
+
const body = {
|
|
622
|
+
amountUsd,
|
|
623
|
+
chain: targetChain,
|
|
624
|
+
};
|
|
625
|
+
if (resolved) {
|
|
626
|
+
body.sourceToken = resolved.address;
|
|
483
627
|
}
|
|
484
628
|
const res = await fetch(`${apiUrl}/llm/credits/topup`, {
|
|
485
629
|
method: "POST",
|
|
@@ -516,7 +660,7 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
516
660
|
output.success(`Added $${result.creditsGranted.toFixed(2)} credits`);
|
|
517
661
|
output.label("New Balance", `$${result.newBalance.toFixed(2)}`);
|
|
518
662
|
if (result.txHash) {
|
|
519
|
-
output.keyValue("Transaction",
|
|
663
|
+
output.keyValue("Transaction", `${BLOCK_EXPLORER_URL[targetChain]}/tx/${result.txHash}`);
|
|
520
664
|
}
|
|
521
665
|
}
|
|
522
666
|
catch (err) {
|
|
@@ -632,45 +776,27 @@ export async function creditsAutoCommand(opts) {
|
|
|
632
776
|
process.exit(1);
|
|
633
777
|
}
|
|
634
778
|
const resolved = [];
|
|
635
|
-
//
|
|
779
|
+
// Highest USD balance wins when the same symbol exists on multiple chains.
|
|
636
780
|
const spin = output.spinner("Resolving tokens…");
|
|
637
|
-
let
|
|
638
|
-
let hasNativeEth = false;
|
|
781
|
+
let balances = {};
|
|
639
782
|
try {
|
|
640
|
-
const balData = await getBalances([
|
|
641
|
-
|
|
642
|
-
baseTokens = baseChain?.tokenBalances ?? [];
|
|
643
|
-
hasNativeEth = !!baseChain && parseFloat(baseChain.nativeBalance) > 0;
|
|
783
|
+
const balData = await getBalances([...TOPUP_CHAINS]);
|
|
784
|
+
balances = balData.balances;
|
|
644
785
|
}
|
|
645
786
|
catch {
|
|
646
787
|
output.warn("Could not fetch balances for token resolution");
|
|
647
788
|
}
|
|
648
789
|
spin.stop();
|
|
649
790
|
for (const input of inputs) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
// Native ETH
|
|
653
|
-
if (sym === "ETH" && hasNativeEth) {
|
|
654
|
-
resolved.push({
|
|
655
|
-
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
656
|
-
chain: "base",
|
|
657
|
-
symbol: "ETH",
|
|
658
|
-
name: "Ethereum",
|
|
659
|
-
decimals: 18,
|
|
660
|
-
});
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
const tb = baseTokens.find((t) => isAddress
|
|
664
|
-
? t.token.baseToken.address.toLowerCase() === input.toLowerCase()
|
|
665
|
-
: t.token.baseToken.symbol.toUpperCase() === sym);
|
|
666
|
-
if (tb) {
|
|
791
|
+
const hit = resolveTokenAcrossChains(input, balances);
|
|
792
|
+
if (hit) {
|
|
667
793
|
resolved.push({
|
|
668
|
-
address:
|
|
669
|
-
chain:
|
|
670
|
-
symbol:
|
|
671
|
-
name:
|
|
672
|
-
decimals:
|
|
673
|
-
imageUrl:
|
|
794
|
+
address: hit.address,
|
|
795
|
+
chain: hit.chain,
|
|
796
|
+
symbol: hit.symbol,
|
|
797
|
+
name: hit.name,
|
|
798
|
+
decimals: hit.decimals,
|
|
799
|
+
imageUrl: hit.imageUrl,
|
|
674
800
|
});
|
|
675
801
|
}
|
|
676
802
|
else {
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -4,7 +4,7 @@ import open from "open";
|
|
|
4
4
|
import { isAddress } from "viem";
|
|
5
5
|
import { CLI_USER_AGENT, DEFAULT_API_URL, getApiUrl, getConfigPath, readConfig, writeConfig, } from "../lib/config.js";
|
|
6
6
|
import * as output from "../lib/output.js";
|
|
7
|
-
const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api";
|
|
7
|
+
const DEFAULT_DASHBOARD_URL = "https://bankr.bot/api-keys";
|
|
8
8
|
const MAX_OTP_ATTEMPTS = 3;
|
|
9
9
|
const INVALID_CODE_STATUSES = [400, 401, 422];
|
|
10
10
|
function fatal(message) {
|
|
@@ -15,7 +15,8 @@ function deriveDashboardUrl(apiUrl) {
|
|
|
15
15
|
if (apiUrl === DEFAULT_API_URL) {
|
|
16
16
|
return DEFAULT_DASHBOARD_URL;
|
|
17
17
|
}
|
|
18
|
-
return (apiUrl.replace(/\/+$/, "").replace("api.", "").replace("api-", "") +
|
|
18
|
+
return (apiUrl.replace(/\/+$/, "").replace("api.", "").replace("api-", "") +
|
|
19
|
+
"/api-keys");
|
|
19
20
|
}
|
|
20
21
|
async function fetchPrivyConfig(apiUrl) {
|
|
21
22
|
const res = await fetch(`${apiUrl}/cli/config`, {
|
|
@@ -220,8 +221,8 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
220
221
|
if (wallet.solAddress) {
|
|
221
222
|
output.dim(` SOL: ${wallet.solAddress}`);
|
|
222
223
|
}
|
|
223
|
-
// Headless mode: when --code is provided, skip
|
|
224
|
-
const headless = !!opts.code;
|
|
224
|
+
// Headless mode: when --code is provided or --ni is set, skip prompts
|
|
225
|
+
const headless = !!opts.code || output.isNonInteractive();
|
|
225
226
|
// Step 3: Accept terms if needed
|
|
226
227
|
if (!wallet.hasAcceptedTerms) {
|
|
227
228
|
output.blank();
|
|
@@ -263,7 +264,7 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
263
264
|
// Fine-grained API flags. walletApi and tokenLaunch default to enabled.
|
|
264
265
|
// --read-write only controls readOnly, not which APIs are enabled.
|
|
265
266
|
const enableWallet = opts.walletApi ?? true;
|
|
266
|
-
// Agent API must be enabled from the website (bankr.bot/api), not the CLI.
|
|
267
|
+
// Agent API must be enabled from the website (bankr.bot/api-keys), not the CLI.
|
|
267
268
|
const enableAgent = false;
|
|
268
269
|
const enableTokenLaunch = opts.tokenLaunch ?? true;
|
|
269
270
|
const enableLlm = opts.llm ??
|
|
@@ -317,8 +318,8 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
317
318
|
];
|
|
318
319
|
output.dim(` Recipients: ${parts.join(", ")}`);
|
|
319
320
|
}
|
|
320
|
-
output.dim(" Agent API: enable at bankr.bot/api");
|
|
321
|
-
output.dim(" Manage keys at bankr.bot/api");
|
|
321
|
+
output.dim(" Agent API: enable at bankr.bot/api-keys");
|
|
322
|
+
output.dim(" Manage keys at bankr.bot/api-keys");
|
|
322
323
|
return apiKeyResult.apiKey;
|
|
323
324
|
}
|
|
324
325
|
// ── SIWE login flow ─────────────────────────────────────────────────
|
|
@@ -365,7 +366,6 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
365
366
|
body: JSON.stringify({
|
|
366
367
|
message,
|
|
367
368
|
signature,
|
|
368
|
-
partnerApiKey: opts.partnerKey,
|
|
369
369
|
keyName: opts.keyName ?? `SIWE-${new Date().toISOString().slice(0, 10)}`,
|
|
370
370
|
readOnly: opts.readOnly ?? false,
|
|
371
371
|
walletApiEnabled: opts.walletApi ?? true,
|
|
@@ -395,7 +395,7 @@ async function siweLoginFlow(apiUrl, opts) {
|
|
|
395
395
|
async function validateApiKey(apiUrl, apiKey) {
|
|
396
396
|
const spin = output.spinner("Validating Bankr API key...");
|
|
397
397
|
try {
|
|
398
|
-
const res = await fetch(`${apiUrl}/
|
|
398
|
+
const res = await fetch(`${apiUrl}/wallet/me`, {
|
|
399
399
|
headers: { "X-API-Key": apiKey, "User-Agent": CLI_USER_AGENT },
|
|
400
400
|
});
|
|
401
401
|
if (res.ok) {
|
|
@@ -404,20 +404,14 @@ async function validateApiKey(apiUrl, apiKey) {
|
|
|
404
404
|
else if (res.status === 401 || res.status === 403) {
|
|
405
405
|
const body = (await res.json().catch(() => ({})));
|
|
406
406
|
const errorType = body.error ?? "";
|
|
407
|
-
if (
|
|
408
|
-
spin.warn("Agent API access is not enabled");
|
|
409
|
-
output.warn("Your API key is valid but does not have Agent API access enabled.");
|
|
410
|
-
output.info("Go to https://bankr.bot/api and enable Agent API access for this key.");
|
|
411
|
-
output.info("Your key has been saved — once enabled, the CLI will work without re-login.");
|
|
412
|
-
}
|
|
413
|
-
else if (errorType === "Authentication required") {
|
|
407
|
+
if (errorType === "Authentication required") {
|
|
414
408
|
spin.fail("API key is not linked to a wallet");
|
|
415
|
-
output.error("This API key is not associated with a wallet. Generate a new key at https://bankr.bot/api");
|
|
409
|
+
output.error("This API key is not associated with a wallet. Generate a new key at https://bankr.bot/api-keys");
|
|
416
410
|
process.exit(1);
|
|
417
411
|
}
|
|
418
412
|
else {
|
|
419
413
|
spin.fail("Invalid Bankr API key");
|
|
420
|
-
output.error("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api");
|
|
414
|
+
output.error("The provided API key is invalid or inactive. Generate a new key at https://bankr.bot/api-keys");
|
|
421
415
|
process.exit(1);
|
|
422
416
|
}
|
|
423
417
|
}
|
|
@@ -497,13 +491,28 @@ export async function loginCommand(opts) {
|
|
|
497
491
|
if (opts.apiKey !== undefined && !opts.apiKey.trim()) {
|
|
498
492
|
fatal("--api-key requires a non-empty value");
|
|
499
493
|
}
|
|
494
|
+
// Non-interactive mode requires a headless flag combo. Without one, the
|
|
495
|
+
// command would hang on a prompt — fail fast with the available paths.
|
|
496
|
+
// Email step 1 (`login email <addr>` without --code) is also headless:
|
|
497
|
+
// it sends the OTP and exits.
|
|
498
|
+
if (output.isNonInteractive()) {
|
|
499
|
+
const hasHeadlessFlag = opts.apiKey ||
|
|
500
|
+
opts.url ||
|
|
501
|
+
(opts.siwe && opts.privateKey) ||
|
|
502
|
+
(opts.email && opts.email.trim());
|
|
503
|
+
if (!hasHeadlessFlag) {
|
|
504
|
+
output.error("Non-interactive login requires one of: --api-key <key>, login siwe --private-key <key>, or login email <address> [--code <otp>]");
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
500
508
|
const config = readConfig();
|
|
501
509
|
const resolvedApiUrl = opts.apiUrl ?? getApiUrl();
|
|
502
510
|
if (resolvedApiUrl !== DEFAULT_API_URL) {
|
|
503
511
|
const nonInteractive = !!(opts.apiKey ||
|
|
504
512
|
opts.url ||
|
|
505
513
|
opts.email !== undefined ||
|
|
506
|
-
opts.siwe
|
|
514
|
+
opts.siwe ||
|
|
515
|
+
output.isNonInteractive());
|
|
507
516
|
if (nonInteractive) {
|
|
508
517
|
config.apiUrl = resolvedApiUrl;
|
|
509
518
|
}
|
|
@@ -557,7 +566,6 @@ export async function loginCommand(opts) {
|
|
|
557
566
|
}
|
|
558
567
|
const { apiKey } = await siweLoginFlow(apiUrl, {
|
|
559
568
|
privateKey: opts.privateKey,
|
|
560
|
-
partnerKey: opts.partnerKey,
|
|
561
569
|
keyName: opts.keyName,
|
|
562
570
|
readOnly: !opts.readWrite,
|
|
563
571
|
walletApi: opts.walletApi,
|
|
@@ -565,9 +573,6 @@ export async function loginCommand(opts) {
|
|
|
565
573
|
...getParsedRestrictions(),
|
|
566
574
|
});
|
|
567
575
|
config.apiKey = apiKey;
|
|
568
|
-
if (opts.partnerKey) {
|
|
569
|
-
config.partnerKey = opts.partnerKey;
|
|
570
|
-
}
|
|
571
576
|
writeConfig(config);
|
|
572
577
|
output.success(`Credentials saved to ${getConfigPath()}`);
|
|
573
578
|
return;
|
package/dist/commands/prompt.js
CHANGED
|
@@ -6,6 +6,9 @@ import * as output from "../lib/output.js";
|
|
|
6
6
|
* Must be manually kept in sync with models in the LLM gateway DB. */
|
|
7
7
|
const VALID_MAX_MODE_MODELS = new Set([
|
|
8
8
|
// Claude (Vertex AI)
|
|
9
|
+
"claude-fable-5",
|
|
10
|
+
"claude-opus-4.8",
|
|
11
|
+
"claude-opus-4.7",
|
|
9
12
|
"claude-opus-4.6",
|
|
10
13
|
"claude-opus-4.5",
|
|
11
14
|
"claude-sonnet-4.6",
|
|
@@ -26,11 +29,13 @@ const VALID_MAX_MODE_MODELS = new Set([
|
|
|
26
29
|
"deepseek-v3.2",
|
|
27
30
|
// Qwen (OpenRouter)
|
|
28
31
|
"qwen3-coder",
|
|
32
|
+
"qwen3.7-plus",
|
|
29
33
|
"qwen3.5-plus",
|
|
30
34
|
// MiniMax (direct)
|
|
31
35
|
"minimax-m2.5",
|
|
32
36
|
"minimax-m2.7",
|
|
33
37
|
// Other (OpenRouter)
|
|
38
|
+
"kimi-k2.7-code",
|
|
34
39
|
"kimi-k2.6",
|
|
35
40
|
"kimi-k2.5",
|
|
36
41
|
"glm-5",
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getSwapQuote, executeSwap } from "../lib/api.js";
|
|
2
|
+
import * as output from "../lib/output.js";
|
|
3
|
+
import { NATIVE_SYMBOLS, VALID_CHAINS } from "../lib/chains.js";
|
|
4
|
+
import { ZERO_ADDRESS, resolveTokenSymbol } from "../lib/tokens.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a token symbol or address to a contract address for swapping.
|
|
7
|
+
* - The chain's native ticker (ETH/POL/BNB) or the zero address → native, sent
|
|
8
|
+
* as the zero address (the API detects + sanitizes native tokens).
|
|
9
|
+
* - Otherwise → symbol/address resolution via the shared resolver.
|
|
10
|
+
*/
|
|
11
|
+
async function resolveSwapToken(tokenInput, chain) {
|
|
12
|
+
const input = tokenInput.trim();
|
|
13
|
+
const nativeSymbol = NATIVE_SYMBOLS[chain] ?? "ETH";
|
|
14
|
+
const upper = input.toUpperCase();
|
|
15
|
+
const isNative = upper === nativeSymbol.toUpperCase() ||
|
|
16
|
+
input.toLowerCase() === ZERO_ADDRESS;
|
|
17
|
+
if (isNative) {
|
|
18
|
+
return { address: ZERO_ADDRESS, symbol: nativeSymbol };
|
|
19
|
+
}
|
|
20
|
+
return resolveTokenSymbol(input, chain);
|
|
21
|
+
}
|
|
22
|
+
export async function swapCommand(opts) {
|
|
23
|
+
const chain = opts.chain ?? "base";
|
|
24
|
+
if (!VALID_CHAINS.has(chain) || chain === "solana") {
|
|
25
|
+
output.error(`Unsupported chain "${chain}". Swaps support EVM chains: base, polygon, mainnet, unichain, worldchain, arbitrum, bnb.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Validate amount client-side so a bad value gives a clear error instead of a
|
|
29
|
+
// generic 500 from the API's parseUnits(amount, decimals). Number() alone
|
|
30
|
+
// accepts "1e-3" / "0x10" / "Infinity" that parseUnits rejects, so require a
|
|
31
|
+
// plain decimal string.
|
|
32
|
+
if (!/^\d*\.?\d+$/.test(opts.amount) || Number(opts.amount) <= 0) {
|
|
33
|
+
output.error(`Invalid amount "${opts.amount}". Provide a positive decimal number, e.g. --amount 0.5.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
// Resolve from/to tokens (symbol → address, native → zero address)
|
|
37
|
+
let fromToken;
|
|
38
|
+
let toToken;
|
|
39
|
+
const resolveSpin = output.spinner(`Resolving ${opts.from} → ${opts.to} on ${chain}...`);
|
|
40
|
+
try {
|
|
41
|
+
[fromToken, toToken] = await Promise.all([
|
|
42
|
+
resolveSwapToken(opts.from, chain),
|
|
43
|
+
resolveSwapToken(opts.to, chain),
|
|
44
|
+
]);
|
|
45
|
+
resolveSpin.stop();
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
resolveSpin.fail(err.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Quote
|
|
52
|
+
const quoteSpin = output.spinner(`Fetching quote for ${opts.amount} ${fromToken.symbol} → ${toToken.symbol}...`);
|
|
53
|
+
let quote;
|
|
54
|
+
try {
|
|
55
|
+
quote = await getSwapQuote({
|
|
56
|
+
fromChain: chain,
|
|
57
|
+
fromToken: fromToken.address,
|
|
58
|
+
toChain: chain,
|
|
59
|
+
toToken: toToken.address,
|
|
60
|
+
amount: opts.amount,
|
|
61
|
+
});
|
|
62
|
+
quoteSpin.stop();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
quoteSpin.fail(`Quote failed: ${err.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
output.blank();
|
|
69
|
+
output.label("You pay", `${output.formatBalance(quote.from.formattedAmount)} ${quote.from.symbol} (${output.formatUsd(quote.from.usdValue)})`);
|
|
70
|
+
output.label("You receive", `~${output.formatBalance(quote.to.formattedAmount)} ${quote.to.symbol} (${output.formatUsd(quote.to.usdValue)})`);
|
|
71
|
+
output.keyValue("Min received", `${output.formatBalance(quote.minBuyAmount)} ${quote.to.symbol}`);
|
|
72
|
+
output.blank();
|
|
73
|
+
if (opts.quoteOnly) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Execute (uses the minBuyAmount from the quote as slippage protection)
|
|
77
|
+
const swapSpin = output.spinner(`Swapping ${opts.amount} ${fromToken.symbol} → ${toToken.symbol}...`);
|
|
78
|
+
try {
|
|
79
|
+
const result = await executeSwap({
|
|
80
|
+
fromChain: chain,
|
|
81
|
+
fromToken: fromToken.address,
|
|
82
|
+
toChain: chain,
|
|
83
|
+
toToken: toToken.address,
|
|
84
|
+
amount: opts.amount,
|
|
85
|
+
minBuyAmount: quote.minBuyAmount,
|
|
86
|
+
});
|
|
87
|
+
if (!result.success) {
|
|
88
|
+
swapSpin.fail(`Swap failed: ${result.error ?? "unknown error"}`);
|
|
89
|
+
// A reverted tx can come back as { success: false } with a hash — surface
|
|
90
|
+
// it so the user can inspect the failed transaction on-chain.
|
|
91
|
+
if (result.hash) {
|
|
92
|
+
output.label("Tx Hash", result.hash);
|
|
93
|
+
}
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
swapSpin.succeed("Swap successful");
|
|
97
|
+
output.label("Tx Hash", result.hash ?? "unknown");
|
|
98
|
+
if (result.amountReceived != null) {
|
|
99
|
+
output.label("Received", `${output.formatBalance(result.amountReceived)} ${toToken.symbol}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
swapSpin.fail(`Swap failed: ${err.message}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=swap.js.map
|