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 +33 -41
- package/dist/api/perps.d.ts +2 -2
- package/dist/api/perps.js +4 -4
- package/dist/commands/assets.js +77 -54
- package/dist/commands/balance.d.ts +2 -0
- package/dist/commands/balance.js +43 -0
- package/dist/commands/deposit.js +129 -62
- package/dist/commands/limit-order.js +1 -1
- package/dist/commands/premium.js +25 -6
- package/dist/commands/swap.js +59 -35
- package/dist/commands/transfer.js +4 -20
- package/dist/commands/withdraw.js +4 -24
- package/dist/formatters.d.ts +2 -4
- package/dist/formatters.js +31 -23
- package/dist/index.js +3 -3
- package/dist/types.d.ts +0 -35
- package/dist/utils.d.ts +12 -1
- package/dist/utils.js +129 -9
- package/package.json +1 -1
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** —
|
|
23
|
-
- **Trading** — Cross-chain swaps, perpetual futures, limit orders
|
|
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 (
|
|
59
|
-
minara swap -
|
|
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
|
|
87
|
-
| `minara assets
|
|
88
|
-
| `minara assets
|
|
89
|
-
| `minara
|
|
90
|
-
| `minara
|
|
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
|
|
94
|
-
minara assets
|
|
95
|
-
minara assets
|
|
96
|
-
minara
|
|
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 (
|
|
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 -
|
|
111
|
-
minara swap -
|
|
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
|
-
> **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/dist/api/perps.d.ts
CHANGED
|
@@ -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 });
|
package/dist/commands/assets.js
CHANGED
|
@@ -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,
|
|
8
|
-
import {
|
|
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
|
|
18
|
+
const res = await get('/users/pnls/all', { token });
|
|
19
19
|
spin.stop();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.log(chalk.
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
46
|
-
perpsApi.getAccountState(token),
|
|
47
|
-
perpsApi.getPositions(token),
|
|
48
|
-
]);
|
|
83
|
+
const res = await perpsApi.getAccountSummary(token);
|
|
49
84
|
spin.stop();
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
65
|
-
if (
|
|
66
|
-
console.log(chalk.dim('
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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,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
|
+
}));
|
package/dist/commands/deposit.js
CHANGED
|
@@ -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,
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
32
|
-
getCurrentUser(creds.accessToken),
|
|
33
|
-
getAccount(creds.accessToken),
|
|
34
|
-
]);
|
|
21
|
+
const res = await getAccount(token);
|
|
35
22
|
spin.stop();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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, {
|