2023 to 2026
Sesame
Bullion brokerage and allocated vault custody
Role: Frontend lead. Owned the Next.js client; contributed to the Rails API.
- lines of code
- ~17.5klines of code
- KYC fields
- 50KYC fields
- payment rails
- 2payment rails
- commits
- 716commits
Context
What this is for
Buying physical gold through a broker involves a regulated onboarding file, a price that moves, a physical object that has to be stored somewhere specific, and a paper trail for all of it. Sesame is the client portal for that, in English and Chinese, oriented at Singapore.
Problem
The thing that was actually hard to get right
Three things that cannot be approximated. The price a client sees has to be the one the desk published, marked in the right direction for the side they are on. Their holding has to be the truth about a physical bar in a vault, not a cached number. And every state change in an order has to leave a document behind.
What I built
The surface area
- A bilingual catalogue of bullion products with live bid- and ask-adjusted prices, basket and checkout.
- Buy and sell order placement, a transaction ledger, and a vault view of current holdings per product per storage lot.
- A 50-field KYC profile covering three tax-residency declarations, US tax status, politically-exposed-person declaration, source of wealth, identity document expiry and full bank coordinates.
- An admin order desk driving five explicit state transitions, each generating the right document and notification.
- A price desk where prices are staged as drafts and published in one gated action.
- Two payment integrations in parallel: Stripe embedded checkout and PayPal's create-then-capture order flow.
The hard parts
Decisions worth defending
Each of these names its trade-off, because a decision without one is a preference.
One base price, two directions
A dealer does not store a buy price and a sell price; it stores a base price and a spread, and applies the spread in opposite directions depending on which side the client is on. So prices are versioned rows rather than mutable columns, each carrying a published flag, and the client-facing catalogue marks up from the latest published base while the vault valuation marks down from the same row. The desk stages new prices as drafts, sees them alongside the live ones, and publishes them in one action.
The trade-off
Two reads of the same row that must never drift apart. Keeping the spread in a single settings record rather than copying it onto prices means a spread change re-values history, which is correct for a quote and would be wrong for a settled trade.
Holdings as a derivation, not a column
A balance column is the obvious design and the wrong one here, because a holding is not a number: it is a quantity of a specific product in a specific storage lot, and the ledger is the record. Current position is therefore derived: group a client's transactions by product, then by storage identifier, take the most recent entry in each group, drop the zeroed ones, and value what remains at the latest published price less the bid spread. There is no balance to get out of sync with the ledger, because there is no balance.
The trade-off
It is computed in application code per product per lot rather than in SQL, so it scales with a client's lot count rather than with an index. At a brokerage's transaction volume that is free; it is the first thing I would push into the database if volumes grew.
Transitions that owe the client a document
An order moving from received to sent-to-vault is not a status change, it is a promise that a confirmation exists. Each of the five transitions is its own endpoint rather than a generic status setter, and the ones that owe a document generate the PDF, attach it to the order as a record, and send it, in that order, so a failure to generate never produces a notification about a document that does not exist. The KYC form itself is a full regulatory document reproduced in HTML and CSS for print, which is how it ends up byte-identical every time rather than depending on a template engine's idea of a page.
The trade-off
Documents are generated inline in the request, with no job queue in this codebase, so a slow render is a slow response. For an operator pressing a button on an order desk that is acceptable and debuggable; it would not survive a bulk action.
Ownership
Who wrote what
Commit counts from the repository history, both of my git identities summed. I would rather give you this than a claim you have to take on trust.
| Surface | Commits | My share |
|---|---|---|
| Next.js client | 266 | 87% of commits, owned the client |
| Rails API | 122 | 28% of commits, contributor |
Stack
What it runs on
Frontend
- Next.js 13 (App Router)
- React 18
- Redux Toolkit + redux-persist
- i18next (EN / 中文)
- MUI 5
- Stripe
- PayPal Orders v2
Backend
- Rails 8.1
- Ruby 4.0
- PostgreSQL
- Devise + JWT
- Pundit
- Rack::Attack
- ferrum (PDF)
Infrastructure
- Heroku
- AWS S3
- Resend
Testing & quality
- RSpec, roughly 1:1 application to spec code
- PDF endpoints tested against real headless Chrome rather than stubs, by policy
Where it is now
Current status
Built and handed over. Last active mid-2026. My share here is frontend-dominant and the commit split above says so.