Skip to main content

Testnet Faucet

dappTerminal includes a built-in testnet token faucet for Sepolia, Holesky, and Optimism Sepolia. Unlike other protocol plugins, the faucet signs transactions server-side and uses a database-backed rate limiting system to prevent abuse.

Plugin: src/plugins/faucet/ Architecture: src/plugins/faucet/ARCHITECTURE.md


Supported Networks

NetworkChain IDDefault Amount
Sepolia111551110.5 ETH
Holesky170001.0 ETH
Optimism Sepolia111554200.3 ETH

Setup

1. Database Setup

The faucet requires PostgreSQL for rate limiting and audit logging.

# Create the database
createdb defi_terminal

# Or using psql
psql -U postgres -c "CREATE DATABASE defi_terminal;"

# Run Prisma migrations
pnpm prisma migrate dev

# Seed initial configuration
pnpm prisma db seed

2. Generate a Faucet Wallet

Create a dedicated testnet-only wallet for the faucet:

// Run in Node.js REPL
const { Wallet } = require('ethers')
const wallet = Wallet.createRandom()
console.log('Address:', wallet.address)
console.log('Private Key:', wallet.privateKey)

Never use a mainnet wallet as the faucet wallet.

3. Fund the Faucet Wallet

Get testnet ETH from public faucets:

Fund each chain you want to support.

4. Configure Environment Variables

cp .env.example .env.local

Add the faucet configuration:

# Required
DATABASE_URL="postgresql://postgres:password@localhost:5432/defi_terminal"
FAUCET_WALLET_PRIVATE_KEY="0xYOUR_TESTNET_PRIVATE_KEY"

# RPC endpoints (defaults provided)
FAUCET_SEPOLIA_RPC="https://rpc.sepolia.org"
FAUCET_HOLESKY_RPC="https://rpc.holesky.ethpandaops.io"
FAUCET_OPTIMISM_SEPOLIA_RPC="https://sepolia.optimism.io"

# Distribution amounts (optional, shown as defaults)
FAUCET_SEPOLIA_AMOUNT="0.5"
FAUCET_HOLESKY_AMOUNT="1.0"
FAUCET_OPTIMISM_SEPOLIA_AMOUNT="0.3"

# Rate limits (optional, shown as defaults)
FAUCET_ADDRESS_COOLDOWN=24 # hours between requests per address
FAUCET_IP_HOURLY_LIMIT=5 # requests per hour from same IP
FAUCET_IP_DAILY_LIMIT=10 # requests per day from same IP

Commands

request <network> [address]

Request testnet tokens for a network.

> request sepolia
→ Requesting 0.5 ETH on Sepolia...
→ Request ID: req_1234abcd
→ Status: PENDING

> request sepolia 0x742d35Cc6634C0532925a3b8D7C3b8F4B1a3c8E
→ Requesting 0.5 ETH for 0x742d...
→ Request ID: req_5678efgh

If no address is provided, tokens are sent to the connected wallet address.

faucet:status <requestId|txHash>

Check the status of a faucet request.

> faucet:status req_1234abcd
→ Status: COMPLETED
→ Tx: 0xabc...def
→ Network: Sepolia
→ Amount: 0.5 ETH
→ Explorer: https://sepolia.etherscan.io/tx/0xabc...def

> faucet:status 0xabc...def
→ Same lookup by tx hash

faucet:history [address] [network]

View request history for an address.

> faucet:history
→ Last 10 requests for 0x742d...

> faucet:history 0x742d... sepolia
→ Sepolia requests for 0x742d...

API Endpoints

EndpointMethodDescription
/api/faucet/requestPOSTRequest tokens
/api/faucet/statusGETCheck request status
/api/faucet/historyGETView request history
/api/faucet/configGETGet faucet configuration (public)

Rate Limiting

The faucet enforces three rate limiting layers:

LayerLimit
Per-address24-hour cooldown per network
Per-IP hourly5 requests/hour
Per-IP daily10 requests/day

All limits are configurable via environment variables and stored in the database (not in-memory), so they survive server restarts and coordinate across instances.

Security note: The faucet endpoint currently trusts x-forwarded-for/x-real-ip headers directly for IP detection. A determined user can spoof these to bypass the IP rate limit. Fixing this requires using the request IP from your hosting platform's trusted source. See Internal: Security Findings.


Architecture

Server-side signing: Unlike DeFi protocols where the client signs, the faucet has a private key on the server. The wallet service (src/lib/faucet/wallet.ts) signs and broadcasts transactions server-side.

Request lifecycle:

request command
→ POST /api/faucet/request
→ Rate limit check (3 layers, DB-backed)
→ Create FaucetRequest record (PENDING)
→ Send transaction via faucet wallet
→ Update FaucetRequest record (COMPLETED/FAILED)
→ Create FaucetAuditLog entry
→ Return request ID

Database models:

  • FaucetRequest — full request lifecycle tracking
  • RateLimitRecord — persistent per-address and per-IP windows
  • FaucetConfig — per-network configuration (amounts, enabled status)
  • FaucetAuditLog — structured event trail for all requests

Monitoring

Check faucet wallet balances regularly. The faucet will fail silently if the wallet runs out of funds. Consider setting up alerts on wallet balance below a threshold.

Prisma scripts for maintenance:

# View recent requests
pnpm prisma studio # Opens visual database browser

# Check rate limit records
psql $DATABASE_URL -c "SELECT * FROM \"RateLimitRecord\" ORDER BY \"updatedAt\" DESC LIMIT 20;"