@bankr/cli 0.3.1 → 0.3.11

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.
@@ -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-", "") + "/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 all interactive prompts
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}/agent/me`, {
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 (res.status === 403 || errorType === "Agent API access not enabled") {
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;
@@ -10,6 +10,7 @@ export declare function profileCreateCommand(opts: {
10
10
  json?: boolean;
11
11
  }): Promise<void>;
12
12
  export declare function profileUpdateCommand(opts: {
13
+ slug?: string;
13
14
  name?: string;
14
15
  description?: string;
15
16
  token?: string;
@@ -17,8 +18,11 @@ export declare function profileUpdateCommand(opts: {
17
18
  website?: string;
18
19
  json?: boolean;
19
20
  }): Promise<void>;
20
- export declare function profileDeleteCommand(): Promise<void>;
21
+ export declare function profileDeleteCommand(opts: {
22
+ slug?: string;
23
+ }): Promise<void>;
21
24
  export declare function profileAddUpdateCommand(opts: {
25
+ slug?: string;
22
26
  title?: string;
23
27
  content?: string;
24
28
  json?: boolean;
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
- import { input, confirm } from "@inquirer/prompts";
3
- import { getOwnProfile, createProfile, updateProfile, deleteProfile, addProjectUpdate, } from "../lib/api.js";
2
+ import { input, confirm, select } from "@inquirer/prompts";
3
+ import { getOwnProfiles, createProfile, updateProfile, deleteProfile, addProjectUpdate, } from "../lib/api.js";
4
4
  import * as output from "../lib/output.js";
5
5
  const BRAND = chalk.hex("#FF613D");
6
6
  const DIM = chalk.dim;
@@ -42,15 +42,41 @@ function printProfile(profile, json) {
42
42
  }
43
43
  console.log();
44
44
  }
45
+ /**
46
+ * Resolve which profile to operate on.
47
+ * If --slug is provided, use it. Otherwise, if there's exactly one profile, use that.
48
+ * If multiple profiles exist, prompt the user to pick.
49
+ */
50
+ async function resolveSlug(profiles, slugOpt) {
51
+ if (slugOpt)
52
+ return slugOpt;
53
+ if (profiles.length === 1)
54
+ return profiles[0].slug;
55
+ const choice = await select({
56
+ message: "Which project?",
57
+ choices: profiles.map((p) => ({
58
+ name: `${p.projectName} (${p.slug})`,
59
+ value: p.slug,
60
+ })),
61
+ theme: output.bankrTheme,
62
+ });
63
+ return choice;
64
+ }
45
65
  export async function profileViewCommand(opts) {
46
66
  try {
47
- const profile = await getOwnProfile();
48
- printProfile(profile, !!opts.json);
67
+ const profiles = await getOwnProfiles();
68
+ if (!profiles.length) {
69
+ output.info("No profiles found. Create one with: bankr profile create");
70
+ return;
71
+ }
72
+ for (const profile of profiles) {
73
+ printProfile(profile, !!opts.json);
74
+ }
49
75
  }
50
76
  catch (err) {
51
77
  const msg = err.message;
52
78
  if (msg.includes("404")) {
53
- output.info("No profile found. Create one with: bankr profile create");
79
+ output.info("No profiles found. Create one with: bankr profile create");
54
80
  }
55
81
  else {
56
82
  output.error(msg);
@@ -126,7 +152,13 @@ export async function profileUpdateCommand(opts) {
126
152
  output.error("No fields to update. Use flags: --name, --description, --token, --image, --website");
127
153
  return;
128
154
  }
129
- const profile = await updateProfile(data);
155
+ const profiles = await getOwnProfiles();
156
+ if (!profiles.length) {
157
+ output.error("No profiles found. Create one first with: bankr profile create");
158
+ return;
159
+ }
160
+ const slug = await resolveSlug(profiles, opts.slug);
161
+ const profile = await updateProfile(slug, data);
130
162
  output.success("Profile updated!");
131
163
  printProfile(profile, !!opts.json);
132
164
  }
@@ -134,10 +166,16 @@ export async function profileUpdateCommand(opts) {
134
166
  output.error(err.message);
135
167
  }
136
168
  }
137
- export async function profileDeleteCommand() {
169
+ export async function profileDeleteCommand(opts) {
138
170
  try {
171
+ const profiles = await getOwnProfiles();
172
+ if (!profiles.length) {
173
+ output.error("No profiles found.");
174
+ return;
175
+ }
176
+ const slug = await resolveSlug(profiles, opts.slug);
139
177
  const yes = await confirm({
140
- message: "Are you sure you want to delete your profile?",
178
+ message: `Are you sure you want to delete "${slug}"?`,
141
179
  default: false,
142
180
  theme: output.bankrTheme,
143
181
  });
@@ -145,7 +183,7 @@ export async function profileDeleteCommand() {
145
183
  output.info("Cancelled.");
146
184
  return;
147
185
  }
148
- await deleteProfile();
186
+ await deleteProfile(slug);
149
187
  output.success("Profile deleted.");
150
188
  }
151
189
  catch (err) {
@@ -170,7 +208,13 @@ export async function profileAddUpdateCommand(opts) {
170
208
  required: true,
171
209
  });
172
210
  }
173
- const profile = await addProjectUpdate({ title, content });
211
+ const profiles = await getOwnProfiles();
212
+ if (!profiles.length) {
213
+ output.error("No profiles found. Create one first with: bankr profile create");
214
+ return;
215
+ }
216
+ const slug = await resolveSlug(profiles, opts.slug);
217
+ const profile = await addProjectUpdate(slug, { title, content });
174
218
  output.success("Project update added!");
175
219
  if (opts.json) {
176
220
  console.log(JSON.stringify(profile, null, 2));
@@ -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,14 +29,17 @@ 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",
42
+ "glm-5.2",
37
43
  ]);
38
44
  export async function promptCommand(text, options = {}) {
39
45
  requireApiKey();
@@ -0,0 +1,8 @@
1
+ export declare function swapCommand(opts: {
2
+ from: string;
3
+ to: string;
4
+ amount: string;
5
+ chain?: string;
6
+ quoteOnly?: boolean;
7
+ }): Promise<void>;
8
+ //# sourceMappingURL=swap.d.ts.map
@@ -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
@@ -1,28 +1,38 @@
1
- import { transfer, searchTokens } from "../lib/api.js";
1
+ import { transfer, resolveRecipient } from "../lib/api.js";
2
2
  import * as output from "../lib/output.js";
3
- import { NATIVE_SYMBOLS, CHAIN_IDS } from "../lib/chains.js";
3
+ import { NATIVE_SYMBOLS } from "../lib/chains.js";
4
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 };
5
+ import { ZERO_ADDRESS, resolveTokenSymbol } from "../lib/tokens.js";
6
+ function looksLikeEnsName(value) {
7
+ const v = value.trim().toLowerCase();
8
+ return v.endsWith(".eth") || v.endsWith(".cb.id");
9
+ }
10
+ async function resolveTransferRecipient(raw) {
11
+ const value = raw.trim();
12
+ if (isAddress(value)) {
13
+ return { address: value, label: value };
14
+ }
15
+ if (!looksLikeEnsName(value)) {
16
+ output.error(`Invalid recipient "${value}". Expected a 0x address or an ENS name (e.g. vitalik.eth, name.base.eth, name.cb.id).`);
17
+ process.exit(1);
12
18
  }
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"}`);
19
+ const spin = output.spinner(`Resolving ENS name "${value}"...`);
20
+ try {
21
+ const resolved = await resolveRecipient(value, "ens");
22
+ if (!resolved.resolved || !resolved.address) {
23
+ spin.fail(`Could not resolve "${value}": ${resolved.error ?? "unknown error"}`);
24
+ process.exit(1);
25
+ }
26
+ spin.succeed(`Resolved ${value} → ${resolved.address}`);
27
+ return {
28
+ address: resolved.address,
29
+ label: `${value} (${resolved.address})`,
30
+ };
31
+ }
32
+ catch (err) {
33
+ spin.fail(`ENS resolution failed for "${value}": ${err.message}`);
34
+ process.exit(1);
24
35
  }
25
- return { address: exactMatch.address, symbol: exactMatch.symbol };
26
36
  }
27
37
  export async function transferCommand(opts) {
28
38
  const isNative = opts.native ?? false;
@@ -31,16 +41,19 @@ export async function transferCommand(opts) {
31
41
  output.error("Token required for ERC20 transfers. Use --token <symbol or address> or --native.");
32
42
  process.exit(1);
33
43
  }
44
+ const recipient = await resolveTransferRecipient(opts.to);
45
+ const recipientAddress = recipient.address;
46
+ const recipientLabel = recipient.label;
34
47
  let tokenAddress;
35
48
  let tokenLabel;
36
49
  if (isNative) {
37
- tokenAddress = "0x0000000000000000000000000000000000000000";
50
+ tokenAddress = ZERO_ADDRESS;
38
51
  tokenLabel = NATIVE_SYMBOLS[chain] ?? "ETH";
39
52
  }
40
53
  else {
41
54
  const spin = output.spinner(`Resolving token "${opts.token}"...`);
42
55
  try {
43
- const resolved = await resolveToken(opts.token, chain);
56
+ const resolved = await resolveTokenSymbol(opts.token, chain);
44
57
  tokenAddress = resolved.address;
45
58
  tokenLabel = resolved.symbol;
46
59
  if (tokenAddress !== opts.token) {
@@ -56,11 +69,11 @@ export async function transferCommand(opts) {
56
69
  }
57
70
  }
58
71
  const chainLabel = chain !== "base" ? ` on ${chain}` : "";
59
- const spin = output.spinner(`Transferring ${opts.amount} ${tokenLabel} to ${opts.to}${chainLabel}...`);
72
+ const spin = output.spinner(`Transferring ${opts.amount} ${tokenLabel} to ${recipientLabel}${chainLabel}...`);
60
73
  try {
61
74
  const result = await transfer({
62
75
  tokenAddress,
63
- recipientAddress: opts.to,
76
+ recipientAddress,
64
77
  amount: opts.amount,
65
78
  isNativeToken: isNative,
66
79
  chain,
@@ -17,7 +17,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { confirm } from "@inquirer/prompts";
19
19
  import * as output from "../lib/output.js";
20
- import { getApiUrl, requireApiKey, readConfig, CLI_USER_AGENT, } from "../lib/config.js";
20
+ import { getApiUrl, requireApiKey, CLI_USER_AGENT } from "../lib/config.js";
21
21
  // ── Config file ─────────────────────────────────────────────────────────
22
22
  const CONFIG_FILENAME = "bankr.webhooks.json";
23
23
  const WEBHOOKS_DIR = "webhooks";
@@ -46,16 +46,11 @@ function saveConfigFile(projectRoot, config) {
46
46
  writeFileSync(join(projectRoot, CONFIG_FILENAME), JSON.stringify(config, null, 2) + "\n");
47
47
  }
48
48
  function authHeaders() {
49
- const headers = {
49
+ return {
50
50
  "X-API-Key": requireApiKey(),
51
51
  "Content-Type": "application/json",
52
52
  "User-Agent": CLI_USER_AGENT,
53
53
  };
54
- const config = readConfig();
55
- if (config.partnerKey) {
56
- headers["X-Partner-Key"] = config.partnerKey;
57
- }
58
- return headers;
59
54
  }
60
55
  async function handleResponse(res) {
61
56
  if (!res.ok) {