diff --git a/src/commands/PerpsCommand.ts b/src/commands/PerpsCommand.ts index 313872c..fdbe664 100644 --- a/src/commands/PerpsCommand.ts +++ b/src/commands/PerpsCommand.ts @@ -5,6 +5,7 @@ import type { Command } from "commander"; import { PerpsClient } from "../clients/PerpsClient.ts"; import { Asset, resolveAsset } from "../lib/Asset.ts"; import { Config } from "../lib/Config.ts"; +import { DateConverter } from "../lib/DateConverter.ts"; import { NumberConverter } from "../lib/NumberConverter.ts"; import { Output } from "../lib/Output.ts"; import { Signer } from "../lib/Signer.ts"; @@ -316,6 +317,12 @@ export class PerpsCommand { "--limit cannot be combined with --tp or --sl. Use 'perps set' after the limit order fills." ); } + if (opts.leverage) { + const leverageNum = Number(opts.leverage); + if (isNaN(leverageNum) || leverageNum < 1 || leverageNum > 100) { + throw new Error("Leverage must be a number between 1 and 100."); + } + } const side = this.normalizeSide(opts.side); const asset = opts.asset.toUpperCase(); @@ -904,14 +911,7 @@ export class PerpsCommand { } private static parseTimestamp(value: string): string { - if (/^\d+$/.test(value)) { - return value; - } - const ms = new Date(value).getTime(); - if (isNaN(ms)) { - throw new Error(`Invalid date: ${value}`); - } - return String(Math.floor(ms / 1000)); + return DateConverter.parseTimestamp(value); } private static async history(opts: { diff --git a/src/commands/SpotCommand.ts b/src/commands/SpotCommand.ts index ab28856..6255940 100644 --- a/src/commands/SpotCommand.ts +++ b/src/commands/SpotCommand.ts @@ -15,6 +15,7 @@ import { } from "../clients/UltraClient.ts"; import { Asset, resolveWalletAsset } from "../lib/Asset.ts"; import { Config } from "../lib/Config.ts"; +import { DateConverter } from "../lib/DateConverter.ts"; import { NumberConverter } from "../lib/NumberConverter.ts"; import { Output } from "../lib/Output.ts"; import { Signer } from "../lib/Signer.ts"; @@ -928,13 +929,6 @@ export class SpotCommand { } private static parseTimestamp(value: string): string { - if (/^\d+$/.test(value)) { - return new Date(Number(value) * 1000).toISOString(); - } - const ms = new Date(value).getTime(); - if (isNaN(ms)) { - throw new Error(`Invalid date: ${value}`); - } - return new Date(ms).toISOString(); + return DateConverter.parseTimestamp(value); } } diff --git a/src/e2e.test.ts b/src/e2e.test.ts index fc3f878..a635df5 100644 --- a/src/e2e.test.ts +++ b/src/e2e.test.ts @@ -106,3 +106,81 @@ describe("keys add (dist/index.js)", () => { expect(second.stdout + second.stderr).toContain("already exists"); }); }); + +describe("perps open (leverage bounds)", () => { + test("rejects leverage below 1", () => { + const result = runCli( + "perps", + "open", + "--asset", + "SOL", + "--side", + "long", + "--amount", + "1", + "--leverage", + "0.5" + ); + expect(result.status).not.toBe(0); + expect(result.stdout + result.stderr).toContain( + "Leverage must be a number between 1 and 100" + ); + }); + + test("rejects leverage above 100", () => { + const result = runCli( + "perps", + "open", + "--asset", + "SOL", + "--side", + "long", + "--amount", + "1", + "--leverage", + "101" + ); + expect(result.status).not.toBe(0); + expect(result.stdout + result.stderr).toContain( + "Leverage must be a number between 1 and 100" + ); + }); + + test("rejects non-numeric leverage", () => { + const result = runCli( + "perps", + "open", + "--asset", + "SOL", + "--side", + "long", + "--amount", + "1", + "--leverage", + "abc" + ); + expect(result.status).not.toBe(0); + expect(result.stdout + result.stderr).toContain( + "Leverage must be a number between 1 and 100" + ); + }); + + test("accepts valid leverage between 1 and 100", () => { + const result = runCli( + "perps", + "open", + "--asset", + "SOL", + "--side", + "long", + "--amount", + "1", + "--leverage", + "50" + ); + // Will fail due to dry-run or API issues, but should NOT fail on leverage validation + expect(result.stdout + result.stderr).not.toContain( + "Leverage must be a number between 1 and 100" + ); + }); +}); diff --git a/src/lib/DateConverter.ts b/src/lib/DateConverter.ts new file mode 100644 index 0000000..a1baeaa --- /dev/null +++ b/src/lib/DateConverter.ts @@ -0,0 +1,16 @@ +export class DateConverter { + /** + * Parse a timestamp string (ISO date or UNIX timestamp) and return Unix seconds. + * Accepts both ISO 8601 date strings and numeric UNIX timestamps. + */ + public static parseTimestamp(value: string): string { + if (/^\d+$/.test(value)) { + return value; + } + const ms = new Date(value).getTime(); + if (isNaN(ms)) { + throw new Error(`Invalid date: ${value}`); + } + return String(Math.floor(ms / 1000)); + } +}