minara 0.2.8 → 0.2.9

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.
@@ -41,7 +41,10 @@ export const configCommand = new Command('config')
41
41
  default: config.baseUrl,
42
42
  validate: (v) => {
43
43
  try {
44
- new URL(v);
44
+ const u = new URL(v);
45
+ if (u.protocol !== 'https:' && u.hostname !== 'localhost' && u.hostname !== '127.0.0.1') {
46
+ return 'Base URL must use HTTPS (HTTP allowed only for localhost)';
47
+ }
45
48
  return true;
46
49
  }
47
50
  catch {
@@ -53,7 +53,7 @@ async function showSpotDeposit(token) {
53
53
  console.log('');
54
54
  }
55
55
  // ─── moonpay (credit card on-ramp) ───────────────────────────────────────
56
- const MOONPAY_PK = 'pk_live_yIf64w79W6ufwip4j51PWbymdwGtI';
56
+ const MOONPAY_PK = process.env.MOONPAY_PK ?? 'pk_live_yIf64w79W6ufwip4j51PWbymdwGtI';
57
57
  const MOONPAY_CURRENCIES = [
58
58
  { name: 'USDC (Base)', code: 'usdc_base', network: 'base' },
59
59
  { name: 'USDC (Ethereum)', code: 'usdc', network: 'ethereum' },
@@ -3,7 +3,7 @@ import { input, select, confirm, number as numberPrompt } from '@inquirer/prompt
3
3
  import chalk from 'chalk';
4
4
  import * as perpsApi from '../api/perps.js';
5
5
  import { requireAuth } from '../config.js';
6
- import { success, info, warn, spinner, assertApiOk, formatOrderSide, wrapAction, requireTransactionConfirmation } from '../utils.js';
6
+ import { success, info, warn, spinner, assertApiOk, formatOrderSide, wrapAction, requireTransactionConfirmation, validateAddress } from '../utils.js';
7
7
  import { requireTouchId } from '../touchid.js';
8
8
  import { printTxResult, printTable, printKV, POSITION_COLUMNS, FILL_COLUMNS } from '../formatters.js';
9
9
  // ─── deposit ─────────────────────────────────────────────────────────────
@@ -48,7 +48,7 @@ const withdrawCmd = new Command('withdraw')
48
48
  : await numberPrompt({ message: 'USDC amount to withdraw:', min: 0.01, required: true });
49
49
  const toAddress = opts.to ?? await input({
50
50
  message: 'Destination address:',
51
- validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
51
+ validate: (v) => validateAddress(v, 'arbitrum'),
52
52
  });
53
53
  console.log(`\n Withdraw : ${chalk.bold(amount)} USDC → ${chalk.yellow(toAddress)}\n`);
54
54
  warn('Withdrawals may take time to process.');
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import { input } from '@inquirer/prompts';
3
3
  import { transfer } from '../api/crosschain.js';
4
4
  import { requireAuth } from '../config.js';
5
- import { success, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken } from '../utils.js';
5
+ import { success, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken, validateAddress } from '../utils.js';
6
6
  import { requireTouchId } from '../touchid.js';
7
7
  import { printTxResult } from '../formatters.js';
8
8
  export const transferCommand = new Command('transfer')
@@ -33,7 +33,7 @@ export const transferCommand = new Command('transfer')
33
33
  // ── 4. Recipient ─────────────────────────────────────────────────────
34
34
  const recipient = opts.to ?? await input({
35
35
  message: 'Recipient address:',
36
- validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
36
+ validate: (v) => validateAddress(v, chain),
37
37
  });
38
38
  // ── 5. Confirm & Touch ID ──────────────────────────────────────────
39
39
  if (!opts.yes) {
@@ -3,7 +3,7 @@ import { input } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
4
  import { transfer, getAssets } from '../api/crosschain.js';
5
5
  import { requireAuth } from '../config.js';
6
- import { success, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken } from '../utils.js';
6
+ import { success, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation, lookupToken, validateAddress } from '../utils.js';
7
7
  import { requireTouchId } from '../touchid.js';
8
8
  import { printTxResult } from '../formatters.js';
9
9
  export const withdrawCommand = new Command('withdraw')
@@ -57,7 +57,7 @@ export const withdrawCommand = new Command('withdraw')
57
57
  // ── 5. Destination ───────────────────────────────────────────────────
58
58
  const recipient = opts.to ?? await input({
59
59
  message: 'Destination address (your external wallet):',
60
- validate: (v) => (v.length > 5 ? true : 'Enter a valid address'),
60
+ validate: (v) => validateAddress(v, chain),
61
61
  });
62
62
  // ── 6. Confirm & Touch ID ──────────────────────────────────────────
63
63
  if (!opts.yes) {
package/dist/config.js CHANGED
@@ -14,8 +14,7 @@ function ensureDir() {
14
14
  // ─── Credentials ─────────────────────────────────────────────────────────────
15
15
  export function saveCredentials(creds) {
16
16
  ensureDir();
17
- writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), 'utf-8');
18
- chmodSync(CREDENTIALS_FILE, 0o600);
17
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { encoding: 'utf-8', mode: 0o600 });
19
18
  }
20
19
  export function loadCredentials() {
21
20
  if (!existsSync(CREDENTIALS_FILE))
@@ -65,7 +64,7 @@ export function saveConfig(config) {
65
64
  ensureDir();
66
65
  const current = loadConfig();
67
66
  const merged = { ...current, ...config };
68
- writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), 'utf-8');
67
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { encoding: 'utf-8', mode: 0o600 });
69
68
  }
70
69
  export function getMinaraDir() {
71
70
  ensureDir();
@@ -11,6 +11,15 @@
11
11
  */
12
12
  import { createServer } from 'node:http';
13
13
  import { URL } from 'node:url';
14
+ // ── Helpers ───────────────────────────────────────────────────────────────
15
+ function escapeHtml(str) {
16
+ return str
17
+ .replace(/&/g, '&')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;')
20
+ .replace(/"/g, '&quot;')
21
+ .replace(/'/g, '&#39;');
22
+ }
14
23
  // ── HTML responses ────────────────────────────────────────────────────────
15
24
  const SUCCESS_HTML = `<!DOCTYPE html>
16
25
  <html>
@@ -48,7 +57,7 @@ const ERROR_HTML = (msg) => `<!DOCTYPE html>
48
57
  <body>
49
58
  <div class="card">
50
59
  <h1>✖ Login Failed</h1>
51
- <p>${msg}</p>
60
+ <p>${escapeHtml(msg)}</p>
52
61
  <p>Please return to the terminal and try again.</p>
53
62
  </div>
54
63
  </body>
package/dist/utils.d.ts CHANGED
@@ -75,5 +75,10 @@ export declare function requireTransactionConfirmation(description: string, toke
75
75
  amount?: string;
76
76
  destination?: string;
77
77
  }): Promise<void>;
78
+ /**
79
+ * Validate a blockchain address based on the target chain.
80
+ * Returns `true` on success or an error message string on failure.
81
+ */
82
+ export declare function validateAddress(address: string, chain?: string): true | string;
78
83
  /** Open a URL in the user's default browser (cross-platform). */
79
84
  export declare function openBrowser(url: string): void;
package/dist/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
- import { exec } from 'node:child_process';
3
+ import { execFile } from 'node:child_process';
4
4
  import { platform } from 'node:os';
5
5
  import { select, confirm } from '@inquirer/prompts';
6
6
  import { SUPPORTED_CHAINS } from './types.js';
@@ -340,16 +340,54 @@ export async function requireTransactionConfirmation(description, token, details
340
340
  process.exit(0);
341
341
  }
342
342
  }
343
+ // ─── Address validation ──────────────────────────────────────────────────────
344
+ const SOLANA_ADDR_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
345
+ const EVM_ADDR_RE = /^0x[0-9a-fA-F]{40}$/;
346
+ /**
347
+ * Validate a blockchain address based on the target chain.
348
+ * Returns `true` on success or an error message string on failure.
349
+ */
350
+ export function validateAddress(address, chain) {
351
+ const v = address.trim();
352
+ if (!v)
353
+ return 'Address is required';
354
+ const c = chain?.toLowerCase();
355
+ if (c === 'solana' || c === 'sol' || c === '101') {
356
+ if (!SOLANA_ADDR_RE.test(v))
357
+ return 'Invalid Solana address (expected base58, 32–44 chars)';
358
+ return true;
359
+ }
360
+ if (c && c !== 'solana') {
361
+ if (!EVM_ADDR_RE.test(v))
362
+ return 'Invalid EVM address (expected 0x + 40 hex chars)';
363
+ return true;
364
+ }
365
+ // Unknown chain — accept either format
366
+ if (!SOLANA_ADDR_RE.test(v) && !EVM_ADDR_RE.test(v)) {
367
+ return 'Invalid address format';
368
+ }
369
+ return true;
370
+ }
343
371
  // ─── Browser ──────────────────────────────────────────────────────────────────
344
372
  /** Open a URL in the user's default browser (cross-platform). */
345
373
  export function openBrowser(url) {
346
374
  const plat = platform();
347
- const cmd = plat === 'darwin' ? 'open' :
348
- plat === 'win32' ? 'start ""' :
349
- /* linux / others */ 'xdg-open';
350
- exec(`${cmd} "${url}"`, (err) => {
375
+ let cmd;
376
+ let args;
377
+ if (plat === 'darwin') {
378
+ cmd = 'open';
379
+ args = [url];
380
+ }
381
+ else if (plat === 'win32') {
382
+ cmd = 'cmd';
383
+ args = ['/c', 'start', '', url];
384
+ }
385
+ else {
386
+ cmd = 'xdg-open';
387
+ args = [url];
388
+ }
389
+ execFile(cmd, args, (err) => {
351
390
  if (err) {
352
- // Don't crash — the user can manually open the URL
353
391
  console.log(chalk.dim(`Could not open browser automatically. Please open this URL manually:`));
354
392
  console.log(chalk.cyan(url));
355
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minara",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "CLI client for Minara.ai — login, trade, deposit/withdraw, chat and more from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",