Skip to content

fix: shared invest fee estimation + nostr investment sync between web app and desktop - #952

Merged
dangershony merged 4 commits into
mainfrom
fix/webapp-invest-fee-and-nostr-investment-sync
Jul 25, 2026
Merged

fix: shared invest fee estimation + nostr investment sync between web app and desktop#952
dangershony merged 4 commits into
mainfrom
fix/webapp-invest-fee-and-nostr-investment-sync

Conversation

@dangershony

@dangershony dangershony commented Jul 24, 2026

Copy link
Copy Markdown
Member

Problem

Two user-reported failures when investing via Lightning on the web app, plus a cross-app discovery gap:

  1. Web app lightning/on-chain invest fails with "fee too low" or "amount is dust". The lightning invoice was sized with a hardcoded 2 sat/vB while the investment tx was later built with the live median fee rate, and the Boltz claim fee was never budgeted. With a single claimed UTXO there is nothing else to pull in, so signing fails or the change falls under dust. The on-chain invoice path was worse: it asked the user to send only the bare investment amount, with no Angor fee or miner fee.
  2. Investments made on the web app's invest page (InvestView.razor) never appeared after importing the seed in the desktop app. That page only saved to browser localStorage � it never pushed the encrypted investments list to nostr, which is the mechanism the desktop uses to discover investments on wallet import.
  3. The desktop PortfolioService cached an empty relay result to LiteDB and treated it as authoritative forever � one transient relay timeout permanently hid all investments.

Fix (shared code used by both web app and desktop)

  • New InvestmentFeeEstimator (Angor.Shared) � single source of truth for the on-chain amount required at a funding address (investment + Angor fee + investment-tx miner fee), with headroom for the locally-built Boltz claim fee on the lightning path. Desktop PaymentFlowConfig.EstimateOnChainRequired now delegates to it (same numbers as before � no desktop behavior change).
  • Web app InvestView.razor: lightning invoices are sized with the live median fee rate; the on-chain invoice modal asks for (and monitors for) the full required amount; the investment tx is built with the same fee rate that was budgeted so estimate and build can never drift apart.
  • New NostrInvestmentStorageService (Angor.Shared) � one shared implementation of the encrypted self-DM investments list (storage-key derivation, encrypt, publish, relay lookup, dedup, newest-first decrypt fallback). Used by the SDK PortfolioService and the web app's InvestView.razor (the active invest page; the legacy Invest/Investor pages are untouched).
  • InvestView.razor now saves the investments list to nostr after both publish paths (direct and founder-approved), so web investments are discoverable when the seed is imported in the desktop app.
  • PortfolioService: never caches an empty relay result and no longer treats an empty local document as authoritative � retries the relay on next load instead.

Testing

  • WebApp.sln and App.Desktop build clean
  • SDK tests: 333 passed
  • Shared tests: 155 passed

… web and desktop

- Add InvestmentFeeEstimator (Angor.Shared): single source of truth for the
  on-chain amount required at a funding address (investment + Angor fee +
  investment-tx miner fee), with headroom for the Boltz claim fee on the
  lightning path. Desktop PaymentFlowConfig now delegates to it.
- Webapp InvestView: size lightning invoices with the live median fee rate
  instead of hardcoded 2 sat/vB, ask for the full required amount on the
  on-chain invoice path, and build the investment tx with the same fee rate
  that was budgeted (fixes 'fee too low' / dust-change failures on single-UTXO
  Boltz claims).
- Add NostrInvestmentStorageService (Angor.Shared): one shared implementation
  of the encrypted self-DM investments list (derive storage keys, encrypt,
  publish, lookup, dedup, newest-first decrypt fallback). Used by the SDK
  PortfolioService and the webapp (Invest, Investor, InvestView).
- Webapp InvestView now saves the investments list to nostr after both publish
  paths, so investments made there are discoverable when the seed is imported
  in the desktop app.
- PortfolioService: never cache an empty relay result and don't treat an empty
  local document as authoritative, so a transient relay failure can no longer
  permanently hide investments.
…nger used; InvestView.razor is the active invest page)
… don't resurrect

Cancel previously removed the record only from the local cache. With the
relay-fallback fix in GetByWalletId, an empty local list falls through to the
relay, which still held the cancelled record and blocked reinvesting with
'You have already invested in this project' (caught by MultiInvestClaimAndRecover UAT).
…estimates

Fund/Subscribe projects derive stages from the selected pattern, not
ProjectInfo.Stages (which is empty for dynamic-stage projects). Use the
pattern's StageCount when sizing lightning/on-chain invoice amounts.
@dangershony
dangershony merged commit 0440859 into main Jul 25, 2026
3 checks passed
@dangershony
dangershony deleted the fix/webapp-invest-fee-and-nostr-investment-sync branch July 25, 2026 10:03
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