WDK Cashback Wallet

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