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/dist/utils.js CHANGED
@@ -115,6 +115,83 @@ export function wrapAction(fn) {
115
115
  }
116
116
  };
117
117
  }
118
+ const NATIVE_TOKEN_ADDRESS = {
119
+ sol: 'So11111111111111111111111111111111111111112',
120
+ solana: 'So11111111111111111111111111111111111111112',
121
+ };
122
+ const EVM_NATIVE = '0x' + '0'.repeat(40);
123
+ function resolveNativeAddress(chain) {
124
+ if (!chain)
125
+ return EVM_NATIVE;
126
+ return NATIVE_TOKEN_ADDRESS[chain.toLowerCase()] ?? EVM_NATIVE;
127
+ }
128
+ const CHAIN_ALIAS = {
129
+ sol: 'solana',
130
+ eth: 'ethereum',
131
+ arb: 'arbitrum',
132
+ op: 'optimism',
133
+ matic: 'polygon',
134
+ poly: 'polygon',
135
+ avax: 'avalanche',
136
+ bnb: 'bsc',
137
+ bera: 'berachain',
138
+ // Numeric chain IDs returned by the token search API
139
+ '101': 'solana',
140
+ '1': 'ethereum',
141
+ '8453': 'base',
142
+ '42161': 'arbitrum',
143
+ '10': 'optimism',
144
+ '56': 'bsc',
145
+ '137': 'polygon',
146
+ '43114': 'avalanche',
147
+ '81457': 'blast',
148
+ '169': 'manta',
149
+ '34443': 'mode',
150
+ '146': 'sonic',
151
+ '80094': 'berachain',
152
+ '196': 'xlayer',
153
+ '4200': 'merlin',
154
+ };
155
+ /**
156
+ * Normalize a chain identifier from the token search API to a supported
157
+ * `Chain` value used by the swap / transfer APIs.
158
+ */
159
+ export function normalizeChain(raw) {
160
+ if (!raw)
161
+ return undefined;
162
+ const lower = raw.toLowerCase();
163
+ if (SUPPORTED_CHAINS.includes(lower))
164
+ return lower;
165
+ return CHAIN_ALIAS[lower];
166
+ }
167
+ /** Capitalize chain name for display (e.g. "solana" → "Solana", "bsc" → "BSC"). */
168
+ function displayChain(raw) {
169
+ const name = normalizeChain(raw) ?? raw ?? 'unknown';
170
+ if (name === 'bsc')
171
+ return 'BSC';
172
+ return name.charAt(0).toUpperCase() + name.slice(1);
173
+ }
174
+ /** Lower = cheaper gas. Used to sort chain choices so the cheapest is first. */
175
+ const CHAIN_GAS_RANK = {
176
+ sol: 1, solana: 1, '101': 1,
177
+ base: 2, '8453': 2,
178
+ arbitrum: 3, arb: 3, '42161': 3,
179
+ optimism: 4, op: 4, '10': 4,
180
+ bsc: 5, bnb: 5, '56': 5,
181
+ polygon: 6, matic: 6, poly: 6, '137': 6,
182
+ sonic: 7, '146': 7,
183
+ avalanche: 8, avax: 8, '43114': 8,
184
+ berachain: 9, bera: 9, '80094': 9,
185
+ blast: 10, '81457': 10,
186
+ manta: 11, '169': 11,
187
+ mode: 12, '34443': 12,
188
+ ethereum: 50, eth: 50, '1': 50,
189
+ };
190
+ function chainGasRank(chain) {
191
+ if (!chain)
192
+ return 99;
193
+ return CHAIN_GAS_RANK[chain.toLowerCase()] ?? 30;
194
+ }
118
195
  /**
119
196
  * Look up token metadata by address, ticker, or name.
120
197
  *
@@ -149,25 +226,54 @@ export async function lookupToken(tokenInput) {
149
226
  if (!isTicker) {
150
227
  const exact = tokens.find((t) => t.address?.toLowerCase() === keyword.toLowerCase());
151
228
  if (exact) {
152
- return { symbol: exact.symbol, name: exact.name, address: exact.address ?? tokenInput };
229
+ return { symbol: exact.symbol, name: exact.name ?? displayChain(exact.chain), address: exact.address ?? resolveNativeAddress(exact.chain), chain: exact.chain };
153
230
  }
154
231
  }
155
232
  if (tokens.length === 1) {
156
233
  const t = tokens[0];
157
- return { symbol: t.symbol, name: t.name, address: t.address ?? tokenInput };
234
+ return { symbol: t.symbol, name: t.name ?? displayChain(t.chain), address: t.address ?? resolveNativeAddress(t.chain), chain: t.chain };
235
+ }
236
+ // Check if all results share the same symbol → multi-chain scenario
237
+ const uniqueSymbols = new Set(tokens.map((t) => t.symbol?.toLowerCase()));
238
+ if (uniqueSymbols.size === 1) {
239
+ const sorted = [...tokens].sort((a, b) => chainGasRank(a.chain) - chainGasRank(b.chain));
240
+ info(`$${sorted[0].symbol} is available on ${sorted.length} chains`);
241
+ const selected = await select({
242
+ message: 'Select chain:',
243
+ choices: sorted.map((t, i) => {
244
+ const chainName = displayChain(t.chain);
245
+ const addr = t.address
246
+ ? chalk.dim(` · ${t.address.slice(0, 10)}…${t.address.slice(-6)}`)
247
+ : chalk.dim(' · native token');
248
+ const tag = i === 0 ? chalk.green(' (lowest gas)') : '';
249
+ return { name: `${chalk.cyan(chainName)}${tag}${addr}`, value: t };
250
+ }),
251
+ });
252
+ return {
253
+ symbol: selected.symbol,
254
+ name: selected.name ?? displayChain(selected.chain),
255
+ address: selected.address ?? resolveNativeAddress(selected.chain),
256
+ chain: selected.chain,
257
+ };
158
258
  }
159
259
  info(`Found ${tokens.length} tokens matching "${tokenInput}"`);
160
260
  const selected = await select({
161
261
  message: 'Select the correct token:',
162
- choices: tokens.map((t) => ({
163
- name: `${t.symbol ? chalk.bold('$' + t.symbol) : '?'} — ${t.name ?? 'Unknown'}\n ${chalk.yellow(t.address ?? '')}`,
164
- value: t,
165
- })),
262
+ choices: tokens.map((t) => {
263
+ const sym = t.symbol ? chalk.bold('$' + t.symbol) : '?';
264
+ const chainName = displayChain(t.chain);
265
+ const label = t.name || chainName;
266
+ const desc = label ? ` — ${label}` : '';
267
+ const chainTag = chainName && chainName !== label ? chalk.dim(` [${chainName}]`) : '';
268
+ const addr = t.address ? `\n ${chalk.yellow(t.address)}` : chalk.dim('\n (native token)');
269
+ return { name: `${sym}${desc}${chainTag}${addr}`, value: t };
270
+ }),
166
271
  });
167
272
  return {
168
273
  symbol: selected.symbol,
169
- name: selected.name,
170
- address: selected.address ?? tokenInput,
274
+ name: selected.name ?? displayChain(selected.chain),
275
+ address: selected.address ?? resolveNativeAddress(selected.chain),
276
+ chain: selected.chain,
171
277
  };
172
278
  }
173
279
  catch {
@@ -197,18 +303,32 @@ export function formatTokenLabel(token) {
197
303
  *
198
304
  * Exits the process if the user declines.
199
305
  */
200
- export async function requireTransactionConfirmation(description, token) {
306
+ export async function requireTransactionConfirmation(description, token, details) {
201
307
  const config = loadConfig();
202
308
  if (config.confirmBeforeTransaction === false)
203
309
  return;
204
310
  console.log('');
205
311
  console.log(chalk.yellow('⚠'), chalk.bold('Transaction confirmation'));
312
+ if (details?.chain) {
313
+ console.log(chalk.dim(' Chain : ') + chalk.cyan(details.chain));
314
+ }
206
315
  if (token) {
207
316
  const ticker = token.symbol ? '$' + token.symbol : undefined;
208
317
  const label = [ticker, token.name].filter(Boolean).join(' — ');
209
318
  console.log(chalk.dim(' Token : ') + (label ? chalk.bold(label) : chalk.dim('Unknown token')));
210
319
  console.log(chalk.dim(' Address : ') + chalk.yellow(token.address));
211
320
  }
321
+ if (details?.side) {
322
+ const s = details.side.toLowerCase();
323
+ const colored = s === 'buy' ? chalk.green.bold(details.side.toUpperCase()) : chalk.red.bold(details.side.toUpperCase());
324
+ console.log(chalk.dim(' Side : ') + colored);
325
+ }
326
+ if (details?.amount) {
327
+ console.log(chalk.dim(' Amount : ') + chalk.bold(details.amount));
328
+ }
329
+ if (details?.destination) {
330
+ console.log(chalk.dim(' To : ') + chalk.yellow(details.destination));
331
+ }
212
332
  console.log(chalk.dim(` Action : ${description}`));
213
333
  console.log('');
214
334
  const ok = await confirm({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minara",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "CLI client for Minara.ai — login, trade, deposit/withdraw, chat and more from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",