A loyalty/rewards points program on Solana, where the points are a real SPL token — not an integer in a database column. Users sign up with an email, the app provisions a custodial Solana wallet for each of them, a business issues a branded points token, and users earn points (minted to their wallet) and redeem rewards (points burned).
Kudos is the second SolRengine reference app and the issuance showcase for the custodial Wallet-per-User path on the Solana Developer Platform (SDP). It's the counterpart to Lamport, which covers wallets and payments — Kudos covers token issuance: create, deploy, mint, burn.
A complete Rails 8 app, verified end-to-end on devnet. See the showcase writeup.
- A business issues the points token.
Token.register!records it off-chain (mint authority = the business's custodial treasury wallet), thendeploy!puts the mint on-chain. The token isKUDO, 0 decimals — points are whole numbers. - A user signs up with an email. No wallet, no seed phrase.
after_create_commitenqueues an async job that provisions a custody wallet for them through SDP; the dashboard pollspending → provisioning → ready. - Earn. A custodial mint sends points to the user's wallet (
token.mint!). The demo awards a fixed amount; a real app mints on a genuine event (a purchase, a referral) — the engine call is identical, only the trigger differs. - Redeem. A custodial burn removes points from the user's wallet (
token.burn!), affordability-checked against the on-chain balance and a rewards catalog. The burn is signed on the user's behalf, because burn authority is the token-account owner, not the mint authority. - Live balance. Because the app initiates every mint and burn, the job that settles them is the doorbell: it re-reads the balance from chain and pushes it to the browser over a Turbo Stream — no polling, no page reload.
| Lives in Rails (SQLite) | Lives on Solana (via SDP) |
|---|---|
| Users, rewards catalog, mint/burn audit rows | The points token (a real SPL mint) |
| Affordability checks, demo earn trigger | A custodial wallet per user |
Session auth (has_secure_password) |
Every mint and burn, signed custodially |
Each mint!/burn! writes an audit row, then sends a single, never-retried POST — SDP has no idempotency key, so a blind re-send could double-mint. An atomic claim guarantees one send.
- Ruby on Rails 8, Ruby 3.3.6
- Hotwire (Turbo + Stimulus), Tailwind CSS 4, esbuild, propshaft
- SQLite multi-database: primary + Solid Cache + Solid Queue + Solid Cable
- Minitest + WebMock (the suite stubs SDP HTTP — no live stack needed to test)
solana-sdp(~> 0.2) — Ruby client for the SDP APIsolrengine-sdp(~> 0.3) — Rails engine: Wallet-per-User provisioning + token issuance (Token/TokenMint/TokenBurn)solrengine-realtime(~> 0.2) — the realtime doorbell that pushes live balancessolrengine-rpc(~> 0.1) — chain WebSocket subscription for the balance monitor
SDP is pre-mainnet and devnet-oriented, so there's no hosted demo — running Kudos needs an SDP stack of your own:
- A running SDP instance — self-hosted or managed. See the self-hosting guide.
- A managed custody provider (e.g. Privy) — local custody is single-wallet and can't provision a wallet per user.
- Kora as the fee-payment provider — the native adapter can't submit transfers.
- A Helius-class RPC endpoint for SPL token balances (public devnet RPC lacks the indexing).
bin/setup # installs deps, prepares the databases, starts the dev serverSet your SDP credentials first — a missing SDP_API_KEY fails loudly at boot:
# .env
SDP_API_KEY=... # required; Bearer key for the SDP API
SDP_API_BASE_URL=http://127.0.0.1:8787 # SDP base URL (this is the default)
SDP_CUSTODY_PROVIDER=privy # a managed provider; local custody can't do wallet-per-userThen seed the rewards catalog and run it:
bin/rails db:seed # starter catalog: Free coffee (50) · Sticker pack (100) · T-shirt (500)
bin/dev # web · js · css · jobs · sdp_watcherFirst run, in the browser:
- Sign up with an email — your custody wallet provisions in the background.
- Visit
/token/newto create and deploy the points token (business setup; runs once). - Earn points on the dashboard, then redeem a reward — watch the balance update live.
bin/rails test # Minitest; SDP HTTP stubbed with WebMock, no live stackMIT
