Skip to content

feat(cli): invoice return — bulk + nametag (depends on sphere-sdk #405)#39

Merged
vrogojin merged 3 commits into
mainfrom
feat/invoice-return-bulk-and-nametag
Jun 6, 2026
Merged

feat(cli): invoice return — bulk + nametag (depends on sphere-sdk #405)#39
vrogojin merged 3 commits into
mainfrom
feat/invoice-return-bulk-and-nametag

Conversation

@vrogojin
Copy link
Copy Markdown
Contributor

@vrogojin vrogojin commented Jun 6, 2026

Summary

Adds three input shapes for sphere invoice return:

sphere invoice return <id>                              # refund every sender (NEW)
sphere invoice return <id> --recipient @alice           # refund all balances owed to @alice (NEW)
sphere invoice return <id> --recipient @alice --asset 3 UCT  # refund exactly (existing, with nametag resolution)

The first two delegate to sphere-sdk PR #405's new AccountingModule.returnAllInvoicePayments(invoiceId, options?). That SDK method iterates getInvoiceStatus().senderBalances and refunds every attributed payment to its recorded sender — so operators don't have to fish per-send DIRECT://… addresses out of invoice status JSON. Particularly important for masked-predicate sends (the privacy default), where the on-chain sender is a one-time DIRECT://… the user cannot guess from their wallet's identity.

The third (explicit) shape also gains @nametag resolution for --recipient — same as invoice deliver --to and invoice create --target. Previously --recipient was passed verbatim to the SDK, so @alice would break with Return amount X exceeds sender net balance 0 (the SDK did a literal-string lookup against the recorded DIRECT://…).

Output shape

Form A (single explicit refund) — single-row:

Return payment result:
  id     : <transferId>
  status : submitted
  recipient : DIRECT://…
  amount    : 3 UCT

Forms B/C (bulk) — multi-row:

Return payment results:
  2 refund(s) submitted:
  [0] 3 UCT → DIRECT://0000abcd…
       id     : <transferId>
       status : submitted
  [1] 4 UCT → DIRECT://0000efgh…
       id     : <transferId>
       status : submitted

The renderer auto-detects via Array.isArray(p.refunds). Both shapes available under --json.

Dependencies

  • sphere-sdk PR #405 (feat/accounting-return-all-invoice-payments) — adds AccountingModule.returnAllInvoicePayments. This PR's forms B and C call it.

This PR is the CLI surface that exposes #405's primitive.

Verification

  • npx tsc --noEmit — clean
  • npx vitest run — 126/126 pass (UX static-guard now green)
  • npx tsup — ESM + CJS + DTS build clean
  • sphere invoice return --help — shows all 3 forms with examples + notes block

Live testnet end-to-end exercise of form C (no flags) in the new accounting soak (sphere-sdk PR #406): bob's bulk-refund correctly drains 3 UCT and credits alice's wallet. The token-level flow is exact.

(There's a separate sphere-sdk attribution bug #404 that prevents the SDK's invoice ledger from reflecting masked-predicate refunds — that's documented as a follow-up; it doesn't block this PR since the bug is upstream of the CLI wrapper.)

Out of scope

  • --asset without --recipient is rejected with a clear error pointing to either form. Refunding a specific amount without specifying the recipient is ambiguous.
  • --asset-index / --target-index selectors stay unimplemented — single-asset single-target invoices are the only shape exercised today.

Related

Vladimir Rogojin added 3 commits June 5, 2026 16:23
== Three input shapes ==

  sphere invoice return <id>                        # refund every sender
  sphere invoice return <id> --recipient @alice     # refund all to @alice
  sphere invoice return <id> --recipient @alice --asset 3 UCT  # refund exactly

Forms without --asset delegate to sphere-sdk's new
AccountingModule.returnAllInvoicePayments (depends on sphere-sdk PR
#404 / branch feat/accounting-return-all-invoice-payments). That
method iterates getInvoiceStatus's senderBalances internally — so
operators don't need to fish per-send DIRECT://… addresses out of
invoice status JSON. This particularly matters for masked-predicate
sends, where the on-chain sender is a one-time DIRECT://… the user
CANNOT guess from their wallet identity.

== --recipient resolution ==

All three forms now resolve @NameTag / chain pubkey / alpha1 → DIRECT://
before SDK handoff, matching the pattern `invoice deliver --to` and
`invoice create --target` already use. Previously the recipient was
passed verbatim, which broke with @NameTag input (the SDK uses it as
a literal address-string lookup key).

== Output ==

Form A (single explicit refund) — single-row shape:
  Return payment result:
    id     : <transferId>
    status : pending
    recipient : DIRECT://...
    amount    : 3 UCT

Forms B/C (bulk) — multi-row shape:
  Return payment results:
    2 refund(s) submitted:
    [0] 3 UCT → DIRECT://0000abcd...
         id     : <transferId>
         status : pending
    [1] 4 UCT → DIRECT://0000efgh...
         id     : <transferId>
         status : pending

The renderer auto-detects via `Array.isArray(p.refunds)`.

== Verification ==

- npx tsc --noEmit: clean
- npx vitest run: 126/126 pass (UX static-guard now green)
- npx tsup: clean ESM + CJS + DTS build
- Smoke: `sphere invoice return --help` shows the new 3-form help
  block; the three forms each error sensibly on validation paths.

== Out of scope ==

- --asset without --recipient is rejected with a clear error
  pointing to either dropping --asset (bulk refund) or adding
  --recipient (explicit refund). This is intentional: refunding a
  specific amount without specifying the recipient is ambiguous.

- `--asset-index` and `--target-index` selectors stay unimplemented
  (single-asset single-target invoices are the only shape exercised
  today; file a follow-up if multi-asset invoices become real).
The bulk-return branch added in this PR's src/legacy/legacy-cli.ts
calls `sphere.accounting!.returnAllInvoicePayments(...)`, a method
that doesn't exist on AccountingModule before sphere-sdk #405. The
prior SHA pin (d949844 = PR #402) leaves CI failing typecheck with
"Property 'returnAllInvoicePayments' does not exist on type 'AccountingModule'".

Bump to 0e3290a (sphere-sdk main post-#405).
@vrogojin vrogojin merged commit bb0c405 into main Jun 6, 2026
2 checks passed
@vrogojin vrogojin deleted the feat/invoice-return-bulk-and-nametag branch June 6, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant