Pay in USD₮, get 5 % back as an on-chain token.
A non-custodial multi-chain wallet plus a cashback pipeline: a customer pays a merchant in USD₮, the backend observes the payment, and 5 % of it comes back as a coupon the customer redeems for an on-chain utility token (UTL).
Backend
NestJS + TypeORM + PostgreSQL. One codebase, five processes: API, issuer, relayer, settlement watcher, monitor.
Smart contracts
Solidity / Foundry. UTL (LayerZero OFT) and
CouponClaim (K-of-N issuer threshold claims) on
Ethereum Sepolia.
Mobile
React Native 0.86 + WDK worklet, MobX. Google sign-in, biometrics, wallet create/restore, encrypted backup. Wallet data screens still on demo state.
Integration
The contracts between the three repos: paymentRef, the
EIP-712 Entitlement, shared fixtures, the REST surface.
Repositories
| Directory | Stack | What it owns |
|---|---|---|
backend/ |
NestJS, TypeScript (strict), TypeORM, PostgreSQL, Redis | Payment detection, pricing, coupon accrual, claim state machine, attestation, relaying, reconciliation, REST API |
contract/ |
Solidity ^0.8.24, Foundry, OpenZeppelin, LayerZero OFT |
UTL reward token, CouponClaim threshold
claim contract, deploy script, invariant suite
|
mobile/ |
React Native 0.86.2, React 19, MobX, WDK React Native Core | Non-custodial wallet UX: key generation inside the WDK worklet, biometric gate, session, encrypted backup, cashback screens |
The whole path, end to end
device ──pays USD₮──▶ merchant address (mainnet: Ethereum / Arbitrum One)
│
WDK Indexer API ◀─────────┘
│ polled, never trusted
▼
┌──────────────────────────────────────────────────────────┐
│ API process │
│ payment poller → pricing → accrual → coupon (5 %) │
│ REST for the app: auth, wallets, secrets, coupons, │
│ claims, transactions, balances │
└──────────────────────────────────────────────────────────┘
│ claim, signed once by the user's wallet (personal_sign)
▼
┌──────────────┐ K signatures ┌──────────────┐ claim() ┌─────────────┐
│ issuer × N │ ───────────────▶ │ relayer │ ────────▶ │ CouponClaim │
│ own RPC+key │ EIP-712 │ own RPC+key │ pays gas │ (Sepolia) │
└──────────────┘ └──────────────┘ └─────────────┘
▲ │
│ Claimed event ▼
┌──────────────┐ ┌────────────────────┐ ┌──────────┐
│ monitor │◀── reconciles ───│ settlement watcher │ │ UTL mint │
│ guardian key │ can pause() └────────────────────┘ └──────────┘
└──────────────┘
The same picture is available as an interactive diagram — click a process for what it holds and what it may write, or step through one payment from the till to a minted token. How the app's requests reach these endpoints is on the mobile page.
Two chains, on purpose. Payments are detected on
mainnet (Ethereum, Arbitrum One) through the hosted WDK Indexer
API. Rewards settle on Ethereum Sepolia, where
CouponClaim mints UTL. Nearly every mistake on this path
comes from confusing the two, so the split is spelled out everywhere it
matters — including in the RPC configuration, which is a map keyed by
srcChainId.
Trust model in one paragraph
Payment detection depends on a third party (the indexer), so
no single process can turn an indexer answer into a mint.
The issuers re-verify every payment against their own nodes and sign an
EIP-712 Entitlement; the relayer re-verifies again before
spending gas and is the only key allowed to write to the chain; the
contract requires K distinct issuer signatures and enforces per-claim and
per-epoch mint caps; the monitor reconciles minted supply against the
payments that justify it and holds the pause key. Each process runs with
its own key and its own RPC endpoint, and refuses to start if it shares an
endpoint with another.
| Process | Holds | May write | Never |
|---|---|---|---|
| API | no chain key | users, wallets, coupons, claims | attest, submit |
| Issuer (×N) | one ISSUER_ROLE key |
attestations, claim rejection |
spend gas, call the indexer |
| Relayer | the only chain-writing key | claim status | create attestations |
| Settlement watcher | no key | settlements, claim status | anything on-chain |
| Monitor | guardian (PAUSER_ROLE) |
nothing | repair, attest, submit |
Status at a glance
| Area | State | Notes |
|---|---|---|
| Backend money path | shipped | 590 tests / 54 suites, 94 % statements, 95 % lines |
| Contracts | deployed |
Sepolia, 119 tests, 100 % line/branch on src/,
Slither clean
|
| Mobile: auth, biometrics, wallet lifecycle, backup | wired to backend | /auth/google, /auth/refresh, /secrets/* |
| Mobile: balances, transactions, coupons, claims | demo state |
In-memory fixtures in WalletStore; the backend
endpoints exist and are unconsumed
|
| Non-EVM verification (BTC, Tron, Spark) | not built | Payments are ingested, but no issuer has a node of that kind |