Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .changeset/fix-trade-json-output-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
"nansen-cli": minor
---

fix: trade quote/execute now output structured JSON; schema command respects wallet and rejects unknown commands; fix deprecated --help examples

## trade quote and execute: JSON output (agent-first)

`nansen trade quote` and `nansen trade execute` previously output only
human-readable text to stdout, returning `undefined` to the CLI runner.
No JSON was emitted — making these commands unusable in agent pipelines
despite the CLI being marketed for agent use.

**What changed:**

- `trade quote` now returns a structured object on success:
`{ quoteId, chain, walletAddress, quotes: [...], executeCommand }`
The human-readable summary is preserved on stderr for TTY users.

- `trade execute` now returns a structured object on success:
`{ status, txHash, chain, chainType, broadcaster, explorerUrl, swapEvents }`

- All validation errors in both commands now throw structured errors
(caught by the CLI runner and emitted as `{"success":false,"error":"..."}`)
instead of printing plain text to stderr and exiting without stdout output.
This includes: missing required params, no wallet configured, invalid quote
ID, all-quotes-failed, and on-chain reverts.

## schema command: wallet support + unknown-command error

- `nansen schema wallet` previously returned the full schema (the wallet
command was not in `SCHEMA.commands`, so the lookup fell through to the
default). `wallet` is now a first-class entry in `schema.json` with
full subcommand and option definitions.

- `nansen schema <unknown>` previously silently returned the full schema.
It now returns `{"success":false,"error":"Unknown schema command: ..."}`.

## schema.json: trade execute --quote param added

`trade execute` schema was missing the `--quote` param entirely — the only
required input. Agents reading the schema before calling the command could
not discover this requirement.

## --help examples: fix deprecated command paths

All `--help` example strings and no-arg command examples used the old
pre-research-namespace paths (e.g. `nansen smart-money netflow`) instead
of the current canonical paths (e.g. `nansen research smart-money netflow`).
Fixed in six hardcoded example strings and in the `generateSubcommandHelp`
auto-generated example builder.
30 changes: 21 additions & 9 deletions src/__tests__/cli.internal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1171,13 +1171,24 @@ describe('schema command', () => {
expect(result.globalOptions).toBeDefined();
});

it('should return full schema for unknown command', async () => {
it('should return wallet subcommands for schema("wallet")', async () => {
const commands = buildCommands({});
const result = await commands.schema(['unknown'], null, {}, {});

// Returns full schema when command not found
expect(result.version).toBeDefined();
expect(result.commands).toBeDefined();
const result = await commands.schema(['wallet'], null, {}, {});

expect(result.command).toBe('wallet');
expect(result.subcommands).toBeDefined();
expect(Object.keys(result.subcommands)).toEqual(
expect.arrayContaining(['create', 'list', 'show', 'default', 'delete', 'send', 'export'])
);
});

it('should throw structured error for unknown schema command', async () => {
const commands = buildCommands({});
// Unknown schema subcommand now throws instead of silently dumping full schema
await expect(commands.schema(['unknown'], null, {}, {})).rejects.toMatchObject({
code: 'UNKNOWN_COMMAND',
message: expect.stringContaining('Unknown schema command: unknown'),
});
});

it('should output JSON', async () => {
Expand Down Expand Up @@ -2414,10 +2425,11 @@ describe('deprecation warnings', () => {
errorOutput: (msg) => errors.push(msg),
exit: () => {}
};
// quote with no args shows its help; confirms handler was reached
// quote with no args now throws MISSING_PARAM; confirms handler was reached
const result = await runCLI(['quote'], deps);
expect(errors.some(e => e.includes('nansen trade quote'))).toBe(true);
expect(result.type).toBe('no-output');
// The handler was reached and returned a structured error (not no-output)
expect(result.type).toBe('error');
expect(result.data?.error).toMatch(/Missing required options/i);
});
});

Expand Down
Loading