minara 0.1.5 → 0.2.1
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/LICENSE +21 -0
- package/README.md +176 -77
- package/dist/api/payment.d.ts +17 -0
- package/dist/api/payment.js +39 -0
- package/dist/api/perps.d.ts +2 -0
- package/dist/api/perps.js +4 -0
- package/dist/api/tokens.d.ts +4 -0
- package/dist/api/tokens.js +8 -0
- package/dist/commands/assets.js +115 -11
- package/dist/commands/balance.d.ts +2 -0
- package/dist/commands/balance.js +43 -0
- package/dist/commands/chat.js +77 -53
- package/dist/commands/config.js +82 -5
- package/dist/commands/copy-trade.js +10 -4
- package/dist/commands/deposit.js +134 -59
- package/dist/commands/discover.js +31 -4
- package/dist/commands/limit-order.js +16 -8
- package/dist/commands/login.js +17 -1
- package/dist/commands/perps.js +48 -13
- package/dist/commands/premium.d.ts +2 -0
- package/dist/commands/premium.js +417 -0
- package/dist/commands/swap.js +80 -22
- package/dist/commands/transfer.js +17 -11
- package/dist/commands/withdraw.js +17 -11
- package/dist/config.d.ts +2 -0
- package/dist/config.js +1 -0
- package/dist/formatters.d.ts +54 -0
- package/dist/formatters.js +384 -0
- package/dist/index.js +13 -3
- package/dist/touchid.d.ts +18 -0
- package/dist/touchid.js +181 -0
- package/dist/types.d.ts +55 -41
- package/dist/utils.d.ts +44 -0
- package/dist/utils.js +224 -1
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -25,15 +25,12 @@ async function* parseSSE(response) {
|
|
|
25
25
|
if (!line)
|
|
26
26
|
continue;
|
|
27
27
|
// Handle AI SDK v5 streaming format: "type:value"
|
|
28
|
-
// e.g., "0:text", "1:reasoning", "9:tool_call"
|
|
29
28
|
const colonIndex = line.indexOf(':');
|
|
30
29
|
if (colonIndex !== -1) {
|
|
31
30
|
const type = line.slice(0, colonIndex);
|
|
32
31
|
const data = line.slice(colonIndex + 1);
|
|
33
|
-
// Type 0 is text content
|
|
34
32
|
if (type === '0' && data) {
|
|
35
33
|
try {
|
|
36
|
-
// Data might be JSON-encoded string like "text" or actual JSON
|
|
37
34
|
const parsed = JSON.parse(data);
|
|
38
35
|
if (typeof parsed === 'string') {
|
|
39
36
|
yield parsed;
|
|
@@ -46,12 +43,9 @@ async function* parseSSE(response) {
|
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
catch {
|
|
49
|
-
// If parsing fails, treat as raw text
|
|
50
46
|
yield data;
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
|
-
// Type 1 is reasoning (can be skipped or shown differently)
|
|
54
|
-
// Type 9 is tool_call (can be skipped for now)
|
|
55
49
|
continue;
|
|
56
50
|
}
|
|
57
51
|
// Handle standard SSE format: "data:json"
|
|
@@ -70,7 +64,6 @@ async function* parseSSE(response) {
|
|
|
70
64
|
yield text;
|
|
71
65
|
}
|
|
72
66
|
catch {
|
|
73
|
-
// Non-JSON data line — might be raw text
|
|
74
67
|
if (data)
|
|
75
68
|
yield data;
|
|
76
69
|
}
|
|
@@ -83,13 +76,13 @@ async function* parseSSE(response) {
|
|
|
83
76
|
}
|
|
84
77
|
}
|
|
85
78
|
export const chatCommand = new Command('chat')
|
|
86
|
-
.description('Chat with Minara AI assistant')
|
|
87
|
-
.argument('[message]', 'Send a single message
|
|
79
|
+
.description('Chat with Minara AI assistant (interactive REPL when no message given)')
|
|
80
|
+
.argument('[message]', 'Send a single message and exit')
|
|
88
81
|
.option('-c, --chat-id <id>', 'Continue existing chat')
|
|
89
82
|
.option('--list', 'List past chats')
|
|
90
83
|
.option('--history <chatId>', 'Show chat history')
|
|
91
84
|
.option('--thinking', 'Enable thinking/degen mode')
|
|
92
|
-
.option('--
|
|
85
|
+
.option('--quality', 'Use quality mode instead of the default fast mode')
|
|
93
86
|
.action(wrapAction(async (messageArg, opts) => {
|
|
94
87
|
const creds = requireAuth();
|
|
95
88
|
// ── List chats ───────────────────────────────────────────────────────
|
|
@@ -122,43 +115,16 @@ export const chatCommand = new Command('chat')
|
|
|
122
115
|
}
|
|
123
116
|
// ── Chat context ─────────────────────────────────────────────────────
|
|
124
117
|
let chatId = opts?.chatId;
|
|
125
|
-
if (!chatId && !messageArg) {
|
|
126
|
-
const mode = await select({
|
|
127
|
-
message: 'Chat mode:',
|
|
128
|
-
choices: [
|
|
129
|
-
{ name: 'Start a new conversation', value: 'new' },
|
|
130
|
-
{ name: 'Continue existing conversation', value: 'continue' },
|
|
131
|
-
],
|
|
132
|
-
});
|
|
133
|
-
if (mode === 'continue') {
|
|
134
|
-
const spin = spinner('Fetching chats…');
|
|
135
|
-
const res = await listChats(creds.accessToken);
|
|
136
|
-
spin.stop();
|
|
137
|
-
const chats = res.data;
|
|
138
|
-
if (chats && chats.length > 0) {
|
|
139
|
-
chatId = await select({
|
|
140
|
-
message: 'Select chat:',
|
|
141
|
-
choices: chats.map((c) => ({
|
|
142
|
-
name: `${(c.chatId).slice(0, 12)}… ${c.name ?? '(untitled)'}`,
|
|
143
|
-
value: c.chatId,
|
|
144
|
-
})),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
info('No existing chats. Starting new.');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
118
|
if (!chatId)
|
|
153
119
|
chatId = randomUUID();
|
|
154
|
-
// ──
|
|
120
|
+
// ── Stream a response and print to stdout ────────────────────────────
|
|
155
121
|
async function sendAndPrint(msg) {
|
|
156
|
-
process.stdout.write(
|
|
122
|
+
process.stdout.write(chalk.green.bold('Minara') + chalk.dim(': '));
|
|
157
123
|
const response = await sendChatStream(creds.accessToken, {
|
|
158
124
|
chatId,
|
|
159
125
|
message: { role: 'user', content: msg },
|
|
160
126
|
thinking: opts?.thinking,
|
|
161
|
-
|
|
127
|
+
workMode: opts?.quality ? 'quality' : 'fast',
|
|
162
128
|
chartOptions: { chartsCountRecommendedLimit: 0 },
|
|
163
129
|
});
|
|
164
130
|
if (!response.ok) {
|
|
@@ -195,35 +161,45 @@ export const chatCommand = new Command('chat')
|
|
|
195
161
|
}
|
|
196
162
|
console.log('\n');
|
|
197
163
|
}
|
|
164
|
+
// ── Single-shot mode: minara chat "message" ──────────────────────────
|
|
198
165
|
if (messageArg) {
|
|
199
166
|
await sendAndPrint(messageArg);
|
|
200
167
|
return;
|
|
201
168
|
}
|
|
202
|
-
// ── REPL
|
|
169
|
+
// ── Interactive REPL mode ────────────────────────────────────────────
|
|
170
|
+
const modeFlags = [
|
|
171
|
+
opts?.quality ? chalk.cyan('quality') : chalk.green('fast'),
|
|
172
|
+
opts?.thinking && chalk.yellow('thinking'),
|
|
173
|
+
].filter(Boolean);
|
|
174
|
+
const modeStr = modeFlags.length ? ` ${chalk.dim('[')}${modeFlags.join(chalk.dim(', '))}${chalk.dim(']')}` : '';
|
|
203
175
|
console.log('');
|
|
204
|
-
console.log(chalk.bold('Minara AI Chat')
|
|
205
|
-
|
|
176
|
+
console.log(chalk.green.bold('Minara AI Chat') +
|
|
177
|
+
chalk.dim(` session:${chatId.slice(0, 8)}`) +
|
|
178
|
+
modeStr);
|
|
179
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
180
|
+
console.log(chalk.dim('Type a message to chat. /help for commands, Ctrl+C to exit.'));
|
|
206
181
|
console.log('');
|
|
207
182
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
208
|
-
// Fix: Pause readline before streaming response to prevent prompt interference
|
|
209
183
|
async function sendAndPrintWithPause(msg) {
|
|
210
|
-
rl.pause();
|
|
184
|
+
rl.pause();
|
|
211
185
|
try {
|
|
212
186
|
await sendAndPrint(msg);
|
|
213
187
|
}
|
|
214
188
|
finally {
|
|
215
|
-
rl.resume();
|
|
216
|
-
process.stdout.write('\n');
|
|
189
|
+
rl.resume();
|
|
190
|
+
process.stdout.write('\n');
|
|
217
191
|
}
|
|
218
192
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
193
|
+
const ask = () => new Promise((resolve) => rl.question(chalk.blue.bold('>>> '), resolve));
|
|
194
|
+
rl.on('close', () => {
|
|
195
|
+
console.log(chalk.dim('\nGoodbye!'));
|
|
196
|
+
process.exit(0);
|
|
221
197
|
});
|
|
222
|
-
rl.on('close', () => { console.log(chalk.dim('\nGoodbye!')); process.exit(0); });
|
|
223
198
|
while (true) {
|
|
224
|
-
const userMsg = (await
|
|
199
|
+
const userMsg = (await ask()).trim();
|
|
225
200
|
if (!userMsg)
|
|
226
201
|
continue;
|
|
202
|
+
// ── REPL commands ──────────────────────────────────────────────────
|
|
227
203
|
if (userMsg.toLowerCase() === 'exit' || userMsg.toLowerCase() === 'quit') {
|
|
228
204
|
console.log(chalk.dim('Goodbye!'));
|
|
229
205
|
rl.close();
|
|
@@ -231,15 +207,63 @@ export const chatCommand = new Command('chat')
|
|
|
231
207
|
}
|
|
232
208
|
if (userMsg === '/new') {
|
|
233
209
|
chatId = randomUUID();
|
|
234
|
-
info(
|
|
210
|
+
info(`New conversation started ${chalk.dim(`(session:${chatId.slice(0, 8)})`)}`);
|
|
235
211
|
continue;
|
|
236
212
|
}
|
|
237
213
|
if (userMsg === '/id') {
|
|
238
214
|
console.log(chalk.dim(`Chat ID: ${chatId}`));
|
|
239
215
|
continue;
|
|
240
216
|
}
|
|
217
|
+
if (userMsg === '/continue') {
|
|
218
|
+
const spin = spinner('Fetching chats…');
|
|
219
|
+
const res = await listChats(creds.accessToken);
|
|
220
|
+
spin.stop();
|
|
221
|
+
const chats = res.data;
|
|
222
|
+
if (chats && chats.length > 0) {
|
|
223
|
+
const selected = await select({
|
|
224
|
+
message: 'Select a chat to continue:',
|
|
225
|
+
choices: chats.map((c) => ({
|
|
226
|
+
name: `${(c.chatId).slice(0, 12)}… ${c.name ?? '(untitled)'}`,
|
|
227
|
+
value: c.chatId,
|
|
228
|
+
})),
|
|
229
|
+
});
|
|
230
|
+
chatId = selected;
|
|
231
|
+
info(`Continuing chat ${chalk.dim(`(session:${chatId.slice(0, 8)})`)}`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
info('No existing chats found.');
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (userMsg === '/list') {
|
|
239
|
+
const spin = spinner('Fetching chats…');
|
|
240
|
+
const res = await listChats(creds.accessToken);
|
|
241
|
+
spin.stop();
|
|
242
|
+
const chats = res.data;
|
|
243
|
+
if (chats && chats.length > 0) {
|
|
244
|
+
console.log('');
|
|
245
|
+
for (const c of chats) {
|
|
246
|
+
const id = chalk.dim(c.chatId.slice(0, 8));
|
|
247
|
+
const name = c.name ?? chalk.dim('(untitled)');
|
|
248
|
+
const time = c.updatedAt ? chalk.dim(` ${c.updatedAt}`) : '';
|
|
249
|
+
console.log(` ${id} ${name}${time}`);
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
info('No chats yet.');
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
241
258
|
if (userMsg === '/help') {
|
|
242
|
-
console.log(
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log(chalk.bold(' Commands:'));
|
|
261
|
+
console.log(chalk.dim(' /new ') + 'Start a new conversation');
|
|
262
|
+
console.log(chalk.dim(' /continue ') + 'Continue an existing conversation');
|
|
263
|
+
console.log(chalk.dim(' /list ') + 'List all historical chats');
|
|
264
|
+
console.log(chalk.dim(' /id ') + 'Show current chat ID');
|
|
265
|
+
console.log(chalk.dim(' exit ') + 'Quit the chat');
|
|
266
|
+
console.log('');
|
|
243
267
|
continue;
|
|
244
268
|
}
|
|
245
269
|
await sendAndPrintWithPause(userMsg);
|
package/dist/commands/config.js
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
import { input, select, confirm } from '@inquirer/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { loadConfig, saveConfig, getMinaraDir } from '../config.js';
|
|
5
|
-
import { success, info, wrapAction } from '../utils.js';
|
|
5
|
+
import { success, info, warn, wrapAction } from '../utils.js';
|
|
6
|
+
import { isTouchIdAvailable } from '../touchid.js';
|
|
6
7
|
export const configCommand = new Command('config')
|
|
7
8
|
.description('View or update CLI configuration')
|
|
8
9
|
.action(wrapAction(async () => {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const confirmTx = config.confirmBeforeTransaction !== false;
|
|
9
12
|
const action = await select({
|
|
10
13
|
message: 'Configuration:',
|
|
11
14
|
choices: [
|
|
12
15
|
{ name: 'Show current config', value: 'show' },
|
|
13
16
|
{ name: 'Set base URL', value: 'baseUrl' },
|
|
17
|
+
{
|
|
18
|
+
name: `Touch ID ${config.touchId ? chalk.green('[ON]') : chalk.dim('[OFF]')}`,
|
|
19
|
+
value: 'touchId',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: `Transaction Confirmation ${confirmTx ? chalk.green('[ON]') : chalk.dim('[OFF]')}`,
|
|
23
|
+
value: 'confirmTx',
|
|
24
|
+
},
|
|
14
25
|
{ name: 'Show config directory path', value: 'path' },
|
|
15
26
|
],
|
|
16
27
|
});
|
|
17
|
-
const config = loadConfig();
|
|
18
28
|
switch (action) {
|
|
19
29
|
case 'show':
|
|
20
30
|
console.log('');
|
|
21
31
|
console.log(chalk.bold('Current Configuration:'));
|
|
22
|
-
console.log(` Base URL
|
|
23
|
-
console.log(`
|
|
32
|
+
console.log(` Base URL : ${chalk.cyan(config.baseUrl)}`);
|
|
33
|
+
console.log(` Touch ID : ${config.touchId ? chalk.green('Enabled') : chalk.dim('Disabled')}`);
|
|
34
|
+
console.log(` Confirm Tx : ${confirmTx ? chalk.green('Enabled') : chalk.dim('Disabled')}`);
|
|
35
|
+
console.log(` Config Dir : ${chalk.dim(getMinaraDir())}`);
|
|
24
36
|
console.log('');
|
|
25
37
|
break;
|
|
26
38
|
case 'baseUrl': {
|
|
@@ -41,6 +53,71 @@ export const configCommand = new Command('config')
|
|
|
41
53
|
success(`Base URL set to ${url}`);
|
|
42
54
|
break;
|
|
43
55
|
}
|
|
56
|
+
case 'touchId': {
|
|
57
|
+
if (config.touchId) {
|
|
58
|
+
// Currently enabled — offer to disable
|
|
59
|
+
const disable = await confirm({
|
|
60
|
+
message: 'Touch ID is currently enabled. Disable it?',
|
|
61
|
+
default: false,
|
|
62
|
+
});
|
|
63
|
+
if (disable) {
|
|
64
|
+
saveConfig({ touchId: false });
|
|
65
|
+
success('Touch ID protection disabled.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Currently disabled — check availability then enable
|
|
70
|
+
console.log('');
|
|
71
|
+
info('Checking Touch ID availability…');
|
|
72
|
+
if (!isTouchIdAvailable()) {
|
|
73
|
+
console.log('');
|
|
74
|
+
warn('Touch ID is not available on this device.');
|
|
75
|
+
console.log(chalk.dim(' Make sure you are on a Mac with Touch ID and have enrolled at least one fingerprint.'));
|
|
76
|
+
console.log('');
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
console.log(chalk.green('✔'), 'Touch ID hardware detected.');
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(chalk.dim(' When enabled, all fund-related operations (transfer, withdraw, swap, order, etc.)'));
|
|
82
|
+
console.log(chalk.dim(' will require Touch ID verification before execution.'));
|
|
83
|
+
console.log('');
|
|
84
|
+
const enable = await confirm({
|
|
85
|
+
message: 'Enable Touch ID protection?',
|
|
86
|
+
default: true,
|
|
87
|
+
});
|
|
88
|
+
if (enable) {
|
|
89
|
+
saveConfig({ touchId: true });
|
|
90
|
+
success('Touch ID protection enabled!');
|
|
91
|
+
console.log(chalk.dim(' All fund-related operations now require fingerprint verification.'));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'confirmTx': {
|
|
97
|
+
if (confirmTx) {
|
|
98
|
+
const disable = await confirm({
|
|
99
|
+
message: 'Transaction confirmation is currently enabled. Disable it?',
|
|
100
|
+
default: false,
|
|
101
|
+
});
|
|
102
|
+
if (disable) {
|
|
103
|
+
saveConfig({ confirmBeforeTransaction: false });
|
|
104
|
+
success('Transaction confirmation disabled.');
|
|
105
|
+
warn('Fund-related operations will no longer require a second confirmation.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const enable = await confirm({
|
|
110
|
+
message: 'Enable transaction confirmation for fund-related operations?',
|
|
111
|
+
default: true,
|
|
112
|
+
});
|
|
113
|
+
if (enable) {
|
|
114
|
+
saveConfig({ confirmBeforeTransaction: true });
|
|
115
|
+
success('Transaction confirmation enabled!');
|
|
116
|
+
console.log(chalk.dim(' All fund-related operations now require a second confirmation before execution.'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
44
121
|
case 'path':
|
|
45
122
|
info(`Config directory: ${getMinaraDir()}`);
|
|
46
123
|
break;
|
|
@@ -3,7 +3,9 @@ import { input, select, confirm, number as numberPrompt } from '@inquirer/prompt
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as ctApi from '../api/copytrade.js';
|
|
5
5
|
import { requireAuth } from '../config.js';
|
|
6
|
-
import { success, info, spinner, assertApiOk, selectChain, wrapAction } from '../utils.js';
|
|
6
|
+
import { success, info, spinner, assertApiOk, selectChain, wrapAction, requireTransactionConfirmation } from '../utils.js';
|
|
7
|
+
import { requireTouchId } from '../touchid.js';
|
|
8
|
+
import { printTxResult, printTable, COPY_TRADE_COLUMNS } from '../formatters.js';
|
|
7
9
|
// ─── create ──────────────────────────────────────────────────────────────
|
|
8
10
|
const createCmd = new Command('create')
|
|
9
11
|
.description('Create a copy trade bot')
|
|
@@ -48,6 +50,8 @@ const createCmd = new Command('create')
|
|
|
48
50
|
if (!ok)
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
53
|
+
await requireTransactionConfirmation(`Copy trade · $${fixedAmount}/trade · target ${targetAddress} · ${chain}`);
|
|
54
|
+
await requireTouchId();
|
|
51
55
|
const spin = spinner('Creating copy trade…');
|
|
52
56
|
const res = await ctApi.createCopyTrade(creds.accessToken, {
|
|
53
57
|
chain, targetAddress, name,
|
|
@@ -60,8 +64,7 @@ const createCmd = new Command('create')
|
|
|
60
64
|
spin.stop();
|
|
61
65
|
assertApiOk(res, 'Failed to create copy trade');
|
|
62
66
|
success('Copy trade created!');
|
|
63
|
-
|
|
64
|
-
console.log(JSON.stringify(res.data, null, 2));
|
|
67
|
+
printTxResult(res.data);
|
|
65
68
|
}));
|
|
66
69
|
// ─── list ────────────────────────────────────────────────────────────────
|
|
67
70
|
const listCmd = new Command('list')
|
|
@@ -78,7 +81,10 @@ const listCmd = new Command('list')
|
|
|
78
81
|
console.log(chalk.dim('No copy trades.'));
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
|
-
console.log(
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(chalk.bold('Copy Trades:'));
|
|
86
|
+
printTable(data, COPY_TRADE_COLUMNS);
|
|
87
|
+
console.log('');
|
|
82
88
|
}));
|
|
83
89
|
// ─── start / stop ────────────────────────────────────────────────────────
|
|
84
90
|
async function pickCopyTrade(token) {
|
package/dist/commands/deposit.js
CHANGED
|
@@ -1,79 +1,154 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { select } from '@inquirer/prompts';
|
|
2
|
+
import { select, confirm, 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
network: 'EVM',
|
|
17
|
-
chains: ['Ethereum', 'Base', 'Arbitrum', 'Optimism', 'Polygon', 'Avalanche', 'BSC', 'Berachain', 'Blast'],
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
if (lower.includes('solana')) {
|
|
21
|
-
return { network: 'Solana', chains: ['Solana'] };
|
|
22
|
-
}
|
|
23
|
-
return { network: key, chains: [key] };
|
|
24
|
-
}
|
|
25
|
-
export const depositCommand = new Command('deposit')
|
|
26
|
-
.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')
|
|
27
15
|
.action(wrapAction(async () => {
|
|
28
16
|
const creds = requireAuth();
|
|
17
|
+
await showSpotDeposit(creds.accessToken);
|
|
18
|
+
}));
|
|
19
|
+
async function showSpotDeposit(token) {
|
|
29
20
|
const spin = spinner('Fetching deposit addresses…');
|
|
30
|
-
const
|
|
31
|
-
getCurrentUser(creds.accessToken),
|
|
32
|
-
getAccount(creds.accessToken),
|
|
33
|
-
]);
|
|
21
|
+
const res = await getAccount(token);
|
|
34
22
|
spin.stop();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.');
|
|
40
32
|
return;
|
|
41
33
|
}
|
|
42
34
|
console.log('');
|
|
43
|
-
console.log(chalk.bold('Deposit Addresses'));
|
|
35
|
+
console.log(chalk.bold('Spot Deposit Addresses'));
|
|
44
36
|
console.log(chalk.dim('Send tokens to the addresses below. Make sure to use the correct network!'));
|
|
45
37
|
console.log('');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
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('');
|
|
58
49
|
}
|
|
59
|
-
console.log(table.toString());
|
|
60
|
-
console.log('');
|
|
61
50
|
console.log(chalk.red.bold('Important:'));
|
|
62
51
|
console.log(chalk.red(' • Only send tokens on the supported chains listed above.'));
|
|
63
52
|
console.log(chalk.red(' • Sending tokens on the wrong network may result in permanent loss.'));
|
|
64
|
-
console.log(chalk.red(' • EVM address supports all EVM-compatible chains (Ethereum, Base, Arbitrum, etc.)'));
|
|
65
53
|
console.log('');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
console.log(`\n Transfer : ${chalk.bold(amount)} USDC ${chalk.dim('Spot wallet')} → ${chalk.cyan('Perps wallet')}\n`);
|
|
117
|
+
if (!opts?.yes) {
|
|
118
|
+
const ok = await confirm({ message: 'Confirm transfer from Spot to Perps?', default: true });
|
|
119
|
+
if (!ok)
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await requireTransactionConfirmation(`Transfer ${amount} USDC from Spot → Perps`, undefined, {
|
|
123
|
+
amount: `${amount} USDC`,
|
|
124
|
+
side: 'Spot → Perps',
|
|
125
|
+
});
|
|
126
|
+
await requireTouchId();
|
|
127
|
+
const spin = spinner('Transferring…');
|
|
128
|
+
const res = await perpsApi.deposit(token, { usdcAmount: amount });
|
|
129
|
+
spin.stop();
|
|
130
|
+
assertApiOk(res, 'Transfer failed');
|
|
131
|
+
success(`Transferred ${amount} USDC from Spot wallet to Perps wallet`);
|
|
132
|
+
printTxResult(res.data);
|
|
133
|
+
}
|
|
134
|
+
// ─── parent ──────────────────────────────────────────────────────────────
|
|
135
|
+
export const depositCommand = new Command('deposit')
|
|
136
|
+
.description('Deposit to spot wallet or perps account')
|
|
137
|
+
.addCommand(spotCmd)
|
|
138
|
+
.addCommand(perpsCmd)
|
|
139
|
+
.action(wrapAction(async () => {
|
|
140
|
+
const action = await select({
|
|
141
|
+
message: 'Deposit to:',
|
|
142
|
+
choices: [
|
|
143
|
+
{ name: 'Spot wallet — view deposit addresses', value: 'spot' },
|
|
144
|
+
{ name: 'Perps wallet — view deposit address or transfer from Spot', value: 'perps' },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
const creds = requireAuth();
|
|
148
|
+
if (action === 'spot') {
|
|
149
|
+
await showSpotDeposit(creds.accessToken);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
await perpsDepositFlow(creds.accessToken);
|
|
78
153
|
}
|
|
79
154
|
}));
|