Client
Fifth & Nine
Selling an on-chain membership to buyers who have never opened a wallet
A talent-management venture needed to sell a digital membership pass to two audiences at once — wallet-native buyers and card-only buyers — and have both end up holding the same on-chain asset. We designed and built the contracts, the payment rails, and the custody layer that made that possible.
Client
Fifth & Nine
Sector
Talent management · Web3 commerce
Engagement
Architecture, smart contracts, payments, full build
Period
April – August 2026
The brief
Fifth & Nine launches, protects, and monetises models, talent, and athletes, starting in Africa. Their commercial thesis rests on removing three things from an industry that runs on them: gate-kept access, opaque payouts, and thin safeguarding.
The product expressing that thesis is the Digital Access Pass — a utility NFT that grants access to content, services, and experiences, tiered by level. Not a security, and deliberately not sold as one.
The problem was never minting an NFT. It was that a meaningful share of the people they most wanted to reach had never used a crypto wallet, and were not going to install one to complete a checkout.
Constraints
Five constraints shaped the architecture. Each one closed off an easier design.
Wallet-native buyers and card-only buyers had to end up holding the identical on-chain pass. Not an equivalent, not a voucher redeemable later — the same token, in the same collection.
Every mint instruction constrains the destination token account's authority to the buyer who signed. That is the correct guarantee for a wallet purchase. It also means a card payment cannot mint to the buyer, because the buyer never signs anything on chain.
Passes are sold outright or across 3, 6, 9, or 12 monthly charges. On the payment processor that is a subscription, not a payment — so the asset has to be minted early, held, and released only once the final invoice clears.
Buyers choose a pass, not a specific scarce edition. Tier assignment had to happen somewhere the buyer could not influence it, while staying reproducible enough to defend afterwards.
The commerce site and the partner-operated talent portal are deployed separately by different teams against one shared database, coordinating over a webhook. A change to the contract between them is invisible from either repository alone.
Approach
The mint instruction binds the recipient to the signer. Three ways out: relax the contract, collect a wallet address at checkout, or take custody.
Relaxing the contract was the cheap fix and the wrong one — it would remove the property that makes a wallet purchase safe for everyone else, to serve buyers who are not using a wallet. Collecting a wallet address at checkout fails on its own terms: the people paying by card are precisely the people who do not yet have an address to give.
So we took custody. A server-held vault wallet signs the mint, holds the pass in its own token account, and transfers it when the buyer claims — which they can do days later, from a wallet they set up at their own pace, following a link in an email.
That choice is what makes the rest of the system interesting. Once payment and delivery run on separate clocks, every write between them has to be safe to repeat. Card processors deliver webhooks at least once. Chain confirmations time out while the transaction still lands. Cron workers overlap. None of those are edge cases at volume — they are Tuesday.
What we built
A single Anchor program hosts every talent's collection as an on-chain account seeded by a 32-byte identifier. Tiers, edition markers, and admin slots are all scoped by that identifier, so collections are isolated from each other inside one deployment. Onboarding a new talent is a transaction costing a fraction of a cent, not a redeploy.
A connected wallet pays in SOL. A card pays in full or across monthly instalments. A holder settles partly in the membership token and partly in SOL under one server-signed quote, with the split computed server-side so the client cannot understate what it owes. A holder with enough balance pays in tokens alone. Every path terminates in the same pass, in the same collection.
The vault mints, holds, and transfers on claim. A background worker drains anything the inline path missed, records the reason on each failure, and escalates to a human at the retry ceiling instead of retrying forever. A separate reconciler releases due holds and reports — never silently repairs — claims it cannot categorise.
Buyers mint a sealed pass with placeholder metadata in a single wallet approval. The tier is assigned afterwards by a server-side worker, drawn from a deterministic function of a held secret and the token's own address, weighted by tier and filtered to what is actually still available. The metadata is then rewritten once and locked immutable. The buyer signs once; the outcome is reproducible from inputs we retain.
An admin console covers the full instruction set — creating collections, configuring tiers and pricing, rotating admins, freezing metadata. Permission is enforced by the program itself across a four-level hierarchy, from platform super-authority down to per-collection admin. The interface reads tier state directly from chain accounts, so adding or renaming a tier needs no code change.
The pass renders as a real-time three.js scene. The first version exhausted memory on mobile devices. The fix was to tier it by device: half-resolution textures, an alpha-blended material in place of the transmissive one that forces an extra full scene render every frame, no shadow map, and a render loop driven on demand at around 30fps rather than continuously. Off-screen instances stop rendering entirely. Desktop keeps full quality.
Engineering decisions
The problem
A token claim debits a custodial balance, then sends on chain. If confirmation times out, the obvious retry re-sends — and the first transaction may well have landed. That is a double payout, in someone else's money.
What we did
The claim moves from claimable to processing before the transfer is submitted, so a retry finds nothing left to reserve and returns the in-flight state instead of sending again. Recovery is deliberately manual: the reconciler reports stuck claims for a human to verify on chain, because from the database alone, "no signature was recorded" and "the signature has not been recorded yet" are indistinguishable.
The problem
Instalment plans need to know how many payments have landed. Incrementing a counter when a webhook arrives is wrong, because webhook delivery is at-least-once and retries are normal operation, not failure.
What we did
Each paid invoice inserts a ledger row under a unique constraint on the invoice identifier — that insert is the idempotency gate. The instalment count is then a recount of paid rows, never an increment. If the ledger write fails, the endpoint returns an error so the processor retries; acknowledging a payment we did not record loses a customer's money quietly. The final payment flips plan state under a compare-and-set, so the claim email is sent exactly once.
The problem
A custodial balance is a promise. If the treasury sells tokens that already back customer balances, the promise silently stops being funded — and nothing in the system notices.
What we did
Sales check a reserve invariant — tokens held, minus tokens sold, must still cover the sum of custodial balances — and refuse when it does not hold. Related: when a required database function is missing, the endpoint returns an explicit error naming it rather than a zero balance. A zero is a lie the interface will render confidently. An error is not.
The problem
For unique-edition tiers the buyer picks a specific edition. Between page load and signature, someone else can take it. The contract then aborts at the edition-marker account — after the buyer has already approved a wallet prompt on a transaction guaranteed to fail.
What we did
A preflight treats the chosen edition as preferred rather than required, resolving against the live sold bitmap plus an in-flight reservation set so two lines in one cart cannot book the same slot. If the preference is gone, it substitutes the lowest free edition silently. The same resolver runs in the card-payment path, so background retries self-heal identically.
Scale
4,565
Two Solana programs: the collection contract that mints and reveals passes, and the SPL token program behind the membership currency.
~38,100
178 source files spanning the public site, checkout, admin console, settlement layer, and background workers.
27
Payment webhooks, cron workers, signed price quotes, member authentication, custody and claim endpoints, admin tooling.
5
Wallet-paid SOL, card in full, card in instalments, a split of membership token plus SOL, and token-only with no card involved.
3 months
124 commits from an empty repository to a production deployment serving both buyer populations.
3
Local development, a staging mirror on its own port and subdomain, and production behind a TLS-terminating reverse proxy.
Outcome
“Built the Fifth & Nine website, smart contracts, and payment systems”
Similar problem
Idempotency, custody, settlement, and audit trails are the parts that are expensive to retrofit. We would rather design them in.