Skip to content
Work index

Research prototype · 2026

Ember

Ember is a research project into whether transactions can be exchanged offline in connectivity-constrained environments, where bank transfers, POS and USSD stop working when the network does.

Two contributors built a prototype that moves signed payments between two offline phones as animated QR codes. Status: research prototype on simulated balances — not a production payment system.

Status
Research prototype on simulated balances. Not a production payment system.
Team
Two contributors
Source
Private repository
Crypto
Ed25519 (tweetnacl) on client and server
Stack
TypeScript · React + Vite (PWA) · Express · tweetnacl · Ed25519 · QR
  • Private team repository — source not public.
Ember prototype wallet home screen: a ₦0 offline balance, Request and Pay buttons, a Fund offline wallet button, a Sync now link and a bottom tab bar.
Fig. 01 — Ember prototype — wallet home screen.

Governing idea

Offline, you can't prevent a double spend. You can bound it and detect it.

01 —

Overview

Bank transfers, POS and USSD all need a live connection. When mobile data or Wi-Fi drops — routinely, in markets and transport parks — commerce stops, even for people holding digital money.

Ember investigates the feasibility of exchanging transactions without that connection. The prototype covers one path end to end: an offline payment between two phones, settled or flagged once either phone reconnects. Everything runs on simulated money.

02 —

Problem and constraints

Preventing an offline double spend requires secure hardware that ordinary phones don't guarantee. So Ember doesn't claim to. It detects the double spend at reconnect and bounds the possible loss to the customer's offline limit — the same trust model used by offline-capable chip cards.

03 —

Architecture and design

While online, a customer pre-funds an offline wallet and receives a bank-signed authorisation with a spending limit. Offline, the merchant shows a payment request as a QR code; the customer scans it, signs the payment with their device key and shows a second QR with the signed packet. The merchant checks both signatures locally, against a bank public key built into the app.

When either phone reconnects, queued payments sync and the server settles them into a mock ledger — or flags them, if a packet is replayed or spending goes past the authorised limit.

Diagram 01

An offline payment, and what happens later

Input₦2,000 at a market stall, both phones offline.

Step 1 of 6 · Fund (online)

Customer pre-funds; the bank signs an authorisation certificate with an offline limit.

LegendNumbered squares are stages; filled means reached in this scenario, dashed means not reached. ◆ marks a stage that is scaffolded, planned or blocked rather than built.
Every stage, in words
  1. 01Fund (online) (server · bank signer) — Customer pre-funds; the bank signs an authorisation certificate with an offline limit.
  2. 02Request (merchant QR) — Merchant shows amount, payee and a one-time nonce. Expires after 120 s.
  3. 03Sign (customer device · Ed25519) — Customer signs the payment packet with their device key and shows it back as a QR.
  4. 04Verify offline (merchant device) — Merchant verifies the bank's and the customer's signatures with no connection.
  5. 05Sync (POST /api/sync) — Whichever phone reconnects first uploads its queued packets. Sync is serialised.
  6. 06Settle or flag (mock ledger) — Validate first, then credit. Replays and over-limit spends are flagged, not settled.
  7. 07Durable ledger (Postgres · atomic, planned) — Atomic, constraint-backed settlement and a hardware-backed signer.

04 —

Implementation — the prototype

What exists is a React/Vite PWA and an Express server, with keys in the browser's local storage and an in-memory ledger that resets on restart.

Settlement was the part most likely to be wrong in quiet ways, so I moved it to validate-before-mutation: a packet is fully checked before any balance changes, transaction ids and request nonces must be unique, and sync requests are serialised. Payment requests now expire after 120 seconds and carry a one-time nonce.

On the transport side I worked on QR reliability — an animated multi-frame encoding with parity recovery, scanner frame reassembly, and a matte anti-glare display that keeps the screen awake.

Ember prototype settlement screen: a mock bank balance of ₦50,000 credited at settlement, a fraud-flag counter at 0, and an empty settlement ledger with All, Settled and Flagged filters.
Fig. 02 — Ember prototype — settlement screen.

05 —

Results and limitations

The prototype demonstrates signed offline exchange over QR, local signature verification, and detection of replayed or over-limit payments at settlement. It does not demonstrate durable or atomic settlement: the ledger is in memory and balances are simulated.

The target architecture in the team's diagrams — a native app with a hardware-backed signer and a Postgres ledger with atomic settlement — is a plan, not an implementation. A review of that design concluded it isn't ready to handle real money until settlement atomicity, the funds-reservation model and the device trust model are specified.

06 —

Open research questions

Proposed — not implemented

These are proposed directions, not implemented features.

  • NFC as a secondary offline transport — researched within the team; not part of the prototype.
  • Bluetooth Low Energy as an alternative transport — proposed; not investigated in code.
  • Settlement and double spending on reconnect — atomic, constraint-backed settlement and a funds-reservation model would be required; neither is built.
  • Device trust — a hardware-backed signer appears in the target architecture only.