@paysponge/sdk 0.1.21 → 0.1.26

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");
@@ -32,7 +34,10 @@ export function buildCliProgram(metadata = {}) {
32
34
  .action((opts) => handleInit(opts, metadata));
33
35
  withAuth(program
34
36
  .command("login")
35
- .description("Claim a pending agent or authenticate and cache credentials")).action((opts) => handleLogin(opts));
37
+ .description("Claim a pending agent or authenticate and cache credentials"))
38
+ .option("--continue-claim", "open the cached claim URL for the current agent")
39
+ .option("--switch", "replace cached credentials with a new agent login")
40
+ .action((opts) => handleLogin(opts));
36
41
  shared(program.command("logout").description("Remove stored credentials")).action((opts) => handleLogout(opts));
37
42
  shared(program
38
43
  .command("whoami")
@@ -48,6 +53,7 @@ export function buildCliProgram(metadata = {}) {
48
53
  .command("advanced")
49
54
  .description("Low-level commands mirroring the raw tool surface");
50
55
  registerToolCommands(advancedCmd, shared);
56
+ applyHelpTheme(program, metadata);
51
57
  return program;
52
58
  }
53
59
  export async function runCli(args, metadata = {}) {
@@ -59,7 +65,17 @@ export async function runCli(args, metadata = {}) {
59
65
  // ---------------------------------------------------------------------------
60
66
  async function handleLogin(opts) {
61
67
  const creds = loadCredentials(opts.credentialsPath);
62
- if (!process.env.SPONGE_API_KEY && creds?.claimUrl) {
68
+ if (opts.switch) {
69
+ deleteCredentials(opts.credentialsPath);
70
+ }
71
+ if (opts.continueClaim) {
72
+ if (!creds?.claimUrl) {
73
+ throw new Error("No pending claim URL found in cached credentials. Run `spongewallet login --switch` to authenticate a different agent.");
74
+ }
75
+ await continueClaimFlow(creds, opts);
76
+ return;
77
+ }
78
+ if (!opts.switch && !process.env.SPONGE_API_KEY && creds?.claimUrl) {
63
79
  await continueClaimFlow(creds, opts);
64
80
  return;
65
81
  }
@@ -174,6 +190,11 @@ async function handleWhoami(opts, meta) {
174
190
  lines.push(`Claim Code: ${creds.claimCode}`);
175
191
  lines.push(`Credentials: ${getCredentialsPath(opts.credentialsPath)}`);
176
192
  p.log.info(lines.join("\n"));
193
+ if (creds.claimUrl) {
194
+ const cmd = meta.commandName ?? "spongewallet";
195
+ p.log.step(`Run \`${cmd} login --continue-claim\` to reopen this claim flow.`);
196
+ p.log.step(`Run \`${cmd} login --switch\` to replace this cached agent with a different one.`);
197
+ }
177
198
  }
178
199
  async function handleMcpPrint(opts) {
179
200
  const wallet = await SpongeWallet.connect({
@@ -219,8 +240,45 @@ async function continueClaimFlow(creds, opts) {
219
240
  p.log.step("Could not open browser automatically. Open the claim URL manually.");
220
241
  }
221
242
  p.log.info("After the browser claim completes, the cached API key will keep working for this agent.");
243
+ p.log.step("To authenticate a different agent, run `spongewallet login --switch`.");
222
244
  p.outro("Claim flow ready");
223
245
  }
246
+ function requiredInput(command, opts, positional, optionKey, flagName) {
247
+ const fromOption = opts[optionKey];
248
+ const value = positional
249
+ ?? (typeof fromOption === "string" ? fromOption : undefined);
250
+ if (!value) {
251
+ command.error(`missing required argument or option: ${flagName}`, {
252
+ exitCode: 1,
253
+ code: "sponge.missing_required_input",
254
+ });
255
+ }
256
+ return value;
257
+ }
258
+ function isTempoChain(chain) {
259
+ return chain === "tempo" || chain === "tempo-testnet";
260
+ }
261
+ function normalizeTempoTokenSymbol(token, chain) {
262
+ const trimmed = token.trim();
263
+ if (trimmed.startsWith("0x") && trimmed.length === 42) {
264
+ return trimmed;
265
+ }
266
+ const normalized = trimmed.toLowerCase().replace(/[\s_-]/g, "");
267
+ const aliasMap = {
268
+ path: "pathUSD",
269
+ pathusd: "pathUSD",
270
+ alpha: "AlphaUSD",
271
+ alphausd: "AlphaUSD",
272
+ beta: "BetaUSD",
273
+ betausd: "BetaUSD",
274
+ theta: "ThetaUSD",
275
+ thetausd: "ThetaUSD",
276
+ usdc: chain === "tempo" ? "USDC.e" : "pathUSD",
277
+ "usdc.e": chain === "tempo" ? "USDC.e" : "pathUSD",
278
+ usdce: chain === "tempo" ? "USDC.e" : "pathUSD",
279
+ };
280
+ return aliasMap[normalized] ?? trimmed;
281
+ }
224
282
  // ---------------------------------------------------------------------------
225
283
  // Helpers
226
284
  // ---------------------------------------------------------------------------
@@ -231,6 +289,89 @@ function defaultAgentName(email) {
231
289
  .replace(/^-+|-+$/g, "");
232
290
  return slug ? `agent-${slug}` : "sponge-agent";
233
291
  }
292
+ const ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
293
+ const HELP_COLOR_ENABLED = Boolean(!process.env.NO_COLOR
294
+ && (process.stdout.isTTY || process.env.FORCE_COLOR));
295
+ function ansi(text, open, close = "\u001B[0m") {
296
+ if (!HELP_COLOR_ENABLED)
297
+ return text;
298
+ return `${open}${text}${close}`;
299
+ }
300
+ function stripAnsi(text) {
301
+ return text.replace(ANSI_PATTERN, "");
302
+ }
303
+ function bold(text) {
304
+ return ansi(text, "\u001B[1m");
305
+ }
306
+ function cyan(text) {
307
+ return ansi(text, "\u001B[36m");
308
+ }
309
+ function green(text) {
310
+ return ansi(text, "\u001B[32m");
311
+ }
312
+ function dim(text) {
313
+ return ansi(text, "\u001B[2m");
314
+ }
315
+ function toTitleCase(value) {
316
+ return value
317
+ .split(/[\s-]+/)
318
+ .filter(Boolean)
319
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
320
+ .join(" ");
321
+ }
322
+ function commandPath(command) {
323
+ const parts = [];
324
+ let current = command;
325
+ while (current) {
326
+ parts.unshift(current.name());
327
+ current = current.parent ?? null;
328
+ }
329
+ return parts;
330
+ }
331
+ function buildHelpBanner(command, metadata) {
332
+ const path = commandPath(command);
333
+ const rootName = metadata.commandName ?? "spongewallet";
334
+ const title = path.length === 1
335
+ ? "Sponge Wallet CLI"
336
+ : `Sponge Wallet ${path.slice(1).map(toTitleCase).join(" ")}`;
337
+ const subtitle = path.length === 1
338
+ ? "Manage agent wallets, swaps, payments, and MCP setup."
339
+ : command.description() || `${rootName} command help`;
340
+ const lines = [bold(green(title)), "", dim(subtitle)];
341
+ const width = Math.max(...lines.map((line) => stripAnsi(line).length));
342
+ const top = cyan(`╭${"─".repeat(width + 2)}╮`);
343
+ const bottom = cyan(`╰${"─".repeat(width + 2)}╯`);
344
+ const body = lines.map((line) => {
345
+ const padding = " ".repeat(width - stripAnsi(line).length);
346
+ return `${cyan("│")} ${line}${padding} ${cyan("│")}`;
347
+ });
348
+ return [top, ...body, bottom].join("\n");
349
+ }
350
+ function applyHelpTheme(command, metadata) {
351
+ command.configureHelp({
352
+ commandDescription: () => "",
353
+ styleTitle: (text) => bold(cyan(text)),
354
+ styleCommandText: (text) => bold(green(text)),
355
+ styleSubcommandText: (text) => green(text),
356
+ styleOptionText: (text) => cyan(text),
357
+ styleArgumentText: (text) => bold(text),
358
+ styleDescriptionText: (text) => text,
359
+ formatHelp(cmd, helper) {
360
+ const description = cmd.description();
361
+ const lines = Help.prototype.formatHelp.call(helper, cmd, helper)
362
+ .trimEnd()
363
+ .split("\n");
364
+ if (description && lines[2] === description && lines[3] === "") {
365
+ lines.splice(2, 2);
366
+ }
367
+ const body = lines.join("\n");
368
+ return `${buildHelpBanner(cmd, metadata)}\n\n${body}\n`;
369
+ },
370
+ });
371
+ for (const subcommand of command.commands) {
372
+ applyHelpTheme(subcommand, metadata);
373
+ }
374
+ }
234
375
  // ---------------------------------------------------------------------------
235
376
  // Curated command tree
236
377
  // ---------------------------------------------------------------------------
@@ -246,12 +387,12 @@ const CHAIN_VALUES = [
246
387
  ];
247
388
  const EVM_CHAIN_VALUES = ["ethereum", "base", "sepolia", "base-sepolia"];
248
389
  const SOLANA_CHAIN_VALUES = ["solana", "solana-devnet"];
390
+ const TEMPO_CHAIN_VALUES = ["tempo", "tempo-testnet"];
249
391
  const ONRAMP_CHAIN_VALUES = ["base", "solana", "polygon"];
250
392
  const PAY_CHAIN_VALUES = ["base", "solana", "tempo", "ethereum"];
251
393
  const PREFERRED_X402_CHAINS = ["base", "solana", "ethereum"];
252
394
  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"))
395
+ shared(program.command("balance").description("Show wallet balances"))
255
396
  .addOption(new Option("--chain <chain>", "specific chain").choices([...CHAIN_VALUES, "all"]))
256
397
  .option("--allowed-chains <chains>", "comma-separated chain allowlist")
257
398
  .option("--only-usdc", "only show USDC balances")
@@ -264,58 +405,98 @@ function registerCuratedCommands(program, shared) {
264
405
  });
265
406
  displayToolResult(getToolDefinition("get_balance"), data);
266
407
  });
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) => {
273
- const wallet = await connectWallet(opts);
274
- const chain = String(opts.chain);
275
- const asset = String(opts.asset);
408
+ shared(program.command("send").description("Send assets on EVM, Solana, or Tempo"))
409
+ .usage("[chain] [to] [asset] [amount] [options]")
410
+ .argument("[chain]", "destination chain")
411
+ .argument("[to]", "recipient address")
412
+ .argument("[asset]", "currency symbol or token symbol/address")
413
+ .argument("[amount]", "amount to send")
414
+ .option("--chain <chain>", "destination chain")
415
+ .option("--to <address>", "recipient address")
416
+ .option("--amount <amount>", "amount to send")
417
+ .option("--asset <asset>", "currency symbol or token symbol/address")
418
+ .addHelpText("after", "\nExamples:\n spongewallet send base 0xabc... USDC 10\n spongewallet send tempo 0xabc... usdce 1\n")
419
+ .action(async (chainArg, toArg, assetArg, amountArg, opts, command) => {
420
+ const chain = requiredInput(command, opts, chainArg, "chain", "--chain");
421
+ const asset = isTempoChain(chain)
422
+ ? normalizeTempoTokenSymbol(requiredInput(command, opts, assetArg, "asset", "--asset"), chain)
423
+ : requiredInput(command, opts, assetArg, "asset", "--asset");
276
424
  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 };
425
+ ? {
426
+ chain,
427
+ to: requiredInput(command, opts, toArg, "to", "--to"),
428
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
429
+ token: asset,
430
+ }
431
+ : {
432
+ chain,
433
+ to: requiredInput(command, opts, toArg, "to", "--to"),
434
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
435
+ currency: asset,
436
+ };
437
+ const wallet = await connectWallet(opts);
279
438
  const data = await wallet.transfer(input);
280
439
  displayToolResult(getToolDefinition(chain.startsWith("solana") ? "solana_transfer" : "evm_transfer"), data);
281
440
  });
282
- shared(walletCmd.command("history").description("Show recent transaction history"))
441
+ shared(program.command("history").description("Show recent transaction history"))
442
+ .usage("[limit] [options]")
443
+ .argument("[limit]", "maximum number of transactions")
283
444
  .option("--limit <n>", "maximum number of transactions", parseInt)
284
445
  .addOption(new Option("--chain <chain>", "filter by chain").choices(CHAIN_VALUES))
285
- .action(async (opts) => {
446
+ .addHelpText("after", "\nExamples:\n spongewallet history\n spongewallet history 20 --chain base\n")
447
+ .action(async (limitArg, opts) => {
286
448
  const wallet = await connectWallet(opts);
449
+ const limit = limitArg !== undefined
450
+ ? parseInt(limitArg, 10)
451
+ : opts.limit;
287
452
  const data = await wallet.getTransactionHistoryDetailed({
288
- limit: opts.limit,
453
+ limit: Number.isFinite(limit) ? limit : undefined,
289
454
  chain: opts.chain,
290
455
  });
291
456
  displayToolResult(getToolDefinition("get_transaction_history"), data);
292
457
  });
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) => {
458
+ shared(program.command("tokens").description("List Solana wallet tokens"))
459
+ .usage("[chain] [options]")
460
+ .argument("[chain]", "Solana network")
461
+ .option("--chain <chain>", "Solana network")
462
+ .addHelpText("after", "\nExamples:\n spongewallet tokens\n spongewallet tokens solana-devnet\n")
463
+ .action(async (chainArg, opts) => {
296
464
  const wallet = await connectWallet(opts);
297
- const data = await wallet.getSolanaTokens(opts.chain);
465
+ const chain = (chainArg ?? opts.chain ?? "solana");
466
+ const data = await wallet.getSolanaTokens(chain);
298
467
  displayToolResult(getToolDefinition("get_solana_tokens"), data);
299
468
  });
300
- shared(walletCmd.command("search-tokens").description("Search the Solana token list"))
301
- .requiredOption("--query <query>", "token symbol or name")
469
+ shared(program.command("search-tokens").description("Search the Solana token list"))
470
+ .usage("[query] [limit] [options]")
471
+ .argument("[query]", "token symbol or name")
472
+ .argument("[limit]", "maximum results")
473
+ .option("--query <query>", "token symbol or name")
302
474
  .option("--limit <n>", "maximum results", parseInt)
303
- .action(async (opts) => {
475
+ .addHelpText("after", "\nExamples:\n spongewallet search-tokens BONK\n spongewallet search-tokens BONK 5\n")
476
+ .action(async (queryArg, limitArg, opts, command) => {
477
+ const limit = limitArg !== undefined
478
+ ? parseInt(limitArg, 10)
479
+ : opts.limit;
480
+ const query = requiredInput(command, opts, queryArg, "query", "--query");
304
481
  const wallet = await connectWallet(opts);
305
- const data = await wallet.searchSolanaTokens(String(opts.query), opts.limit);
482
+ const data = await wallet.searchSolanaTokens(query, Number.isFinite(limit) ? limit : undefined);
306
483
  displayToolResult(getToolDefinition("search_solana_tokens"), data);
307
484
  });
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"))
485
+ shared(program.command("onramp").description("Create a fiat-to-crypto onramp link"))
486
+ .usage("[chain] [fiatAmount] [options]")
487
+ .argument("[chain]", "destination chain")
488
+ .argument("[fiatAmount]", "prefill fiat amount")
489
+ .option("--chain <chain>", "destination chain")
310
490
  .option("--wallet-address <address>", "destination wallet address (defaults to agent wallet)")
311
491
  .addOption(new Option("--provider <provider>", "onramp provider").choices(["auto", "stripe", "coinbase"]).default("auto"))
312
492
  .option("--fiat-amount <amount>", "prefill fiat amount")
313
493
  .option("--fiat-currency <code>", "fiat currency code")
314
494
  .option("--lock-wallet-address", "lock destination wallet address")
315
495
  .option("--redirect-url <url>", "redirect URL after checkout")
316
- .action(async (opts) => {
496
+ .addHelpText("after", "\nExamples:\n spongewallet onramp\n spongewallet onramp base 100\n spongewallet onramp solana 250 --fiat-currency usd\n")
497
+ .action(async (chainArg, fiatAmountArg, opts) => {
317
498
  const wallet = await connectWallet(opts);
318
- const chain = String(opts.chain ?? "base");
499
+ const chain = String(chainArg ?? opts.chain ?? "base");
319
500
  const walletAddress = opts.walletAddress
320
501
  ?? (await wallet.getAddress(chain))
321
502
  ?? "";
@@ -323,7 +504,7 @@ function registerCuratedCommands(program, shared) {
323
504
  wallet_address: walletAddress,
324
505
  chain: chain,
325
506
  provider: opts.provider,
326
- fiat_amount: opts.fiatAmount,
507
+ fiat_amount: (fiatAmountArg ?? opts.fiatAmount),
327
508
  fiat_currency: opts.fiatCurrency,
328
509
  lock_wallet_address: Boolean(opts.lockWalletAddress),
329
510
  redirect_url: opts.redirectUrl,
@@ -332,11 +513,17 @@ function registerCuratedCommands(program, shared) {
332
513
  });
333
514
  const txCmd = program.command("tx").description("Transaction status and signing");
334
515
  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) => {
516
+ .usage("[chain] [txHash] [options]")
517
+ .argument("[chain]", "transaction chain")
518
+ .argument("[txHash]", "transaction hash or signature")
519
+ .option("--tx-hash <hash>", "transaction hash or signature")
520
+ .option("--chain <chain>", "transaction chain")
521
+ .addHelpText("after", "\nExamples:\n spongewallet tx status base 0x123...\n spongewallet tx status solana 5K2...\n")
522
+ .action(async (chainArg, txHashArg, opts, command) => {
523
+ const txHash = requiredInput(command, opts, txHashArg, "txHash", "--tx-hash");
524
+ const chain = requiredInput(command, opts, chainArg, "chain", "--chain");
338
525
  const wallet = await connectWallet(opts);
339
- const data = await wallet.getTransactionStatus(String(opts.txHash), opts.chain);
526
+ const data = await wallet.getTransactionStatus(txHash, chain);
340
527
  displayToolResult(getToolDefinition("get_transaction_status"), data);
341
528
  });
342
529
  shared(txCmd.command("sign").description("Sign a Solana transaction without submitting"))
@@ -355,70 +542,118 @@ function registerCuratedCommands(program, shared) {
355
542
  });
356
543
  const swapCmd = program.command("swap").description("Quotes and swaps");
357
544
  shared(swapCmd.command("solana").description("Swap on Solana"))
545
+ .usage("[from] [to] [amount] [options]")
546
+ .argument("[from]", "input token")
547
+ .argument("[to]", "output token")
548
+ .argument("[amount]", "amount to swap")
358
549
  .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")
550
+ .option("--from <token>", "input token")
551
+ .option("--to <token>", "output token")
552
+ .option("--amount <amount>", "amount to swap")
362
553
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
363
- .action(async (opts) => {
554
+ .addHelpText("after", "\nExamples:\n spongewallet swap solana SOL USDC 1\n spongewallet swap solana --chain solana --from SOL --to USDC --amount 1\n")
555
+ .action(async (fromArg, toArg, amountArg, opts, command) => {
556
+ const from = requiredInput(command, opts, fromArg, "from", "--from");
557
+ const to = requiredInput(command, opts, toArg, "to", "--to");
558
+ const amount = requiredInput(command, opts, amountArg, "amount", "--amount");
364
559
  const wallet = await connectWallet(opts);
365
560
  const data = await wallet.swap({
366
561
  chain: opts.chain,
367
- from: String(opts.from),
368
- to: String(opts.to),
369
- amount: String(opts.amount),
562
+ from,
563
+ to,
564
+ amount,
370
565
  slippageBps: opts.slippageBps,
371
566
  });
372
567
  displayToolResult(getToolDefinition("solana_swap"), data);
373
568
  });
374
569
  shared(swapCmd.command("quote").description("Get a Jupiter quote without executing"))
570
+ .usage("[from] [to] [amount] [options]")
571
+ .argument("[from]", "input token")
572
+ .argument("[to]", "output token")
573
+ .argument("[amount]", "amount to quote")
375
574
  .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")
575
+ .option("--from <token>", "input token")
576
+ .option("--to <token>", "output token")
577
+ .option("--amount <amount>", "amount to quote")
379
578
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
380
- .action(async (opts) => {
579
+ .addHelpText("after", "\nExamples:\n spongewallet swap quote SOL USDC 1\n spongewallet swap quote --chain solana --from SOL --to USDC --amount 1\n")
580
+ .action(async (fromArg, toArg, amountArg, opts, command) => {
381
581
  await executeToolCommand(opts, "jupiter_swap_quote", {
382
582
  chain: opts.chain,
383
- input_token: opts.from,
384
- output_token: opts.to,
385
- amount: opts.amount,
583
+ input_token: requiredInput(command, opts, fromArg, "from", "--from"),
584
+ output_token: requiredInput(command, opts, toArg, "to", "--to"),
585
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
386
586
  slippage_bps: opts.slippageBps,
387
587
  });
388
588
  });
389
589
  shared(swapCmd.command("execute").description("Execute a previously quoted Jupiter swap"))
390
- .requiredOption("--quote-id <id>", "quote ID to execute")
391
- .action(async (opts) => {
590
+ .usage("[quoteId] [options]")
591
+ .argument("[quoteId]", "quote ID to execute")
592
+ .option("--quote-id <id>", "quote ID to execute")
593
+ .addHelpText("after", "\nExamples:\n spongewallet swap execute quote_123\n")
594
+ .action(async (quoteIdArg, opts, command) => {
392
595
  await executeToolCommand(opts, "jupiter_swap_execute", {
393
- quote_id: String(opts.quoteId),
596
+ quote_id: requiredInput(command, opts, quoteIdArg, "quoteId", "--quote-id"),
394
597
  });
395
598
  });
396
599
  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")
600
+ .usage("[from] [to] [amount] [options]")
601
+ .argument("[from]", "input token")
602
+ .argument("[to]", "output token")
603
+ .argument("[amount]", "amount to swap")
604
+ .option("--from <token>", "input token")
605
+ .option("--to <token>", "output token")
606
+ .option("--amount <amount>", "amount to swap")
400
607
  .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
401
- .action(async (opts) => {
608
+ .addHelpText("after", "\nExamples:\n spongewallet swap base ETH USDC 0.1\n spongewallet swap base --from ETH --to USDC --amount 0.1\n")
609
+ .action(async (fromArg, toArg, amountArg, opts, command) => {
402
610
  await executeToolCommand(opts, "base_swap", {
403
- input_token: opts.from,
404
- output_token: opts.to,
405
- amount: opts.amount,
611
+ input_token: requiredInput(command, opts, fromArg, "from", "--from"),
612
+ output_token: requiredInput(command, opts, toArg, "to", "--to"),
613
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
614
+ slippage_bps: opts.slippageBps,
615
+ });
616
+ });
617
+ shared(swapCmd.command("tempo").description("Swap stablecoins on Tempo via native DEX"))
618
+ .usage("[from] [to] [amount] [options]")
619
+ .argument("[from]", "input token")
620
+ .argument("[to]", "output token")
621
+ .argument("[amount]", "amount to swap")
622
+ .addOption(new Option("--chain <chain>", "Tempo network").choices(TEMPO_CHAIN_VALUES).default("tempo"))
623
+ .option("--from <token>", "input token")
624
+ .option("--to <token>", "output token")
625
+ .option("--amount <amount>", "amount to swap")
626
+ .option("--slippage-bps <bps>", "slippage in basis points", parseInt)
627
+ .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")
628
+ .action(async (fromArg, toArg, amountArg, opts, command) => {
629
+ const chain = String(opts.chain ?? "tempo");
630
+ await executeToolCommand(opts, "tempo_swap", {
631
+ chain,
632
+ input_token: normalizeTempoTokenSymbol(requiredInput(command, opts, fromArg, "from", "--from"), chain),
633
+ output_token: normalizeTempoTokenSymbol(requiredInput(command, opts, toArg, "to", "--to"), chain),
634
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
406
635
  slippage_bps: opts.slippageBps,
407
636
  });
408
637
  });
409
638
  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")
639
+ .usage("[sourceChain] [destinationChain] [token] [amount] [options]")
640
+ .argument("[sourceChain]", "source chain")
641
+ .argument("[destinationChain]", "destination chain")
642
+ .argument("[token]", "token to bridge")
643
+ .argument("[amount]", "amount to bridge")
644
+ .option("--source-chain <chain>", "source chain")
645
+ .option("--destination-chain <chain>", "destination chain")
646
+ .option("--token <token>", "token to bridge")
647
+ .option("--amount <amount>", "amount to bridge")
414
648
  .option("--destination-token <token>", "token to receive on destination")
415
649
  .option("--recipient-address <address>", "recipient address on destination")
416
- .action(async (opts) => {
650
+ .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")
651
+ .action(async (sourceChainArg, destinationChainArg, tokenArg, amountArg, opts, command) => {
417
652
  await executeToolCommand(opts, "bridge", {
418
- source_chain: opts.sourceChain,
419
- destination_chain: opts.destinationChain,
420
- token: opts.token,
421
- amount: opts.amount,
653
+ source_chain: requiredInput(command, opts, sourceChainArg, "sourceChain", "--source-chain"),
654
+ destination_chain: requiredInput(command, opts, destinationChainArg, "destinationChain", "--destination-chain"),
655
+ token: requiredInput(command, opts, tokenArg, "token", "--token"),
656
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
422
657
  destination_token: opts.destinationToken,
423
658
  recipient_address: opts.recipientAddress,
424
659
  });
@@ -479,21 +714,28 @@ function registerCuratedCommands(program, shared) {
479
714
  await executeToolCommand(opts, "get_key_list", {});
480
715
  });
481
716
  shared(keysCmd.command("get").description("Get a stored key value"))
482
- .requiredOption("--service <service>", "service name")
483
- .action(async (opts) => {
717
+ .usage("[service] [options]")
718
+ .argument("[service]", "service name")
719
+ .option("--service <service>", "service name")
720
+ .addHelpText("after", "\nExamples:\n spongewallet keys get openai\n")
721
+ .action(async (serviceArg, opts, command) => {
484
722
  await executeToolCommand(opts, "get_key_value", {
485
- service: String(opts.service),
723
+ service: requiredInput(command, opts, serviceArg, "service", "--service"),
486
724
  });
487
725
  });
488
726
  shared(keysCmd.command("set").description("Store a service key"))
489
- .requiredOption("--service <service>", "service name")
490
- .requiredOption("--key <secret>", "key or secret to store")
727
+ .usage("[service] [key] [options]")
728
+ .argument("[service]", "service name")
729
+ .argument("[key]", "key or secret to store")
730
+ .option("--service <service>", "service name")
731
+ .option("--key <secret>", "key or secret to store")
491
732
  .option("--label <label>", "friendly label")
492
733
  .option("--metadata <json>", "metadata as JSON", parseJsonObject)
493
- .action(async (opts) => {
734
+ .addHelpText("after", "\nExamples:\n spongewallet keys set openai sk-... --label primary\n")
735
+ .action(async (serviceArg, keyArg, opts, command) => {
494
736
  await executeToolCommand(opts, "store_key", {
495
- service: opts.service,
496
- key: opts.key,
737
+ service: requiredInput(command, opts, serviceArg, "service", "--service"),
738
+ key: requiredInput(command, opts, keyArg, "key", "--key"),
497
739
  label: opts.label,
498
740
  metadata: opts.metadata,
499
741
  });
@@ -560,41 +802,57 @@ function registerCuratedCommands(program, shared) {
560
802
  displayToolResult(getToolDefinition("submit_plan"), data);
561
803
  });
562
804
  shared(planCmd.command("approve").description("Approve and execute a submitted plan"))
563
- .requiredOption("--plan-id <id>", "plan ID")
564
- .action(async (opts) => {
805
+ .usage("[planId] [options]")
806
+ .argument("[planId]", "plan ID")
807
+ .option("--plan-id <id>", "plan ID")
808
+ .addHelpText("after", "\nExamples:\n spongewallet plan approve plan_123\n")
809
+ .action(async (planIdArg, opts, command) => {
810
+ const planId = requiredInput(command, opts, planIdArg, "planId", "--plan-id");
565
811
  const wallet = await connectWallet(opts);
566
- const data = await wallet.approvePlan(String(opts.planId));
812
+ const data = await wallet.approvePlan(planId);
567
813
  displayToolResult(getToolDefinition("approve_plan"), data);
568
814
  });
569
815
  const tradeCmd = program.command("trade").description("Single trade proposal flow");
570
816
  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")
817
+ .usage("[from] [to] [amount] --reason <text> [options]")
818
+ .argument("[from]", "input token")
819
+ .argument("[to]", "output token")
820
+ .argument("[amount]", "amount to trade")
821
+ .option("--from <token>", "input token")
822
+ .option("--to <token>", "output token")
823
+ .option("--amount <amount>", "amount to trade")
574
824
  .requiredOption("--reason <text>", "reason shown to the user")
575
- .action(async (opts) => {
825
+ .addHelpText("after", "\nExamples:\n spongewallet trade propose ETH USDC 0.5 --reason \"Reduce exposure\"\n")
826
+ .action(async (fromArg, toArg, amountArg, opts, command) => {
827
+ const inputToken = requiredInput(command, opts, fromArg, "from", "--from");
828
+ const outputToken = requiredInput(command, opts, toArg, "to", "--to");
829
+ const amount = requiredInput(command, opts, amountArg, "amount", "--amount");
576
830
  const wallet = await connectWallet(opts);
577
831
  const data = await wallet.proposeTrade({
578
- input_token: String(opts.from),
579
- output_token: String(opts.to),
580
- amount: String(opts.amount),
832
+ input_token: inputToken,
833
+ output_token: outputToken,
834
+ amount,
581
835
  reason: String(opts.reason),
582
836
  });
583
837
  displayToolResult(getToolDefinition("propose_trade"), data);
584
838
  });
585
839
  const authCmd = program.command("auth").description("Authentication helpers");
586
840
  shared(authCmd.command("siwe").description("Generate a SIWE signature"))
587
- .requiredOption("--domain <domain>", "requesting domain")
588
- .requiredOption("--uri <uri>", "resource URI")
841
+ .usage("[domain] [uri] [options]")
842
+ .argument("[domain]", "requesting domain")
843
+ .argument("[uri]", "resource URI")
844
+ .option("--domain <domain>", "requesting domain")
845
+ .option("--uri <uri>", "resource URI")
589
846
  .option("--statement <text>", "human-readable statement")
590
847
  .option("--chain-id <id>", "chain ID", parseInt)
591
848
  .option("--expiration-time <iso>", "expiration time")
592
849
  .option("--not-before <iso>", "not before time")
593
850
  .option("--resources <json>", "resources array as JSON", parseJsonValue)
594
- .action(async (opts) => {
851
+ .addHelpText("after", "\nExamples:\n spongewallet auth siwe app.example.com https://app.example.com\n")
852
+ .action(async (domainArg, uriArg, opts, command) => {
595
853
  await executeToolCommand(opts, "generate_siwe", {
596
- domain: opts.domain,
597
- uri: opts.uri,
854
+ domain: requiredInput(command, opts, domainArg, "domain", "--domain"),
855
+ uri: requiredInput(command, opts, uriArg, "uri", "--uri"),
598
856
  statement: opts.statement,
599
857
  chain_id: opts.chainId,
600
858
  expiration_time: opts.expirationTime,
@@ -603,7 +861,105 @@ function registerCuratedCommands(program, shared) {
603
861
  });
604
862
  });
605
863
  const marketCmd = program.command("market").description("Trading venue integrations");
606
- shared(marketCmd.command("hyperliquid").description("Trade or inspect Hyperliquid"))
864
+ const hyperliquidCmd = marketCmd.command("hyperliquid").description("Trade or inspect Hyperliquid");
865
+ shared(hyperliquidCmd.command("status").description("Show Hyperliquid account status"))
866
+ .action(async (opts) => {
867
+ await executeHyperliquidAction(opts, { action: "status" });
868
+ });
869
+ shared(hyperliquidCmd.command("markets").description("List Hyperliquid markets"))
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 markets\n spongewallet market hyperliquid markets 10\n")
876
+ .action(async (limitArg, offsetArg, opts) => {
877
+ await executeHyperliquidAction(opts, {
878
+ action: "markets",
879
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
880
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
881
+ });
882
+ });
883
+ shared(hyperliquidCmd.command("positions").description("List open Hyperliquid positions"))
884
+ .action(async (opts) => {
885
+ await executeHyperliquidAction(opts, { action: "positions" });
886
+ });
887
+ shared(hyperliquidCmd.command("orders").description("List open Hyperliquid orders"))
888
+ .usage("[limit] [offset] [options]")
889
+ .argument("[limit]", "result limit")
890
+ .argument("[offset]", "result offset")
891
+ .option("--limit <n>", "result limit", parseInt)
892
+ .option("--offset <n>", "result offset", parseInt)
893
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid orders\n spongewallet market hyperliquid orders 20\n")
894
+ .action(async (limitArg, offsetArg, opts) => {
895
+ await executeHyperliquidAction(opts, {
896
+ action: "orders",
897
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
898
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
899
+ });
900
+ });
901
+ shared(hyperliquidCmd.command("fills").description("List recent Hyperliquid fills"))
902
+ .usage("[limit] [offset] [options]")
903
+ .argument("[limit]", "result limit")
904
+ .argument("[offset]", "result offset")
905
+ .option("--limit <n>", "result limit", parseInt)
906
+ .option("--offset <n>", "result offset", parseInt)
907
+ .addHelpText("after", "\nExamples:\n spongewallet market hyperliquid fills\n spongewallet market hyperliquid fills 20\n")
908
+ .action(async (limitArg, offsetArg, opts) => {
909
+ await executeHyperliquidAction(opts, {
910
+ action: "fills",
911
+ limit: limitArg !== undefined ? parseInt(limitArg, 10) : opts.limit,
912
+ offset: offsetArg !== undefined ? parseInt(offsetArg, 10) : opts.offset,
913
+ });
914
+ });
915
+ shared(hyperliquidCmd.command("order").description("Place a Hyperliquid order"))
916
+ .argument("[symbol]", "market symbol")
917
+ .argument("[side]", "buy or sell")
918
+ .argument("[type]", "order type")
919
+ .argument("[amount]", "order amount")
920
+ .argument("[price]", "limit price")
921
+ .option("--symbol <symbol>", "market symbol")
922
+ .option("--side <side>", "buy or sell")
923
+ .option("--type <type>", "order type")
924
+ .option("--amount <amount>", "order amount")
925
+ .option("--price <price>", "limit price")
926
+ .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")
927
+ .action(async (symbolArg, sideArg, typeArg, amountArg, priceArg, opts, command) => {
928
+ await executeHyperliquidAction(opts, {
929
+ action: "order",
930
+ symbol: requiredInput(command, opts, symbolArg, "symbol", "--symbol"),
931
+ side: requiredInput(command, opts, sideArg, "side", "--side"),
932
+ type: requiredInput(command, opts, typeArg, "type", "--type"),
933
+ amount: requiredInput(command, opts, amountArg, "amount", "--amount"),
934
+ price: priceArg ?? opts.price,
935
+ });
936
+ });
937
+ shared(hyperliquidCmd.command("cancel").description("Cancel a Hyperliquid order"))
938
+ .argument("[orderId]", "order ID")
939
+ .option("--order-id <id>", "order ID")
940
+ .action(async (orderIdArg, opts, command) => {
941
+ await executeHyperliquidAction(opts, {
942
+ action: "cancel",
943
+ order_id: requiredInput(command, opts, orderIdArg, "orderId", "--order-id"),
944
+ });
945
+ });
946
+ shared(hyperliquidCmd.command("cancel-all").description("Cancel all Hyperliquid orders"))
947
+ .action(async (opts) => {
948
+ await executeHyperliquidAction(opts, { action: "cancel_all" });
949
+ });
950
+ shared(hyperliquidCmd.command("leverage").description("Set Hyperliquid leverage"))
951
+ .argument("[symbol]", "market symbol")
952
+ .argument("[leverage]", "leverage")
953
+ .option("--symbol <symbol>", "market symbol")
954
+ .option("--leverage <n>", "leverage", parseFloat)
955
+ .action(async (symbolArg, leverageArg, opts, command) => {
956
+ await executeHyperliquidAction(opts, {
957
+ action: "set_leverage",
958
+ symbol: requiredInput(command, opts, symbolArg, "symbol", "--symbol"),
959
+ leverage: leverageArg !== undefined ? parseFloat(leverageArg) : opts.leverage,
960
+ });
961
+ });
962
+ shared(hyperliquidCmd.command("raw").description("Call a raw Hyperliquid action"))
607
963
  .requiredOption("--action <action>", "hyperliquid action")
608
964
  .option("--symbol <symbol>", "market symbol")
609
965
  .option("--side <side>", "buy or sell")
@@ -616,8 +972,7 @@ function registerCuratedCommands(program, shared) {
616
972
  .option("--offset <n>", "result offset", parseInt)
617
973
  .option("--json <json>", "additional args as JSON", parseJsonObject)
618
974
  .action(async (opts) => {
619
- const wallet = await connectWallet(opts);
620
- const data = await wallet.hyperliquid({
975
+ await executeHyperliquidAction(opts, {
621
976
  ...opts.json,
622
977
  action: String(opts.action),
623
978
  symbol: opts.symbol,
@@ -630,7 +985,6 @@ function registerCuratedCommands(program, shared) {
630
985
  limit: opts.limit,
631
986
  offset: opts.offset,
632
987
  });
633
- displayToolResult(getToolDefinition("hyperliquid"), data);
634
988
  });
635
989
  }
636
990
  async function connectWallet(opts) {
@@ -639,6 +993,11 @@ async function connectWallet(opts) {
639
993
  credentialsPath: opts.credentialsPath,
640
994
  });
641
995
  }
996
+ async function executeHyperliquidAction(opts, input) {
997
+ const wallet = await connectWallet(opts);
998
+ const data = await wallet.hyperliquid(input);
999
+ displayToolResult(getToolDefinition("hyperliquid"), data);
1000
+ }
642
1001
  async function executeToolCommand(opts, toolName, input) {
643
1002
  const wallet = await connectWallet(opts);
644
1003
  const tools = await wallet.tools();
@@ -792,6 +1151,7 @@ const toolFormatters = {
792
1151
  const chains = data;
793
1152
  const rows = [];
794
1153
  let emptyCount = 0;
1154
+ let totalUsd = 0;
795
1155
  for (const [chain, info] of Object.entries(chains)) {
796
1156
  if (TESTNET_CHAINS.has(chain))
797
1157
  continue;
@@ -806,6 +1166,11 @@ const toolFormatters = {
806
1166
  amount: b.amount,
807
1167
  usd: b.usdValue ? `$${b.usdValue}` : "-",
808
1168
  });
1169
+ if (b.usdValue) {
1170
+ const parsed = Number(b.usdValue);
1171
+ if (Number.isFinite(parsed))
1172
+ totalUsd += parsed;
1173
+ }
809
1174
  }
810
1175
  }
811
1176
  if (rows.length === 0) {
@@ -826,10 +1191,147 @@ const toolFormatters = {
826
1191
  console.log(row(r.chain, r.token, r.amount, r.usd));
827
1192
  }
828
1193
  console.log();
1194
+ console.log(` Total: $${totalUsd.toFixed(2)}`);
1195
+ console.log();
829
1196
  if (emptyCount > 0) {
830
1197
  p.log.step(`${emptyCount} chain${emptyCount !== 1 ? "s" : ""} with no balance`);
831
1198
  }
832
1199
  },
1200
+ hyperliquid(data) {
1201
+ if (!isRecord(data)) {
1202
+ p.log.success("Hyperliquid");
1203
+ console.log(JSON.stringify(data, null, 2));
1204
+ return;
1205
+ }
1206
+ const action = getValueByKey(data, "tool_call.arguments.action");
1207
+ const title = action
1208
+ ? `Hyperliquid ${toTitleCase(String(action).replace(/_/g, " "))}`
1209
+ : "Hyperliquid";
1210
+ if (getValueByKey(data, "address") && isRecord(getValueByKey(data, "balances"))) {
1211
+ const perps = getValueByKey(data, "balances.perps");
1212
+ const spot = getValueByKey(data, "balances.spot");
1213
+ const openOrders = getValueByKey(data, "openOrders");
1214
+ const spotRows = Object.entries(spot ?? {}).map(([symbol, value]) => ({
1215
+ symbol,
1216
+ amount: getValueByKey(value, "amount"),
1217
+ usdValue: getValueByKey(value, "usdValue"),
1218
+ }));
1219
+ p.log.success(title);
1220
+ p.log.info([
1221
+ `Wallet: ${formatInlineValue(getValueByKey(data, "address"))}`,
1222
+ `Perps total: ${formatInlineValue(getValueByKey(perps, "total.USDC"))} USDC`,
1223
+ `Perps free: ${formatInlineValue(getValueByKey(perps, "free.USDC"))} USDC`,
1224
+ `Perps used: ${formatInlineValue(getValueByKey(perps, "used.USDC"))} USDC`,
1225
+ `Spot assets: ${spotRows.length}`,
1226
+ `Open orders: ${formatInlineValue(getValueByKey(data, "openOrderCount"))}`,
1227
+ ].join("\n"));
1228
+ if (spotRows.length > 0) {
1229
+ renderTable("Spot balances", [
1230
+ { key: "symbol", label: "Symbol" },
1231
+ { key: "amount", label: "Amount" },
1232
+ { key: "usdValue", label: "USD Value" },
1233
+ ], spotRows);
1234
+ }
1235
+ if (Array.isArray(openOrders) && openOrders.length > 0) {
1236
+ renderTable("Open orders", [
1237
+ { key: "symbol", label: "Symbol" },
1238
+ { key: "side", label: "Side" },
1239
+ { key: "price", label: "Price" },
1240
+ { key: "remaining", label: "Remaining" },
1241
+ { key: "status", label: "Status" },
1242
+ ], openOrders);
1243
+ }
1244
+ return;
1245
+ }
1246
+ const positions = getValueByKey(data, "positions");
1247
+ if (Array.isArray(positions)) {
1248
+ if (positions.length === 0) {
1249
+ p.log.info("No open Hyperliquid positions.");
1250
+ return;
1251
+ }
1252
+ renderTable(title, [
1253
+ { key: "symbol", label: "Symbol" },
1254
+ { key: "side", label: "Side" },
1255
+ { key: "contracts", label: "Size" },
1256
+ { key: "entryPrice", label: "Entry" },
1257
+ { key: "markPrice", label: "Mark" },
1258
+ { key: "leverage", label: "Lev" },
1259
+ { key: "unrealizedPnl", label: "PnL" },
1260
+ ], positions);
1261
+ return;
1262
+ }
1263
+ const orders = getValueByKey(data, "orders");
1264
+ if (Array.isArray(orders)) {
1265
+ if (orders.length === 0) {
1266
+ p.log.info("No open Hyperliquid orders.");
1267
+ return;
1268
+ }
1269
+ renderTable(title, [
1270
+ { key: "symbol", label: "Symbol" },
1271
+ { key: "side", label: "Side" },
1272
+ { key: "price", label: "Price" },
1273
+ { key: "remaining", label: "Remaining" },
1274
+ { key: "reduceOnly", label: "Reduce" },
1275
+ { key: "status", label: "Status" },
1276
+ ], orders);
1277
+ return;
1278
+ }
1279
+ const fills = getValueByKey(data, "fills");
1280
+ if (Array.isArray(fills)) {
1281
+ if (fills.length === 0) {
1282
+ p.log.info("No recent Hyperliquid fills.");
1283
+ return;
1284
+ }
1285
+ renderTable(title, [
1286
+ { key: "symbol", label: "Symbol" },
1287
+ { key: "side", label: "Side" },
1288
+ { key: "price", label: "Price" },
1289
+ { key: "amount", label: "Amount" },
1290
+ { key: "closedPnl", label: "PnL" },
1291
+ { key: "datetime", label: "Time" },
1292
+ ], fills);
1293
+ return;
1294
+ }
1295
+ const markets = getValueByKey(data, "markets");
1296
+ if (Array.isArray(markets)) {
1297
+ if (markets.length === 0) {
1298
+ p.log.info("No Hyperliquid markets found.");
1299
+ return;
1300
+ }
1301
+ renderTable(title, [
1302
+ { key: "symbol", label: "Symbol" },
1303
+ { key: "type", label: "Type" },
1304
+ { key: "base", label: "Base" },
1305
+ { key: "quote", label: "Quote" },
1306
+ { key: "maxLeverage", label: "Max Lev" },
1307
+ ], markets);
1308
+ const total = getValueByKey(data, "total");
1309
+ const nextOffset = getValueByKey(data, "nextOffset");
1310
+ if (total !== undefined) {
1311
+ p.log.info(`Showing ${markets.length} of ${formatInlineValue(total)} markets.`);
1312
+ }
1313
+ if (nextOffset !== null && nextOffset !== undefined) {
1314
+ p.log.info(`Next offset: ${formatInlineValue(nextOffset)}`);
1315
+ }
1316
+ return;
1317
+ }
1318
+ const cleanData = Object.fromEntries(Object.entries(data).filter(([key]) => key !== "tool_call"));
1319
+ if (renderFields(title, [
1320
+ { key: "message", label: "Message" },
1321
+ { key: "status", label: "Status" },
1322
+ { key: "orderId", label: "Order ID" },
1323
+ { key: "clientOrderId", label: "Client Order ID" },
1324
+ { key: "symbol", label: "Symbol" },
1325
+ { key: "leverage", label: "Leverage" },
1326
+ { key: "cancelled", label: "Cancelled" },
1327
+ { key: "address", label: "Address" },
1328
+ { key: "webChartUrl", label: "Chart" },
1329
+ ], cleanData)) {
1330
+ return;
1331
+ }
1332
+ p.log.success(title);
1333
+ console.log(JSON.stringify(cleanData, null, 2));
1334
+ },
833
1335
  };
834
1336
  function isRecord(value) {
835
1337
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -926,9 +1428,25 @@ function renderTxOutput(title, data) {
926
1428
  if (!isRecord(data))
927
1429
  return false;
928
1430
  const hash = getValueByKey(data, ["transactionHash", "txHash", "signature"]);
1431
+ const inputAmount = getValueByKey(data, ["inputToken.amount", "input_token.amount"]);
1432
+ const inputSymbol = getValueByKey(data, ["inputToken.symbol", "input_token.symbol"]);
1433
+ const outputAmount = getValueByKey(data, ["outputToken.amount", "output_token.amount"]);
1434
+ const outputSymbol = getValueByKey(data, ["outputToken.symbol", "output_token.symbol"]);
1435
+ const flow = inputAmount && inputSymbol && outputAmount && outputSymbol
1436
+ ? `${formatInlineValue(inputAmount)} ${formatInlineValue(inputSymbol)} -> ${formatInlineValue(outputAmount)} ${formatInlineValue(outputSymbol)}`
1437
+ : undefined;
1438
+ const sourceChain = getValueByKey(data, ["sourceChain", "source_chain"]);
1439
+ const destinationChain = getValueByKey(data, ["destinationChain", "destination_chain"]);
1440
+ const route = sourceChain && destinationChain
1441
+ ? `${formatInlineValue(sourceChain)} -> ${formatInlineValue(destinationChain)}`
1442
+ : undefined;
929
1443
  const lines = [
1444
+ route ? `Route: ${route}` : undefined,
1445
+ flow ? `Flow: ${flow}` : undefined,
930
1446
  hash ? `Transaction: ${formatInlineValue(hash)}` : undefined,
931
1447
  getValueByKey(data, "status") ? `Status: ${formatInlineValue(getValueByKey(data, "status"))}` : undefined,
1448
+ getValueByKey(data, "priceImpactPct") ? `Price impact: ${formatInlineValue(getValueByKey(data, "priceImpactPct"))}%` : undefined,
1449
+ getValueByKey(data, "gasUsed") ? `Gas used: ${formatInlineValue(getValueByKey(data, "gasUsed"))}` : undefined,
932
1450
  getValueByKey(data, "explorerUrl") ? `Explorer: ${formatInlineValue(getValueByKey(data, "explorerUrl"))}` : undefined,
933
1451
  getValueByKey(data, "chain") ? `Chain: ${formatInlineValue(getValueByKey(data, "chain"))}` : undefined,
934
1452
  getValueByKey(data, "from") ? `Signer: ${formatInlineValue(getValueByKey(data, "from"))}` : undefined,