Skip to content
Merged
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
30 changes: 19 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,30 @@ jobs:
# integrity — a branch pointer can be force-pushed or rebased,
# silently changing the code CI builds against.
#
# This SHA is the tip of sphere-sdk `main` ("merge: PR #395 #394
# automated CID delivery re-enabled + 512 KiB inline cap + demo
# playbook"). That commit exports the symbols `src/shared/sphere-
# providers.ts` consumes from `@unicitylabs/sphere-sdk/impl/nodejs`:
# `createUxfCarPublisher`, `DEFAULT_IPFS_GATEWAYS`,
# `PublishToIpfsCallback`. It also exposes `AccountingModule.
# deliverInvoice`, which `invoice-deliver` (PR #18 / issue #226)
# calls. Pinning to `main` (rather than the previous integration-
# branch tip 02cb4550) avoids the "unable to read tree" failure
# when an integration tip is rebased away.
# This SHA is the tip of sphere-sdk `main` (PR #402 / #401 — invoice
# OUTBOX verifier wiring on top of PR #400 / #397's route-invoice-
# delivery-through-the-token-pipeline architectural fix).
#
# Why this matters for sphere-cli: #397 replaces the bespoke
# `invoice_delivery:` NIP-17 DM with the standard TOKEN_TRANSFER
# pipeline (Nostr kind 31113). The `manual-test-accounting-
# roundtrip.sh` soak (and any cross-device invoice consumer) only
# works against this new pipeline; the prior pin (3f3dadf, the
# CID-delivery merge BEFORE #397) leaves the receiver decoder
# unable to ingest invoice DMs. Local repro: alice's `invoice
# list` returns "No invoices found" forever even though bob's
# `invoice deliver` reports `sent: 1, failed: 0`.
#
# This SHA also keeps the exports `src/shared/sphere-providers.ts`
# consumes (`createUxfCarPublisher`, `DEFAULT_IPFS_GATEWAYS`,
# `PublishToIpfsCallback`) and `AccountingModule.deliverInvoice`
# that `invoice-deliver` (PR #18 / issue #226) calls.
#
# Bump this SHA when a new sphere-sdk commit is required; remove
# this whole workaround once sphere-sdk publishes v0.7.1+ to npm
# with the post-extraction exports.
env:
SPHERE_SDK_SHA: 3f3dadf9d03eb29db87f062921751f24bfefdec8
SPHERE_SDK_SHA: d9498445e96be63f6a474f2607372eb4c16247d2
run: |
git clone https://github.com/unicity-sphere/sphere-sdk.git ../../sphere-sdk
# The pinned SHA may not be on a branch tip after future merges;
Expand Down
55 changes: 37 additions & 18 deletions src/legacy/legacy-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4499,22 +4499,33 @@ async function main(): Promise<void> {

const assets: InvoiceRequestedAsset[] = [];
// Collect ALL `--asset <amount> <coin>` occurrences (multi-asset).
// Canonical UX (#32): the amount is in HUMAN-READABLE units (e.g.
// "7 UCT" = 7 whole UCT, "0.5 BTC" = 0.5 BTC), matching what
// `payments send` accepts. Convert to smallest-unit integers here
// before handing off to the SDK; AccountingModule.createInvoice
// itself treats the amount as a raw atom count (decimals: 0).
for (let i = 0; i < args.length; i++) {
if (args[i] !== '--asset') continue;
const pair = consumeAssetPair(args, i + 1);
if (!pair) {
failWithHelp('invoice-create', '--asset expects two positional tokens: --asset <amount> <coin>');
}
if (!/^[1-9][0-9]*$/.test(pair.amount)) {
failWithHelp('invoice-create', `invalid amount "${pair.amount}" — must be a positive integer in smallest units (no decimals, no leading zeros)`);
// resolveCoin fails fast on unknown symbols. Use it for both the
// canonical symbol (handed to the SDK) and the decimals (used
// for the human → smallest unit conversion).
const { symbol: resolvedSymbol, decimals } = resolveCoin(pair.coin);
let smallest: bigint;
try {
smallest = toSmallestUnit(pair.amount, decimals);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
failWithHelp('invoice-create', `invalid amount "${pair.amount}" — ${msg}`);
}
if (smallest <= 0n) {
failWithHelp('invoice-create', `invalid amount "${pair.amount}" — must be positive`);
}
// AccountingModule.createInvoice validates coinId as
// /^[A-Za-z0-9]+$/ with length ≤20 — i.e. it expects the
// human-readable symbol (UCT, USDU, ...), NOT the 64-char
// hex token-type id that `payments.send` uses. resolveCoin
// fails fast on unknown symbols; we hand the SYMBOL through.
const { symbol: resolvedSymbol } = resolveCoin(pair.coin);
assets.push({ coin: [resolvedSymbol, pair.amount] });
// SDK convention: coin: [symbol, smallest-unit-as-decimal-string].
assets.push({ coin: [resolvedSymbol, smallest.toString()] });
}
// NFT input — collect when no --asset was provided.
if (assets.length === 0 && nftId) {
Expand Down Expand Up @@ -4823,19 +4834,27 @@ async function main(): Promise<void> {
if (!pair) {
failWithHelp('invoice-return', '--asset expects two positional tokens: --asset <amount> <coin>');
}
if (!/^[1-9][0-9]*$/.test(pair.amount)) {
failWithHelp('invoice-return', `invalid amount "${pair.amount}" — must be a positive integer string (smallest unit, no leading zeros, e.g. 1000000)`);
// Canonical UX (#32): amount is in HUMAN units (e.g. "100 UCT" = 100
// whole UCT). Convert to smallest-unit integer before handing off to
// the SDK. Same SDK convention as invoice-create — the
// AccountingModule's ReturnPaymentParams.coinId is the symbol
// (UCT, USDU, ...), not the 64-char hex.
const { symbol: returnSymbol, decimals: returnDecimals } = resolveCoin(pair.coin);
let returnSmallest: bigint;
try {
returnSmallest = toSmallestUnit(pair.amount, returnDecimals);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
failWithHelp('invoice-return', `invalid amount "${pair.amount}" — ${msg}`);
}
if (returnSmallest <= 0n) {
failWithHelp('invoice-return', `invalid amount "${pair.amount}" — must be positive`);
}
// Same SDK convention as invoice-create — the AccountingModule's
// ReturnPaymentParams.coinId is the symbol (UCT, USDU, ...), not
// the 64-char hex. resolveCoin still validates that the symbol
// is known to the TokenRegistry.
const returnCoinId = resolveCoin(pair.coin).symbol;

const returnParams: ReturnPaymentParams = {
recipient: args[recipientIdx + 1],
amount: pair.amount,
coinId: returnCoinId,
amount: returnSmallest.toString(),
coinId: returnSymbol,
};

const result = await sphere.accounting!.returnInvoicePayment(invoiceId, returnParams);
Expand Down
Loading