A production-ready REST API server and CLI application for distributing Unicity testnet tokens via web interface or command line.
- 🌐 REST API Server - Full REST API under
/api/v1/ - 💧 Web Interface - User-friendly faucet UI at
/faucet/index.html - 📊 History Tracking - Track all faucet requests with API key protected history
- 🗄️ Database Storage - SQLite database for request history
- 📁 Token Storage - Automatic token file storage in
./data/tokens/ - 🐳 Docker Ready - Single command deployment with Docker Compose
- 🔐 API Key Protection - Secure history and admin access with API key authentication
- 🔄 Admin API - Refresh token registry without restart
- 📱 Phone Number Support - Send tokens to phone numbers (e.g., +14155552671)
- 🔒 Privacy-Preserving - SHA-256 hashed nametags for Nostr lookups
-
Navigate to faucet directory:
cd faucet -
Set up environment variables (required):
cp .env.example .env # Edit .env and set: # - FAUCET_API_KEY (for history access) # - FAUCET_MNEMONIC (your faucet wallet mnemonic - GENERATE A NEW ONE!)
-
Start the server:
docker compose up
-
Access the faucet:
docker compose downdocker compose logs -f# Default coin (Solana) with default amount
./gradlew run --args="--nametag=alice"
# Send to a phone number (automatically normalized and hashed)
./gradlew run --args="--nametag=+14155552671"
# Specify amount and coin
./gradlew run --args="--nametag=alice --amount=0.01 --coin=bitcoin"
./gradlew run --args="--nametag=alice --amount=0.5 --coin=ethereum"
./gradlew run --args="--nametag=alice --amount=100 --coin=tether"All endpoints are prefixed with /api/v1/faucet/.
Get list of supported crypto assets.
Response:
{
"success": true,
"coins": [
{
"id": "...",
"name": "solana",
"symbol": "SOL",
"decimals": 9,
"description": "Solana",
"iconUrl": "https://..."
}
]
}Submit a faucet request to mint and send tokens.
Request Body:
{
"unicityId": "alice",
"coin": "solana",
"amount": 0.05
}Response:
{
"success": true,
"message": "Token sent successfully",
"data": {
"requestId": 1,
"unicityId": "alice",
"coin": "Solana",
"symbol": "SOL",
"amount": 0.05,
"amountInSmallestUnits": "50000000",
"recipientNostrPubkey": "..."
}
}Error Response:
{
"success": false,
"error": "Nametag not found: alice"
}Get faucet request history (requires API key).
Headers:
X-API-Key: your-api-key
Query Parameters:
limit(optional, default: 100, max: 1000)offset(optional, default: 0)
Response:
{
"success": true,
"data": {
"requests": [...],
"pagination": {
"limit": 100,
"offset": 0,
"total": 250
}
}
}Health check endpoint.
Response:
{
"status": "healthy"
}Refresh token registry from remote URL without restart.
Headers:
X-API-Key: your-api-key
Response:
{
"success": true,
"message": "Registry refreshed successfully",
"coinsLoaded": 5
}Example:
curl -X POST https://your-faucet-url/api/v1/admin/refresh-registry \
-H "X-API-Key: YOUR_FAUCET_API_KEY"| Variable | Description | Default | Required |
|---|---|---|---|
FAUCET_MNEMONIC |
BIP-39 mnemonic phrase for faucet wallet | Falls back to config file | Yes |
FAUCET_API_KEY |
API key for history access | change-me-in-production |
Yes |
DATA_DIR |
Directory for database and token files | ./data |
No |
PORT |
Server port | 8080 |
No |
Edit src/main/resources/faucet-config.json:
{
"registryUrl": "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json",
"nostrRelay": "ws://unicity-nostr-relay-20250927-alb-1919039002.me-central-1.elb.amazonaws.com:8080",
"aggregatorUrl": "https://goggregator-test.unicity.network",
"faucetMnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"defaultAmount": 1000,
"defaultCoin": "solana"
}IMPORTANT:
- Set
FAUCET_MNEMONICenvironment variable in.env(overrides the config file) - The mnemonic in config file is just a fallback
- Generate a new secure mnemonic for production!
All coins are loaded dynamically from the registry:
| Coin | Symbol | Decimals | CoinGecko ID |
|---|---|---|---|
| solana | SOL | 9 | solana |
| bitcoin | BTC | 8 | bitcoin |
| ethereum | ETH | 18 | ethereum |
| tether | USDT | 6 | tether |
| usd-coin | USDC | 6 | usd-coin |
To add new coins, update the unicity-ids.testnet.json registry.
All data is stored in the ./data directory:
./data/faucet.db- SQLite database with request history./data/tokens/- Individual token files for each request
This directory is automatically created and persisted via Docker volume mount.
- Fetches coin definitions from GitHub
- Caches locally in
~/.unicity/registry-cache.json - Cache valid for 24 hours
- Use
--refreshto force update
- Queries Nostr relay:
{"kinds": [30078], "#t": ["nametag"]} - Uses SHA-256 hashed nametags (never exposes raw nametags)
- Gets recipient's Nostr public key from binding
- Creates proxy address:
ProxyAddress.create(TokenId.fromNameTag(nametag))
- Uses token type from registry (non-fungible "unicity")
- Uses coin ID from registry
- Applies correct decimals (e.g., 1.5 SOL → 1,500,000,000 units)
- Mints to faucet's address first
- Creates
TransferCommitmentto proxy address - Submits to aggregator
- Waits for inclusion proof
- Creates transfer transaction
- Encrypts transfer package with NIP-04
- Sends to recipient's Nostr pubkey
- Format:
token_transfer:{"sourceToken":"...","transferTx":"..."} - Wallet receives, verifies, and finalizes
┌─────────────────┐
│ Web Browser │
│ (User) │
└────────┬────────┘
│ HTTP
▼
┌─────────────────┐
│ Javalin │ REST API Server
│ Web Server │ - /api/v1/faucet/*
│ │ - /api/v1/admin/*
└────────┬────────┘
│
▼
┌─────────────────┐
│ FaucetService │ Business Logic
│ │ - Token minting
│ │ - Nametag resolution
│ │ - Nostr delivery
└────────┬────────┘
│
├──────────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ SQLite Database │ │ Token Storage │
│ (History) │ │ (./data/tokens) │
└─────────────────┘ └─────────────────┘
-
Build the project:
./gradlew build
-
Set environment variables:
export FAUCET_API_KEY="your-api-key" export DATA_DIR="./data" export PORT=8080
-
Run the server:
./gradlew run --args="org.unicitylabs.faucet.FaucetServer"
./gradlew test
./gradlew test --tests "FaucetE2ETest.testCompleteTokenTransferFlow"Usage: faucet [-hV] -n=<nametag> [-a=<amount>] [-c=<coin>] [--refresh] [--config=<configPath>]
Options:
-n, --nametag=<nametag> Recipient's nametag or phone number (required)
-a, --amount=<amount> Token amount in human-readable units (e.g., 0.05)
-c, --coin=<coin> Coin to mint (e.g., 'bitcoin', 'ethereum', 'solana')
--refresh Force refresh registry from GitHub (ignores cache)
--config=<path> Path to config file
-h, --help Show this help message and exit
-V, --version Print version information and exit
- API Key: Always set a strong
FAUCET_API_KEYin production - Mnemonic: The faucet mnemonic in config should be kept secure
- Network: Consider running behind a reverse proxy (nginx, Traefik) with HTTPS
- Rate Limiting: Consider implementing rate limiting for production use
- Privacy: Nametags are automatically SHA-256 hashed for privacy
- Ensure Docker has enough memory (4GB+ recommended)
- Check that all source files are present
- Try
docker compose build --no-cache
- Check that port 8080 is not already in use
- Verify faucet-config.json is valid JSON
- Check logs:
docker compose logs -f
- Ensure
./datadirectory has write permissions - Delete
./data/faucet.dbto reset the database
- Verify Nostr relay is accessible
- Check that nametag has been minted and published
- Ensure aggregator URL is correct
# Clear cache
rm ~/.unicity/registry-cache.json
# Or use --refresh flag
./gradlew run --args="--nametag=alice --refresh"faucet/
├── src/
│ ├── main/
│ │ ├── java/org/unicitylabs/faucet/
│ │ │ ├── db/ # Database models and DAO
│ │ │ ├── FaucetServer.java # REST API server
│ │ │ ├── FaucetService.java # Business logic
│ │ │ ├── FaucetCLI.java # CLI interface
│ │ │ ├── TokenMinter.java # Token minting
│ │ │ ├── NametagResolver.java # Nametag resolution
│ │ │ └── UnicityTokenRegistry.java # Registry loader
│ │ └── resources/
│ │ ├── public/faucet/ # Web UI files
│ │ ├── faucet-config.json # Faucet configuration
│ │ └── trustbase-testnet.json # Trust base
│ └── test/
├── build.gradle.kts
├── Dockerfile
├── docker-compose.yml
└── README.md
- Unicity Java SDK 1.2+ (state transitions, proxy addressing)
- Unicity Nostr SDK 1.0+ (Nostr client, token transfer protocol)
- Javalin 5.6+ (web server)
- SQLite JDBC (database)
- Jackson (JSON/CBOR serialization)
- BouncyCastle (cryptography)
- Picocli (CLI)
- BitcoinJ (BIP-39 mnemonic)
Part of the Unicity Protocol project - MIT License