minara 0.2.0 → 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/README.md CHANGED
@@ -18,9 +18,9 @@
18
18
 
19
19
  ## Features
20
20
 
21
- - **AI Chat** — Crypto-native AI for on-chain analysis, market research, and DeFi due diligence. Interactive REPL & single-shot queries with fast / quality / thinking modes
22
- - **Wallet** — Balances, deposits, and withdrawals across all supported chains
23
- - **Trading** — Cross-chain swaps, perpetual futures, limit orders, and copy trading. Accepts `$TICKER`, token name, or contract address
21
+ - **AI Chat** — Crypto-native AI for on-chain analysis, market research, and DeFi due diligence. Interactive REPL & single-shot queries with `fast` / `quality` / `thinking` modes
22
+ - **Wallet & Balance** — Unified balance view, spot holdings with PnL, perps account overview, deposits and withdrawals
23
+ - **Chain-Abstracted Trading** — Cross-chain swaps with automatic chain detection, perpetual futures, and limit orders. Accepts `$TICKER`, token name, or contract address
24
24
  - **Market Discovery** — Trending tokens, Fear & Greed Index, on-chain metrics, and token / stock search
25
25
 
26
26
  ## Installation
@@ -55,8 +55,8 @@ minara chat
55
55
  # Or send a single question
56
56
  minara chat "What's the best DeFi yield right now?"
57
57
 
58
- # Swap tokens (accepts ticker or address)
59
- minara swap -c solana -t '$BONK' -s buy -a 100
58
+ # Swap tokens (chain auto-detected from token)
59
+ minara swap -t '$BONK' -s buy -a 100
60
60
 
61
61
  # View trending tokens
62
62
  minara discover trending
@@ -81,19 +81,23 @@ minara login --apple # Apple ID (opens browser)
81
81
 
82
82
  ### Wallet & Funds
83
83
 
84
- | Command | Description |
85
- | --------------------- | ----------------------------------------------------- |
86
- | `minara assets` | View wallet assets (interactive: spot / perps / both) |
87
- | `minara assets spot` | View spot wallet balances across all chains |
88
- | `minara assets perps` | View perps account balance and open positions |
89
- | `minara deposit` | Show deposit addresses and supported networks |
90
- | `minara withdraw` | Withdraw tokens to an external wallet |
84
+ | Command | Description |
85
+ | --------------------- | ----------------------------------------------- |
86
+ | `minara balance` | Combined USDC/USDT balance across spot and perps |
87
+ | `minara assets` | Full overview: spot holdings + perps account |
88
+ | `minara assets spot` | Spot wallet: portfolio value, cost, PnL, holdings |
89
+ | `minara assets perps` | Perps account: equity, margin, positions |
90
+ | `minara deposit` | Deposit to spot (view addresses) or perps (direct / from spot) |
91
+ | `minara withdraw` | Withdraw tokens to an external wallet |
91
92
 
92
93
  ```bash
93
- minara assets # Interactive: Spot / Perps / Both
94
- minara assets spot # Spot wallet across all chains
95
- minara assets perps # Perps account balance + positions
96
- minara deposit
94
+ minara balance # Quick total: Spot + Perps available balance
95
+ minara assets # Full overview (spot + perps)
96
+ minara assets spot # Spot wallet with PnL breakdown
97
+ minara assets perps # Perps equity, margin, positions
98
+ minara deposit # Interactive: Spot (addresses) or Perps (address / transfer)
99
+ minara deposit spot # Show spot wallet deposit addresses (EVM + Solana)
100
+ minara deposit perps # Perps: show Arbitrum deposit address, or transfer from Spot → Perps
97
101
  minara withdraw -c solana -t '$SOL' -a 10 --to <address>
98
102
  minara withdraw # Interactive mode (accepts ticker or address)
99
103
  ```
@@ -102,23 +106,25 @@ minara withdraw # Interactive mode (accepts ticker or address)
102
106
 
103
107
  | Command | Description |
104
108
  | ----------------- | ---------------------------------- |
105
- | `minara swap` | Swap tokens (cross-chain) |
109
+ | `minara swap` | Swap tokens (chain auto-detected) |
106
110
  | `minara transfer` | Transfer tokens to another address |
107
111
 
108
112
  ```bash
109
- minara swap # Interactive
110
- minara swap -c solana -s buy -t '$BONK' -a 100 # By ticker
111
- minara swap -c solana -s buy -t <address> -a 100 # By contract address
113
+ minara swap # Interactive: side → token → amount
114
+ minara swap -s buy -t '$BONK' -a 100 # Buy by ticker (chain auto-detected)
115
+ minara swap -s sell -t '$NVDAx' -a all # Sell entire balance
112
116
  minara swap --dry-run # Simulate without executing
113
117
  ```
114
118
 
115
- > **Token input:** All token fields (`-t`) accept a `$TICKER` (e.g. `$BONK`), a token name, or a contract address. When multiple tokens match, you'll be prompted to select the correct one with full contract addresses displayed.
119
+ > **Chain abstraction:** The chain is automatically detected from the token. If a token exists on multiple chains (e.g. USDC), you'll be prompted to pick one, sorted by gas cost (lowest first). Sell mode supports `all` to sell full balance, and caps amounts exceeding your balance.
120
+ >
121
+ > **Token input:** All token fields (`-t`) accept a `$TICKER` (e.g. `$BONK`), a token name, or a contract address.
116
122
 
117
123
  ### Perpetual Futures
118
124
 
119
125
  | Command | Description |
120
126
  | --------------------------- | ----------------------------------------- |
121
- | `minara perps deposit` | Deposit USDC to Hyperliquid perps account |
127
+ | `minara perps deposit` | Deposit USDC to perps (or use `minara deposit perps`) |
122
128
  | `minara perps withdraw` | Withdraw USDC from perps account |
123
129
  | `minara perps positions` | View all open positions |
124
130
  | `minara perps order` | Place an order (interactive builder) |
@@ -149,23 +155,6 @@ minara limit-order list # Show all orders with status
149
155
  minara limit-order cancel abc123 # Cancel order by ID
150
156
  ```
151
157
 
152
- ### Copy Trading
153
-
154
- | Command | Description |
155
- | ------------------------------- | --------------------------- |
156
- | `minara copy-trade create` | Create a new copy-trade bot |
157
- | `minara copy-trade list` | List all copy-trade bots |
158
- | `minara copy-trade start <id>` | Start a paused bot |
159
- | `minara copy-trade stop <id>` | Pause a running bot |
160
- | `minara copy-trade delete <id>` | Delete a bot permanently |
161
-
162
- ```bash
163
- minara copy-trade create # Interactive: target wallet, chain, amount, options
164
- minara copy-trade list # Show all bots with status
165
- minara copy-trade start abc123 # Resume a paused bot
166
- minara copy-trade stop abc123 # Pause a running bot
167
- ```
168
-
169
158
  ### AI Chat
170
159
 
171
160
  | Command | Description |
@@ -279,12 +268,15 @@ All fund-related operations go through a multi-layer safety flow:
279
268
  4. Execute
280
269
  ```
281
270
 
282
- The **transaction confirmation** shows the token ticker, name, full contract address, and operation details before asking for final approval:
271
+ The **transaction confirmation** shows chain, token, address, side, amount, and operation details before asking for final approval:
283
272
 
284
273
  ```
285
274
  ⚠ Transaction confirmation
275
+ Chain : solana
286
276
  Token : $BONK — Bonk
287
277
  Address : DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
278
+ Side : BUY
279
+ Amount : 100 USD
288
280
  Action : BUY swap · 100 USD · solana
289
281
  ? Are you sure you want to proceed? (y/N)
290
282
  ```
@@ -299,7 +291,7 @@ Minara CLI supports macOS Touch ID to protect all fund-related operations. When
299
291
  minara config # Select "Touch ID" to enable / disable
300
292
  ```
301
293
 
302
- **Protected operations:** `withdraw`, `transfer`, `swap`, `perps deposit`, `perps withdraw`, `perps order`, `limit-order create`, `copy-trade create`
294
+ **Protected operations:** `withdraw`, `transfer`, `swap`, `deposit` (Spot→Perps transfer), `perps deposit`, `perps withdraw`, `perps order`, `limit-order create`
303
295
 
304
296
  > **Note:** Touch ID requires macOS with Touch ID hardware. The `--yes` flag skips the initial confirmation prompt but does **not** bypass transaction confirmation or Touch ID.
305
297
 
@@ -11,8 +11,6 @@ export declare function cancelOrders(token: string, dto: PerpsCancelOrdersDto):
11
11
  export declare function modifyOrders(token: string, dto: PerpsCancelOrdersDto): Promise<import("../types.js").ApiResponse<TransactionResult>>;
12
12
  /** Update leverage */
13
13
  export declare function updateLeverage(token: string, dto: UpdateLeverageDto): Promise<import("../types.js").ApiResponse<void>>;
14
- /** Get perps account state (balance, equity, margin) */
15
- export declare function getAccountState(token: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>>>;
16
14
  /** Get all positions */
17
15
  export declare function getPositions(token: string): Promise<import("../types.js").ApiResponse<PerpsPosition[]>>;
18
16
  /** Get completed trades */
@@ -23,6 +21,8 @@ export declare function getTokenPrices(token: string): Promise<import("../types.
23
21
  export declare function getFundRecords(token: string, page: number, limit: number): Promise<import("../types.js").ApiResponse<Record<string, unknown>[]>>;
24
22
  /** Get equity history chart */
25
23
  export declare function getEquityHistory(token: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>>>;
24
+ /** Get perps account summary (balance, equity, positions, PnL) */
25
+ export declare function getAccountSummary(token: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>>>;
26
26
  /** Get all decisions */
27
27
  export declare function getDecisions(token: string): Promise<import("../types.js").ApiResponse<Record<string, unknown>[]>>;
28
28
  /** Claim rewards */
package/dist/api/perps.js CHANGED
@@ -23,10 +23,6 @@ export function modifyOrders(token, dto) {
23
23
  export function updateLeverage(token, dto) {
24
24
  return post('/v1/tx/perps/update-leverage', { token, body: dto });
25
25
  }
26
- /** Get perps account state (balance, equity, margin) */
27
- export function getAccountState(token) {
28
- return get('/v1/tx/perps/account-state', { token });
29
- }
30
26
  /** Get all positions */
31
27
  export function getPositions(token) {
32
28
  return get('/v1/tx/perps/positions/all', { token });
@@ -47,6 +43,10 @@ export function getFundRecords(token, page, limit) {
47
43
  export function getEquityHistory(token) {
48
44
  return get('/v1/tx/perps/equity-history-chart/all', { token });
49
45
  }
46
+ /** Get perps account summary (balance, equity, positions, PnL) */
47
+ export function getAccountSummary(token) {
48
+ return get('/v1/fully-managed/account-summary', { token });
49
+ }
50
50
  /** Get all decisions */
51
51
  export function getDecisions(token) {
52
52
  return get('/v1/tx/perps/decisions/all', { token });
@@ -1,11 +1,10 @@
1
1
  import { Command } from 'commander';
2
- import { select } from '@inquirer/prompts';
3
2
  import chalk from 'chalk';
4
- import { getAssets } from '../api/crosschain.js';
5
3
  import * as perpsApi from '../api/perps.js';
4
+ import { get } from '../api/client.js';
6
5
  import { requireAuth } from '../config.js';
7
- import { spinner, unwrapApi, wrapAction } from '../utils.js';
8
- import { printKV, printTable, ASSET_COLUMNS, POSITION_COLUMNS } from '../formatters.js';
6
+ import { spinner, wrapAction } from '../utils.js';
7
+ import { printTable, SPOT_COLUMNS, POSITION_COLUMNS } from '../formatters.js';
9
8
  // ─── spot ────────────────────────────────────────────────────────────────
10
9
  const spotCmd = new Command('spot')
11
10
  .description('View spot wallet assets across chains')
@@ -13,22 +12,62 @@ const spotCmd = new Command('spot')
13
12
  const creds = requireAuth();
14
13
  await showSpotAssets(creds.accessToken);
15
14
  }));
15
+ const MIN_DISPLAY_VALUE = 0.01;
16
16
  async function showSpotAssets(token) {
17
17
  const spin = spinner('Fetching spot assets…');
18
- const res = await getAssets(token);
18
+ const res = await get('/users/pnls/all', { token });
19
19
  spin.stop();
20
- const data = unwrapApi(res, 'Failed to fetch spot assets');
21
- if (Array.isArray(data) && data.length === 0) {
22
- console.log(chalk.dim('No spot assets found.'));
20
+ if (!res.success || !res.data) {
21
+ console.log('');
22
+ console.log(chalk.bold('Spot Wallet Assets:'));
23
+ console.log(chalk.dim(' Could not fetch spot assets.'));
24
+ if (res.error?.message)
25
+ console.log(chalk.dim(` ${res.error.message}`));
26
+ console.log('');
23
27
  return;
24
28
  }
29
+ const all = res.data;
30
+ const holdings = [];
31
+ let totalValue = 0;
32
+ let totalRealizedPnl = 0;
33
+ let totalUnrealizedPnl = 0;
34
+ let hasUnrealizedPnl = false;
35
+ for (const t of all) {
36
+ const bal = Number(t.balance ?? 0);
37
+ const price = Number(t.marketPrice ?? 0);
38
+ const apiVal = Number(t.portfolioValue ?? 0);
39
+ const value = apiVal > 0 ? apiVal : bal * price;
40
+ const uPnl = Number(t.unrealizedPnl ?? 0);
41
+ const rPnl = Number(t.realizedPnl ?? 0);
42
+ totalValue += value;
43
+ totalRealizedPnl += rPnl;
44
+ if (uPnl !== 0) {
45
+ totalUnrealizedPnl += uPnl;
46
+ hasUnrealizedPnl = true;
47
+ }
48
+ if (bal > 0 && value >= MIN_DISPLAY_VALUE) {
49
+ holdings.push({ ...t, _value: value });
50
+ }
51
+ }
52
+ const fmt = (n) => `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
53
+ const pnlFmt = (n) => {
54
+ if (n === 0)
55
+ return chalk.dim('$0.00');
56
+ const color = n >= 0 ? chalk.green : chalk.red;
57
+ return color(`${n >= 0 ? '+' : ''}${fmt(n)}`);
58
+ };
25
59
  console.log('');
26
- console.log(chalk.bold('Spot Wallet Assets:'));
27
- if (Array.isArray(data)) {
28
- printTable(data, ASSET_COLUMNS);
60
+ console.log(chalk.bold('Spot Wallet:'));
61
+ console.log(` Portfolio Value : ${fmt(totalValue)}`);
62
+ console.log(` Unrealized PnL : ${pnlFmt(totalUnrealizedPnl)}`);
63
+ console.log(` Realized PnL : ${pnlFmt(totalRealizedPnl)}`);
64
+ console.log('');
65
+ console.log(chalk.bold(`Holdings (${holdings.length}):`));
66
+ if (holdings.length === 0) {
67
+ console.log(chalk.dim(' No spot assets with balance.'));
29
68
  }
30
69
  else {
31
- printKV(data);
70
+ printTable(holdings, SPOT_COLUMNS);
32
71
  }
33
72
  console.log('');
34
73
  }
@@ -40,42 +79,38 @@ const perpsCmd = new Command('perps')
40
79
  await showPerpsAssets(creds.accessToken);
41
80
  }));
42
81
  async function showPerpsAssets(token) {
43
- // Fetch account state and positions in parallel
44
82
  const spin = spinner('Fetching perps account…');
45
- const [accountRes, positionsRes] = await Promise.all([
46
- perpsApi.getAccountState(token),
47
- perpsApi.getPositions(token),
48
- ]);
83
+ const res = await perpsApi.getAccountSummary(token);
49
84
  spin.stop();
50
- // ── Account state ───────────────────────────────────────────────────
85
+ if (!res.success || !res.data) {
86
+ console.log(chalk.dim(' Could not fetch perps account.'));
87
+ if (res.error?.message)
88
+ console.log(chalk.dim(` ${res.error.message}`));
89
+ return;
90
+ }
91
+ const d = res.data;
92
+ const fmt = (n) => `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
93
+ const pnlFmt = (n) => {
94
+ const color = n >= 0 ? chalk.green : chalk.red;
95
+ return color(`${n >= 0 ? '+' : ''}${fmt(n)}`);
96
+ };
97
+ // ── Account overview ───────────────────────────────────────────────
51
98
  console.log('');
52
99
  console.log(chalk.bold('Perps Account:'));
53
- if (accountRes.success && accountRes.data) {
54
- printKV(accountRes.data);
55
- }
56
- else {
57
- console.log(chalk.dim(' Could not fetch account state.'));
58
- if (accountRes.error?.message) {
59
- console.log(chalk.dim(` ${accountRes.error.message}`));
60
- }
61
- }
100
+ console.log(` Equity : ${fmt(Number(d.equityValue ?? 0))}`);
101
+ console.log(` Available : ${fmt(Number(d.dispatchableValue ?? 0))}`);
102
+ console.log(` Margin Used : ${fmt(Number(d.totalMarginUsed ?? 0))}`);
103
+ console.log(` Unrealized PnL: ${pnlFmt(Number(d.totalUnrealizedPnl ?? 0))}`);
104
+ console.log(` Withdrawable : ${fmt(Number(d.withdrawableValue ?? 0))}`);
62
105
  // ── Positions ───────────────────────────────────────────────────────
106
+ const positions = Array.isArray(d.positions) ? d.positions : [];
63
107
  console.log('');
64
- console.log(chalk.bold('Open Positions:'));
65
- if (!positionsRes.success) {
66
- console.log(chalk.dim(' Could not fetch positions.'));
67
- if (positionsRes.error?.message) {
68
- console.log(chalk.dim(` ${positionsRes.error.message}`));
69
- }
108
+ console.log(chalk.bold(`Open Positions (${positions.length}):`));
109
+ if (positions.length === 0) {
110
+ console.log(chalk.dim(' No open positions.'));
70
111
  }
71
112
  else {
72
- const positions = positionsRes.data;
73
- if (!positions || (Array.isArray(positions) && positions.length === 0)) {
74
- console.log(chalk.dim(' No open positions.'));
75
- }
76
- else {
77
- printTable(positions, POSITION_COLUMNS);
78
- }
113
+ printTable(positions, POSITION_COLUMNS);
79
114
  }
80
115
  console.log('');
81
116
  }
@@ -85,19 +120,7 @@ export const assetsCommand = new Command('assets')
85
120
  .addCommand(spotCmd)
86
121
  .addCommand(perpsCmd)
87
122
  .action(wrapAction(async () => {
88
- const action = await select({
89
- message: 'View assets:',
90
- choices: [
91
- { name: 'Spot wallet', value: 'spot' },
92
- { name: 'Perps account', value: 'perps' },
93
- { name: 'Both', value: 'both' },
94
- ],
95
- });
96
123
  const creds = requireAuth();
97
- if (action === 'spot' || action === 'both') {
98
- await showSpotAssets(creds.accessToken);
99
- }
100
- if (action === 'perps' || action === 'both') {
101
- await showPerpsAssets(creds.accessToken);
102
- }
124
+ await showSpotAssets(creds.accessToken);
125
+ await showPerpsAssets(creds.accessToken);
103
126
  }));
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const balanceCommand: Command;
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { get } from '../api/client.js';
4
+ import * as perpsApi from '../api/perps.js';
5
+ import { requireAuth } from '../config.js';
6
+ import { spinner, wrapAction } from '../utils.js';
7
+ const STABLES = new Set(['usdc', 'usdt']);
8
+ export const balanceCommand = new Command('balance')
9
+ .description('Show combined USDC / USDT balance across spot and perps')
10
+ .action(wrapAction(async () => {
11
+ const creds = requireAuth();
12
+ const spin = spinner('Fetching balances…');
13
+ const [spotRes, perpsRes] = await Promise.all([
14
+ get('/users/pnls/all', { token: creds.accessToken }),
15
+ perpsApi.getAccountSummary(creds.accessToken),
16
+ ]);
17
+ spin.stop();
18
+ const fmt = (n) => `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
19
+ let spotStable = 0;
20
+ if (spotRes.success && Array.isArray(spotRes.data)) {
21
+ for (const t of spotRes.data) {
22
+ const sym = String(t.tokenSymbol ?? '').toLowerCase();
23
+ if (STABLES.has(sym)) {
24
+ const bal = Number(t.balance ?? 0);
25
+ const price = Number(t.marketPrice ?? 1);
26
+ spotStable += bal * price;
27
+ }
28
+ }
29
+ }
30
+ let perpsAvailable = 0;
31
+ if (perpsRes.success && perpsRes.data) {
32
+ const d = perpsRes.data;
33
+ perpsAvailable = Number(d.dispatchableValue ?? 0);
34
+ }
35
+ const total = spotStable + perpsAvailable;
36
+ console.log('');
37
+ console.log(chalk.bold('Balance:'));
38
+ console.log(` Spot (USDC/USDT) : ${fmt(spotStable)}`);
39
+ console.log(` Perps (available) : ${fmt(perpsAvailable)}`);
40
+ console.log(` ${'─'.repeat(30)}`);
41
+ console.log(` Total : ${chalk.bold(fmt(total))}`);
42
+ console.log('');
43
+ }));
@@ -1,83 +1,150 @@
1
1
  import { Command } from 'commander';
2
- import { select } from '@inquirer/prompts';
2
+ import { select, number as numberPrompt } from '@inquirer/prompts';
3
3
  import chalk from 'chalk';
4
- import Table from 'cli-table3';
5
- import { getCurrentUser } from '../api/auth.js';
6
4
  import { getAccount } from '../api/crosschain.js';
5
+ import { getCurrentUser } from '../api/auth.js';
6
+ import * as perpsApi from '../api/perps.js';
7
7
  import { requireAuth } from '../config.js';
8
- import { info, spinner, unwrapApi, wrapAction } from '../utils.js';
9
- import { printKV } from '../formatters.js';
10
- /**
11
- * Map wallet type keys from /auth/me human-readable chain info.
12
- */
13
- function describeWalletType(key) {
14
- const lower = key.toLowerCase();
15
- if (lower.includes('evm') || lower === 'spot-evm' || lower === 'abstraction-evm') {
16
- return {
17
- network: 'EVM',
18
- chains: ['Ethereum', 'Base', 'Arbitrum', 'Optimism', 'Polygon', 'Avalanche', 'BSC', 'Berachain', 'Blast'],
19
- };
20
- }
21
- if (lower.includes('solana')) {
22
- return { network: 'Solana', chains: ['Solana'] };
23
- }
24
- return { network: key, chains: [key] };
25
- }
26
- export const depositCommand = new Command('deposit')
27
- .description('Show your deposit addresses and supported networks')
8
+ import { info, success, spinner, assertApiOk, wrapAction, requireTransactionConfirmation } from '../utils.js';
9
+ import { requireTouchId } from '../touchid.js';
10
+ import { printTxResult } from '../formatters.js';
11
+ const EVM_CHAINS = 'Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche, BSC, Berachain, Blast';
12
+ // ─── spot ────────────────────────────────────────────────────────────────
13
+ const spotCmd = new Command('spot')
14
+ .description('Show spot wallet deposit addresses')
28
15
  .action(wrapAction(async () => {
29
16
  const creds = requireAuth();
17
+ await showSpotDeposit(creds.accessToken);
18
+ }));
19
+ async function showSpotDeposit(token) {
30
20
  const spin = spinner('Fetching deposit addresses…');
31
- const [userRes, accountRes] = await Promise.all([
32
- getCurrentUser(creds.accessToken),
33
- getAccount(creds.accessToken),
34
- ]);
21
+ const res = await getAccount(token);
35
22
  spin.stop();
36
- const user = unwrapApi(userRes, 'Failed to fetch account info');
37
- const wallets = user.wallets;
38
- if (!wallets || Object.keys(wallets).length === 0) {
39
- info('No wallet addresses found. Your account may not have been fully initialized.');
40
- info('Try logging in at https://minara.ai first, then run this command again.');
23
+ if (!res.success || !res.data) {
24
+ info('Could not fetch deposit addresses. Try logging in at https://minara.ai first.');
25
+ return;
26
+ }
27
+ const data = res.data;
28
+ const evmAddr = data.evmAddress;
29
+ const solAddr = data.solanaAddress;
30
+ if (!evmAddr && !solAddr) {
31
+ info('No deposit addresses found. Your account may not have been fully initialized.');
41
32
  return;
42
33
  }
43
34
  console.log('');
44
- console.log(chalk.bold('Deposit Addresses'));
35
+ console.log(chalk.bold('Spot Deposit Addresses'));
45
36
  console.log(chalk.dim('Send tokens to the addresses below. Make sure to use the correct network!'));
46
37
  console.log('');
47
- const table = new Table({
48
- head: [chalk.white('Network'), chalk.white('Address'), chalk.white('Supported Chains')],
49
- colWidths: [14, 48, 40],
50
- wordWrap: true,
51
- });
52
- const seen = new Set();
53
- for (const [walletType, address] of Object.entries(wallets)) {
54
- if (!address || seen.has(address))
55
- continue;
56
- seen.add(address);
57
- const { network, chains } = describeWalletType(walletType);
58
- table.push([chalk.cyan.bold(network), chalk.yellow(address), chains.join(', ')]);
38
+ if (solAddr) {
39
+ console.log(` ${chalk.cyan.bold('Solana')}`);
40
+ console.log(` Address : ${chalk.yellow(solAddr)}`);
41
+ console.log(` Chains : Solana`);
42
+ console.log('');
43
+ }
44
+ if (evmAddr) {
45
+ console.log(` ${chalk.cyan.bold('EVM')}`);
46
+ console.log(` Address : ${chalk.yellow(evmAddr)}`);
47
+ console.log(` Chains : ${EVM_CHAINS}`);
48
+ console.log('');
59
49
  }
60
- console.log(table.toString());
61
- console.log('');
62
50
  console.log(chalk.red.bold('Important:'));
63
51
  console.log(chalk.red(' • Only send tokens on the supported chains listed above.'));
64
52
  console.log(chalk.red(' • Sending tokens on the wrong network may result in permanent loss.'));
65
- console.log(chalk.red(' • EVM address supports all EVM-compatible chains (Ethereum, Base, Arbitrum, etc.)'));
66
53
  console.log('');
67
- if (accountRes.success && accountRes.data) {
68
- const wantDetails = await select({
69
- message: 'Would you like to see detailed account info?',
70
- choices: [
71
- { name: 'Yes', value: true },
72
- { name: 'No', value: false },
73
- ],
74
- default: false,
54
+ }
55
+ // ─── perps ───────────────────────────────────────────────────────────────
56
+ const perpsCmd = new Command('perps')
57
+ .description('Deposit USDC to perps account')
58
+ .option('-a, --amount <amount>', 'USDC amount (for transfer)')
59
+ .option('-y, --yes', 'Skip confirmation')
60
+ .action(wrapAction(async (opts) => {
61
+ const creds = requireAuth();
62
+ await perpsDepositFlow(creds.accessToken, opts);
63
+ }));
64
+ async function perpsDepositFlow(token, opts) {
65
+ const method = await select({
66
+ message: 'How would you like to deposit to perps?',
67
+ choices: [
68
+ { name: 'Show perps deposit address (for external transfers)', value: 'address' },
69
+ { name: `${chalk.bold('Transfer from Spot wallet → Perps wallet')} (internal)`, value: 'transfer' },
70
+ ],
71
+ });
72
+ if (method === 'address') {
73
+ await showPerpsDepositAddresses(token);
74
+ }
75
+ else {
76
+ await transferSpotToPerps(token, opts);
77
+ }
78
+ }
79
+ async function showPerpsDepositAddresses(token) {
80
+ const spin = spinner('Fetching perps deposit addresses…');
81
+ const res = await getCurrentUser(token);
82
+ spin.stop();
83
+ if (!res.success || !res.data) {
84
+ info('Could not fetch perps addresses. Try logging in at https://minara.ai first.');
85
+ return;
86
+ }
87
+ const wallets = res.data.wallets ?? {};
88
+ const perpsEvm = wallets['perpetual-evm'];
89
+ if (!perpsEvm) {
90
+ info('No perps deposit address found. Your perps account may not have been initialized yet.');
91
+ return;
92
+ }
93
+ console.log('');
94
+ console.log(chalk.bold('Perps Deposit Address'));
95
+ console.log(chalk.dim('Send USDC to the address below to fund your perps account directly.'));
96
+ console.log('');
97
+ console.log(` ${chalk.cyan.bold('EVM (Arbitrum)')}`);
98
+ console.log(` Address : ${chalk.yellow(perpsEvm)}`);
99
+ console.log('');
100
+ console.log(chalk.red.bold('Important:'));
101
+ console.log(chalk.red(' • Only send USDC on Arbitrum to this address.'));
102
+ console.log(chalk.red(' • Sending other tokens or using the wrong network may result in permanent loss.'));
103
+ console.log('');
104
+ }
105
+ async function transferSpotToPerps(token, opts) {
106
+ console.log('');
107
+ console.log(chalk.yellow.bold('⚠ This will transfer USDC from your Spot wallet to your Perps wallet.'));
108
+ console.log('');
109
+ const amount = opts?.amount
110
+ ? parseFloat(opts.amount)
111
+ : await numberPrompt({ message: 'USDC amount to transfer from Spot → Perps (min 5):', min: 5, required: true });
112
+ if (!amount || amount < 5) {
113
+ console.error(chalk.red('✖'), 'Minimum deposit is 5 USDC');
114
+ process.exit(1);
115
+ }
116
+ if (!opts?.yes) {
117
+ await requireTransactionConfirmation(`Transfer ${amount} USDC from Spot → Perps`, undefined, {
118
+ amount: `${amount} USDC`,
119
+ side: 'Spot → Perps',
75
120
  });
76
- if (wantDetails) {
77
- console.log('');
78
- console.log(chalk.bold('Account Details:'));
79
- printKV(accountRes.data);
80
- console.log('');
81
- }
121
+ }
122
+ await requireTouchId();
123
+ const spin = spinner('Transferring…');
124
+ const res = await perpsApi.deposit(token, { usdcAmount: amount });
125
+ spin.stop();
126
+ assertApiOk(res, 'Transfer failed');
127
+ success(`Transferred ${amount} USDC from Spot wallet to Perps wallet`);
128
+ printTxResult(res.data);
129
+ }
130
+ // ─── parent ──────────────────────────────────────────────────────────────
131
+ export const depositCommand = new Command('deposit')
132
+ .description('Deposit to spot wallet or perps account')
133
+ .addCommand(spotCmd)
134
+ .addCommand(perpsCmd)
135
+ .action(wrapAction(async () => {
136
+ const action = await select({
137
+ message: 'Deposit to:',
138
+ choices: [
139
+ { name: 'Spot wallet — view deposit addresses', value: 'spot' },
140
+ { name: 'Perps wallet — view deposit address or transfer from Spot', value: 'perps' },
141
+ ],
142
+ });
143
+ const creds = requireAuth();
144
+ if (action === 'spot') {
145
+ await showSpotDeposit(creds.accessToken);
146
+ }
147
+ else {
148
+ await perpsDepositFlow(creds.accessToken);
82
149
  }
83
150
  }));
@@ -54,7 +54,7 @@ const createCmd = new Command('create')
54
54
  if (!ok)
55
55
  return;
56
56
  }
57
- await requireTransactionConfirmation(`Limit ${side} · $${amount} · price ${priceCondition} $${targetPrice} · ${chain}`, tokenInfo);
57
+ await requireTransactionConfirmation(`Limit ${side} · $${amount} · price ${priceCondition} $${targetPrice} · ${chain}`, tokenInfo, { chain, side, amount: `$${amount}` });
58
58
  await requireTouchId();
59
59
  const spin = spinner('Creating limit order…');
60
60
  const res = await loApi.createLimitOrder(creds.accessToken, {