Skip to content

Invoice delivery: payer can't discover invoices targeting their address even after explicit sync #226

Description

@vrogojin

Summary

When Alice mints an invoice targeting @bob-tag (or DIRECT://...), Bob's wallet has no automatic path to discover the invoice. sphere invoice pay $INV errors with No invoice found matching prefix: ... even after Bob explicitly runs sphere payments sync AND sphere payments receive --finalize.

This blocks the §C invoice round-trip in manual-test-full-recovery.sh (PR #222 / docs/issue-218-full-recovery-manual-test) and is the practical reason the §C end-to-end didn't pass on testnet during today's #223 cross-process validation.

Reproduction

# Alice mints invoice targeting @bob
cd ./peer1/.alice
sphere invoice create --target @bob-tag --asset "11000000 UCT" --memo "test"
# Capture INV=<invoiceId from JSON output>

# Bob tries to pay — invoice is invisible to him
cd ./peer1/.bob
sphere payments sync                  # ← does NOT pull alice's invoice
sphere payments receive --finalize    # ← does NOT pull alice's invoice
sphere invoice pay "$INV"
# → No invoice found matching prefix: 00009ccb170b3f9e182499d7f2bae130e78593b71622cdd9299cb80f440c9ae6f8c0
# → Error: Cannot read properties of undefined (reading 'invoiceId')

Validated against live testnet on 2026-05-22 (full log: /tmp/manual-cli-test-223-v10.log from today's session).

What we know

  • sphere invoice pay <id> calls sphere.accounting.getInvoices() and filters locally (legacy-cli.ts:4049). No network lookup is attempted for unknown invoice IDs.
  • The invoice token is minted in Alice's profile/wallet. It's an L3 token owned by Alice (with tokenType = INVOICE_TOKEN_TYPE_HEX) whose terms target Bob's DIRECT:// address.
  • payments sync and payments receive --finalize on Bob's side don't pull invoices, even though Bob's address is in the invoice's terms.targets[0].address.
  • The walkthrough doc (PR docs(manual-test)(#218): full two-peer + daemon + IPFS-only recovery walkthrough #222 §C.3) assumed peer2 daemons would catch incoming events like invoice:payment / transfer:incoming, but:
    • The daemon path itself has a separate --detach stale-PID bug (tracked separately)
    • Even with daemons running, the SDK lacks a clear "deliver invoice to payer" mechanism

Suggested solutions (architectural alternatives)

Pick one of the three or some combination:

Option A: Per-invoice DM delivery (NIP-17)

At invoice mint time, Alice sends Bob a NIP-17 gift-wrapped DM containing the invoice token JSON (or a CID reference + payload). Bob's CommunicationsModule / receive pipeline parses the DM, recognizes the invoice envelope, calls accounting.importInvoice(tokenJson), and the invoice lands in Bob's local ledger.

Pros: leverages existing NIP-17 plumbing; explicit point-to-point delivery; works without OrbitDB cross-profile reads. Cons: requires Bob to be subscribed to Nostr at mint time OR catch up via fetchPendingEvents; one more DM-shape to spec.

Option B: Cross-profile OrbitDB lookup

Bob's profile sync queries Alice's published profile (already discoverable via Alice's identity binding event) for invoices targeting <bob's directAddress>. This is effectively a per-target index inside the OrbitDB Profile.

Pros: no extra wire format; works for any third-party invoice (Bob can also discover invoices Eve minted for him without prior pairing). Cons: requires Bob to know about Alice (or to brute-poll the directory); cross-profile OrbitDB reads have their own scaling/permissions story.

Option C: IPFS-pinned per-target invoice index

A discoverable index keyed by recipient DIRECT://: at mint time the creator publishes an entry { target, invoiceId, ts } to an aggregator-side index (or a per-target pubsub topic). Payer sync queries the index for entries matching their address.

Pros: aggregator-mediated, central-but-content-addressed. Cons: requires aggregator support; less private (any peer could query).

Acceptance criteria

Related work

Priority / blast radius

Medium-high. Invoices are advertised as a wallet feature (CLAUDE.md "Invoicing — sphere.accounting"); the create→pay flow as designed is incomplete cross-process without this. End-to-end CLI invoice testing is blocked.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions