Skip to content
Draft
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
29 changes: 20 additions & 9 deletions src/commands/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,28 @@ export function registerFetch402Command(program: Command) {
// the only supported rail is BTC/sats over lightning — the cap is
// inherently a sats spend limit — but it goes through the shared
// classifier so the surface matches the rest of the CLI and can grow.
// The classifier speaks the whole CLI's vocabulary (fiat codes, chain
// networks), so its raw errors would advertise rails fetch doesn't
// accept and contradict this command's help; collapse any rejection —
// and any non-BTC/sats rail it does classify — into one message that
// matches what fetch actually supports.
if (options.maxAmount !== undefined) {
const rail = classifyRail({
currency: options.currency,
unit: options.unit,
network: options.network,
});
const capRailError = new Error(
"fetch's --max-amount spend cap currently supports only " +
"--currency BTC --unit sats --network lightning",
);
let rail;
try {
rail = classifyRail({
currency: options.currency,
unit: options.unit,
network: options.network,
});
} catch {
throw capRailError;
}
if (rail.kind !== "bitcoin" || rail.unit !== "sats") {
throw new Error(
"fetch's --max-amount spend cap currently supports only " +
"--currency BTC --unit sats --network lightning",
);
throw capRailError;
}
} else if (options.currency || options.unit || options.network) {
throw new Error(
Expand Down
24 changes: 24 additions & 0 deletions src/test/fetch-max-amount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,28 @@ describe("fetch --max-amount strict parsing", () => {
"currently supports only --currency BTC --unit sats --network lightning",
);
});

// The cap goes through the shared amount model, whose errors speak the whole
// CLI's vocabulary (fiat codes, chain names like "arbitrum"). Those would
// advertise rails fetch can't pay a cap in and contradict its help, so every
// rejected combination must surface fetch's own single message instead.
test.each([
["--max-amount 5", "no denomination at all"],
["--max-amount 5 --currency USD --unit sats", "fiat with a sub-unit"],
["--max-amount 5 --currency BTC", "BTC without --network"],
["--max-amount 5 --currency USDC --network arbitrum", "a token on a chain"],
])(
"surfaces fetch's own cap message, not the shared classifier's, for %s (%s)",
(flags) => {
const result = runCli<ErrorOutput>(`fetch http://example.invalid ${flags}`);
expect(result.success).toBe(false);
expect(result.output.error).toBe(
"fetch's --max-amount spend cap currently supports only " +
"--currency BTC --unit sats --network lightning",
);
// None of the shared model's cross-rail vocabulary leaks through.
expect(result.output.error).not.toContain("arbitrum");
expect(result.output.error).not.toContain("BTC|USD|EUR|USDC");
},
);
});
Loading