@paysponge/sdk 0.1.20 → 0.1.25

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/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Command, Option } from "commander";
1
+ import { Command, Help, Option } from "commander";
2
2
  import * as p from "@clack/prompts";
3
3
  import { SpongeWallet } from "./client.js";
4
4
  import { deviceFlowAuth } from "./auth/device-flow.js";
@@ -17,6 +17,8 @@ export function buildCliProgram(metadata = {}) {
17
17
  .name(cmdName)
18
18
  .description(`${pkgName} – CLI for managing agent wallets`)
19
19
  .version(`${pkgName} v${version}`, "-v, --version");
20
+ program.showSuggestionAfterError();
21
+ program.showHelpAfterError();
20
22
  const shared = (cmd) => cmd
21
23
  .option("--base-url <url>", "custom API URL")
22
24
  .option("--credentials-path <path>", "custom credentials file path");
@@ -48,6 +50,7 @@ export function buildCliProgram(metadata = {}) {
48
50
  .command("advanced")
49
51
  .description("Low-level commands mirroring the raw tool surface");
50
52
  registerToolCommands(advancedCmd, shared);
53
+ applyHelpTheme(program, metadata);
51
54
  return program;
52
55
  }
53
56
  export async function runCli(args, metadata = {}) {
@@ -221,6 +224,39 @@ async function continueClaimFlow(creds, opts) {
221
224
  p.log.info("After the browser claim completes, the cached API key will keep working for this agent.");
222
225
  p.outro("Claim flow ready");
223
226
  }
227
+ function requiredInput(opts, positional, optionKey, flagName) {
228
+ const fromOption = opts[optionKey];
229
+ const value = positional
230
+ ?? (typeof fromOption === "string" ? fromOption : undefined);
231
+ if (!value) {
232
+ throw new Error(`missing required argument or option: ${flagName}`);
233
+ }
234
+ return value;
235
+ }
236
+ function isTempoChain(chain) {
237
+ return chain === "tempo" || chain === "tempo-testnet";
238
+ }
239
+ function normalizeTempoTokenSymbol(token, chain) {
240
+ const trimmed = token.trim();
241
+ if (trimmed.startsWith("0x") && trimmed.length === 42) {
242
+ return trimmed;
243
+ }
244
+ const normalized = trimmed.toLowerCase().replace(/[\s_-]/g, "");
245
+ const aliasMap = {
246
+ path: "pathUSD",
247
+ pathusd: "pathUSD",
248
+ alpha: "AlphaUSD",
249
+ alphausd: "AlphaUSD",
250
+ beta: "BetaUSD",
251
+ betausd: "BetaUSD",
252
+ theta: "ThetaUSD",
253
+ thetausd: "ThetaUSD",
254
+ usdc: chain === "tempo" ? "USDC.e" : "pathUSD",
255
+ "usdc.e": chain === "tempo" ? "USDC.e" : "pathUSD",
256
+ usdce: chain === "tempo" ? "USDC.e" : "pathUSD",
257
+ };
258
+ return aliasMap[normalized] ?? trimmed;
259
+ }
224
260
  // ---------------------------------------------------------------------------
225
261
  // Helpers
226
262
  // ---------------------------------------------------------------------------
@@ -231,6 +267,89 @@ function defaultAgentName(email) {
231
267
  .replace(/^-+|-+$/g, "");
232
268
  return slug ? `agent-${slug}` : "sponge-agent";
233
269
  }
270
+ const ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
271
+ const HELP_COLOR_ENABLED = Boolean(!process.env.NO_COLOR
272
+ && (process.stdout.isTTY || process.env.FORCE_COLOR));
273
+ function ansi(text, open, close = "\u001B[0m") {
274
+ if (!HELP_COLOR_ENABLED)
275
+ return text;
276
+ return `${open}${text}${close}`;
277
+ }
278
+ function stripAnsi(text) {
279
+ return text.replace(ANSI_PATTERN, "");
280
+ }
281
+ function bold(text) {
282
+ return ansi(text, "\u001B[1m");
283
+ }
284
+ function cyan(text) {
285
+ return ansi(text, "\u001B[36m");
286
+ }
287
+ function green(text) {
288
+ return ansi(text, "\u001B[32m");
289
+ }
290
+ function dim(text) {
291
+ return ansi(text, "\u001B[2m");
292
+ }
293
+ function toTitleCase(value) {
294
+ return value
295
+ .split(/[\s-]+/)
296
+ .filter(Boolean)
297
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
298
+ .join(" ");
299
+ }
300
+ function commandPath(command) {
301
+ const parts = [];
302
+ let current = command;
303
+ while (current) {
304
+ parts.unshift(current.name());
305
+ current = current.parent ?? null;
306
+ }
307
+ return parts;
308
+ }
309
+ function buildHelpBanner(command, metadata) {
310
+ const path = commandPath(command);
311
+ const rootName = metadata.commandName ?? "spongewallet";
312
+ const title = path.length === 1
313
+ ? "Sponge Wallet CLI"
314
+ : `Sponge Wallet ${path.slice(1).map(toTitleCase).join(" ")}`;
315
+ const subtitle = path.length === 1
316
+ ? "Manage agent wallets, swaps, payments, and MCP setup."
317
+ : command.description() || `${rootName} command help`;
318
+ const lines = [bold(green(title)), "", dim(subtitle)];
319
+ const width = Math.max(...lines.map((line) => stripAnsi(line).length));
320
+ const top = cyan(`╭${"─".repeat(width + 2)}╮`);
321
+ const bottom = cyan(`╰${"─".repeat(width + 2)}╯`);
322
+ const body = lines.map((line) => {
323
+ const padding = " ".repeat(width - stripAnsi(line).length);
324
+ return `${cyan("│")} ${line}${padding} ${cyan("│")}`;
325
+ });
326
+ return [top, ...body, bottom].join("\n");
327
+ }
328
+ function applyHelpTheme(command, metadata) {
329
+ command.configureHelp({
330
+ commandDescription: () => "",
331
+ styleTitle: (text) => bold(cyan(text)),
332
+ styleCommandText: (text) => bold(green(text)),
333
+ styleSubcommandText: (text) => green(text),
334
+ styleOptionText: (text) => cyan(text),
335
+ styleArgumentText: (text) => bold(text),
336
+ styleDescriptionText: (text) => text,
337
+ formatHelp(cmd, helper) {
338
+ const description = cmd.description();
339
+ const lines = Help.prototype.formatHelp.call(helper, cmd, helper)
340
+ .trimEnd()
341
+ .split("\n");
342
+ if (description && lines[2] === description && lines[3] === "") {
343
+ lines.splice(2, 2);
344
+ }
345
+ const body = lines.join("\n");
346
+ return `${buildHelpBanner(cmd, metadata)}\n\n${body}\n`;
347
+ },
348
+ });
349
+ for (const subcommand of command.commands) {
350
+ applyHelpTheme(subcommand, metadata);
351
+ }
352
+ }
234
353
  // ---------------------------------------------------------------------------
235
354
  // Curated command tree
236
355
  // ---------------------------------------------------------------------------
@@ -246,12 +365,12 @@ const CHAIN_VALUES = [
246
365
  ];
247
366
  const EVM_CHAIN_VALUES = ["ethereum", "base", "sepolia", "base-sepolia"];
248
367
  const SOLANA_CHAIN_VALUES = ["solana", "solana-devnet"];
368
+ const TEMPO_CHAIN_VALUES = ["tempo", "tempo-testnet"];
249
369
  const ONRAMP_CHAIN_VALUES = ["base", "solana", "polygon"];
250
370
  const PAY_CHAIN_VALUES = ["base", "solana", "tempo", "ethereum"];
251
371
  const PREFERRED_X402_CHAINS = ["base", "solana", "ethereum"];
252
372
  function registerCuratedCommands(program, shared) {
253
- const walletCmd = program.command("wallet").description("Wallet balances, transfers, and addresses");
254
- shared(walletCmd.command("balance").description("Show wallet balances"))
373
+ shared(program.command("balance").description("Show wallet balances"))
255
374
  .addOption(new Option("--chain <chain>", "specific chain").choices([...CHAIN_VALUES, "all"]))
256
375
  .option("--allowed-chains <chains>", "comma-separated chain allowlist")
257
376
  .option("--only-usdc", "only show USDC balances")
@@ -264,58 +383,97 @@ function registerCuratedCommands(program, shared) {
264
383
  });
265
384
  displayToolResult(getToolDefinition("get_balance"), data);
266
385
  });
267
- shared(walletCmd.command("send").description("Send assets on EVM, Solana, or Tempo"))
268
- .addOption(new Option("--chain <chain>", "destination chain").choices(CHAIN_VALUES).makeOptionMandatory())
269
- .requiredOption("--to <address>", "recipient address")
270
- .requiredOption("--amount <amount>", "amount to send")
271
- .requiredOption("--asset <asset>", "currency symbol or token symbol/address")
272
- .action(async (opts) => {
386
+ shared(program.command("send").description("Send assets on EVM, Solana, or Tempo"))
387
+ .usage("<chain> <to> <asset> <amount> [options]")
388
+ .argument("<chain>", "destination chain")
389
+ .argument("<to>", "recipient address")
390
+ .argument("<asset>", "currency symbol or token symbol/address")
391
+ .argument("<amount>", "amount to send")
392
+ .option("--chain <chain>", "destination chain")
393
+ .option("--to <address>", "recipient address")
394
+ .option("--amount <amount>", "amount to send")
395
+ .option("--asset <asset>", "currency symbol or token symbol/address")
396
+ .addHelpText("after", "\nExamples:\n spongewallet send base 0xabc... USDC 10\n spongewallet send tempo 0xabc... usdce 1\n")
397
+ .action(async (chainArg, toArg, assetArg, amountArg, opts) => {
273
398
  const wallet = await connectWallet(opts);
274
- const chain = String(opts.chain);
275
- const asset = String(opts.asset);
399
+ const chain = requiredInput(opts, chainArg, "chain", "--chain");
400
+ const asset = isTempoChain(chain)
401
+ ? normalizeTempoTokenSymbol(requiredInput(opts, assetArg, "asset", "--asset"), chain)
402
+ : requiredInput(opts, assetArg, "asset", "--asset");
276
403
  const input = chain === "tempo" || chain === "tempo-testnet"
277
- ? { chain, to: String(opts.to), amount: String(opts.amount), token: asset }
278
- : { chain, to: String(opts.to), amount: String(opts.amount), currency: asset };
404
+ ? {
405
+ chain,
406
+ to: requiredInput(opts, toArg, "to", "--to"),
407
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
408
+ token: asset,
409
+ }
410
+ : {
411
+ chain,
412
+ to: requiredInput(opts, toArg, "to", "--to"),
413
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
414
+ currency: asset,
415
+ };
279
416
  const data = await wallet.transfer(input);
280
417
  displayToolResult(getToolDefinition(chain.startsWith("solana") ? "solana_transfer" : "evm_transfer"), data);
281
418
  });
282
- shared(walletCmd.command("history").description("Show recent transaction history"))
419
+ shared(program.command("history").description("Show recent transaction history"))
420
+ .usage("[limit] [options]")
421
+ .argument("[limit]", "maximum number of transactions")
283
422
  .option("--limit <n>", "maximum number of transactions", parseInt)
284
423
  .addOption(new Option("--chain <chain>", "filter by chain").choices(CHAIN_VALUES))
285
- .action(async (opts) => {
424
+ .addHelpText("after", "\nExamples:\n spongewallet history\n spongewallet history 20 --chain base\n")
425
+ .action(async (limitArg, opts) => {
286
426
  const wallet = await connectWallet(opts);
427
+ const limit = limitArg !== undefined
428
+ ? parseInt(limitArg, 10)
429
+ : opts.limit;
287
430
  const data = await wallet.getTransactionHistoryDetailed({
288
- limit: opts.limit,
431
+ limit: Number.isFinite(limit) ? limit : undefined,
289
432
  chain: opts.chain,
290
433
  });
291
434
  displayToolResult(getToolDefinition("get_transaction_history"), data);
292
435
  });
293
- shared(walletCmd.command("tokens").description("List Solana wallet tokens"))
294
- .addOption(new Option("--chain <chain>", "Solana network").choices(SOLANA_CHAIN_VALUES).default("solana"))
295
- .action(async (opts) => {
436
+ shared(program.command("tokens").description("List Solana wallet tokens"))
437
+ .usage("[chain] [options]")
438
+ .argument("[chain]", "Solana network")
439
+ .option("--chain <chain>", "Solana network")
440
+ .addHelpText("after", "\nExamples:\n spongewallet tokens\n spongewallet tokens solana-devnet\n")
441
+ .action(async (chainArg, opts) => {
296
442
  const wallet = await connectWallet(opts);
297
- const data = await wallet.getSolanaTokens(opts.chain);
443
+ const chain = (chainArg ?? opts.chain ?? "solana");
444
+ const data = await wallet.getSolanaTokens(chain);
298
445
  displayToolResult(getToolDefinition("get_solana_tokens"), data);
299
446
  });
300
- shared(walletCmd.command("search-tokens").description("Search the Solana token list"))
301
- .requiredOption("--query <query>", "token symbol or name")
447
+ shared(program.command("search-tokens").description("Search the Solana token list"))
448
+ .usage("<query> [limit] [options]")
449
+ .argument("<query>", "token symbol or name")
450
+ .argument("[limit]", "maximum results")
451
+ .option("--query <query>", "token symbol or name")
302
452
  .option("--limit <n>", "maximum results", parseInt)
303
- .action(async (opts) => {
453
+ .addHelpText("after", "\nExamples:\n spongewallet search-tokens BONK\n spongewallet search-tokens BONK 5\n")
454
+ .action(async (queryArg, limitArg, opts) => {
304
455
  const wallet = await connectWallet(opts);
305
- const data = await wallet.searchSolanaTokens(String(opts.query), opts.limit);
456
+ const limit = limitArg !== undefined
457
+ ? parseInt(limitArg, 10)
458
+ : opts.limit;
459
+ const data = await wallet.searchSolanaTokens(requiredInput(opts, queryArg, "query", "--query"), Number.isFinite(limit) ? limit : undefined);
306
460
  displayToolResult(getToolDefinition("search_solana_tokens"), data);
307
461
  });
308
- shared(walletCmd.command("onramp").description("Create a fiat-to-crypto onramp link"))
309
- .addOption(new Option("--chain <chain>", "destination chain").choices(ONRAMP_CHAIN_VALUES).default("base"))
462
+ shared(program.command("onramp").description("Create a fiat-to-crypto onramp link"))
463
+ .usage("[chain] [fiatAmount] [options]")
464
+ .argument("[chain]", "destination chain")
465
+ .argument("[fiatAmount]", "prefill fiat amount")
466
+ .option("--chain <chain>", "destination chain")
310
467
  .option("--wallet-address <address>", "destination wallet address (defaults to agent wallet)")
311
468
  .addOption(new Option("--provider <provider>", "onramp provider").choices(["auto", "stripe", "coinbase"]).default("auto"))
312
469
  .option("--fiat-amount <amount>", "prefill fiat amount")
313
470
  .option("--fiat-currency <code>", "fiat currency code")
314
471
  .option("--lock-wallet-address", "lock destination wallet address")
315
472
  .option("--redirect-url <url>", "redirect URL after checkout")
316
- .action(async (opts) => {
473
+ .addHelpText("after", "\nExamples:\n spongewallet onramp\n spongewallet onramp base 100\n spongewallet onramp solana 250 --fiat-currency usd\n")
474
+ .action(async (chainArg, fiatAmountArg, opts) => {
317
475
  const wallet = await connectWallet(opts);
318
- const chain = String(opts.chain ?? "base");
476
+ const chain = String(chainArg ?? opts.chain ?? "base");
319
477
  const walletAddress = opts.walletAddress
320
478
  ?? (await wallet.getAddress(chain))
321
479
  ?? "";
@@ -323,7 +481,7 @@ function registerCuratedCommands(program, shared) {
323
481
  wallet_address: walletAddress,
324
482
  chain: chain,
325
483
  provider: opts.provider,
326
- fiat_amount: opts.fiatAmount,
484
+ fiat_amount: (fiatAmountArg ?? opts.fiatAmount),
327
485
  fiat_currency: opts.fiatCurrency,
328
486
  lock_wallet_address: Boolean(opts.lockWalletAddress),
329
487
  redirect_url: opts.redirectUrl,
@@ -332,11 +490,15 @@ function registerCuratedCommands(program, shared) {
332
490
  });
333
491
  const txCmd = program.command("tx").description("Transaction status and signing");
334
492
  shared(txCmd.command("status").description("Check transaction status"))
335
- .requiredOption("--tx-hash <hash>", "transaction hash or signature")
336
- .addOption(new Option("--chain <chain>", "transaction chain").choices(CHAIN_VALUES).makeOptionMandatory())
337
- .action(async (opts) => {
493
+ .usage("<chain> <txHash> [options]")
494
+ .argument("<chain>", "transaction chain")
495
+ .argument("<txHash>", "transaction hash or signature")
496
+ .option("--tx-hash <hash>", "transaction hash or signature")
497
+ .option("--chain <chain>", "transaction chain")
498
+ .addHelpText("after", "\nExamples:\n spongewallet tx status base 0x123...\n spongewallet tx status solana 5K2...\n")
499
+ .action(async (chainArg, txHashArg, opts) => {
338
500
  const wallet = await connectWallet(opts);
339
- const data = await wallet.getTransactionStatus(String(opts.txHash), opts.chain);
501
+ const data = await wallet.getTransactionStatus(requiredInput(opts, txHashArg, "txHash", "--tx-hash"), requiredInput(opts, chainArg, "chain", "--chain"));
340
502
  displayToolResult(getToolDefinition("get_transaction_status"), data);
341
503
  });
342
504
  shared(txCmd.command("sign").description("Sign a Solana transaction without submitting"))
@@ -355,70 +517,115 @@ function registerCuratedCommands(program, shared) {
355
517
  });
356
518
  const swapCmd = program.command("swap").description("Quotes and swaps");
357
519
  shared(swapCmd.command("solana").description("Swap on Solana"))
520
+ .usage("[from] [to] [amount] [options]")
521
+ .argument("[from]", "input token")
522
+ .argument("[to]", "output token")
523
+ .argument("[amount]", "amount to swap")
358
524
  .addOption(new Option("--chain <chain>", "Solana network").choices(SOLANA_CHAIN_VALUES).default("solana"))
359
- .requiredOption("--from <token>", "input token")
360
- .requiredOption("--to <token>", "output token")
361
- .requiredOption("--amount <amount>", "amount to swap")
525
+ .option("--from <token>", "input token")
526
+ .option("--to <token>", "output token")
527
+ .option("--amount <amount>", "amount to swap")
362
528
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
363
- .action(async (opts) => {
529
+ .addHelpText("after", "\nExamples:\n spongewallet swap solana SOL USDC 1\n spongewallet swap solana --chain solana --from SOL --to USDC --amount 1\n")
530
+ .action(async (fromArg, toArg, amountArg, opts) => {
364
531
  const wallet = await connectWallet(opts);
365
532
  const data = await wallet.swap({
366
533
  chain: opts.chain,
367
- from: String(opts.from),
368
- to: String(opts.to),
369
- amount: String(opts.amount),
534
+ from: requiredInput(opts, fromArg, "from", "--from"),
535
+ to: requiredInput(opts, toArg, "to", "--to"),
536
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
370
537
  slippageBps: opts.slippageBps,
371
538
  });
372
539
  displayToolResult(getToolDefinition("solana_swap"), data);
373
540
  });
374
541
  shared(swapCmd.command("quote").description("Get a Jupiter quote without executing"))
542
+ .usage("[from] [to] [amount] [options]")
543
+ .argument("[from]", "input token")
544
+ .argument("[to]", "output token")
545
+ .argument("[amount]", "amount to quote")
375
546
  .addOption(new Option("--chain <chain>", "Solana network").choices(SOLANA_CHAIN_VALUES).default("solana"))
376
- .requiredOption("--from <token>", "input token")
377
- .requiredOption("--to <token>", "output token")
378
- .requiredOption("--amount <amount>", "amount to swap")
547
+ .option("--from <token>", "input token")
548
+ .option("--to <token>", "output token")
549
+ .option("--amount <amount>", "amount to quote")
379
550
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
380
- .action(async (opts) => {
551
+ .addHelpText("after", "\nExamples:\n spongewallet swap quote SOL USDC 1\n spongewallet swap quote --chain solana --from SOL --to USDC --amount 1\n")
552
+ .action(async (fromArg, toArg, amountArg, opts) => {
381
553
  await executeToolCommand(opts, "jupiter_swap_quote", {
382
554
  chain: opts.chain,
383
- input_token: opts.from,
384
- output_token: opts.to,
385
- amount: opts.amount,
555
+ input_token: requiredInput(opts, fromArg, "from", "--from"),
556
+ output_token: requiredInput(opts, toArg, "to", "--to"),
557
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
386
558
  slippage_bps: opts.slippageBps,
387
559
  });
388
560
  });
389
561
  shared(swapCmd.command("execute").description("Execute a previously quoted Jupiter swap"))
390
- .requiredOption("--quote-id <id>", "quote ID to execute")
391
- .action(async (opts) => {
562
+ .usage("<quoteId> [options]")
563
+ .argument("<quoteId>", "quote ID to execute")
564
+ .option("--quote-id <id>", "quote ID to execute")
565
+ .addHelpText("after", "\nExamples:\n spongewallet swap execute quote_123\n")
566
+ .action(async (quoteIdArg, opts) => {
392
567
  await executeToolCommand(opts, "jupiter_swap_execute", {
393
- quote_id: String(opts.quoteId),
568
+ quote_id: requiredInput(opts, quoteIdArg, "quoteId", "--quote-id"),
394
569
  });
395
570
  });
396
571
  shared(swapCmd.command("base").description("Swap on Base via 0x"))
397
- .requiredOption("--from <token>", "input token")
398
- .requiredOption("--to <token>", "output token")
399
- .requiredOption("--amount <amount>", "amount to swap")
572
+ .usage("[from] [to] [amount] [options]")
573
+ .argument("[from]", "input token")
574
+ .argument("[to]", "output token")
575
+ .argument("[amount]", "amount to swap")
576
+ .option("--from <token>", "input token")
577
+ .option("--to <token>", "output token")
578
+ .option("--amount <amount>", "amount to swap")
400
579
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
401
- .action(async (opts) => {
580
+ .addHelpText("after", "\nExamples:\n spongewallet swap base ETH USDC 0.1\n spongewallet swap base --from ETH --to USDC --amount 0.1\n")
581
+ .action(async (fromArg, toArg, amountArg, opts) => {
402
582
  await executeToolCommand(opts, "base_swap", {
403
- input_token: opts.from,
404
- output_token: opts.to,
405
- amount: opts.amount,
583
+ input_token: requiredInput(opts, fromArg, "from", "--from"),
584
+ output_token: requiredInput(opts, toArg, "to", "--to"),
585
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
586
+ slippage_bps: opts.slippageBps,
587
+ });
588
+ });
589
+ shared(swapCmd.command("tempo").description("Swap stablecoins on Tempo via native DEX"))
590
+ .usage("[from] [to] [amount] [options]")
591
+ .argument("[from]", "input token")
592
+ .argument("[to]", "output token")
593
+ .argument("[amount]", "amount to swap")
594
+ .addOption(new Option("--chain <chain>", "Tempo network").choices(TEMPO_CHAIN_VALUES).default("tempo"))
595
+ .option("--from <token>", "input token")
596
+ .option("--to <token>", "output token")
597
+ .option("--amount <amount>", "amount to swap")
598
+ .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
599
+ .addHelpText("after", "\nExamples:\n spongewallet swap tempo pathUSD USDC.e 1\n spongewallet swap tempo --chain tempo --from pathUSD --to USDC.e --amount 1\n")
600
+ .action(async (fromArg, toArg, amountArg, opts) => {
601
+ const chain = String(opts.chain ?? "tempo");
602
+ await executeToolCommand(opts, "tempo_swap", {
603
+ chain,
604
+ input_token: normalizeTempoTokenSymbol(requiredInput(opts, fromArg, "from", "--from"), chain),
605
+ output_token: normalizeTempoTokenSymbol(requiredInput(opts, toArg, "to", "--to"), chain),
606
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
406
607
  slippage_bps: opts.slippageBps,
407
608
  });
408
609
  });
409
610
  shared(program.command("bridge").description("Bridge assets between chains"))
410
- .requiredOption("--source-chain <chain>", "source chain")
411
- .requiredOption("--destination-chain <chain>", "destination chain")
412
- .requiredOption("--token <token>", "token to bridge")
413
- .requiredOption("--amount <amount>", "amount to bridge")
611
+ .usage("[sourceChain] [destinationChain] [token] [amount] [options]")
612
+ .argument("[sourceChain]", "source chain")
613
+ .argument("[destinationChain]", "destination chain")
614
+ .argument("[token]", "token to bridge")
615
+ .argument("[amount]", "amount to bridge")
616
+ .option("--source-chain <chain>", "source chain")
617
+ .option("--destination-chain <chain>", "destination chain")
618
+ .option("--token <token>", "token to bridge")
619
+ .option("--amount <amount>", "amount to bridge")
414
620
  .option("--destination-token <token>", "token to receive on destination")
415
621
  .option("--recipient-address <address>", "recipient address on destination")
416
- .action(async (opts) => {
622
+ .addHelpText("after", "\nExamples:\n spongewallet bridge base solana USDC 25\n spongewallet bridge base hyperliquid USDC 50\n spongewallet bridge --source-chain base --destination-chain polymarket --token USDC --amount 50\n")
623
+ .action(async (sourceChainArg, destinationChainArg, tokenArg, amountArg, opts) => {
417
624
  await executeToolCommand(opts, "bridge", {
418
- source_chain: opts.sourceChain,
419
- destination_chain: opts.destinationChain,
420
- token: opts.token,
421
- amount: opts.amount,
625
+ source_chain: requiredInput(opts, sourceChainArg, "sourceChain", "--source-chain"),
626
+ destination_chain: requiredInput(opts, destinationChainArg, "destinationChain", "--destination-chain"),
627
+ token: requiredInput(opts, tokenArg, "token", "--token"),
628
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
422
629
  destination_token: opts.destinationToken,
423
630
  recipient_address: opts.recipientAddress,
424
631
  });
@@ -479,21 +686,28 @@ function registerCuratedCommands(program, shared) {
479
686
  await executeToolCommand(opts, "get_key_list", {});
480
687
  });
481
688
  shared(keysCmd.command("get").description("Get a stored key value"))
482
- .requiredOption("--service <service>", "service name")
483
- .action(async (opts) => {
689
+ .usage("<service> [options]")
690
+ .argument("<service>", "service name")
691
+ .option("--service <service>", "service name")
692
+ .addHelpText("after", "\nExamples:\n spongewallet keys get openai\n")
693
+ .action(async (serviceArg, opts) => {
484
694
  await executeToolCommand(opts, "get_key_value", {
485
- service: String(opts.service),
695
+ service: requiredInput(opts, serviceArg, "service", "--service"),
486
696
  });
487
697
  });
488
698
  shared(keysCmd.command("set").description("Store a service key"))
489
- .requiredOption("--service <service>", "service name")
490
- .requiredOption("--key <secret>", "key or secret to store")
699
+ .usage("<service> <key> [options]")
700
+ .argument("<service>", "service name")
701
+ .argument("<key>", "key or secret to store")
702
+ .option("--service <service>", "service name")
703
+ .option("--key <secret>", "key or secret to store")
491
704
  .option("--label <label>", "friendly label")
492
705
  .option("--metadata <json>", "metadata as JSON", parseJsonObject)
493
- .action(async (opts) => {
706
+ .addHelpText("after", "\nExamples:\n spongewallet keys set openai sk-... --label primary\n")
707
+ .action(async (serviceArg, keyArg, opts) => {
494
708
  await executeToolCommand(opts, "store_key", {
495
- service: opts.service,
496
- key: opts.key,
709
+ service: requiredInput(opts, serviceArg, "service", "--service"),
710
+ key: requiredInput(opts, keyArg, "key", "--key"),
497
711
  label: opts.label,
498
712
  metadata: opts.metadata,
499
713
  });
@@ -560,41 +774,53 @@ function registerCuratedCommands(program, shared) {
560
774
  displayToolResult(getToolDefinition("submit_plan"), data);
561
775
  });
562
776
  shared(planCmd.command("approve").description("Approve and execute a submitted plan"))
563
- .requiredOption("--plan-id <id>", "plan ID")
564
- .action(async (opts) => {
777
+ .usage("<planId> [options]")
778
+ .argument("<planId>", "plan ID")
779
+ .option("--plan-id <id>", "plan ID")
780
+ .addHelpText("after", "\nExamples:\n spongewallet plan approve plan_123\n")
781
+ .action(async (planIdArg, opts) => {
565
782
  const wallet = await connectWallet(opts);
566
- const data = await wallet.approvePlan(String(opts.planId));
783
+ const data = await wallet.approvePlan(requiredInput(opts, planIdArg, "planId", "--plan-id"));
567
784
  displayToolResult(getToolDefinition("approve_plan"), data);
568
785
  });
569
786
  const tradeCmd = program.command("trade").description("Single trade proposal flow");
570
787
  shared(tradeCmd.command("propose").description("Propose a trade for approval"))
571
- .requiredOption("--from <token>", "input token")
572
- .requiredOption("--to <token>", "output token")
573
- .requiredOption("--amount <amount>", "amount to trade")
788
+ .usage("<from> <to> <amount> --reason <text> [options]")
789
+ .argument("<from>", "input token")
790
+ .argument("<to>", "output token")
791
+ .argument("<amount>", "amount to trade")
792
+ .option("--from <token>", "input token")
793
+ .option("--to <token>", "output token")
794
+ .option("--amount <amount>", "amount to trade")
574
795
  .requiredOption("--reason <text>", "reason shown to the user")
575
- .action(async (opts) => {
796
+ .addHelpText("after", "\nExamples:\n spongewallet trade propose ETH USDC 0.5 --reason \"Reduce exposure\"\n")
797
+ .action(async (fromArg, toArg, amountArg, opts) => {
576
798
  const wallet = await connectWallet(opts);
577
799
  const data = await wallet.proposeTrade({
578
- input_token: String(opts.from),
579
- output_token: String(opts.to),
580
- amount: String(opts.amount),
800
+ input_token: requiredInput(opts, fromArg, "from", "--from"),
801
+ output_token: requiredInput(opts, toArg, "to", "--to"),
802
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
581
803
  reason: String(opts.reason),
582
804
  });
583
805
  displayToolResult(getToolDefinition("propose_trade"), data);
584
806
  });
585
807
  const authCmd = program.command("auth").description("Authentication helpers");
586
808
  shared(authCmd.command("siwe").description("Generate a SIWE signature"))
587
- .requiredOption("--domain <domain>", "requesting domain")
588
- .requiredOption("--uri <uri>", "resource URI")
809
+ .usage("<domain> <uri> [options]")
810
+ .argument("<domain>", "requesting domain")
811
+ .argument("<uri>", "resource URI")
812
+ .option("--domain <domain>", "requesting domain")
813
+ .option("--uri <uri>", "resource URI")
589
814
  .option("--statement <text>", "human-readable statement")
590
815
  .option("--chain-id <id>", "chain ID", parseInt)
591
816
  .option("--expiration-time <iso>", "expiration time")
592
817
  .option("--not-before <iso>", "not before time")
593
818
  .option("--resources <json>", "resources array as JSON", parseJsonValue)
594
- .action(async (opts) => {
819
+ .addHelpText("after", "\nExamples:\n spongewallet auth siwe app.example.com https://app.example.com\n")
820
+ .action(async (domainArg, uriArg, opts) => {
595
821
  await executeToolCommand(opts, "generate_siwe", {
596
- domain: opts.domain,
597
- uri: opts.uri,
822
+ domain: requiredInput(opts, domainArg, "domain", "--domain"),
823
+ uri: requiredInput(opts, uriArg, "uri", "--uri"),
598
824
  statement: opts.statement,
599
825
  chain_id: opts.chainId,
600
826
  expiration_time: opts.expirationTime,
@@ -603,7 +829,105 @@ function registerCuratedCommands(program, shared) {
603
829
  });
604
830
  });
605
831
  const marketCmd = program.command("market").description("Trading venue integrations");
606
- shared(marketCmd.command("hyperliquid").description("Trade or inspect Hyperliquid"))
832
+ const hyperliquidCmd = marketCmd.command("hyperliquid").description("Trade or inspect Hyperliquid");
833
+ shared(hyperliquidCmd.command("status").description("Show Hyperliquid account status"))
834
+ .action(async (opts) => {
835
+ await executeHyperliquidAction(opts, { action: "status" });
836
+ });
837
+ shared(hyperliquidCmd.command("markets").description("List Hyperliquid markets"))
838
+ .usage("[limit] [offset] [options]")
839
+ .argument("[limit]", "result limit")
840
+ .argument("[offset]", "result offset")
841
+ .option("--limit <n>", "result limit", parseInt)
842
+ .option("--offset <n>", "result offset", parseInt)
843
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid markets\n spongewallet market hyperliquid markets 10\n")
844
+ .action(async (limitArg, offsetArg, opts) => {
845
+ await executeHyperliquidAction(opts, {
846
+ action: "markets",
847
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
848
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
849
+ });
850
+ });
851
+ shared(hyperliquidCmd.command("positions").description("List open Hyperliquid positions"))
852
+ .action(async (opts) => {
853
+ await executeHyperliquidAction(opts, { action: "positions" });
854
+ });
855
+ shared(hyperliquidCmd.command("orders").description("List open Hyperliquid orders"))
856
+ .usage("[limit] [offset] [options]")
857
+ .argument("[limit]", "result limit")
858
+ .argument("[offset]", "result offset")
859
+ .option("--limit <n>", "result limit", parseInt)
860
+ .option("--offset <n>", "result offset", parseInt)
861
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid orders\n spongewallet market hyperliquid orders 20\n")
862
+ .action(async (limitArg, offsetArg, opts) => {
863
+ await executeHyperliquidAction(opts, {
864
+ action: "orders",
865
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
866
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
867
+ });
868
+ });
869
+ shared(hyperliquidCmd.command("fills").description("List recent Hyperliquid fills"))
870
+ .usage("[limit] [offset] [options]")
871
+ .argument("[limit]", "result limit")
872
+ .argument("[offset]", "result offset")
873
+ .option("--limit <n>", "result limit", parseInt)
874
+ .option("--offset <n>", "result offset", parseInt)
875
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid fills\n spongewallet market hyperliquid fills 20\n")
876
+ .action(async (limitArg, offsetArg, opts) => {
877
+ await executeHyperliquidAction(opts, {
878
+ action: "fills",
879
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
880
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
881
+ });
882
+ });
883
+ shared(hyperliquidCmd.command("order").description("Place a Hyperliquid order"))
884
+ .argument("<symbol>", "market symbol")
885
+ .argument("<side>", "buy or sell")
886
+ .argument("<type>", "order type")
887
+ .argument("<amount>", "order amount")
888
+ .argument("[price]", "limit price")
889
+ .option("--symbol <symbol>", "market symbol")
890
+ .option("--side <side>", "buy or sell")
891
+ .option("--type <type>", "order type")
892
+ .option("--amount <amount>", "order amount")
893
+ .option("--price <price>", "limit price")
894
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid order ETH buy market 0.1\n spongewallet market hyperliquid order ETH buy limit 0.1 3000\n")
895
+ .action(async (symbolArg, sideArg, typeArg, amountArg, priceArg, opts) => {
896
+ await executeHyperliquidAction(opts, {
897
+ action: "order",
898
+ symbol: requiredInput(opts, symbolArg, "symbol", "--symbol"),
899
+ side: requiredInput(opts, sideArg, "side", "--side"),
900
+ type: requiredInput(opts, typeArg, "type", "--type"),
901
+ amount: requiredInput(opts, amountArg, "amount", "--amount"),
902
+ price: priceArg ?? opts.price,
903
+ });
904
+ });
905
+ shared(hyperliquidCmd.command("cancel").description("Cancel a Hyperliquid order"))
906
+ .argument("<orderId>", "order ID")
907
+ .option("--order-id <id>", "order ID")
908
+ .action(async (orderIdArg, opts) => {
909
+ await executeHyperliquidAction(opts, {
910
+ action: "cancel",
911
+ order_id: requiredInput(opts, orderIdArg, "orderId", "--order-id"),
912
+ });
913
+ });
914
+ shared(hyperliquidCmd.command("cancel-all").description("Cancel all Hyperliquid orders"))
915
+ .action(async (opts) => {
916
+ await executeHyperliquidAction(opts, { action: "cancel_all" });
917
+ });
918
+ shared(hyperliquidCmd.command("leverage").description("Set Hyperliquid leverage"))
919
+ .argument("<symbol>", "market symbol")
920
+ .argument("<leverage>", "leverage")
921
+ .option("--symbol <symbol>", "market symbol")
922
+ .option("--leverage <n>", "leverage", parseFloat)
923
+ .action(async (symbolArg, leverageArg, opts) => {
924
+ await executeHyperliquidAction(opts, {
925
+ action: "set_leverage",
926
+ symbol: requiredInput(opts, symbolArg, "symbol", "--symbol"),
927
+ leverage: leverageArg !== undefined ? parseFloat(leverageArg) : opts.leverage,
928
+ });
929
+ });
930
+ shared(hyperliquidCmd.command("raw").description("Call a raw Hyperliquid action"))
607
931
  .requiredOption("--action <action>", "hyperliquid action")
608
932
  .option("--symbol <symbol>", "market symbol")
609
933
  .option("--side <side>", "buy or sell")
@@ -616,8 +940,7 @@ function registerCuratedCommands(program, shared) {
616
940
  .option("--offset <n>", "result offset", parseInt)
617
941
  .option("--json <json>", "additional args as JSON", parseJsonObject)
618
942
  .action(async (opts) => {
619
- const wallet = await connectWallet(opts);
620
- const data = await wallet.hyperliquid({
943
+ await executeHyperliquidAction(opts, {
621
944
  ...opts.json,
622
945
  action: String(opts.action),
623
946
  symbol: opts.symbol,
@@ -630,7 +953,6 @@ function registerCuratedCommands(program, shared) {
630
953
  limit: opts.limit,
631
954
  offset: opts.offset,
632
955
  });
633
- displayToolResult(getToolDefinition("hyperliquid"), data);
634
956
  });
635
957
  }
636
958
  async function connectWallet(opts) {
@@ -639,6 +961,11 @@ async function connectWallet(opts) {
639
961
  credentialsPath: opts.credentialsPath,
640
962
  });
641
963
  }
964
+ async function executeHyperliquidAction(opts, input) {
965
+ const wallet = await connectWallet(opts);
966
+ const data = await wallet.hyperliquid(input);
967
+ displayToolResult(getToolDefinition("hyperliquid"), data);
968
+ }
642
969
  async function executeToolCommand(opts, toolName, input) {
643
970
  const wallet = await connectWallet(opts);
644
971
  const tools = await wallet.tools();
@@ -792,6 +1119,7 @@ const toolFormatters = {
792
1119
  const chains = data;
793
1120
  const rows = [];
794
1121
  let emptyCount = 0;
1122
+ let totalUsd = 0;
795
1123
  for (const [chain, info] of Object.entries(chains)) {
796
1124
  if (TESTNET_CHAINS.has(chain))
797
1125
  continue;
@@ -806,6 +1134,11 @@ const toolFormatters = {
806
1134
  amount: b.amount,
807
1135
  usd: b.usdValue ? `$${b.usdValue}` : "-",
808
1136
  });
1137
+ if (b.usdValue) {
1138
+ const parsed = Number(b.usdValue);
1139
+ if (Number.isFinite(parsed))
1140
+ totalUsd += parsed;
1141
+ }
809
1142
  }
810
1143
  }
811
1144
  if (rows.length === 0) {
@@ -826,10 +1159,147 @@ const toolFormatters = {
826
1159
  console.log(row(r.chain, r.token, r.amount, r.usd));
827
1160
  }
828
1161
  console.log();
1162
+ console.log(` Total: $${totalUsd.toFixed(2)}`);
1163
+ console.log();
829
1164
  if (emptyCount > 0) {
830
1165
  p.log.step(`${emptyCount} chain${emptyCount !== 1 ? "s" : ""} with no balance`);
831
1166
  }
832
1167
  },
1168
+ hyperliquid(data) {
1169
+ if (!isRecord(data)) {
1170
+ p.log.success("Hyperliquid");
1171
+ console.log(JSON.stringify(data, null, 2));
1172
+ return;
1173
+ }
1174
+ const action = getValueByKey(data, "tool_call.arguments.action");
1175
+ const title = action
1176
+ ? `Hyperliquid ${toTitleCase(String(action).replace(/_/g, " "))}`
1177
+ : "Hyperliquid";
1178
+ if (getValueByKey(data, "address") && isRecord(getValueByKey(data, "balances"))) {
1179
+ const perps = getValueByKey(data, "balances.perps");
1180
+ const spot = getValueByKey(data, "balances.spot");
1181
+ const openOrders = getValueByKey(data, "openOrders");
1182
+ const spotRows = Object.entries(spot ?? {}).map(([symbol, value]) => ({
1183
+ symbol,
1184
+ amount: getValueByKey(value, "amount"),
1185
+ usdValue: getValueByKey(value, "usdValue"),
1186
+ }));
1187
+ p.log.success(title);
1188
+ p.log.info([
1189
+ `Wallet: ${formatInlineValue(getValueByKey(data, "address"))}`,
1190
+ `Perps total: ${formatInlineValue(getValueByKey(perps, "total.USDC"))} USDC`,
1191
+ `Perps free: ${formatInlineValue(getValueByKey(perps, "free.USDC"))} USDC`,
1192
+ `Perps used: ${formatInlineValue(getValueByKey(perps, "used.USDC"))} USDC`,
1193
+ `Spot assets: ${spotRows.length}`,
1194
+ `Open orders: ${formatInlineValue(getValueByKey(data, "openOrderCount"))}`,
1195
+ ].join("\n"));
1196
+ if (spotRows.length > 0) {
1197
+ renderTable("Spot balances", [
1198
+ { key: "symbol", label: "Symbol" },
1199
+ { key: "amount", label: "Amount" },
1200
+ { key: "usdValue", label: "USD Value" },
1201
+ ], spotRows);
1202
+ }
1203
+ if (Array.isArray(openOrders) && openOrders.length > 0) {
1204
+ renderTable("Open orders", [
1205
+ { key: "symbol", label: "Symbol" },
1206
+ { key: "side", label: "Side" },
1207
+ { key: "price", label: "Price" },
1208
+ { key: "remaining", label: "Remaining" },
1209
+ { key: "status", label: "Status" },
1210
+ ], openOrders);
1211
+ }
1212
+ return;
1213
+ }
1214
+ const positions = getValueByKey(data, "positions");
1215
+ if (Array.isArray(positions)) {
1216
+ if (positions.length === 0) {
1217
+ p.log.info("No open Hyperliquid positions.");
1218
+ return;
1219
+ }
1220
+ renderTable(title, [
1221
+ { key: "symbol", label: "Symbol" },
1222
+ { key: "side", label: "Side" },
1223
+ { key: "contracts", label: "Size" },
1224
+ { key: "entryPrice", label: "Entry" },
1225
+ { key: "markPrice", label: "Mark" },
1226
+ { key: "leverage", label: "Lev" },
1227
+ { key: "unrealizedPnl", label: "PnL" },
1228
+ ], positions);
1229
+ return;
1230
+ }
1231
+ const orders = getValueByKey(data, "orders");
1232
+ if (Array.isArray(orders)) {
1233
+ if (orders.length === 0) {
1234
+ p.log.info("No open Hyperliquid orders.");
1235
+ return;
1236
+ }
1237
+ renderTable(title, [
1238
+ { key: "symbol", label: "Symbol" },
1239
+ { key: "side", label: "Side" },
1240
+ { key: "price", label: "Price" },
1241
+ { key: "remaining", label: "Remaining" },
1242
+ { key: "reduceOnly", label: "Reduce" },
1243
+ { key: "status", label: "Status" },
1244
+ ], orders);
1245
+ return;
1246
+ }
1247
+ const fills = getValueByKey(data, "fills");
1248
+ if (Array.isArray(fills)) {
1249
+ if (fills.length === 0) {
1250
+ p.log.info("No recent Hyperliquid fills.");
1251
+ return;
1252
+ }
1253
+ renderTable(title, [
1254
+ { key: "symbol", label: "Symbol" },
1255
+ { key: "side", label: "Side" },
1256
+ { key: "price", label: "Price" },
1257
+ { key: "amount", label: "Amount" },
1258
+ { key: "closedPnl", label: "PnL" },
1259
+ { key: "datetime", label: "Time" },
1260
+ ], fills);
1261
+ return;
1262
+ }
1263
+ const markets = getValueByKey(data, "markets");
1264
+ if (Array.isArray(markets)) {
1265
+ if (markets.length === 0) {
1266
+ p.log.info("No Hyperliquid markets found.");
1267
+ return;
1268
+ }
1269
+ renderTable(title, [
1270
+ { key: "symbol", label: "Symbol" },
1271
+ { key: "type", label: "Type" },
1272
+ { key: "base", label: "Base" },
1273
+ { key: "quote", label: "Quote" },
1274
+ { key: "maxLeverage", label: "Max Lev" },
1275
+ ], markets);
1276
+ const total = getValueByKey(data, "total");
1277
+ const nextOffset = getValueByKey(data, "nextOffset");
1278
+ if (total !== undefined) {
1279
+ p.log.info(`Showing ${markets.length} of ${formatInlineValue(total)} markets.`);
1280
+ }
1281
+ if (nextOffset !== null && nextOffset !== undefined) {
1282
+ p.log.info(`Next offset: ${formatInlineValue(nextOffset)}`);
1283
+ }
1284
+ return;
1285
+ }
1286
+ const cleanData = Object.fromEntries(Object.entries(data).filter(([key]) => key !== "tool_call"));
1287
+ if (renderFields(title, [
1288
+ { key: "message", label: "Message" },
1289
+ { key: "status", label: "Status" },
1290
+ { key: "orderId", label: "Order ID" },
1291
+ { key: "clientOrderId", label: "Client Order ID" },
1292
+ { key: "symbol", label: "Symbol" },
1293
+ { key: "leverage", label: "Leverage" },
1294
+ { key: "cancelled", label: "Cancelled" },
1295
+ { key: "address", label: "Address" },
1296
+ { key: "webChartUrl", label: "Chart" },
1297
+ ], cleanData)) {
1298
+ return;
1299
+ }
1300
+ p.log.success(title);
1301
+ console.log(JSON.stringify(cleanData, null, 2));
1302
+ },
833
1303
  };
834
1304
  function isRecord(value) {
835
1305
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -926,9 +1396,25 @@ function renderTxOutput(title, data) {
926
1396
  if (!isRecord(data))
927
1397
  return false;
928
1398
  const hash = getValueByKey(data, ["transactionHash", "txHash", "signature"]);
1399
+ const inputAmount = getValueByKey(data, ["inputToken.amount", "input_token.amount"]);
1400
+ const inputSymbol = getValueByKey(data, ["inputToken.symbol", "input_token.symbol"]);
1401
+ const outputAmount = getValueByKey(data, ["outputToken.amount", "output_token.amount"]);
1402
+ const outputSymbol = getValueByKey(data, ["outputToken.symbol", "output_token.symbol"]);
1403
+ const flow = inputAmount && inputSymbol && outputAmount && outputSymbol
1404
+ ? `${formatInlineValue(inputAmount)} ${formatInlineValue(inputSymbol)} -> ${formatInlineValue(outputAmount)} ${formatInlineValue(outputSymbol)}`
1405
+ : undefined;
1406
+ const sourceChain = getValueByKey(data, ["sourceChain", "source_chain"]);
1407
+ const destinationChain = getValueByKey(data, ["destinationChain", "destination_chain"]);
1408
+ const route = sourceChain && destinationChain
1409
+ ? `${formatInlineValue(sourceChain)} -> ${formatInlineValue(destinationChain)}`
1410
+ : undefined;
929
1411
  const lines = [
1412
+ route ? `Route: ${route}` : undefined,
1413
+ flow ? `Flow: ${flow}` : undefined,
930
1414
  hash ? `Transaction: ${formatInlineValue(hash)}` : undefined,
931
1415
  getValueByKey(data, "status") ? `Status: ${formatInlineValue(getValueByKey(data, "status"))}` : undefined,
1416
+ getValueByKey(data, "priceImpactPct") ? `Price impact: ${formatInlineValue(getValueByKey(data, "priceImpactPct"))}%` : undefined,
1417
+ getValueByKey(data, "gasUsed") ? `Gas used: ${formatInlineValue(getValueByKey(data, "gasUsed"))}` : undefined,
932
1418
  getValueByKey(data, "explorerUrl") ? `Explorer: ${formatInlineValue(getValueByKey(data, "explorerUrl"))}` : undefined,
933
1419
  getValueByKey(data, "chain") ? `Chain: ${formatInlineValue(getValueByKey(data, "chain"))}` : undefined,
934
1420
  getValueByKey(data, "from") ? `Signer: ${formatInlineValue(getValueByKey(data, "from"))}` : undefined,