minara 0.4.5 → 0.4.7
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/dist/api/crosschain.d.ts +2 -2
- package/dist/commands/deposit.js +16 -64
- package/dist/commands/discover.js +2 -3
- package/dist/commands/limit-order.js +47 -13
- package/dist/commands/perps.js +49 -15
- package/dist/commands/premium.js +0 -77
- package/dist/commands/swap.js +21 -12
- package/dist/commands/transfer.js +16 -0
- package/dist/commands/withdraw.js +37 -19
- package/dist/formatters.d.ts +9 -0
- package/dist/formatters.js +112 -0
- package/dist/types.d.ts +48 -0
- package/dist/utils.js +7 -7
- package/package.json +1 -1
package/dist/api/crosschain.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CrossChainSwapDto, CrossChainTransferDto, CrossChainActivitiesDto, CrossChainAccount, WalletAsset, TransactionResult } from '../types.js';
|
|
1
|
+
import type { CrossChainSwapDto, CrossChainTransferDto, CrossChainActivitiesDto, CrossChainAccount, WalletAsset, TransactionResult, CrossChainSwapsSimulateItem } from '../types.js';
|
|
2
2
|
/** Get cross-chain account info */
|
|
3
3
|
export declare function getAccount(token: string): Promise<import("../types.js").ApiResponse<CrossChainAccount>>;
|
|
4
4
|
/** Get wallet assets */
|
|
@@ -8,7 +8,7 @@ export declare function swap(token: string, dto: CrossChainSwapDto): Promise<imp
|
|
|
8
8
|
/** Execute multiple swaps */
|
|
9
9
|
export declare function swaps(token: string, swapList: CrossChainSwapDto[]): Promise<import("../types.js").ApiResponse<TransactionResult[]>>;
|
|
10
10
|
/** Simulate swaps (dry-run) */
|
|
11
|
-
export declare function swapsSimulate(token: string, swapList: CrossChainSwapDto[]): Promise<import("../types.js").ApiResponse<
|
|
11
|
+
export declare function swapsSimulate(token: string, swapList: CrossChainSwapDto[]): Promise<import("../types.js").ApiResponse<CrossChainSwapsSimulateItem[]>>;
|
|
12
12
|
/** Transfer tokens */
|
|
13
13
|
export declare function transfer(token: string, dto: CrossChainTransferDto): Promise<import("../types.js").ApiResponse<TransactionResult>>;
|
|
14
14
|
/** Get activities */
|
package/dist/commands/deposit.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getAccount } from '../api/crosschain.js';
|
|
|
5
5
|
import { getCurrentUser } from '../api/auth.js';
|
|
6
6
|
import * as perpsApi from '../api/perps.js';
|
|
7
7
|
import { requireAuth } from '../config.js';
|
|
8
|
-
import { info, success,
|
|
8
|
+
import { info, success, spinner, assertApiOk, wrapAction, requireTransactionConfirmation } from '../utils.js';
|
|
9
9
|
import { requireTouchId } from '../touchid.js';
|
|
10
10
|
import { printTxResult } from '../formatters.js';
|
|
11
11
|
const EVM_CHAINS = 'Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche, BSC, Berachain, Blast';
|
|
@@ -52,65 +52,27 @@ async function showSpotDeposit(token) {
|
|
|
52
52
|
console.log(chalk.red(' • Sending tokens on the wrong network may result in permanent loss.'));
|
|
53
53
|
console.log('');
|
|
54
54
|
}
|
|
55
|
-
// ─── moonpay (credit card on-ramp) ───────────────────────────────────────
|
|
56
|
-
const MOONPAY_PK = process.env.MOONPAY_PK ?? 'pk_live_yIf64w79W6ufwip4j51PWbymdwGtI';
|
|
57
|
-
const MOONPAY_CURRENCIES = [
|
|
58
|
-
{ name: 'USDC (Base)', code: 'usdc_base', network: 'base' },
|
|
59
|
-
{ name: 'USDC (Ethereum)', code: 'usdc', network: 'ethereum' },
|
|
60
|
-
{ name: 'USDC (Arbitrum)', code: 'usdc_arbitrum', network: 'arbitrum' },
|
|
61
|
-
{ name: 'USDC (Polygon)', code: 'usdc_polygon', network: 'polygon' },
|
|
62
|
-
{ name: 'ETH', code: 'eth', network: 'ethereum' },
|
|
63
|
-
{ name: 'ETH (Base)', code: 'eth_base', network: 'base' },
|
|
64
|
-
{ name: 'SOL', code: 'sol', network: 'solana' },
|
|
65
|
-
];
|
|
66
|
-
async function moonPayOnRamp(token) {
|
|
67
|
-
const addrSpin = spinner('Fetching wallet address…');
|
|
68
|
-
const accountRes = await getAccount(token);
|
|
69
|
-
addrSpin.stop();
|
|
70
|
-
const account = accountRes.data;
|
|
71
|
-
const evmAddr = account?.evmAddress;
|
|
72
|
-
const solAddr = account?.solanaAddress;
|
|
73
|
-
if (!evmAddr && !solAddr) {
|
|
74
|
-
warn('No wallet address found. Your account may not be fully initialized.');
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const currency = await select({
|
|
78
|
-
message: 'Currency to buy:',
|
|
79
|
-
choices: MOONPAY_CURRENCIES
|
|
80
|
-
.filter((c) => c.network === 'solana' ? !!solAddr : !!evmAddr)
|
|
81
|
-
.map((c) => ({ name: c.name, value: c })),
|
|
82
|
-
});
|
|
83
|
-
const walletAddress = currency.network === 'solana' ? solAddr : evmAddr;
|
|
84
|
-
// Build MoonPay buy URL (no walletAddress in URL — requires server-side signing
|
|
85
|
-
// which depends on backend MoonPay secret key config).
|
|
86
|
-
// User pastes their address in MoonPay's form instead.
|
|
87
|
-
const params = new URLSearchParams();
|
|
88
|
-
params.set('apiKey', MOONPAY_PK);
|
|
89
|
-
params.set('currencyCode', currency.code);
|
|
90
|
-
params.set('defaultCurrencyCode', currency.code);
|
|
91
|
-
const buyUrl = `https://buy.moonpay.com?${params.toString()}`;
|
|
92
|
-
console.log('');
|
|
93
|
-
console.log(chalk.bold('Buy Crypto with Credit Card (MoonPay)'));
|
|
94
|
-
console.log('');
|
|
95
|
-
console.log(` Currency : ${chalk.cyan(currency.name)}`);
|
|
96
|
-
console.log(` Wallet : ${chalk.yellow(walletAddress)}`);
|
|
97
|
-
console.log(chalk.dim(' ↑ Copy this address and paste it in MoonPay when prompted.'));
|
|
98
|
-
console.log('');
|
|
99
|
-
info('Opening MoonPay in your browser…');
|
|
100
|
-
openBrowser(buyUrl);
|
|
101
|
-
console.log(chalk.dim(' Complete the purchase in your browser. Funds will arrive in your Minara wallet.'));
|
|
102
|
-
console.log('');
|
|
103
|
-
}
|
|
104
55
|
// ─── perps ───────────────────────────────────────────────────────────────
|
|
105
56
|
const perpsCmd = new Command('perps')
|
|
106
57
|
.description('Deposit USDC to perps account')
|
|
107
|
-
.option('-a, --amount <amount>', 'USDC amount (for transfer)')
|
|
58
|
+
.option('-a, --amount <amount>', 'USDC amount (for transfer from Spot)')
|
|
59
|
+
.option('--address', 'Show perps deposit address (non-interactive)')
|
|
108
60
|
.option('-y, --yes', 'Skip confirmation')
|
|
109
61
|
.action(wrapAction(async (opts) => {
|
|
110
62
|
const creds = requireAuth();
|
|
111
63
|
await perpsDepositFlow(creds.accessToken, opts);
|
|
112
64
|
}));
|
|
113
65
|
async function perpsDepositFlow(token, opts) {
|
|
66
|
+
// Non-interactive mode: --address or --amount specified
|
|
67
|
+
if (opts?.address) {
|
|
68
|
+
await showPerpsDepositAddresses(token);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (opts?.amount) {
|
|
72
|
+
await transferSpotToPerps(token, opts);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Interactive mode
|
|
114
76
|
const method = await select({
|
|
115
77
|
message: 'How would you like to deposit to perps?',
|
|
116
78
|
choices: [
|
|
@@ -177,35 +139,25 @@ async function transferSpotToPerps(token, opts) {
|
|
|
177
139
|
printTxResult(res.data);
|
|
178
140
|
}
|
|
179
141
|
// ─── parent ──────────────────────────────────────────────────────────────
|
|
180
|
-
const buyCmd = new Command('buy')
|
|
181
|
-
.description('Buy crypto with credit card via MoonPay')
|
|
182
|
-
.action(wrapAction(async () => {
|
|
183
|
-
const creds = requireAuth();
|
|
184
|
-
await moonPayOnRamp(creds.accessToken);
|
|
185
|
-
}));
|
|
186
142
|
export const depositCommand = new Command('deposit')
|
|
187
143
|
.alias('receive')
|
|
188
|
-
.description('Deposit / receive to spot wallet or perps account
|
|
144
|
+
.description('Deposit / receive to spot wallet or perps account')
|
|
189
145
|
.addCommand(spotCmd)
|
|
190
146
|
.addCommand(perpsCmd)
|
|
191
|
-
.
|
|
147
|
+
.allowExcessArguments(false)
|
|
192
148
|
.action(wrapAction(async () => {
|
|
193
149
|
const action = await select({
|
|
194
150
|
message: 'Deposit to:',
|
|
195
151
|
choices: [
|
|
196
152
|
{ name: 'Spot wallet — view deposit addresses', value: 'spot' },
|
|
197
153
|
{ name: 'Perps wallet — view deposit address or transfer from Spot', value: 'perps' },
|
|
198
|
-
{ name: `Buy crypto with credit card ${chalk.dim('(MoonPay)')}`, value: 'buy' },
|
|
199
154
|
],
|
|
200
155
|
});
|
|
201
156
|
const creds = requireAuth();
|
|
202
157
|
if (action === 'spot') {
|
|
203
158
|
await showSpotDeposit(creds.accessToken);
|
|
204
159
|
}
|
|
205
|
-
else if (action === 'perps') {
|
|
206
|
-
await perpsDepositFlow(creds.accessToken);
|
|
207
|
-
}
|
|
208
160
|
else {
|
|
209
|
-
await
|
|
161
|
+
await perpsDepositFlow(creds.accessToken);
|
|
210
162
|
}
|
|
211
163
|
}));
|
|
@@ -20,11 +20,10 @@ function flattenStock(item) {
|
|
|
20
20
|
// ─── trending ────────────────────────────────────────────────────────────
|
|
21
21
|
const trendingCmd = new Command('trending')
|
|
22
22
|
.description('View trending tokens or stocks')
|
|
23
|
-
.argument('[category]', 'tokens or stocks (default: interactive)')
|
|
24
23
|
.option('-t, --type <category>', 'Trending type: tokens or stocks (skips interactive prompt)')
|
|
25
|
-
.action(wrapAction(async (
|
|
24
|
+
.action(wrapAction(async (options) => {
|
|
26
25
|
let category;
|
|
27
|
-
const typeOpt = options?.type?.toLowerCase()
|
|
26
|
+
const typeOpt = options?.type?.toLowerCase();
|
|
28
27
|
if (typeOpt === 'tokens' || typeOpt === 'token') {
|
|
29
28
|
category = 'tokens';
|
|
30
29
|
}
|
|
@@ -9,36 +9,73 @@ import { printTxResult, printTable, LIMIT_ORDER_COLUMNS } from '../formatters.js
|
|
|
9
9
|
// ─── create ──────────────────────────────────────────────────────────────
|
|
10
10
|
const createCmd = new Command('create')
|
|
11
11
|
.description('Create a limit order')
|
|
12
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
12
|
+
.option('-y, --yes', 'Skip transaction confirmation (Touch ID still required)')
|
|
13
|
+
.option('--chain <chain>', 'Blockchain (ethereum, base, solana, etc.)')
|
|
14
|
+
.option('--side <side>', 'buy or sell')
|
|
15
|
+
.option('--token <ticker|address>', 'Token symbol or contract address')
|
|
16
|
+
.option('--condition <condition>', 'Price condition (above or below)')
|
|
17
|
+
.option('--price <number>', 'Target price in USD')
|
|
18
|
+
.option('--amount <number>', 'Amount in USD')
|
|
19
|
+
.option('--expiry <hours>', 'Expiry time in hours')
|
|
13
20
|
.action(wrapAction(async (opts) => {
|
|
14
21
|
const creds = requireAuth();
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
// Validate all flags upfront before any interactive prompts
|
|
23
|
+
if (opts.side && !['buy', 'sell'].includes(opts.side)) {
|
|
24
|
+
throw new Error(`Invalid side: ${opts.side}. Must be "buy" or "sell".`);
|
|
25
|
+
}
|
|
26
|
+
if (opts.condition && !['above', 'below'].includes(opts.condition)) {
|
|
27
|
+
throw new Error(`Invalid condition: ${opts.condition}. Must be "above" or "below".`);
|
|
28
|
+
}
|
|
29
|
+
if (opts.price !== undefined) {
|
|
30
|
+
const price = parseFloat(opts.price);
|
|
31
|
+
if (isNaN(price) || price <= 0) {
|
|
32
|
+
throw new Error('Target price must be a positive number.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (opts.amount !== undefined) {
|
|
36
|
+
const amountVal = parseFloat(opts.amount);
|
|
37
|
+
if (isNaN(amountVal) || amountVal <= 0) {
|
|
38
|
+
throw new Error('Amount must be a positive number.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (opts.expiry !== undefined) {
|
|
42
|
+
const expiryVal = parseFloat(opts.expiry);
|
|
43
|
+
if (isNaN(expiryVal) || expiryVal <= 0) {
|
|
44
|
+
throw new Error('Expiry must be a positive number of hours.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const chain = opts.chain ?? await selectChain('Chain:', true);
|
|
48
|
+
const side = opts.side ?? await select({
|
|
17
49
|
message: 'Side:',
|
|
18
50
|
choices: [
|
|
19
51
|
{ name: 'Buy', value: 'buy' },
|
|
20
52
|
{ name: 'Sell', value: 'sell' },
|
|
21
53
|
],
|
|
22
54
|
});
|
|
23
|
-
const tokenInput = await input({
|
|
55
|
+
const tokenInput = opts.token ?? await input({
|
|
24
56
|
message: 'Target token (contract address or ticker):',
|
|
25
57
|
validate: (v) => (v.length > 0 ? true : 'Required'),
|
|
26
58
|
});
|
|
27
59
|
const tokenInfo = await lookupToken(tokenInput);
|
|
28
|
-
const priceCondition = await select({
|
|
60
|
+
const priceCondition = opts.condition ?? await select({
|
|
29
61
|
message: 'Trigger when price is:',
|
|
30
62
|
choices: [
|
|
31
63
|
{ name: 'Above target price', value: 'above' },
|
|
32
64
|
{ name: 'Below target price', value: 'below' },
|
|
33
65
|
],
|
|
34
66
|
});
|
|
35
|
-
const targetPrice = await numberPrompt({ message: 'Target price (USD):', required: true });
|
|
36
|
-
const
|
|
67
|
+
const targetPrice = opts.price ? parseFloat(opts.price) : await numberPrompt({ message: 'Target price (USD):', required: true });
|
|
68
|
+
const amountInput = opts.amount ?? await input({
|
|
37
69
|
message: 'Amount (USD):',
|
|
38
70
|
validate: (v) => (parseFloat(v) > 0 ? true : 'Enter positive number'),
|
|
39
71
|
});
|
|
40
|
-
const
|
|
41
|
-
const
|
|
72
|
+
const amount = typeof amountInput === 'string' ? amountInput : String(amountInput);
|
|
73
|
+
const expireHoursRaw = opts.expiry ? parseFloat(opts.expiry) : await numberPrompt({ message: 'Expire after (hours):', default: 24 });
|
|
74
|
+
const expireHours = expireHoursRaw ?? 24;
|
|
75
|
+
if (isNaN(expireHours) || expireHours <= 0) {
|
|
76
|
+
throw new Error('Expiry must be a positive number of hours.');
|
|
77
|
+
}
|
|
78
|
+
const expiredAt = Math.floor(Date.now() / 1000) + expireHours * 3600;
|
|
42
79
|
console.log('');
|
|
43
80
|
console.log(chalk.bold('Limit Order:'));
|
|
44
81
|
console.log(` Chain : ${chalk.cyan(chain)}`);
|
|
@@ -50,11 +87,8 @@ const createCmd = new Command('create')
|
|
|
50
87
|
console.log(` Expires : ${new Date(expiredAt * 1000).toLocaleString()}`);
|
|
51
88
|
console.log('');
|
|
52
89
|
if (!opts.yes) {
|
|
53
|
-
|
|
54
|
-
if (!ok)
|
|
55
|
-
return;
|
|
90
|
+
await requireTransactionConfirmation(`Limit ${side} · $${amount} · price ${priceCondition} $${targetPrice} · ${chain}`, tokenInfo, { chain, side, amount: `$${amount}` });
|
|
56
91
|
}
|
|
57
|
-
await requireTransactionConfirmation(`Limit ${side} · $${amount} · price ${priceCondition} $${targetPrice} · ${chain}`, tokenInfo, { chain, side, amount: `$${amount}` });
|
|
58
92
|
await requireTouchId();
|
|
59
93
|
const spin = spinner('Creating limit order…');
|
|
60
94
|
const res = await loApi.createLimitOrder(creds.accessToken, {
|
package/dist/commands/perps.js
CHANGED
|
@@ -957,10 +957,11 @@ const closeCmd = new Command('close')
|
|
|
957
957
|
success(`Position closed — ${sideLabel} ${symbol} ${sz}`);
|
|
958
958
|
printTxResult(orderRes.data);
|
|
959
959
|
}));
|
|
960
|
-
// ─── leverage ────────────────────────────────────────────────────────────
|
|
961
960
|
const leverageCmd = new Command('leverage')
|
|
962
961
|
.description('Update leverage for a symbol')
|
|
963
962
|
.option(WALLET_OPT[0], WALLET_OPT[1])
|
|
963
|
+
.option('-s, --symbol <TOKEN>', 'Target token symbol (e.g. ETH, SOL, BTC)')
|
|
964
|
+
.option('-l, --leverage <VALUE>', 'Leverage multiplier (e.g. 2, 3, 5, 10)')
|
|
964
965
|
.action(wrapAction(async (opts) => {
|
|
965
966
|
const creds = requireAuth();
|
|
966
967
|
const resolved = await resolveWallet(creds.accessToken, opts.wallet, 'Update leverage on which wallet?');
|
|
@@ -970,8 +971,18 @@ const leverageCmd = new Command('leverage')
|
|
|
970
971
|
const metaSpin = spinner('Fetching available assets…');
|
|
971
972
|
const assets = await perpsApi.getAssetMeta();
|
|
972
973
|
metaSpin.stop();
|
|
974
|
+
// Validate symbol if provided via CLI
|
|
973
975
|
let symbol;
|
|
974
|
-
if (
|
|
976
|
+
if (opts.symbol) {
|
|
977
|
+
symbol = opts.symbol.toUpperCase();
|
|
978
|
+
const assetMeta = assets.find((a) => a.name.toUpperCase() === symbol);
|
|
979
|
+
if (!assetMeta) {
|
|
980
|
+
const validSymbols = assets.map((a) => a.name).join(', ');
|
|
981
|
+
console.error(chalk.red('✖'), `Invalid symbol: ${opts.symbol}. Supported: ${validSymbols}`);
|
|
982
|
+
process.exit(1);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else if (assets.length > 0) {
|
|
975
986
|
symbol = await select({
|
|
976
987
|
message: 'Asset:',
|
|
977
988
|
choices: assets.map((a) => {
|
|
@@ -988,19 +999,42 @@ const leverageCmd = new Command('leverage')
|
|
|
988
999
|
}
|
|
989
1000
|
const meta = assets.find((a) => a.name.toUpperCase() === symbol.toUpperCase());
|
|
990
1001
|
const maxLev = meta?.maxLeverage ?? 50;
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1002
|
+
// Validate and parse leverage
|
|
1003
|
+
let leverage;
|
|
1004
|
+
if (opts.leverage) {
|
|
1005
|
+
leverage = parseFloat(opts.leverage);
|
|
1006
|
+
if (isNaN(leverage) || leverage < 1) {
|
|
1007
|
+
console.error(chalk.red('✖'), `Invalid leverage: ${opts.leverage}. Must be a number >= 1.`);
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
if (leverage > maxLev) {
|
|
1011
|
+
console.error(chalk.red('✖'), `Leverage ${leverage}x exceeds maximum ${maxLev}x for ${symbol}.`);
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
leverage = await numberPrompt({
|
|
1017
|
+
message: `Leverage (1–${maxLev}x):`,
|
|
1018
|
+
min: 1,
|
|
1019
|
+
max: maxLev,
|
|
1020
|
+
required: true,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
// Determine margin mode (cross vs isolated)
|
|
1024
|
+
// In non-interactive mode with both symbol and leverage, default to cross margin
|
|
1025
|
+
let isCross;
|
|
1026
|
+
if (opts.symbol && opts.leverage) {
|
|
1027
|
+
isCross = true;
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
isCross = await select({
|
|
1031
|
+
message: 'Margin mode:',
|
|
1032
|
+
choices: [
|
|
1033
|
+
{ name: 'Cross', value: true },
|
|
1034
|
+
{ name: 'Isolated', value: false },
|
|
1035
|
+
],
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1004
1038
|
const spin = spinner('Updating leverage…');
|
|
1005
1039
|
const res = await perpsApi.updateLeverage(creds.accessToken, { symbol, isCross, leverage: leverage, subAccountId: walletId });
|
|
1006
1040
|
spin.stop();
|
package/dist/commands/premium.js
CHANGED
|
@@ -288,81 +288,6 @@ async function handleCryptoCheckout(token, planId) {
|
|
|
288
288
|
printKV(data);
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
-
// ─── buy-credits ────────────────────────────────────────────────────────
|
|
292
|
-
const buyCreditsCmd = new Command('buy-credits')
|
|
293
|
-
.description('Buy a one-time credit package')
|
|
294
|
-
.action(wrapAction(async () => {
|
|
295
|
-
const creds = requireAuth();
|
|
296
|
-
const spin = spinner('Fetching packages…');
|
|
297
|
-
const plansRes = await paymentApi.getPlans();
|
|
298
|
-
spin.stop();
|
|
299
|
-
assertApiOk(plansRes, 'Failed to fetch packages');
|
|
300
|
-
const { packages } = plansRes.data;
|
|
301
|
-
if (packages.length === 0) {
|
|
302
|
-
info('No credit packages available.');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
// Select package
|
|
306
|
-
const selectedPkgId = await select({
|
|
307
|
-
message: 'Select a credit package:',
|
|
308
|
-
choices: packages.map((pkg) => ({
|
|
309
|
-
name: `$${pkg.amount} — ${Number(pkg.credit).toLocaleString()} credits`,
|
|
310
|
-
value: pkg._id,
|
|
311
|
-
})),
|
|
312
|
-
});
|
|
313
|
-
const selectedPkg = packages.find((p) => p._id === selectedPkgId);
|
|
314
|
-
// Select payment method
|
|
315
|
-
const payMethod = await select({
|
|
316
|
-
message: 'Payment method:',
|
|
317
|
-
choices: [
|
|
318
|
-
{ name: 'Credit Card (Stripe)', value: 'stripe' },
|
|
319
|
-
{ name: 'Crypto (USDC on-chain)', value: 'crypto' },
|
|
320
|
-
],
|
|
321
|
-
});
|
|
322
|
-
console.log('');
|
|
323
|
-
console.log(chalk.bold('Package Summary:'));
|
|
324
|
-
console.log(` Price : ${chalk.bold('$' + selectedPkg.amount)}`);
|
|
325
|
-
console.log(` Credits : ${Number(selectedPkg.credit).toLocaleString()}`);
|
|
326
|
-
console.log(` Payment : ${payMethod === 'stripe' ? 'Credit Card (Stripe)' : 'Crypto (USDC)'}`);
|
|
327
|
-
console.log('');
|
|
328
|
-
const ok = await confirm({ message: 'Proceed to checkout?', default: true });
|
|
329
|
-
if (!ok) {
|
|
330
|
-
console.log(chalk.dim('Cancelled.'));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (payMethod === 'stripe') {
|
|
334
|
-
const spin2 = spinner('Creating checkout session…');
|
|
335
|
-
const res = await paymentApi.checkoutPackage(creds.accessToken, selectedPkgId, 'https://minara.ai/payment/success', 'https://minara.ai/payment/cancel');
|
|
336
|
-
spin2.stop();
|
|
337
|
-
assertApiOk(res, 'Failed to create checkout');
|
|
338
|
-
const url = res.data?.url ?? res.data?.checkoutUrl;
|
|
339
|
-
if (url) {
|
|
340
|
-
success('Opening browser for payment…');
|
|
341
|
-
console.log(chalk.cyan(` ${url}`));
|
|
342
|
-
openBrowser(url);
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
success('Checkout created:');
|
|
346
|
-
printKV(res.data);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
const spin2 = spinner('Creating crypto checkout…');
|
|
351
|
-
const res = await paymentApi.cryptoCheckoutPackage(creds.accessToken, selectedPkgId);
|
|
352
|
-
spin2.stop();
|
|
353
|
-
assertApiOk(res, 'Failed to create crypto checkout');
|
|
354
|
-
const url = res.data?.url ?? res.data?.checkoutUrl;
|
|
355
|
-
if (url) {
|
|
356
|
-
success('Opening browser for crypto payment…');
|
|
357
|
-
console.log(chalk.cyan(` ${url}`));
|
|
358
|
-
openBrowser(url);
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
success('Crypto checkout:');
|
|
362
|
-
printKV(res.data);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}));
|
|
366
291
|
// ─── cancel ─────────────────────────────────────────────────────────────
|
|
367
292
|
const cancelCmd = new Command('cancel')
|
|
368
293
|
.description('Cancel your current subscription')
|
|
@@ -398,7 +323,6 @@ export const premiumCommand = new Command('premium')
|
|
|
398
323
|
.addCommand(plansCmd)
|
|
399
324
|
.addCommand(statusCmd)
|
|
400
325
|
.addCommand(subscribeCmd)
|
|
401
|
-
.addCommand(buyCreditsCmd)
|
|
402
326
|
.addCommand(cancelCmd)
|
|
403
327
|
.action(wrapAction(async () => {
|
|
404
328
|
const action = await select({
|
|
@@ -407,7 +331,6 @@ export const premiumCommand = new Command('premium')
|
|
|
407
331
|
{ name: 'View available plans', value: 'plans' },
|
|
408
332
|
{ name: 'View my subscription', value: 'status' },
|
|
409
333
|
{ name: 'Subscribe / Change plan', value: 'subscribe' },
|
|
410
|
-
{ name: 'Buy credit package', value: 'buy-credits' },
|
|
411
334
|
{ name: 'Cancel subscription', value: 'cancel' },
|
|
412
335
|
],
|
|
413
336
|
});
|
package/dist/commands/swap.js
CHANGED
|
@@ -6,16 +6,24 @@ import { get } from '../api/client.js';
|
|
|
6
6
|
import { requireAuth } from '../config.js';
|
|
7
7
|
import { success, info, warn, spinner, assertApiOk, wrapAction, requireTransactionConfirmation, lookupToken, normalizeChain } from '../utils.js';
|
|
8
8
|
import { requireTouchId } from '../touchid.js';
|
|
9
|
-
import { printTxResult,
|
|
9
|
+
import { printTxResult, printSwapSimulation } from '../formatters.js';
|
|
10
10
|
export const swapCommand = new Command('swap')
|
|
11
11
|
.description('Swap tokens (cross-chain spot trading)')
|
|
12
12
|
.option('-s, --side <side>', 'buy or sell')
|
|
13
13
|
.option('-t, --token <address|ticker>', 'Token contract address or ticker symbol')
|
|
14
14
|
.option('-a, --amount <amount>', 'USD amount (buy) or token amount (sell)')
|
|
15
|
+
.option('-c, --chain <chain>', 'Blockchain (ethereum, base, solana, etc.)')
|
|
15
16
|
.option('-y, --yes', 'Skip confirmation')
|
|
16
17
|
.option('--dry-run', 'Simulate without executing')
|
|
17
18
|
.action(wrapAction(async (opts) => {
|
|
18
19
|
const creds = requireAuth();
|
|
20
|
+
// ── 0. Validate CLI options early ────────────────────────────────────
|
|
21
|
+
if (opts.amount) {
|
|
22
|
+
const amountNum = parseFloat(opts.amount);
|
|
23
|
+
if (opts.amount.toLowerCase() !== 'all' && (isNaN(amountNum) || amountNum <= 0)) {
|
|
24
|
+
throw new Error('Amount must be a positive number');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
// ── 1. Side ──────────────────────────────────────────────────────────
|
|
20
28
|
let side = opts.side;
|
|
21
29
|
if (!side) {
|
|
@@ -33,10 +41,17 @@ export const swapCommand = new Command('swap')
|
|
|
33
41
|
validate: (v) => (v.length > 0 ? true : 'Please enter a token address or ticker'),
|
|
34
42
|
});
|
|
35
43
|
const tokenInfo = await lookupToken(tokenInput);
|
|
36
|
-
// ── 3. Chain (
|
|
37
|
-
|
|
44
|
+
// ── 3. Chain (use explicit flag or derive from token) ────────────────
|
|
45
|
+
let chain = opts.chain ? normalizeChain(opts.chain) : undefined;
|
|
46
|
+
if (opts.chain && !chain) {
|
|
47
|
+
warn(`Unsupported chain: ${opts.chain}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
38
50
|
if (!chain) {
|
|
39
|
-
|
|
51
|
+
chain = normalizeChain(tokenInfo.chain);
|
|
52
|
+
}
|
|
53
|
+
if (!chain) {
|
|
54
|
+
warn('Unable to determine chain. Use --chain to specify.');
|
|
40
55
|
return;
|
|
41
56
|
}
|
|
42
57
|
// ── 4. Amount ────────────────────────────────────────────────────────
|
|
@@ -89,17 +104,11 @@ export const swapCommand = new Command('swap')
|
|
|
89
104
|
}]);
|
|
90
105
|
spin.stop();
|
|
91
106
|
assertApiOk(simRes, 'Simulation failed');
|
|
92
|
-
|
|
93
|
-
console.log(chalk.bold('Simulation Result:'));
|
|
94
|
-
if (Array.isArray(simRes.data)) {
|
|
107
|
+
if (simRes.data && Array.isArray(simRes.data)) {
|
|
95
108
|
for (const item of simRes.data) {
|
|
96
|
-
|
|
97
|
-
console.log('');
|
|
109
|
+
printSwapSimulation(item);
|
|
98
110
|
}
|
|
99
111
|
}
|
|
100
|
-
else if (simRes.data) {
|
|
101
|
-
printKV(simRes.data);
|
|
102
|
-
}
|
|
103
112
|
return;
|
|
104
113
|
}
|
|
105
114
|
// ── 7. Confirm & Touch ID ──────────────────────────────────────────
|
|
@@ -15,6 +15,22 @@ export const transferCommand = new Command('transfer')
|
|
|
15
15
|
.option('-y, --yes', 'Skip confirmation')
|
|
16
16
|
.action(wrapAction(async (opts) => {
|
|
17
17
|
const creds = requireAuth();
|
|
18
|
+
// ── 0. Validate CLI options early ────────────────────────────────────
|
|
19
|
+
if (opts.amount) {
|
|
20
|
+
const amountNum = parseFloat(opts.amount);
|
|
21
|
+
if (isNaN(amountNum) || amountNum <= 0) {
|
|
22
|
+
throw new Error('Amount must be a positive number');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (opts.to) {
|
|
26
|
+
if (!opts.chain) {
|
|
27
|
+
throw new Error('--chain is required when --to is provided');
|
|
28
|
+
}
|
|
29
|
+
const addrValidation = validateAddress(opts.to, opts.chain);
|
|
30
|
+
if (addrValidation !== true) {
|
|
31
|
+
throw new Error(addrValidation);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
18
34
|
// ── 1. Chain ─────────────────────────────────────────────────────────
|
|
19
35
|
const chain = opts.chain ?? await selectChain();
|
|
20
36
|
// ── 2. Token ─────────────────────────────────────────────────────────
|
|
@@ -15,27 +15,45 @@ export const withdrawCommand = new Command('withdraw')
|
|
|
15
15
|
.option('-y, --yes', 'Skip confirmation')
|
|
16
16
|
.action(wrapAction(async (opts) => {
|
|
17
17
|
const creds = requireAuth();
|
|
18
|
-
// ──
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
// ── 0. Validate CLI options early ────────────────────────────────────
|
|
19
|
+
if (opts.amount) {
|
|
20
|
+
const amountNum = parseFloat(opts.amount);
|
|
21
|
+
if (isNaN(amountNum) || amountNum <= 0) {
|
|
22
|
+
throw new Error('Amount must be a positive number');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (opts.to) {
|
|
26
|
+
if (!opts.chain) {
|
|
27
|
+
throw new Error('--chain is required when --to is provided');
|
|
28
|
+
}
|
|
29
|
+
const addrValidation = validateAddress(opts.to, opts.chain);
|
|
30
|
+
if (addrValidation !== true) {
|
|
31
|
+
throw new Error(addrValidation);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// ── 1. Show current assets for reference (only in interactive mode)
|
|
35
|
+
if (!opts.chain && !opts.token && !opts.amount && !opts.to) {
|
|
36
|
+
const assetsSpin = spinner('Fetching your assets…');
|
|
37
|
+
const assetsRes = await getAssets(creds.accessToken);
|
|
38
|
+
assetsSpin.stop();
|
|
39
|
+
if (assetsRes.success && assetsRes.data) {
|
|
40
|
+
const assets = assetsRes.data;
|
|
41
|
+
if (Array.isArray(assets) && assets.length > 0) {
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.dim('Your current assets:'));
|
|
44
|
+
for (const asset of assets.slice(0, 15)) {
|
|
45
|
+
const sym = asset.symbol ?? asset.tokenSymbol ?? '';
|
|
46
|
+
const bal = asset.balance ?? asset.amount ?? '';
|
|
47
|
+
const ch = asset.chain ?? asset.chainName ?? '';
|
|
48
|
+
if (sym || bal) {
|
|
49
|
+
console.log(chalk.dim(` ${sym} ${bal} (${ch})`));
|
|
50
|
+
}
|
|
33
51
|
}
|
|
52
|
+
if (assets.length > 15) {
|
|
53
|
+
console.log(chalk.dim(` … and ${assets.length - 15} more`));
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
34
56
|
}
|
|
35
|
-
if (assets.length > 15) {
|
|
36
|
-
console.log(chalk.dim(` … and ${assets.length - 15} more`));
|
|
37
|
-
}
|
|
38
|
-
console.log('');
|
|
39
57
|
}
|
|
40
58
|
}
|
|
41
59
|
// ── 2. Chain ─────────────────────────────────────────────────────────
|
package/dist/formatters.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type { CrossChainSwapsSimulateResultDto, CrossChainSwapsSimulateErrorDto } from './types.js';
|
|
2
|
+
/** Convert wei (10^18) string to USD decimal string */
|
|
3
|
+
export declare function formatWeiToUsd(weiStr: string): string;
|
|
4
|
+
/** Convert BigInt amount string using token decimals */
|
|
5
|
+
export declare function formatTokenAmount(amountStr: string, decimals: number): string;
|
|
1
6
|
/** Enable raw JSON output (all formatters fall back to JSON.stringify). */
|
|
2
7
|
export declare function setRawJson(enabled: boolean): void;
|
|
3
8
|
/** Check if raw JSON output mode is active. */
|
|
@@ -57,3 +62,7 @@ export declare function printFearGreed(data: Record<string, unknown>): void;
|
|
|
57
62
|
* Pretty-print BTC/crypto metrics — flatten currentQuote, skip ohlcvQuotes.
|
|
58
63
|
*/
|
|
59
64
|
export declare function printCryptoMetrics(data: Record<string, unknown>): void;
|
|
65
|
+
/** Check if a simulation result is an error */
|
|
66
|
+
export declare function isSimulateError(item: CrossChainSwapsSimulateResultDto | CrossChainSwapsSimulateErrorDto): item is CrossChainSwapsSimulateErrorDto;
|
|
67
|
+
/** Pretty-print swap simulation result with wei-to-USD conversion */
|
|
68
|
+
export declare function printSwapSimulation(result: CrossChainSwapsSimulateResultDto | CrossChainSwapsSimulateErrorDto): void;
|
package/dist/formatters.js
CHANGED
|
@@ -5,6 +5,35 @@
|
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import Table from 'cli-table3';
|
|
8
|
+
// ─── Wei / BigInt formatting ──────────────────────────────────────────────
|
|
9
|
+
/** Convert wei (10^18) string to USD decimal string */
|
|
10
|
+
export function formatWeiToUsd(weiStr) {
|
|
11
|
+
try {
|
|
12
|
+
const wei = BigInt(weiStr);
|
|
13
|
+
// Divide by 10^18 (wei to ether conversion)
|
|
14
|
+
const dollars = Number(wei) / 1e18;
|
|
15
|
+
return dollars.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return weiStr; // fallback to original if parsing fails
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Convert BigInt amount string using token decimals */
|
|
22
|
+
export function formatTokenAmount(amountStr, decimals) {
|
|
23
|
+
try {
|
|
24
|
+
const amount = BigInt(amountStr);
|
|
25
|
+
const value = Number(amount) / Math.pow(10, decimals);
|
|
26
|
+
return value.toLocaleString('en-US', { maximumFractionDigits: decimals });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return amountStr; // fallback to original if parsing fails
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Check if a string looks like a wei value (large BigInt string) */
|
|
33
|
+
function isWeiString(value) {
|
|
34
|
+
// Wei strings are typically large numbers (18+ digits)
|
|
35
|
+
return /^\d{15,}$/.test(value);
|
|
36
|
+
}
|
|
8
37
|
// ─── Raw JSON mode ───────────────────────────────────────────────────────
|
|
9
38
|
let _rawJson = false;
|
|
10
39
|
/** Enable raw JSON output (all formatters fall back to JSON.stringify). */
|
|
@@ -58,6 +87,10 @@ export function formatValue(value, key) {
|
|
|
58
87
|
// Hex addresses — must check before numeric coercion (0x… is valid Number)
|
|
59
88
|
if (/^0x[0-9a-fA-F]{20,}$/.test(value))
|
|
60
89
|
return chalk.yellow(value);
|
|
90
|
+
// Wei-format USD fee strings (key contains "FeeInUsd" and value is large BigInt string)
|
|
91
|
+
if (key && /FeeInUsd$/i.test(key) && isWeiString(value)) {
|
|
92
|
+
return `$${formatWeiToUsd(value)}`;
|
|
93
|
+
}
|
|
61
94
|
// Numeric string that looks like a price / amount
|
|
62
95
|
const num = Number(value);
|
|
63
96
|
if (!isNaN(num) && value.trim() !== '') {
|
|
@@ -452,6 +485,85 @@ export function printCryptoMetrics(data) {
|
|
|
452
485
|
console.log(` ${chalk.dim(label.padEnd(maxLen))} : ${val}`);
|
|
453
486
|
}
|
|
454
487
|
}
|
|
488
|
+
// ─── Swap Simulation ─────────────────────────────────────────────────────
|
|
489
|
+
/** Check if a simulation result is an error */
|
|
490
|
+
export function isSimulateError(item) {
|
|
491
|
+
return 'error' in item;
|
|
492
|
+
}
|
|
493
|
+
/** Pretty-print swap simulation result with wei-to-USD conversion */
|
|
494
|
+
export function printSwapSimulation(result) {
|
|
495
|
+
if (_rawJson) {
|
|
496
|
+
console.log(JSON.stringify(result, null, 2));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Handle error case
|
|
500
|
+
if (isSimulateError(result)) {
|
|
501
|
+
console.log(chalk.red.bold(` Error: ${result.error}`));
|
|
502
|
+
if (result.message) {
|
|
503
|
+
console.log(chalk.dim(` Message: ${result.message}`));
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
// Convert wei fees to USD (fees are always in 10^18 format)
|
|
508
|
+
const totalFeeUsd = formatWeiToUsd(result.totalFeeInUsd);
|
|
509
|
+
const gasFeeUsd = formatWeiToUsd(result.gasFeeInUsd);
|
|
510
|
+
const serviceFeeUsd = formatWeiToUsd(result.serviceFeeInUsd);
|
|
511
|
+
const lpFeeUsd = formatWeiToUsd(result.lpFeeInUsd);
|
|
512
|
+
// Sanity check: warn if total fee exceeds $1000
|
|
513
|
+
const totalFeeNum = parseFloat(totalFeeUsd.replace(/,/g, ''));
|
|
514
|
+
const feeWarning = totalFeeNum > 1000 ? chalk.yellow(' ⚠️ (unusually high)') : '';
|
|
515
|
+
// Print header
|
|
516
|
+
console.log('');
|
|
517
|
+
console.log(chalk.bold(' Simulation Result:'));
|
|
518
|
+
// Print token changes (using token.decimals for amount conversion)
|
|
519
|
+
if (result.increased.length > 0) {
|
|
520
|
+
console.log(chalk.green.bold(' Tokens Received:'));
|
|
521
|
+
for (const change of result.increased) {
|
|
522
|
+
const token = change.token;
|
|
523
|
+
const decimals = token.decimals ?? 18;
|
|
524
|
+
const amountFormatted = formatTokenAmount(change.amount, decimals);
|
|
525
|
+
const amountUsd = change.amountInUSD ? formatWeiToUsd(change.amountInUSD) : null;
|
|
526
|
+
console.log(` ${chalk.bold(`$${token.symbol}`)} — ${token.name}`);
|
|
527
|
+
console.log(` ${chalk.dim('Address')} : ${chalk.yellow(token.address)}`);
|
|
528
|
+
console.log(` ${chalk.dim('Amount')} : ${amountFormatted}`);
|
|
529
|
+
if (amountUsd)
|
|
530
|
+
console.log(` ${chalk.dim('Value')} : $${amountUsd}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (result.decreased.length > 0) {
|
|
534
|
+
console.log(chalk.red.bold(' Tokens Spent:'));
|
|
535
|
+
for (const change of result.decreased) {
|
|
536
|
+
const token = change.token;
|
|
537
|
+
const decimals = token.decimals ?? 18;
|
|
538
|
+
const amountFormatted = formatTokenAmount(change.amount, decimals);
|
|
539
|
+
const amountUsd = change.amountInUSD ? formatWeiToUsd(change.amountInUSD) : null;
|
|
540
|
+
console.log(` ${chalk.bold(`$${token.symbol}`)} — ${token.name}`);
|
|
541
|
+
console.log(` ${chalk.dim('Address')} : ${chalk.yellow(token.address)}`);
|
|
542
|
+
console.log(` ${chalk.dim('Amount')} : ${amountFormatted}`);
|
|
543
|
+
if (amountUsd)
|
|
544
|
+
console.log(` ${chalk.dim('Value')} : $${amountUsd}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Print fees
|
|
548
|
+
console.log('');
|
|
549
|
+
console.log(chalk.bold(' Fees:'));
|
|
550
|
+
console.log(` ${chalk.dim('Total Fee')} : $${totalFeeUsd}${feeWarning}`);
|
|
551
|
+
console.log(` ${chalk.dim('Gas Fee')} : $${gasFeeUsd}`);
|
|
552
|
+
console.log(` ${chalk.dim('Service Fee')} : $${serviceFeeUsd}`);
|
|
553
|
+
console.log(` ${chalk.dim('LP Fee')} : $${lpFeeUsd}`);
|
|
554
|
+
// Print other info
|
|
555
|
+
console.log('');
|
|
556
|
+
console.log(` ${chalk.dim('Slippage')} : ${result.slippageBps} bps`);
|
|
557
|
+
if (result.priceImpact !== null && result.priceImpact !== undefined) {
|
|
558
|
+
// -1 means price impact could not be calculated or is negligible
|
|
559
|
+
const impactStr = String(result.priceImpact).trim();
|
|
560
|
+
const impactNum = parseFloat(impactStr);
|
|
561
|
+
const impactDisplay = impactNum === -1 || impactStr === '-1'
|
|
562
|
+
? chalk.dim('— (negligible)')
|
|
563
|
+
: `${impactStr}%`;
|
|
564
|
+
console.log(` ${chalk.dim('Price Impact')} : ${impactDisplay}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
455
567
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
456
568
|
function truncate(str, len) {
|
|
457
569
|
return str.length > len ? str.slice(0, len - 1) + '…' : str;
|
package/dist/types.d.ts
CHANGED
|
@@ -123,6 +123,54 @@ export interface TransactionResult {
|
|
|
123
123
|
status?: string;
|
|
124
124
|
[key: string]: unknown;
|
|
125
125
|
}
|
|
126
|
+
export interface TokenInfoDto {
|
|
127
|
+
name: string;
|
|
128
|
+
symbol: string;
|
|
129
|
+
address: string;
|
|
130
|
+
chainId: number;
|
|
131
|
+
decimals: number;
|
|
132
|
+
realDecimals: number;
|
|
133
|
+
price: number;
|
|
134
|
+
image?: string;
|
|
135
|
+
assetId?: string;
|
|
136
|
+
type?: string;
|
|
137
|
+
rank?: number | null;
|
|
138
|
+
slotIndex?: number | null;
|
|
139
|
+
}
|
|
140
|
+
export interface TokenChangeDto {
|
|
141
|
+
token: TokenInfoDto;
|
|
142
|
+
/** Amount of token changed (BigInt string, use token.decimals for conversion) */
|
|
143
|
+
amount: string;
|
|
144
|
+
/** Amount in USD (BigInt string in wei/10^18, may be null) */
|
|
145
|
+
amountInUSD: string | null;
|
|
146
|
+
}
|
|
147
|
+
export interface CrossChainSwapsSimulateResultDto {
|
|
148
|
+
/** Tokens that increased in balance */
|
|
149
|
+
increased: TokenChangeDto[];
|
|
150
|
+
/** Tokens that decreased in balance */
|
|
151
|
+
decreased: TokenChangeDto[];
|
|
152
|
+
/** Total fee in USD (BigInt string in wei/10^18) */
|
|
153
|
+
totalFeeInUsd: string;
|
|
154
|
+
/** Gas fee in USD (BigInt string in wei/10^18) */
|
|
155
|
+
gasFeeInUsd: string;
|
|
156
|
+
/** Service fee in USD (BigInt string in wei/10^18) */
|
|
157
|
+
serviceFeeInUsd: string;
|
|
158
|
+
/** LP fee in USD (BigInt string in wei/10^18) */
|
|
159
|
+
lpFeeInUsd: string;
|
|
160
|
+
/** Slippage in basis points */
|
|
161
|
+
slippageBps: number;
|
|
162
|
+
/** Price impact (may be null) */
|
|
163
|
+
priceImpact: string | null;
|
|
164
|
+
}
|
|
165
|
+
export interface CrossChainSwapsSimulateErrorDto {
|
|
166
|
+
error: string;
|
|
167
|
+
message?: string;
|
|
168
|
+
}
|
|
169
|
+
export type CrossChainSwapsSimulateItem = CrossChainSwapsSimulateResultDto | CrossChainSwapsSimulateErrorDto;
|
|
170
|
+
export interface CrossChainSwapsSimulateResponseDto {
|
|
171
|
+
success: boolean;
|
|
172
|
+
data: CrossChainSwapsSimulateItem[];
|
|
173
|
+
}
|
|
126
174
|
export type SwapSide = 'buy' | 'sell';
|
|
127
175
|
export interface CrossChainSwapDto {
|
|
128
176
|
chain: Chain;
|
package/dist/utils.js
CHANGED
|
@@ -211,11 +211,11 @@ export async function lookupToken(tokenInput) {
|
|
|
211
211
|
const { searchTokens } = await import('./api/tokens.js');
|
|
212
212
|
const res = await searchTokens(keyword);
|
|
213
213
|
spin.stop();
|
|
214
|
-
if (!res.success
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
if (!res.success) {
|
|
215
|
+
throw new Error(res.error?.message ?? 'Failed to lookup token');
|
|
216
|
+
}
|
|
217
|
+
if (!res.data || res.data.length === 0) {
|
|
218
|
+
throw new Error(`Unknown token: ${tokenInput}`);
|
|
219
219
|
}
|
|
220
220
|
let tokens = res.data;
|
|
221
221
|
if (isTicker) {
|
|
@@ -276,9 +276,9 @@ export async function lookupToken(tokenInput) {
|
|
|
276
276
|
chain: selected.chain,
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
|
-
catch {
|
|
279
|
+
catch (err) {
|
|
280
280
|
spin.stop();
|
|
281
|
-
|
|
281
|
+
throw err;
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
/**
|