Case Study · Fintech

Built for a small-business finance startup. Client named on request, under NDA.

  • Fintech
  • Codebase takeover
  • Local-first encryption

Taking over someone else’s React Native app, and encrypting what it held

A finance app for people who keep supplier contracts and unpaid invoice values on their phone. We inherited the codebase in React Native and continued in React Native rather than rewriting it in our own stack — then moved the records behind the Android Keystore, where a lost phone stops being a data breach.

Abstract illustration of the app’s layout A stylised phone outline with a locked summary panel above a list of blocked-out record rows. This is an abstract illustration in the Nautilus brand colours, not a screenshot — no real interface, no real data and no client branding is shown.
Sector
Fintech
Engagement
Existing-codebase takeover
Platforms
Android (React Native)
Our role
Takeover, features, security, release
The Brief

Photograph a document, and never think about it again

A small-business finance startup was building a tool for owners and freelancers who need to track invoices and bills without running an accounting department. The intended flow was simple to describe: photograph a document, have it categorised automatically, and browse everything organised by client or by vendor.

The part that shaped every technical decision came at the end of the brief. The records had to stay protected if the phone were lost or stolen. Not “we should look at security before launch” — local-first storage and PIN-based encryption were stated as requirements up front, and they determined the architecture.

The Problem

An inherited stack, and a failure mode that stays invisible

Two things made this harder than the feature list suggests.

The first was the codebase. It already existed, and it was React Native. Most of our work is Flutter, and the honest options were to continue in a stack that isn’t our default, or to propose a rewrite that would have spent the client’s budget re-reaching the position they already occupied.

The second was that finance apps routinely get local storage wrong, and the failure is invisible until it matters. Records get written to ordinary local storage because it is faster to ship and because nothing looks broken afterwards. The consequence only shows up later: a device backup extraction, a forensic tool, or a rooted phone reads the whole file in plaintext. For a user whose device holds supplier contracts, outstanding invoice values and a client payment history, that is not an abstract risk — it is a set of records with direct commercial value to whoever ends up holding the handset.

The Decision

Continue in React Native rather than rewrite in Flutter

The default

A Flutter shop takes a React Native project and rebuilds it in Flutter, because that is the stack the team is fastest and most confident in.

Why it fails here

The rewrite is billed to the client, and at the end of it they own the same product they started with — minus the budget and the time, plus whatever regressions a full port introduces. Our preference is not a reason for them to pay twice.

What we did

We assessed the project, accepted it on its own terms, and delivered in the stack it was written in — including the security work, which was the substantive part of the engagement.

What it bought

The client’s existing investment kept its value, and the money went into the encrypted vault and the capture flow instead of into reproducing work that already existed. It is also the honest test of whether “partner-level engagement” means anything: it is easy to say when the project happens to match your preferences.

The Decision

The PIN is a key, not a screen lock

The default

Gate the app behind a PIN screen and store the records normally underneath. The user experiences it as protection, and in the ordinary case it is.

Why it fails here

A PIN that only controls navigation is defeated by anything that reads the filesystem without going through the app — which is precisely the scenario the requirement exists to cover. The threat model is a phone in someone else’s hands, and a phone in someone else’s hands does not have to open your app.

What we did

Sensitive records and credentials go through react-native-keychain, which uses the OS-level secure store — Android Keystore. MMKV handles fast local state behind its own encryption layer, and AsyncStorage carries only the genuinely low-sensitivity persistence. The PIN unlocks the vault rather than the screen.

What it bought

Losing the device stops being a data breach. The split also makes the sensitivity of each piece of state an explicit decision at the point of writing it, rather than something inferred later from where it happened to end up.

What We Built

Three parts, and a boundary between them

Capture and categorisation

Invoice and bill capture with image processing, tagged by client, vendor, type and date. Validation runs at the point of entry, so malformed records are rejected before they reach storage rather than cleaned up afterwards.

PIN-protected encrypted vault

All financial records stored locally and encrypted, with the PIN gating both app open and the sensitive screens within it. Nothing leaves the device unless the user explicitly exports it.

A network layer the vault doesn’t depend on

An Axios-based client for the server-sync features, cleanly separated from vault data. The vault works whether or not the network does, and a change to the API surface cannot reach the stored records.

Organisation-based views

Filter by client, by vendor, by date range or by status — the questions a small-business owner actually asks at the end of a month, answerable without an export.

Under the Hood

What we used, and what each choice bought

React Native
The client’s existing stack — inherited deliberately, not rewritten
react-native-keychain
Sensitive records land in the OS secure store (Android Keystore)
MMKV
Fast local state with its own encryption layer
AsyncStorage
Lower-sensitivity persistence, kept explicitly separate
Formik + Yup
Validation at capture, so bad records never enter the vault
Axios
A clean network boundary — the vault never depends on it
The Outcome

What shipped

The app shipped on Google Play as a production application. The encrypted local vault gives small-business users real protection for financial records without requiring cloud sync, or a backend to be trusted with them. The React Native codebase was handed back clean — validation throughout, a well-typed network layer, and a storage split that documents its own intent.

0
financial records that leave the device unless the user exports them
2
storage tiers, split by sensitivity rather than by convenience
1
PIN — the key that decrypts the vault, not a screen lock

Build facts, verified against the delivered app. We don’t publish outcome numbers we didn’t measure.

Evidence

What you can check for yourself

How each claim on this page can be verified

5.0 / 5 across 5 verified client reviews
Publicly verifiable
100% Job Success over 49 completed jobs
Publicly verifiable
Upwork profile badge
Shipped to Google Play as a production app
From our delivery record
Adoption, retention or revenue after launch
Not recorded

What we can’t show you here

Some of this engagement stays private. Naming what’s missing seems fairer than quietly leaving gaps.

  • The screens. The interface is the client’s product; the illustrations on this page are abstract by design.
  • The client’s name, at their preference.
Why It Matters

Two things that transfer from this build

If you have an existing React Native app that needs a feature, a fix or a full handoff, we can take it. We are not a Flutter-only shop that declines everything else — we assess the project, meet the codebase where it is, and deliver in it. You should be sceptical of a partner whose recommended first step is always a rewrite into their preferred stack.

And if you’re building anything that holds financial or personal records on a device, decide where the encryption boundary sits before you build the screens. Retrofitting it means migrating live user data, and by then the shape of the app is arguing against you.

Inherited a codebase you’re not sure about?

We’ll read it and tell you honestly whether it needs a rewrite or a steady hand. Most of the time it’s the second one.

Talk to us Book a free call

More proof: all case studies · the engineering blog · what clients say