Skip to content
Merged
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
50 changes: 41 additions & 9 deletions src/legacy/legacy-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,15 +1511,19 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
},
'invoice-pay': {
usage: 'invoice-pay <id-or-prefix> [--amount <value>] [--target-index <n>]',
description: 'Pay an invoice. By default pays the remaining amount for the first target. For multi-target invoices, specify --target-index.',
description: 'Pay an invoice. By default pays the remaining amount for the first target. Use --amount for partial or over-payment; the coin is implicit in the invoice.',
flags: [
{ flag: '--amount <value>', description: 'Amount to pay in smallest units (positive integer). Defaults to remaining amount.' },
{ flag: '--amount <value>', description: 'Amount to pay in HUMAN units of the invoice\'s coin (e.g. "50" = 50 UCT, "0.5" = 0.5 UCT). Defaults to remaining amount needed to cover the target.' },
{ flag: '--target-index <n>', description: 'Target index for multi-target invoices (0-based)', default: '0' },
],
examples: [
'npm run cli -- invoice-pay a1b2c3d4',
'npm run cli -- invoice-pay a1b2c3d4 --amount 500000',
'npm run cli -- invoice-pay a1b2c3d4 --target-index 1 --amount 250000',
'sphere invoice pay a1b2c3d4',
'sphere invoice pay a1b2c3d4 --amount 0.5',
'sphere invoice pay a1b2c3d4 --target-index 1 --amount 25',
],
notes: [
'--amount is in human units (whole coins, fractional allowed) — matches `payments send` and `invoice create --asset`.',
'NFT targets reject --amount (use full-pay form without --amount).',
],
},
'invoice-return': {
Expand Down Expand Up @@ -4793,11 +4797,39 @@ async function main(): Promise<void> {

const payParamsMut: Record<string, unknown> = { targetIndex };
if (amountIdx2 !== -1 && args[amountIdx2 + 1]) {
const rawAmount = args[amountIdx2 + 1];
if (!/^[1-9][0-9]*$/.test(rawAmount!)) {
failWithHelp('invoice-pay', `invalid amount "${rawAmount}" — must be a positive integer in smallest units (no decimals, no leading zeros)`);
const rawAmount = args[amountIdx2 + 1]!;
// Issue #36 — canonical UX: `--amount <value>` is in HUMAN units
// (e.g. "50" = 50 whole UCT, "0.5" = 0.5 UCT), matching
// `payments send 50 UCT` and `invoice create --asset 50 UCT`.
// The coin is implicit in the invoice's target/asset entry.
// Convert to smallest-unit integer here before handing off; the
// SDK's PayInvoiceParams.amount is a raw atom-count string.
const invoiceRef = sphere.accounting!.getInvoice(invoiceId);
if (!invoiceRef) {
failWithHelp('invoice-pay', `invoice ${invoiceId} not found in memory`);
}
const target = invoiceRef.terms.targets[targetIndex];
if (!target) {
failWithHelp('invoice-pay', `--target-index ${targetIndex} out of range (invoice has ${invoiceRef.terms.targets.length} target(s))`);
}
// assetIndex is currently CLI-fixed to 0 (see "Out of scope" in #36).
const asset = target.assets[0];
if (!asset || !asset.coin) {
failWithHelp('invoice-pay', '--amount only applies to coin-asset targets, not NFT');
}
const [coinSymbol] = asset.coin;
const { decimals } = resolveCoin(coinSymbol);
let smallest: bigint;
try {
smallest = toSmallestUnit(rawAmount, decimals);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
failWithHelp('invoice-pay', `invalid amount "${rawAmount}" — ${msg}`);
}
if (smallest <= 0n) {
failWithHelp('invoice-pay', `invalid amount "${rawAmount}" — must be positive`);
}
payParamsMut['amount'] = rawAmount;
payParamsMut['amount'] = smallest.toString();
}
const payParams = payParamsMut as unknown as PayInvoiceParams;

Expand Down
Loading