Inspiration
In Rwanda about four in ten people save in an ikimina; in Senegal it's the tontine or natt, in Kenya the chama. These groups pay school fees, restock shops and cover emergencies. But the pot always depends on one person; a treasurer with a cash box, or one mobile-money account. Reports from Botswana describe "disappearing treasurers", Senegalese news covers tontine embezzlement, and groups argue over notebooks. We wanted the group to keep control of its own money. Lightning has a primitive that mobile money doesn't: a payment that can be locked, and then either delivered or returned. Twese means "all of us" in Kinyarwanda and that's the rule of the product.
What it does
- An organiser creates a circle: name, contribution (in sats), a payment window (e.g. during the meeting), and members in payout order; first names only, no phone numbers or IDs.
- Everyone gets a private link (shareable on WhatsApp).
- When a round opens, the recipient's own wallet issues one Lightning hold invoice per member via Nostr Wallet Connect.
- Members pay by QR code or deep link from any Lightning wallet. Their payment is locked in the network; nobody can spend it yet.
- When every payment is locked, Twese releases them all at once to the recipient. If time runs out (or the organiser cancels), every locked payment is returned automatically.
- History with a cryptographic receipt (payment hash) for each contribution, an activity log, a wallet view, indicative RWF/FCFA amounts, and English / French / Kinyarwanda.
Twese never holds the pot: the money path is payer → recipient over Lightning. Twese only decides release or refund.
How we built it
- Lightning via Nostr Wallet Connect (NIP-47) using
@getalby/sdk—make_hold_invoice,lookup_invoice,settle_hold_invoice,cancel_hold_invoice. No node to operate. - Before writing the app we ran probes against
faucet.nwc.devsandbox wallets: a plain invoice paid in 432 ms, then hold invoices through both the settle and cancel paths. - Server: Node.js 24, Fastify, zod validation, built-in SQLite, a 3-second round engine (state machine with idempotent release/refund and a late-payment sweeper).
- Security: wallet links and preimages encrypted with AES-256-GCM; capability tokens stored as SHA-256 and kept in the URL fragment; live mode refuses wallet connections that can send money; every invoice decoded and checked against the requested hash and amount; strict CSP; rate limits; no names or secrets in logs (tested).
- Front end: vanilla JS PWA, no framework, 19 KB gzipped, offline shell, 48 px touch targets.
- Testing: 27 unit/API tests, a live end-to-end test over real NWC relays (payout + refund rounds with exact balance checks), and a scripted UI walkthrough in a phone viewport that also produced our screenshots and demo recording.
Challenges we ran into
- Hold invoices have real limits. They lock HTLCs along the route and receiving nodes fail them before their CLTV margin, so collection must happen in hours. We capped windows at 3 h, re-check every lock, and designed around "pay during the meeting".
- Wallet quirks. Our live test found that a sandbox wallet answers
NOT_FOUNDwhen asked to cancel a hold invoice nobody paid, which left rounds stuck. We fixed it and added a sweeper that refunds anyone who pays such an invoice late. - Race conditions. A member could pay seconds before a cancel and be shown as "not paid". The engine now asks the wallet for the true state before every cancel.
- An independent review found real bugs a way for a link holder to swap a payout wallet, cancel/release races, and a window longer than nodes safely hold payments. We fixed all of them with tests before submitting.
- No Docker on the dev machine, so we chose NWC + built-in SQLite and kept the setup to one command.
Accomplishments that we're proud of
- An end-to-end Lightning flow where the coordinator never holds funds, and payout-wallet changes are locked during a member's round and visible to everyone.
- All-or-nothing payouts and automatic refunds, verified with real protocol traffic and exact balance checks.
- Least privilege enforced in code (spending-capable wallet connections are refused in live mode).
- A 2.6 s usable first load on emulated Slow 3G with 4× CPU slowdown.
What we learned
- Hold invoices are a strong, underused building block for group finance and they come with real network-citizenship constraints.
- NWC makes "bring your own wallet" practical, but capabilities differ between wallets, so apps must check
get_infoand degrade gracefully. - Writing the threat model first made the design simpler: by keeping money out of our server we removed whole classes of risk.
What's next for Twese
- Mainnet pilots with 3–5 real groups in Kigali and Dakar, small amounts, recipients on Alby Hub.
- Publish each round's payment hashes to Nostr so members can verify the coordinator independently.
- Recurring contributions with member-approved NWC budgets.
- SMS/USSD reminders for feature-phone members; Wolof; native review of Kinyarwanda/French.
- Lightning → MTN MoMo / Wave cash-out through licensed partners.
Honest status
Working MVP in sandbox mode: payments use sandbox sats from the NWC test faucet, not real bitcoin. Live-mode safeguards are implemented and unit-tested but have not been used with real funds. No real users yet. Demo narration uses a synthetic voice.
Built With
- alby
- bitcoin
- fastify
- hold-invoices
- javascript
- lightning-network
- nip-47
- node.js
- nostr-wallet-connect
- pwa
- sqlite
- zod
