Miden Faucet
The Miden Faucet is a token distribution service that provides developers and users with test tokens for the Miden blockchain without cost.
It implements proof-of-work challenges to ensure fair distribution of tokens, balancing accessibility for legitimate users while preventing malicious actors from draining the token supply.
It contains a library and a CLI binary.
Miden faucet library
The Miden faucet library is a Rust library that can be integrated into projects, allowing developers to run the core faucet functionality.
The library provides a Faucet
struct that can be run in a thread and receive minting requests through a channel.
Miden faucet CLI
The Miden faucet also includes a command-line interface (CLI) that allows to run the faucet behind a REST API and serves a frontend to interact with it.
More information about the CLI can be found in the CLI reference.
More information about the REST API can be found in the API reference.
Installation
We provide Debian packages for official releases for the Faucet software. Alternatively, it also can be installed from source on most systems using the Rust package manager cargo
.
Debian package
Official Debian packages are available under our releases page.
Both amd64
and arm64
packages are available.
Note that the packages include a systemd
service which is disabled by default.
To install, download the desired releases .deb
package and checksum files. Install using
sudo dpkg -i $package_name.deb
You can (and should) verify the checksum prior to installation using a SHA256 utility. This differs from platform to platform, but on most linux distros:
sha256sum --check $checksum_file.deb.checksum
can be used so long as the checksum file and the package file are in the same folder.
Install using cargo
Install Rust version 1.89 or greater using the official Rust installation instructions.
Depending on the platform, you may need to install additional libraries. For example, on Ubuntu 22.04 the following command ensures that all required libraries are installed.
sudo apt install llvm clang bindgen pkg-config libssl-dev libsqlite3-dev
Install the latest faucet binary:
cargo install miden-faucet --locked
This will install the latest official version of the faucet. You can install a specific version x.y.z
using
cargo install miden-faucet --locked --version x.y.z
You can also use cargo
to compile the node from the source code if for some reason you need a specific git revision.
Note that since these aren't official releases we cannot provide much support for any issues you run into, so consider
this for advanced use only. The incantation is a little different as you'll be targeting our repo instead:
# Install from a specific branch
cargo install --locked --git https://github.com/0xMiden/miden-faucet miden-faucet --branch <branch>
# Install a specific tag
cargo install --locked --git https://github.com/0xMiden/miden-faucet miden-faucet --tag <tag>
# Install a specific git revision
cargo install --locked --git https://github.com/0xMiden/miden-faucet miden-faucet --rev <git-sha>
More information on the various cargo install
options can be found
here.
Updating
Updating the faucet to a new version is as simply as re-running the install process.
If the node version is updated, you may encounter an error like this:
Error: faucet failed
Caused by:
0: transaction executor error
1: failed to execute transaction kernel program:
× advice provider error at clock cycle 152
╰─▶ × value for key 0x85dee386c7e023b13a5cf16def1c421c57f76c9135a665bdd5b547d6a54d1b15 not present in the advice map
This is a common error that occurs when the Miden network undergoes updates during its active development phase. The error happens because the local client store contains data that's incompatible with the updated node version.
To resolve this issue, delete the faucet_client_store.sqlite3
file and restart the faucet. This will force the client to re-sync with the updated network state.
Quick Start
Get the Miden Faucet running in minutes.
Prerequisites
- Miden Faucet installed (see Installation)
- Access to a Miden node (testnet, devnet, or local)
Step 1: Create a Faucet Account
First, we need to create a faucet account that will hold the tokens to be distributed. This command generates a new account with the specified token configuration and saves the account data to a local file (faucet.mac
). The account is not yet deployed to the network - that will happen when the faucet is running and the first transaction is sent to the node.
miden-faucet create-faucet-account \
--output-path ./faucet.mac \
--token-symbol MIDEN \
--decimals 6 \
--max-supply 100000000000000000
Step 2: Start the Faucet Server
Next, start the faucet server by specifying the endpoint it should listen on, the Miden node it will connect to, and the account file to use for distributing tokens. The server will handle incoming token requests and manage the minting process.
miden-faucet start \
--endpoint http://localhost:8080 \
--node-url https://rpc.testnet.miden.io \
--account ./faucet.mac
Step 3: Access the Web Interface
The faucet provides a web interface for easy token requests. Open http://localhost:8080
in your browser to access it.
Step 4: Request Test Tokens
Once the faucet is running, you can request test tokens through either the web interface or the REST API.
Via Web Interface
- Enter your Miden account ID or account bech32 address.
- Select token amount
- Choose note type (private or public)
- Submit request
Via API
You can also programmatically interact with the REST API to mint tokens. Check out the complete working examples below. Make sure the faucet is running at http://localhost:8080
before using them.
Common Configurations
Localhost
If you have a Miden Node running locally, you can run the faucet against that node.
miden-faucet start \
--endpoint http://localhost:8080 \
--node-url http://localhost:57291 \
--account ./faucet.mac \
--network localhost
Development
Connect to the node deployed in Miden Devnet.
miden-faucet start \
--endpoint http://localhost:8080 \
--node-url https://rpc.devnet.miden.io \
--account ./faucet.mac \
--network devnet
Testnet
Connect to the node deployed in Miden Testnet.
miden-faucet start \
--endpoint http://localhost:8080 \
--node-url https://rpc.testnet.miden.io \
--account ./faucet.mac \
--explorer-url https://testnet.midenscan.com \
--network testnet
CLI configuration and usage
This guide shows the available commands and their configuration options to run with the Miden Faucet CLI.
Available Commands
Command | Description |
---|---|
start | Start the faucet server |
create-faucet-account | Create a new faucet account |
create-api-keys | Generate API keys for authentication |
help | Show help information |
Configuration Methods
The Miden Faucet can be configured using:
- Command-line arguments
- Environment variables
Command-Line Arguments
Basic Configuration
miden-faucet start \
--endpoint <URL> \
--node-url <URL> \
--account <PATH> \
--network <NETWORK>
All Available Options
Option | Description | Default | Required |
---|---|---|---|
--endpoint | Faucet endpoint | - | Yes |
--node-url | Miden node RPC endpoint | - | Yes |
--account | Path to faucet account file | - | Yes |
--network | Network configuration | localhost | No |
--timeout | RPC request timeout | 5s | No |
--max-claimable-amount | Max claimable base units per request | 1000000000 | No |
--store | SQLite store path | faucet_client_store.sqlite3 | No |
--explorer-url | Midenscan URL | - | No |
Proof of Work Configuration
Option | Description | Default | Required |
---|---|---|---|
--pow-secret | Secret to sign PoW challenges. This should NOT be shared | "" | No |
--pow-baseline | Base PoW difficulty (0-32). It's the starting difficulty when no requests are pending | 12 | No |
--pow-challenge-lifetime | Challenge validity duration, i.e. how long challenges remain valid. This affects the rate limiting, since it works by rejecting new submissions while the previous submitted challenge is still valid | 30s | No |
--pow-cleanup-interval | Cache cleanup interval, i.e. how often expired challenges are removed | 2s | No |
--pow-growth-rate | Difficulty growth rate, i.e. how quickly difficulty increases with load. When set to 1, the difficulty will roughly double when the number of requests doubles. | 1 | No |
Advanced Configuration
Option | Description | Default | Required |
---|---|---|---|
--remote-tx-prover-url | Remote transaction prover | - | No |
--api-keys | Comma-separated API keys | - | No |
--enable-otel | Enable OpenTelemetry | false | No |
--batch-size | Maximum number of P2ID notes to create per transaction | 32 | No |
Environment Variables
All configuration options can be set using environment variables:
# Basic configuration
export MIDEN_FAUCET_ENDPOINT=http://localhost:8080
export MIDEN_FAUCET_NODE_URL=https://rpc.testnet.miden.io
export MIDEN_FAUCET_ACCOUNT_PATH=./faucet.mac
export MIDEN_FAUCET_NETWORK=testnet
export MIDEN_FAUCET_EXPLORER_URL=https://testnet.midenscan.com
# Proof of Work
export MIDEN_FAUCET_POW_SECRET=your-secret-here
export MIDEN_FAUCET_POW_BASELINE=12
export MIDEN_FAUCET_POW_CHALLENGE_LIFETIME=30s
export MIDEN_FAUCET_POW_GROWTH_RATE=1
# Advanced
export MIDEN_FAUCET_MAX_CLAIMABLE_AMOUNT=1000
export MIDEN_FAUCET_TIMEOUT=10s
export MIDEN_FAUCET_ENABLE_OTEL=true
export MIDEN_FAUCET_API_KEYS=key1,key2,key3
export MIDEN_FAUCET_BATCH_SIZE=32
Network Configurations
Predefined Networks
Localhost
--network localhost
- Explorer URL: Not available
- Address Display:
mlcl
- Use Case: Local development
Devnet
--network devnet
- Explorer URL: Not available
- Address Display:
mdev
- Use Case: Development testing
Testnet
--network testnet
- Explorer URL: https://testnet.midenscan.com/
- Address Display:
mtst
- Use Case: Integration testing
Custom Network
--network custom
- Explorer URL: Not available
- Address Display:
mcst
- Use Case: Run your custom network
API Key Configuration
Generate API Keys
miden-faucet create-api-keys 5
This generates 5 API keys that can be used for authentication. They are printed to stdout.
API Key Benefits
- Rate Limiting: Separate rate limits per API key
- Access Control: Distribute keys to different users/teams
Store Configuration
SQLite Store
This is the store that is used by the Miden Client to store all the faucet account state. Default is SQLite:
--store ./faucet_client_store.sqlite3.sqlite3
Monitoring Configuration
OpenTelemetry
Enable OpenTelemetry for production monitoring:
--enable-otel
Configuration Example
miden-faucet start \
--endpoint http://localhost:8080 \
--node-url http://localhost:57291 \
--account ./faucet.mac \
--network localhost \
--pow-baseline 8 \
--pow-challenge-lifetime 1s
For detailed options, run miden-faucet [COMMAND] --help
.
Usage
The faucet can be accessed by the HTTP API interactively through the frontend or programmatically by building the requests manually.
Programmatic API Usage
The faucet provides a REST API. The typical flow to request tokens involves:
- Requesting a Proof-of-Work challenge from
/pow
const baseUrl = 'http://localhost:8080';
const accountId = 'mlcl1qq8mcy8pdvl0cgqfkjzf8efjjsnlzf7q';
const powUrl = new URL('/pow', baseUrl);
powUrl.searchParams.set('account_id', accountId);
const powResp = await fetch(powUrl);
if (!powResp.ok) throw new Error(`PoW error: ${powResp.status} ${await powResp.text()}`);
const powJson: any = await powResp.json();
const challenge: string = powJson.challenge;
const target: bigint = BigInt(powJson.target);
- Solving the computational challenge
// Dependencies: npm i @noble/hashes
import { sha_256 } from '@noble/hashes/sha2.js';
import { utf8ToBytes } from '@noble/hashes/utils.js';
let nonce = 0;
while (true) {
nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
try {
// Compute hash using SHA-256 with the challenge and nonce
let hash = sha_256.create();
hash.update(utf8ToBytes(challenge)); // Use the hex-encoded challenge string directly
// Convert nonce to 8-byte big-endian format to match backend
const nonceBytes = new ArrayBuffer(8);
const nonceView = new DataView(nonceBytes);
nonceView.setBigUint64(0, BigInt(nonce), false); // false = big-endian
const nonceByteArray = new Uint8Array(nonceBytes);
hash.update(nonceByteArray);
// Take the first 8 bytes of the hash and parse them as u64 in big-endian
const hashBytes: Uint8Array = hash.digest().slice(0, 8);
let digest = BigInt('0x' + Array.from(hashBytes).map(b => b.toString(16).padStart(2, '0')).join(''));
// Check if the hash is less than the target
if (digest < target) {
console.log('Found nonce:', nonce);
return nonce;
}
} catch (error: any) {
throw new Error('Failed to compute hash: ' + error.message);
}
}
- Requesting tokens along with your solved challenge to
/get_tokens
const params = new URLSearchParams({
account_id: accountId,
is_private_note: 'true',
asset_amount: '100',
challenge: challenge,
nonce: nonce.toString()
});
const response = await fetch(`${baseUrl}/get_tokens?${params}`);
if (!response.ok) throw new Error(`Get tokens error: ${response.status} ${await response.text()}`);
const text = await response.text();
const json = JSON.parse(text);
const noteId = json.note_id;
const txId = json.tx_id;
const explorerUrl = json.explorer_url;
- Requesting note to download generated notes
You must complete this step to retrieve private notes. For public notes, normal client sync is sufficient.
const response = await fetch(`${baseUrl}/get_note?note_id=${noteId}`);
if (!response.ok) throw new Error(`Get note error: ${response.status} ${await response.text()}`);
const text = await response.text();
const json = JSON.parse(text);
// Decode note with base64
const noteData = Buffer.from(json.data_base64, 'base64');
fs.writeFileSync('note.mno', noteData);
Examples
Check out the complete working examples below. Make sure the faucet API is running at http://localhost:8080
before using them.
REST API Reference
REST API for programmatic access to the faucet service.
The Miden Faucet API follows a two-step process to request tokens:
- Request a Challenge (
GET /pow
): Obtain a proof-of-work challenge that must be solved computationally - Request tokens (
GET /get_tokens
): Submit the solved challenge along with your token request
For detailed information about the token request flow, see the Architecture section.
Responses
HTTP Status Codes
Code | Description |
---|---|
200 | Success |
400 | Bad Request |
429 | Rate limited |
500 | Internal Server Error |
Endpoints
Proof of Work Challenge
Endpoint: GET /pow
-
Purpose: Request a proof-of-work challenge
-
Query Parameters:
account_id
(string, required): The account ID requesting the challengeapi_key
(string, optional): API key for authentication
-
Response: JSON object containing:
challenge
(string): The encoded challenge string in hexadecimal formattarget
(number): The target value for the proof-of-work challenge. A solution is valid if the hashH(challenge, nonce)
is less than this target. As the hashing function we useSHA-256
timestamp
(number): The timestamp when the challenge was created (seconds since UNIX epoch)
Get Tokens
Endpoint: GET /get_tokens
-
Purpose: Request tokens
-
Query Parameters:
account_id
(string, required): The account ID requesting tokensis_private_note
(boolean, required): Whether to create a private noteasset_amount
(number, required): Requested asset amount (in base units)challenge
(string, required): The encoded challenge from the/pow
endpointnonce
(number, required): The nonce used to solve the challengeapi_key
(string, optional): API key for authentication
-
Response: JSON object containing:
tx_id
(string): ID of the created transactionnote_id
(string): ID of the created note
Get Metadata
Endpoint: GET /metadata
-
Purpose: Request the faucet metadata to show on the frontend
-
Response: JSON object containing:
id
(string): ID of the faucet accountissuance
(number): amount of tokens issued by the faucet (in base units)max_supply
(number): maximum available supply of the faucet (in base units)decimals
(number): number of decimals of the token minted by the faucet. It is needed to convert base units into token amounts.explorer_url
(string): URL to view the transaction in the explorer. Only present if available for the current network.
Get Note
Endpoint: GET /get_note
-
Purpose: Request a specific note by its ID
-
Query Parameters:
note_id
(string, required): The ID of the note to retrieve
-
Response: JSON object containing:
note_id
(string): The ID of the requested notedata_base64
(string): The note data encoded in base64 format. This data should be decoded and saved as a file with.mno
extension andapplication/octet-stream
media type
Rate Limiting
When a challenge is submitted for an account, that same account cannot submit a new challenge while the previous one is still valid. This effectively rate limits accounts minting requests. Though, the same account can use API keys to submit multiple challenges simultaneously:
- Without API Key: Global rate limiting based on account ID
- With API Key: Separate rate limits per API key
API Keys
Some endpoints support API key authentication for enhanced PoW.
The PoW difficulty of the faucet scales with the number of active requests. When using an API key, the difficulty will only depend on the number of active requests under that API key.
To request a challenge using an API key:
GET /pow?account_id=0xca8203e8e58cf72049b061afca78ce&api_key=miden_faucet_wONsvRXLZ9FgQG+nlkaq9f2X53cLswe4HSzEIUjFIkQ=
Architecture Overview
This document provides a high-level overview of the Miden faucet architecture.
System Architecture
The high-level structure of the project looks like follows:
Core Components
1. Web Frontend
- Technology: Vanilla JavaScript, HTML5, CSS3
- Purpose: User interface for token requests
- Features:
- Token amount selection
- Address input
- Public/private note selection
- PoW challenge solving
- Request status display
2. REST API Server
- Technology: Axum HTTP framework
- Purpose: HTTP API endpoints for programmatic access
- Features:
- Input parsing and validation
- Handle account metadata
3. Faucet lib
- Technology: Rust library (
crates/faucet
) - Purpose: Faucet core logic
- Features:
- Account handling
- Request batching
- Notes creation
- Token issuance tracking
4. PoW Rate Limiter
- Technology: Rust library (
crates/pow
) - Purpose: Proof of Work rate limiter
- Features:
- PoW challenges issuing, tracking and validation
- Rate limiting
5. Miden Client
- Purpose: Connection with the Miden Node
- Features:
- Transaction creation, execution, and submission
Token request Flow
The basic HTTP requests for minting tokens involves /pow
and /get_tokens
. These are the entry points for the minting process. This is how the whole flow looks like:
Detailed Flow
-
Request Initiation
- User submits token request (web or API)
- System generates proof-of-work challenge
- Challenge stored in cache with expiration
-
Challenge Resolution
- User solves computational challenge
- Solution validated against challenge
- Rate limiting enforced
-
Token Distribution
- Validated request processed
- Token transaction created
- Note generated (public or private)
- Transaction is created, executed, and stored in the local database
- Transaction is proven and submitted to Miden Node
-
Response
- Transaction ID and Note ID returned
Why do we need a backend?
Could we not simply use the Miden Web SDK without a backend? The reason is security: if we were to use the Web SDK without a backend, the account's private key would need to be shared with the frontend. This would expose the private key to anyone, allowing them to potentially mint unlimited tokens and bypass all the rate limiting and throttling mechanisms.
The solution is to have a single backend running that receives and validates the minting requests and stores the account's private key needed to mint the tokens.
Security Features
The faucet implements several security measures to prevent abuse:
-
Proof of Work requests:
- Users must complete a computational challenge before their request is processed.
- The challenge difficulty increases with the load. The load is measured by the amount of challenges that were submitted but still haven't expired.
- Each challenge is signed with a secret only known by the server. It should NOT be shared.
- Rate limiting: if an account submitted a challenge, it can't submit another one until the previous one is expired. The challenge lifetime duration is fixed and set when running the faucet.
- API Keys: the faucet is initialized with a set of API Keys that can be distributed to developers. The difficulty of the challenges requested using the API Key will increase only with the load of that key, it won't be influenced by the overall load of the faucet.
-
Requests batching:
- Maximum batch size: 100 requests
- Requests are processed in batches to optimize performance
- Failed requests within a batch are handled individually