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
29 changes: 25 additions & 4 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,15 @@ function addCreateIntent(parent: Command): Command {
.requiredOption('--rate-min <bigint>', 'Minimum acceptable rate (string-encoded bigint)')
.requiredOption('--rate-max <bigint>', 'Maximum acceptable rate (string-encoded bigint)')
.requiredOption('--volume-min <bigint>', 'Minimum volume per match')
.requiredOption('--volume-total <bigint>', 'Total intent volume')
.option('--expiry-ms <ms>', 'Expiry duration in milliseconds (default: 24h)')
// Wire shape aligned with src/trader/acp-types.ts:23 (volume_max)
// and trader-command-handler.ts:331 (expiry_sec). The CLI flag
// stays in milliseconds for ergonomic consistency with other
// timeout flags; we convert at the wire boundary via
// floor(ms/1000). See sphere-cli PR #7 for the equivalent fix
// in the canonical CLI; this trader-ctl shim mirrors the same
// wire shape so direct-docker e2e tests don't break either.
.requiredOption('--volume-max <bigint>', 'Total intent volume')
.option('--expiry-ms <ms>', 'Expiry duration in milliseconds (default: 24h, must be ≥1000ms and ≤7 days)')
.action(async function (this: Command) {
const opts = parseGlobalOpts(this);
const local = this.opts() as Record<string, string | undefined>;
Expand All @@ -178,10 +185,24 @@ function addCreateIntent(parent: Command): Command {
rate_min: local['rateMin'],
rate_max: local['rateMax'],
volume_min: local['volumeMin'],
volume_total: local['volumeTotal'],
volume_max: local['volumeMax'],
};
if (local['expiryMs'] !== undefined) {
params['expiry_ms'] = Number.parseInt(local['expiryMs'], 10);
const n = Number.parseInt(local['expiryMs'], 10);
if (!Number.isFinite(n) || n <= 0) {
fail(`--expiry-ms must be a positive integer (got "${local['expiryMs']}")`, 2);
}
if (n < 1000) {
// Sub-second expiries floor to 0 and would be rejected by
// the trader with an opaque "expiry_sec must be positive"
// error. Catch at the CLI layer with a clear message.
fail(`--expiry-ms must be at least 1000 (1 second); got ${n}`, 2);
}
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
if (n > sevenDaysMs) {
fail(`--expiry-ms must not exceed 7 days (${sevenDaysMs}ms); got ${n}`, 2);
}
params['expiry_sec'] = Math.floor(n / 1000);
}
await runCommand(opts, 'CREATE_INTENT', params);
});
Expand Down
6 changes: 3 additions & 3 deletions test/e2e-live/basic-roundtrip.e2e-live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ describe('Basic round-trip trading', () => {
rate_min: 1n,
rate_max: 1n,
volume_min: 100n,
volume_total: 1000n,
volume_max: 1000n,
});

expect(intents.buyerIntentId).toBeTruthy();
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('Basic round-trip trading', () => {
'5',
'--volume-min',
'50',
'--volume-total',
'--volume-max',
'500',
'--expiry-ms',
String(10 * 60_000),
Expand Down Expand Up @@ -246,7 +246,7 @@ describe('Basic round-trip trading', () => {
'7',
'--volume-min',
'10',
'--volume-total',
'--volume-max',
'100',
'--expiry-ms',
String(expiryMs),
Expand Down
20 changes: 10 additions & 10 deletions test/e2e-live/edge-cases.e2e-live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async function createIntent(
rateMin: bigint;
rateMax: bigint;
volumeMin: bigint;
volumeTotal: bigint;
volumeMax: bigint;
expiryMs?: number;
},
): Promise<string> {
Expand All @@ -109,8 +109,8 @@ async function createIntent(
args.rateMax.toString(),
'--volume-min',
args.volumeMin.toString(),
'--volume-total',
args.volumeTotal.toString(),
'--volume-max',
args.volumeMax.toString(),
];
if (args.expiryMs !== undefined) {
argv.push('--expiry-ms', String(args.expiryMs));
Expand Down Expand Up @@ -221,14 +221,14 @@ describe('Edge cases', () => {
rateMin: 100n,
rateMax: 200n,
volumeMin: 10n,
volumeTotal: 100n,
volumeMax: 100n,
});
const bobId = await createIntent(bob, {
direction: 'buy',
rateMin: 1n,
rateMax: 50n,
volumeMin: 10n,
volumeTotal: 100n,
volumeMax: 100n,
});

const stillUnmatched = await intentsRemainUnmatched(
Expand Down Expand Up @@ -257,14 +257,14 @@ describe('Edge cases', () => {
rateMin: 1n,
rateMax: 1n,
volumeMin: 50n,
volumeTotal: 100n,
volumeMax: 100n,
});
const buyId = await createIntent(alice, {
direction: 'buy',
rateMin: 1n,
rateMax: 1n,
volumeMin: 50n,
volumeTotal: 100n,
volumeMax: 100n,
});

// Wait long enough for >1 scan cycle and assert nothing settled.
Expand Down Expand Up @@ -313,7 +313,7 @@ describe('Edge cases', () => {
);

it(
'volume_min greater than counterparty volume_total → no match',
'volume_min greater than counterparty volume_max → no match',
async () => {
await cancelActiveIntents(alice);
await cancelActiveIntents(bob);
Expand All @@ -325,14 +325,14 @@ describe('Edge cases', () => {
rateMin: 1n,
rateMax: 1n,
volumeMin: 1000n,
volumeTotal: 5000n,
volumeMax: 5000n,
});
const bobId = await createIntent(bob, {
direction: 'buy',
rateMin: 1n,
rateMax: 1n,
volumeMin: 10n,
volumeTotal: 100n,
volumeMax: 100n,
});

const stillUnmatched = await intentsRemainUnmatched(
Expand Down
2 changes: 1 addition & 1 deletion test/e2e-live/helpers/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ export type CreateMatchingIntents = (
rate_min: bigint;
rate_max: bigint;
volume_min: bigint;
volume_total: bigint;
volume_max: bigint;
},
) => Promise<MatchingIntents>;

Expand Down
4 changes: 2 additions & 2 deletions test/e2e-live/helpers/scenario-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ interface MatchingIntentsTerms {
rate_min: bigint;
rate_max: bigint;
volume_min: bigint;
volume_total: bigint;
volume_max: bigint;
}

/**
Expand All @@ -50,7 +50,7 @@ function createIntentArgv(
'--rate-min', terms.rate_min.toString(),
'--rate-max', terms.rate_max.toString(),
'--volume-min', terms.volume_min.toString(),
'--volume-total', terms.volume_total.toString(),
'--volume-max', terms.volume_max.toString(),
];
}

Expand Down
Loading