Mint Karma soulbound tokens on Status Network Hoodi Testnet. Developers enter an EVM address, select a tier, and receive Karma to test gasless transactions via RLN.
Built with Next.js, ethers.js, Prisma (SQLite), and Tailwind CSS.
Service is deployed via infra-role-compose-repo Ansible role in the infra-sites repository from two branches:
- Pushes to
developdeploy to https://dev-karma-faucet.status.network/ - Pushes to
masterdeploy to https://karma-faucet.status.network/
Changes are picked up via GitHub webhooks.
- Node.js v22+
- An admin/operator wallet with
DEFAULT_ADMIN_ROLEorOPERATOR_ROLEon the SimpleKarmaDistributor (the on-chain safety layer through which this faucet mints — mints never touch the Karma ERC20 directly) - The wallet must have enough KARMA to be at least a High-Throughput tier user on the Status Network testnet.
npm install
cp .env.example .envEdit .env with your values. Especially don't forget to fill in MINTER_PRIVATE_KEY, KARMA_CONTRACT_ADDRESS, and KARMA_DISTRIBUTOR_ADDRESS.
Then create the SQLite database from the committed migrations:
npx prisma generate
npx prisma migrate deployThe *.db file is not in git. migrate deploy applies everything under prisma/migrations/ and creates the database file on disk.
For ongoing schema changes, use npx prisma migrate dev --name <description> and commit the new folder under prisma/migrations/. In CI or production, run npx prisma migrate deploy with DATABASE_URL pointing at your database.
| Variable | Required | Default | Description |
|---|---|---|---|
MINTER_PRIVATE_KEY |
Yes | — | Private key of admin/operator wallet |
KARMA_CONTRACT_ADDRESS |
Yes | — | Karma ERC20 address (used for minter balance reads only) |
KARMA_DISTRIBUTOR_ADDRESS |
Yes | — | SimpleKarmaDistributor address — all faucet mints go through this contract |
RPC_URL |
No | https://public.hoodi.rpc.status.network |
RPC endpoint |
CHAIN_ID |
No | 374 |
Chain ID |
TOKEN_DECIMALS |
No | 18 |
Karma token decimals |
COOLDOWN_SECONDS |
No | 86400 |
Per-address cooldown (24h) |
DATABASE_URL |
No | file:./dev.db |
SQLite file: URL, path relative to prisma/ → prisma/dev.db (gitignored) |
NEXT_PUBLIC_EXPLORER_URL |
No | https://hoodiscan.status.network |
Block explorer URL |
NEXT_PUBLIC_CHAIN_NAME |
No | Status Network Hoodi Testnet |
Display name |
npm run devOpen http://localhost:3000.
npm run build
npm startDeployment note: This app uses in-memory rate limiting alongside a SQLite-backed mint store, so it must run as a persistent Node.js process — not serverless (Vercel, Lambda, etc.). Per-address cooldowns are persisted in SQLite and survive restarts.
The faucet enforces cooldowns through three layered checks, evaluated concurrently where possible:
- In-memory address throttle — Per-address cooldown held in process memory for fast rejection of repeated requests. Resets on restart.
- Persistent address cooldown — Per-address cooldown stored in SQLite upon mint store. Survives restarts and prevents TOCTOU races by inserting a
pendingrecord before the mint transaction is sent. - On-chain fallback — When no in-memory or DB record exists, the faucet scans for any recent Karma mints to the target address. If a recent mint is found, the address is back-filled into the DB and in-memory stores so subsequent layers catch it immediately.
Mint records track status (pending → confirmed / failed) and are verified on-chain in the background (via Next.js after()) once the response has been sent. Failed mints clean up the pending record so the address isn't permanently blocked.
| Tier | Karma | Daily Tx Quota |
|---|---|---|
| Entry | 1 | 2/day |
| Newbie | 2–49 | 6/day |
| Basic | 50–499 | 16/day |
| Active | 500–4.99K | 96/day |
| Regular | 5K–19.99K | 480/day |
| Power | 20K–99.99K | 960/day |
Higher tiers (Pro, High-Throughput, S-Tier, Legendary) are not provided through this faucet — contact the team directly to obtain them.
{ "address": "0x...", "tierId": "basic" }Returns 200 with { txHash, explorerUrl, tier, karmaAmount } on success. Each request sends two transactions on the distributor — mint(account, amount) (credits a virtual balance) followed by redeemRewards(account) (transfers the Karma to the user). On the happy path txHash is the redeem tx, which is the one containing the ERC20 Transfer visible on explorers.
If the redeem call fails after a successful mint, the response additionally carries a warning field and txHash points at the mint tx instead. The user's Karma is reserved as a virtual balance on the distributor and remains redeemable later (redeemRewards is permissionless), so the rate limit still applies.
Returns 200 with { status, chainId, minterEthBalance, minterKarmaBalance, contractAddress, distributorAddress, distributorAvailableSupply }. distributorAvailableSupply is the remaining pre-funded Karma on the distributor — when this reaches zero, mints will start failing until the rewards supplier tops it up.