-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Operator Wiki
This operator wiki describes the v2 version of the faucet.
You can download packaged releases of the faucet from the Releases Page.
You need a machine with NodeJS >= 18 installed.
Download powfaucet-server-all.tar.gz from the latest release and run the following commands to start the faucet:
tar -xzf powfaucet-server-all.tar.gz
./run-faucet.sh
For Windows, download and extract powfaucet-server-all.zip from the latest release and run run-faucet.bat.
After running these commands, the faucet should be accessible at http://localhost:8080
The faucet server process needs to run continuously. Consider running it in a screen session or as a systemd service.
On first start, the faucet creates a copy of the example configuration at faucet-config.yaml. Some settings are initialized with random values for security (faucetSecret, ethWalletKey).
Read through the configuration file to customize the faucet for your needs.
The configuration is described in more detail in this wiki.
There are also platform-specific all-in-one executables available for Windows and Linux. These are standalone Node.js binaries with the faucet scripts and static folder bundled in. No local Node.js installation is required.
This works fine for testing environments, but a locally installed Node.js is recommended for better performance.
A docker image is available for this faucet: pk910/powfaucet
There are various images available:
-
v2-stable: The latest v2.x.x release -
v2-latest: The latestmasterbranch version (automatically built) -
v2.x.x: Version-specific images for all v2.x.x releases.
The docker image uses nginx to serve static files and proxies API/WebSocket requests to the node backend internally. The default working directory inside the container is /data.
Follow these steps to run the docker image:
-
Create a new directory that will be used for persistent faucet data
mkdir faucet-data cd faucet-data -
Create default config
docker run --rm -v $(pwd):/data pk910/powfaucet:v2-stable --create-configThis should create a copy of the example config called
faucet-config.yaml. -
Edit
faucet-config.yamlto configure the faucet for your needs.nano faucet-config.yamlChange
ethRpcHostto use a reliable RPC host (Infura, Alchemy, ...) and configure the faucet modules for your needs. -
Start the container
docker run -d --restart unless-stopped --name=powfaucet -v $(pwd):/data -p 8080:8080 pk910/powfaucet:v2-stable
You should now be able to access the faucet via http://localhost:8080
Read logs:
docker logs powfaucet --follow
Stop container:
docker rm -f powfaucet
The following environment variables can be used to control the docker image behavior:
-
DISABLE_NGINX: Set to any value to skip launching nginx (useful when running behind your own reverse proxy). -
FAUCET_NGINX_LOG: Set to1to enable nginx access logs to stdout.
To build the faucet from source, you need a machine with git and NodeJS >= 22 installed. The OS doesn't matter; it is tested on Debian and Windows, but others should work as well.
Clone the repository, install dependencies & build the faucet:
git clone https://github.com/pk910/PoWFaucet.git
cd PoWFaucet
npm install
cd faucet-client
npm install
node ./build-client.js
cd ..
npm run start
After running these commands, the faucet should be accessible at http://localhost:8080
The faucet server process needs to run continuously. Consider running it in a screen session or as a systemd service.
On first start, the faucet creates a copy of the example configuration at faucet-config.yaml. Some settings are initialized with random values for security (faucetSecret, ethWalletKey).
Read through the configuration file to customize the faucet for your needs.
The configuration is described in more detail in this wiki.
For productive setups I suggest using a more complex webserver than the built in low-level static server, because it does not support ssl, caching and stuff.
Note: When using the docker image, nginx is already included and handles static file serving and proxying internally. You can still put another reverse proxy in front of it if needed (e.g. for TLS termination). Set DISABLE_NGINX=1 if you want to handle all proxying yourself.
To setup the faucet with a proper webserver, you just need to point the document root to the /static folder of the faucet and forward websocket (Endpoint: /ws/) and api (Endpoint: /api/) calls to the faucet process.
See a more detailed description and example configs for apache & nginx here: docs/webserver-setup.md
The PoWFaucet UI can be embedded as an independent component into an existing website. This allows a wide range of customization as the faucet can be styled for custom needs.
An example implementation that loads the demo1 instance as a component on a JSFiddle page: https://jsfiddle.net/pk910/nf1k9vxq
The integration is easily done with the following steps:
- Install the faucet and make the faucet site accessible via a public reachable URL
- Add faucet style to the page you want to show the faucet on
<link rel="Stylesheet" type="text/css" href="https://faucet.example.com/css/powfaucet.css">- Add faucet script to the page you want to show the faucet on
<script src="https://faucet.example.com/js/powfaucet.js"></script>- Add container element where the faucet gets rendered to
<div class="faucet-bootstrap" id="powfaucet"></div>- Initialize & render the faucet
<script type="text/javascript">
(function() {
// get container element
var container = document.getElementById("powfaucet");
// faucet UI config
var config = {
baseUrl: "https://faucet.example.com"
};
// render UI
PoWFaucet.initializeFaucet(container, config);
})();
</script>Always load the faucet script and style from the faucet server instead of including the JS/CSS files in your site directly, to avoid version mismatches after faucet upgrades.
For security, the faucet must explicitly allow embedding from certain origins via the CORS policy:
corsAllowOrigin:
- "https://fiddle.jshell.net"Most configuration settings can be changed without restarting the faucet process.
This is helpful because restarting the process causes a high number of session reconnects and might even result in a loss of rewards when losing connectivity in time-critical situations (e.g. a few seconds before session timeout).
To reload the configuration without restarting, send a SIGUSR1 signal to the faucet process.
You can use the following command in combination with the faucetPidFile configuration to reload the config of a running instance:
kill -SIGUSR1 $(cat ./faucet-pid.txt)
The faucet should reload the config file and print something like this when receiving the signal:
# Received SIGUSR1 signal - reloading faucet config
All session progress is restored from the database when the server restarts, so nothing is lost even if you do need to restart.
To update the faucet, you just need to load the latest release and overwrite the existing files. The faucet automatically upgrades a existing database when needed.
To demonstrate some scenarios with different protection methods, I've created a bunch of demo instances of the faucet.
You can find them here: https://github.com/pk910/PoWFaucet/blob/master/docs/demo/README.md
The faucet supports two database drivers: SQLite (default) and MySQL.
database:
driver: "sqlite"
file: "faucet-store.db"The file path is relative to the data directory. SQLite is the simplest option and works well for most setups.
database:
driver: "mysql"
host: "127.0.0.1"
port: 3306
username: "faucet"
password: "secret"
database: "powfaucet"
poolLimit: 5 # optional, default: 5MySQL is recommended for high-traffic deployments or when you want to separate the database from the faucet process.
The faucet can distribute standard ERC20 tokens instead of native network tokens.
To enable this, configure the following settings:
-
faucetCoinType: Set to"erc20" -
faucetCoinContract: Set to the address of the ERC20 token contract
The faucet uses a standard ERC20 ABI to transfer funds via the transfer call.
The number of decimals and balances are queried via decimals / balanceOf.
Be aware that most amount-related configuration options are based on the smallest unit values ("wei") and assume 18 decimal places.
For tokens with a different number of decimals, you need to adjust these settings to reflect the intended values for your token.
The default drop amount can be configured via the maxDropAmount and minDropAmount settings:
# min/max drop amount (max is the default if no module lowers it)
maxDropAmount: 1000000000000000000 # 1 ETH
minDropAmount: 10000000000000000 # 0.01 ETHBy default, the faucet drops the amount specified by maxDropAmount, but faucet modules may change the drop size according to their protection logic. The final drop size is limited by the min/max amounts specified.
The faucet has a modular protection system.
Despite the name "PoW Faucet", the PoW protection is an optional module just like the others.
There are various modules available that can be enabled independently to fit your protection needs.
Add captcha protection to front page (session start) and/or claim page (session claim)
You need to set a proper siteKey / secret from HCaptcha, Google / reCaptcha or Cloudflare / Turnstile (since v2.2.5)
modules:
captcha:
# enable / disable captcha protection
enabled: true
# captcha provider
# hcaptcha: HCaptcha (https://hcaptcha.com)
# recaptcha: ReCAPTCHA (https://developers.google.com/recaptcha)
# turnstile: Turnstile (https://www.cloudflare.com/products/turnstile)
provider: "hcaptcha"
# captcha site key
siteKey: "00000000-0000-0000-0000-000000000000"
# captcha secret key
secret: "0xCensoredHCaptchaSecretKey"
# require captcha to start a new session (default: false)
checkSessionStart: false
# require captcha to start claim payout (default: false)
checkBalanceClaim: falseLimits the number of concurrent sessions on a per IP / per ETH Address base.
Concurrent sessions are sessions in running state. However, sessions leave the running state almost immediately if no module introduces a long-running task (e.g. mining). So this module is only effective in combination with such a module.
modules:
concurrency-limit:
# enable / disable concurrency limit
enabled: true
concurrencyLimit: 1 # only allow 1 concurrent session per IP / target addr
byAddrOnly: false # only check concurrency by target address
byIPOnly: false # only check concurrency by IP address
#messageByAddr: "" # custom error message when limit is exceeded by same target address
#messageByIP: "" # custom error message when limit is exceeded by same IP addressThis module allows entering a mainnet ENS name instead of a wallet address.
By default this is a optional feature. But ENS names can also be made a strict requirement and therefore be used as a protection method via the required setting.
modules:
ensname:
# enable / disable ENS module
enabled: true
# RPC host for ENS name resolver (mainnet RPC)
rpcHost: "https://rpc.flashbots.net/"
# Custom ENS Resolver contract address
#ensAddr: "0x"
# require ENS name for sessions
required: falseThis module checks the target wallet on the testnet the faucet is running on whenever a session is started.
There are several restrictions that can be enforced:
-
maxBalance: Max amount of funds the user is allowed to have in his wallet to be allowed to use the faucet. -
denyContract: Deny sessions for contract addresses to avoid farming with forwarder contracts.
modules:
ethinfo:
# enable / disable max balance protection
enabled: true
# check balance and deny session if balance exceeds the limit (in wei)
maxBalance: 50000000000000000000 # 50 ETH
# deny sessions for contract addresses
denyContract: trueThis module introduces a global reward factor based on the faucet wallet balance.
It is used to lower the drop amounts when the faucet wallet gets low on funds.
modules:
faucet-balance:
# enable / disable faucet balance protection
enabled: true
# reward restriction based on faucet wallet balance (lowest value applies)
fixedRestriction:
1000000000000: 90 # 90% if lower than 1000 ETH
500000000000: 50 # 50% if lower than 500 ETH
200000000000: 10 # 10% if lower than 200 ETH
100000000000: 5 # 5% if lower than 100 ETH
# dynamic reward restriction based on faucet balance
# alternative to fixedRestriction; if both are set, the lower factor is applied
# limits rewards linearly (100% >= targetBalance, 0% <= spareFundsAmount)
dynamicRestriction:
targetBalance: 1100000000000000000000 # no restriction with faucet balance > 1100 ETHThis module introduces a global reward factor based on the faucet outflow.
To keep track of the outflow, the module uses an internal "outflow balance".
The outflow balance initially starts at 0 and gets increased by the configured amount (amount/duration) every second.
Whenever a session gets a reward assigned, that amount is subtracted from the outflow balance.
When the outflow balance gets negative, there are more funds requested than the outflow limit allows and the reward is reduced. The reduction is applied linearly down to 0% the closer the outflow balance gets to lowerLimit.
When the outflow balance gets positive, there are less funds requested than allowed. The positive balance is a 'buffer' for future activity spikes. However, the balance won't grow higher than upperLimit.
This logic ensures that the average mining reward does not exceed the specified limit over the long term. In the short term, the reward for a single day may exceed the limit by the configured boundaries, but the algorithm then becomes more restrictive the next day (negative outflow balance), so the average converges back to the limit.
The status of the faucet-outflow module is displayed on the faucet status page, allowing the operator to monitor the internal outflow balance and the applied reward reduction.
modules:
faucet-outflow:
# enable / disable faucet outflow protection
enabled: true
# limit outflow to 1000 ETH per day
amount: 1000000000000000000000 # 1000 ETH
duration: 86400 # 1 day
# outflow balance limits
lowerLimit: -500000000000000000000 # -500 ETH (reward approaches 0% as balance reaches this)
upperLimit: 500000000000000000000 # 500 ETH (buffer for activity spikes)This module adds the ability to authenticate with a GitHub account before session start.
Besides strict barriers, the authentication can also be made optional to apply reward factors based on GitHub profile stats.
The module needs a GitHub API key and secret to handle the OAuth authentication flow.
To register an application, go to the GitHub Developer Settings and register an "OAuth App".
The authorization callback URL for the faucet is {faucet-host}/api/githubCallback (replace {faucet-host} with your hostname).
Note that the callback URL specified in the GitHub app settings matches subdomains as well.
So for all faucets running under xy-faucet.pk910.de, the same GitHub app can be used with the callback URL set to https://pk910.de/api/githubCallback. That URL doesn't need to actually work; it's used as a pattern. When the authentication workflow is triggered from a valid subdomain, the OAuth flow automatically redirects to the correct subdomain on completion.
See the GitHub documentation for details.
Additional settings:
-
cacheTime: How long to cache GitHub profile data (in seconds, default: 86400). -
redirectUrl: Custom redirect URL for the OAuth callback (optional).
modules:
## GitHub login protection
github:
# enable / disable GitHub login protection
enabled: true
# GitHub API credentials
appClientId: "" # client id from GitHub app
appSecret: "" # app secret from GitHub app
# authentication timeout
authTimeout: 86400
# cache time for GitHub profile data
cacheTime: 86400 # 1 day
# GitHub account checks
checks:
- required: true # simplest check - just requires being logged in
- minAccountAge: 604800 # min account age (7 days)
minRepoCount: 10 # min number of repositories (includes forked ones)
minFollowers: 10 # min number of followers
minOwnRepoCount: 5 # min number of own repos (non-forked)
minOwnRepoStars: 5 # min number of stars on own repos (non-forked)
required: true # require passing this check or throw error
message: "Your github account does not meet the minimum requirements" # custom error message
- minFollowers: 50
minOwnRepoCount: 10
minOwnRepoStars: 1000
rewardFactor: 2 # double reward if account looks trustful
# recurring restrictions based on github account
restrictions:
# max 10 sessions / 10 ETH per day for each github account
- limitCount: 10
limitAmount: 10000000000000000000 # 10 ETH
duration: 86400 # 1 dayThis module fetches IP-related information from an external API and allows defining restrictions based on it.
The API endpoint is specified via the apiUrl setting. It defaults to the free JSON API provided by ip-api.com. When using a custom API, ensure the request/response formats match the format provided by ip-api.com.
The gathered information used for restrictions looks as follows:
ETH: <Wallet-Address>
IP: <IP-Address>
Ident: <Browser-Fingerprint>
Country: <Country-Code>
Region: <Region-Code>
City: <City-Name>
ISP: <ISP-Name>
Org: <Organization-Name>
AS: <AS-Number-and-Name>
Proxy: <true/false>
Hosting: <true/false>
Based on this information, several optional restrictions can be applied:
-
Basic property restrictions (
restrictions)
Restricts rewards based on country code or the Proxy/Hosting flag.
Values can be a simple number (reward percentage) or an object with additional options. -
Regex-based restrictions (
restrictionsPattern)
Restricts rewards via regular expressions matched against any of the gathered information. -
Dynamically loaded restrictions from file (
restrictionsFile)
Similar to the regex-based mechanism, but loads restrictions from an external file. The file is refreshed periodically, so it can be updated dynamically by an external process.
The format of the YAML file is as follows:restrictions: - pattern: "^IP: 1\\.2\\." reward: 0 # set reward to 0% message: "Sorry, this faucet is not intended for farmers!" blocked: true # kill session, but allow claiming the already collected reward - pattern: "^ETH: 0x0000000000000000000000000000000000001337" reward: 50 # set reward to 50% message: "I just don't like you! Reward reduced to 50%" notify: true # display message as notification - pattern: "^Org: Hostingrsdotcom" reward: 0 message: "Sorry, this faucet is not intended for farmers!" blocked: "kill" # "kill" terminates the session without allowing to claim the collected rewards
The three restriction classes can be configured individually and can be left out entirely if not needed. All configured restriction classes are processed in the order listed above.
modules:
ipinfo:
# enable / disable IP-Info protection
enabled: true
# ip info lookup api url (default: http://ip-api.com/json/{ip}?fields=21155839)
apiUrl: "http://ip-api.com/json/{ip}?fields=21155839"
# ip info caching time (stored in database)
cacheTime: 86400 # 1 day
# require valid ip info (throw error and fail session if lookup failed)
required: false
# Basic property restrictions
restrictions:
# percentage of drop amount if IP is in a hosting range (default: 100), 0 to block entirely
hosting: 1
# percentage of drop amount if IP is in a proxy range (default: 100), 0 to block entirely
proxy: 1
# percentage of drop amount if IP is from given country code (DE/US/...), 0 to block entirely
#US: 100
# Regex-based restrictions
restrictionsPattern:
"^.*Tencent Cloud.*$": 1 # 1% if info matches pattern
"^.*UCLOUD.*$": 1
"^.*Server Hosting.*$": 1
"^.*SCloud.*$": 1
# Dynamically loaded restrictions from file
restrictionsFile:
yaml: "./restrictions.yaml"
refresh: 30 # reload file every 30 secThis module checks the target wallet on mainnet when a session is started.
The following restrictions can be enforced:
-
minBalance: Minimum balance the user must hold in their mainnet wallet. -
minTxCount: Minimum number of transactions the user must have sent from their mainnet wallet. -
minErc20Balances: List of minimum ERC20 token balances required in the user's mainnet wallet. Each entry includes the token contract address (address), the number of decimals (decimals), the tokenname, and the minimum required balance (minBalance).
modules:
mainnet-wallet:
# enable / disable mainnet wallet protection
enabled: true
# RPC host for ETH mainnet
rpcHost: "https://rpc.flashbots.net/"
# require minimum balance on mainnet wallet
minBalance: 10000000000000000 # 0.01 ETH
# require minimum number of transactions from mainnet wallet (nonce count)
minTxCount: 5
# require minimum ERC20 token balances on mainnet wallet
minErc20Balances:
- name: "WETH"
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # WETH on mainnet
decimals: 18
minBalance: 10000000000000000 # 0.01 WETHThis module fetches the Gitcoin Passport for a target address when a session is started.
The passport is scored using the configured scoring matrix. Each stamp can be given an individual score value. The resulting score is then used to select a reward factor applied to all session rewards.
The module can be used to increase rewards for more legitimate users in mining scenarios, or lower rewards for less legitimate users in other scenarios.
You need an API Key from the Gitcoin Passport Scorer Dashboard to load passports automatically.
To allow optional manual submission and verification of the passport JSON, specify the public key of the stamp signer for verification.
For Gitcoin Passport, that is currently did:key:z6MkghvGHLobLEdj1bgRLhS4LPGJAvbMA1tn2zcRyqmYU5LC
Additional settings:
-
requireMinScore: Minimum passport score required to start a session (0 to disable). -
skipProxyCheckScore: Skip proxy check for IP when wallet score exceeds this value (0 to disable). -
skipHostingCheckScore: Skip hosting check for IP when wallet score exceeds this value (0 to disable). -
allowGuestRefresh: Allow passport refresh without an active session (e.g. for skipping hosting/proxy checks). -
guestRefreshCooldown: Refresh cooldown without an active session (defaults torefreshCooldownif 0).
modules:
passport:
enabled: true
scorerApiKey: "" # Gitcoin Passport Scorer API Key
trustedIssuers:
- "did:key:z6MkghvGHLobLEdj1bgRLhS4LPGJAvbMA1tn2zcRyqmYU5LC" # Gitcoin Passport
passportCachePath: "passport-cache"
refreshCooldown: 300 # allow refreshing every 5 mins
cacheTime: 86400 # cache passports for 1 day (does not affect manual refreshing)
stampDeduplicationTime: 604800 # allow reusing a stamp in another passport after 7 days
skipProxyCheckScore: 0 # skip proxy check for IP when wallet score exceeds this
skipHostingCheckScore: 0 # skip hosting check for IP when wallet score exceeds this
allowGuestRefresh: false # allow passport refresh without active session
guestRefreshCooldown: 0 # refresh cooldown without active session (default to refreshCooldown if 0)
# applied reward factors based on passport score
boostFactor:
1: 1.1 # x1.1 if score >= 1
5: 1.5 # x1.5 if score >= 5
10: 2 # x2 if score >= 10
# stamp scoring matrix
stampScoring:
"Google": 1
"Twitter": 1
"Ens": 2
"GnosisSafe": 2
"Poh": 4
"POAP": 1
"GitPOAP": 1
"Facebook": 1
"FacebookFriends": 1
"FacebookProfilePicture": 1
"Brightid": 10
"Github": 1
"Coinbase": 1
"FiveOrMoreGithubRepos": 1
"TenOrMoreGithubFollowers": 2
"FiftyOrMoreGithubFollowers": 4
"ForkedGithubRepoProvider": 1
"StarredGithubRepoProvider": 1
"Linkedin": 1
"Discord": 1
"TwitterTweetGT10": 1
"TwitterFollowerGT100": 1
"TwitterFollowerGT500": 2
"TwitterFollowerGTE1000": 3
"TwitterFollowerGT5000": 5
"SelfStakingBronze": 1
"SelfStakingSilver": 1
"SelfStakingGold": 1
"CommunityStakingBronze": 1
"CommunityStakingSilver": 1
"CommunityStakingGold": 1
"ClearTextSimple": 1
"ClearTextTwitter": 1
"ClearTextGithubOrg": 1
"SnapshotProposalsProvider": 1
"SnapshotVotesProvider": 1
"EthGasProvider": 1
"FirstEthTxnProvider": 1
"EthGTEOneTxnProvider": 1
"GitcoinContributorStatistics#numGrantsContributeToGte#1": 1
"GitcoinContributorStatistics#numGrantsContributeToGte#10": 1
"GitcoinContributorStatistics#numGrantsContributeToGte#25": 2
"GitcoinContributorStatistics#numGrantsContributeToGte#100": 2
"GitcoinContributorStatistics#totalContributionAmountGte#10": 1
"GitcoinContributorStatistics#totalContributionAmountGte#100": 2
"GitcoinContributorStatistics#totalContributionAmountGte#1000": 2
"GitcoinContributorStatistics#numRoundsContributedToGte#1": 1
"GitcoinContributorStatistics#numGr14ContributionsGte#1": 1
"GitcoinGranteeStatistics#numOwnedGrants#1": 1
"GitcoinGranteeStatistics#numGrantContributors#10": 1
"GitcoinGranteeStatistics#numGrantContributors#25": 1
"GitcoinGranteeStatistics#numGrantContributors#100": 2
"GitcoinGranteeStatistics#totalContributionAmount#100": 1
"GitcoinGranteeStatistics#totalContributionAmount#1000": 2
"GitcoinGranteeStatistics#totalContributionAmount#10000": 4
"GitcoinGranteeStatistics#numGrantsInEcoAndCauseRound#1": 1
"gtcPossessionsGte#100": 2
"gtcPossessionsGte#10": 1
"ethPossessionsGte#32": 1
"ethPossessionsGte#10": 1
"ethPossessionsGte#1": 1
"NFT": 1
"Lens": 1
"ZkSync": 1This module introduces the mining requirement the faucet is originally known for.
When enabled, session rewards are not dropped immediately. Instead, they must be collected by providing processing power for mining in the browser.
The module is highly configurable. The most important settings are:
-
powShareReward: The reward a session gets for each eligible hash. -
powSessionTimeout: The maximum time a user may mine on the faucet.
Keep in mind that abusive users might use devices they don't own for mining. Don't make this too long, and consider using captchas to enforce some user interaction. -
powDifficulty: The difficulty of the mining algorithm. See Eligible Hashes for more details. -
powHashrateSoftLimit/powHashrateHardLimit: Controls the maximum allowed mining hashrate. The soft limit is enforced client-side only and may be slightly inaccurate due to clock drifts. The hard limit is checked server-side to prevent abusive users from bypassing the client-side limitation. It should be slightly higher than the soft limit to avoid unwanted nonce rejections. -
powSessionsPerServer: Maximum number of mining sessions per server sub-process (0 = unlimited).
See Eligible Hashes and Hash Verification Process for more details about additional settings.
All other settings default to the values shown below and can be omitted to keep the config readable.
modules:
pow:
# enable / disable PoW protection
enabled: true
# reward amount per eligible hash (in wei)
powShareReward: 12500000000000000 # 0.0125
# maximum mining session time (in seconds)
powSessionTimeout: 18000 # 5h
# maximum number of seconds a session can idle (no connection) until it gets closed
powIdleTimeout: 1800 # 30min
# maximum allowed mining hashrate (will be throttled to this rate when faster)
powHashrateSoftLimit: 1000 # soft limit (enforced client-side)
powHashrateHardLimit: 1100 # hard limit (reject shares with too high nonces)
# number of 0-bits a hash needs to start with to be eligible for a reward
powDifficulty: 11
## All settings below can be omitted to keep the config readable.
## They default to the values shown here.
# penalty for not responding to a verification request (percent of powShareReward)
verifyMinerMissPenaltyPerc: 10 # 10% of powShareReward
# reward for responding to a verification request in time (percent of powShareReward)
verifyMinerRewardPerc: 15 # 15% of powShareReward
# websocket ping interval
#powPingInterval: 60
# kill websocket if no ping/pong for that number of seconds
#powPingTimeout: 120
# mining algorithm to use
powHashAlgo: "argon2" # scrypt / cryptonight / argon2 / nickminer
# scrypt mining parameters
powScryptParams:
# N - iterations count: affects memory and CPU usage, must be a power of 2
cpuAndMemory: 4096
# r - block size: affects memory and CPU usage
blockSize: 8
# p - parallelism factor: threads to run in parallel, affects the memory & CPU usage, should be 1 as webworker is single threaded
parallelization: 1
# klen - how many bytes to generate as output, e.g. 16 bytes (128 bits)
keyLength: 16
# cryptonight mining parameters
powCryptoNightParams:
# crypto night hash algo & variant
algo: 0
variant: 0
height: 0
# argon2 mining parameters
powArgon2Params:
type: 0
version: 13
timeCost: 4
memoryCost: 4096
parallelization: 1
keyLength: 16
# nickminer mining parameters (see
powNickMinerParams:
# signature hash of the transaction to mine
hash: "cb60ff3d42eea9d30a249c26a957310b57e5dfacd6c2f56b842f40682d676e8f"
sigR: "0539" # R value of the signature
sigV: 27 # V value of the signature
count: 100 # number of hashes to check per round
suffix: "beac02" # contract address suffix to search for (high prio)
prefix: "0000" # contract address prefix to search for (low prio)
# Proof of Work shares need to be verified to prevent malicious users from just sending in random numbers.
# As that can lead to a huge verification workload on the server, this faucet can redistribute shares back to other miners for verification.
# These randomly selected miners need to check the share and return its validity to the server within 10 seconds or they're penalized.
# If there's a mismatch in validity-result the share is checked again locally and miners returning a bad verification result are slashed.
# Bad shares always result in a slashing (termination of session and loss of all collected mining balance)
# percentage of shares validated locally (0 - 100)
verifyLocalPercent: 10
# max number of shares in local validation queue
verifyLocalMaxQueue: 100
# min number of mining sessions for verification redistribution
# only local verification if not enough active sessions (should be higher than verifyMinerIndividuals)
verifyMinerPeerCount: 4
# percentage of shares validated locally if there are not enough sessions for verification redistribution (0 - 100)
verifyLocalLowPeerPercent: 80
# percentage of shares to redistribute to miners for verification (0 - 100)
verifyMinerPercent: 75
# number of other mining sessions to redistribute a share to for verification
verifyMinerIndividuals: 2
# max number of pending verifications per miner before not sending any more verification requests
verifyMinerMaxPending: 5
# max number of missed verifications before not sending any more verification requests
verifyMinerMaxMissed: 10
# timeout for verification requests
# client gets penalized if not responding within this timespan
verifyMinerTimeout: 30Hashes eligible for a reward must start with a specific number of 0-bits. This requirement is called difficulty and is configured via the powDifficulty setting within the pow module.
The default is 11, which is reasonable for small testnets and test instances.
For higher activity (> 500 sessions), increasing the difficulty is strongly recommended to reduce traffic and server-side processing time.
A lower difficulty is more user-friendly because eligible hashes are found more frequently and the mining balance increases faster. However, a lower difficulty also increases traffic and server-side validation workload.
A difficulty of 11 means on average 1 of 2^11 hashes is eligible for a reward.
With an average hashrate of ~300 H/s, that leads to about 1 eligible hash every 7 seconds per mining session.
| Difficulty | 100 H/s | 300 H/s | 500 H/s | 800 H/s | 1000 H/s |
|---|---|---|---|---|---|
| 11 | 20,5 s | 6,8 s | 4,1 s | 2,6 s | 2 s |
| 12 | 41 s | 13,6 s | 8,2 s | 5,1 s | 4,1 s |
| 13 | 82 s | 27,3 s | 16,4 s | 10,2 s | 8,2 s |
To prevent malicious users from sending random hashes that don't meet the required criteria, all hashes must be verified. This can create an extremely high verification load on the server. To mitigate this, the faucet can redistribute the verification work to other randomly selected miners ("verifiers") who check whether a hash is valid.
The redistribution feature is optional but enabled by default. For security, it only activates when a minimum number of sessions are actively mining.
The security of verification redistribution relies on random selection of multiple verifiers. The miner does not know whether their result is redistributed to an honest or malicious verifier. There should always be at least 2 verifiers per hash. If the verifiers' results differ, the hash is re-checked locally.
Invalid hashes or incorrect verifications always result in immediate session termination and loss of all collected funds. Because of this, it is not critical if a group of malicious users manages to get a few invalid hashes through. If they repeatedly submit invalid hashes, one will eventually be redistributed to an honest verifier, the invalidity is detected, and the attacker loses all rewards.
The redistribution process is highly configurable via the verify* settings. The defaults should work fine for most setups.
The reward accumulated during mining is a combination of two portions:
- Reward for eligible hashes
- Reward for verifying hashes from other miners
The reward for eligible hashes is configured via the powShareReward option.
It is also the base value (100%) for all other reward and penalty options.
The reward for verifications is configured via verifyMinerRewardPerc and the penalty for not completing verifications via verifyMinerMissPenaltyPerc.
Both are percentage values based on powShareReward. For example, setting verifyMinerRewardPerc to 15 means 15% of powShareReward is awarded for completing a verification.
The verification reward primarily benefits slow miners who don't find many eligible hashes themselves. It shouldn't be set too high to avoid farming verification rewards without actually mining.
As described above, there should always be at least 2 verifiers per hash to prevent malicious verifier behavior. The number of verifiers is configured via verifyMinerIndividuals. Keep in mind that verifyMinerRewardPerc is paid to each verifier, so the total cost per eligible hash from an operator perspective is powShareReward + verifyMinerIndividuals * (powShareReward / 100 * verifyMinerRewardPerc).
The hashpower from the faucet can be used to vanity-generate keyless contract deployments as commonly used for EIPs.
The algorithm is called nickminer (derived from "Nick's method") and can be used like any other mining algorithm.
Some extra steps are needed to make the mining results usable for real on-chain deployments. Here are the steps to generate a keyless deployment with a signature mined from the faucet. You need Go installed on your system.
-
Install dependencies (nick tool)
go install github.com/pk910/nick@latest
-
Prepare Transaction:
nick build --initcode="0x60425000"Replace
0x60425000with the initcode of the contract you want to deploy.In the build command output, you'll find the signature hash:
... Sig Hash: 0x45fe116882ed0b5439bbfca93afa1373823d3186063fc9b1d1aa440ced7f97f7 # <- this one! TX Hash: 0xcb18738fbdf0c28b373466d9c72dd18f0c4ea60222484c6b9a54968a76cdea9b ... -
Configure faucet:
Now configure the faucet to mine addresses for this transaction.
You can specify a suffix which clients mine on, so it needs to be at least 3 bytes (6 hex characters) long.
In addition you can specify a prefix, which is used to further filter relevant hashes on server side.
You also need to specify a filename where relevant matches are written to.## Proof of Work (mining) protection pow: # enable / disable PoW protection enabled: true # number of matching bits the hash needs to include from start or end to be eligible for a reward powDifficulty: 19 # note: with nickminer this needs to be about 7-8 bits higher than for the other mining algorithms # mining algorithm to use powHashAlgo: "nickminer" # scrypt / cryptonight / argon2 / nickminer powNickMinerParams: hash: "45fe116882ed0b5439bbfca93afa1373823d3186063fc9b1d1aa440ced7f97f7" # sig hash from nick tool without 0x prefix sigR: "0539" # sig-r value, default as used by nick tool sigV: 27 # sig-v value, leave as 27! count: 100 # number of hashes to check in a single loop suffix: "beac02" # suffix bytes to search for (needs to be at least 2 bytes) prefix: "0000" # prefix bytes to search for (in addition to the suffix) relevantDifficulty: 24 # number of matching bits from where results are logged to the relevantFile relevantFile: "./nickminer.log" # filename to write relevant hashes to
-
Run the faucet
Run the faucet and wait for relevant results.
The faucet will output a file like this and append new matches as found:0xf5003e69642af091decca15ef431589e11beac02 (d: 24): hash: 0xcb60ff3d42eea9d30a249c26a957310b57e5dfacd6c2f56b842f40682d676e8f, sigR: 0x0539, sigS: 0x075a71494c945f471480588a03b724b175004a 0xcc6fc16c4da4530fe3ec9b9afe9d605119beac02 (d: 24): hash: 0xcb60ff3d42eea9d30a249c26a957310b57e5dfacd6c2f56b842f40682d676e8f, sigR: 0x0539, sigS: 0x0c53ce494c945f471480588a03b724b1750017 0x9f17d858a5e7e05dc324906f90ec2fcdb9beac02 (d: 24): hash: 0xcb60ff3d42eea9d30a249c26a957310b57e5dfacd6c2f56b842f40682d676e8f, sigR: 0x0539, sigS: 0x0282df0e7d57b72642348f062eec81b29f003dIn the first column you can see the found contract addresses with matching suffix.
Thed:column shows the number of matching bits, starting from the right of the suffix and continuing from the left of prefix.
ThesigR&sigSvalues are the relevant information to reconstruct the full transaction to deploy the contract to the address in the first column. -
Build the final transaction for a mined result
nick build --initcode="0x60425000" --sig-r "0x0539" --sig-s "0x0282df0e7d57b72642348f062eec81b29f003d"Replace sig-r & sig-s with the values returned from the faucet.
The tool now returns the final transaction in various formats and prints the deployer & contract address.
You can now simply go ahead funding the deployer and deploying the contract to the mined address.
This module defines restrictions for recurring users.
Restrictions are based on IP and/or ETH wallet address. Multiple restrictions can be defined for different amounts and durations.
Keep in mind that session data is removed from the database after the time defined by the global sessionCleanup setting. Restrictions can only aggregate data within that time frame.
Each limit entry supports the following options:
-
limitCount: Maximum number of sessions allowed within the duration. -
limitAmount: Maximum total drop amount (in wei) allowed within the duration. -
duration: Time window in seconds to aggregate data from. -
byAddrOnly: Only check by target address (ignore IP). -
byIPOnly: Only check by IP address (ignore target address). -
ip4Subnet: Subnet mask for IPv4 grouping (e.g. 24 for /24 subnets). -
action: Action to take when the limit is exceeded. -
message: Custom error message shown when the limit is exceeded. -
rewards: Reward percentage when this limit is exceeded (instead of blocking).
modules:
recurring-limits:
# enable / disable recurring limits protection
enabled: true
limits: # array of individual limits, which all need to pass
- limitCount: 10 # limit number of sessions to 10
duration: 3600 # aggregate data from last 1 hour
- limitCount: 100 # limit number of sessions to 100
limitAmount: 10000000000000000000 # limit total drop amount to 10 ETH
duration: 86400 # aggregate data from last 1 day
- limitAmount: 20000000000000000000 # 20 ETH
duration: 172800 # 2 days
byAddrOnly: trueThis module allows whitelisting specific IPs via regex patterns. Whitelisted sessions can receive modified reward factors and optionally skip other protection modules.
Patterns can be defined inline or loaded from an external YAML file that is refreshed periodically.
modules:
whitelist:
enabled: true
# inline whitelist patterns (matched against remote IP)
whitelistPattern:
"^10\\.0\\.0\\.": # match IPs starting with 10.0.0.
reward: 200 # double reward (200%)
skipModules: ["pow", "recurring-limits"] # skip these modules
# load patterns from external YAML file
whitelistFile:
yaml: "./whitelist.yaml" # path to YAML file (can be a list of paths)
refresh: 30 # reload interval in secondsThe YAML file format:
restrictions:
- pattern: "^192\\.168\\."
reward: 150
skipModules: ["pow"]
message: "Internal user"
notify: trueThis module requires a valid voucher code to start a session. Voucher codes are single-use and can optionally override the drop amount.
Voucher codes can be mass-generated using the built-in CLI tool:
node bundle/powfaucet.cjs create-voucher --count 10 --amount 1ETH --prefix TEST
This prints the generated codes to stdout, one per line. The codes are stored in the faucet database.
Each voucher code can only be used once. If the session associated with a voucher fails, the voucher can be reused.
modules:
voucher:
enabled: true
voucherLabel: "Enter your voucher code" # label shown in the UI
infoHtml: "<p>Enter the voucher code you received.</p>" # optional info HTMLThis module adds authentication via Zupass zero-knowledge proofs, typically used for event-based access (e.g. conference attendees).
Authenticated users can receive special grants that override normal restrictions, such as skipping certain modules or receiving boosted rewards.
modules:
zupass:
enabled: true
zupassUrl: "https://zupass.org/"
zupassApiUrl: "https://api.zupass.org/"
redirectUrl: "https://your-faucet.example.com/"
requireLogin: false # require zupass login for all sessions
concurrencyLimit: 2 # max concurrent sessions per zupass identity
# event configuration
event:
name: "My Event"
eventIds:
- "event-uuid-here"
# proof verification
verify:
signer: ["public-key-hex"]
#productId: ["product-uuid"]
#eventId: ["event-uuid"]
# grants for authenticated users
grants:
- limitAmount: 100000000000000000000 # 100 ETH
duration: 86400
skipModules: ["recurring-limits"]
rewardFactor: 2
- limitCount: 1
duration: 86400
rewardFactor: 2
skipModules: ["recurring-limits", "pow", "faucet-outflow", "faucet-balance", "ipinfo"]
# UI customization
loginLogo: "/images/zupass_logo.jpg"
loginLabel: "Event attendee? Login with your ticket."
userLabel: "Authenticated with event ticket."
infoHtml: |
Benefits of authenticating with your ticket:<br>
<ul>
<li>Request funds without mining (once a day)</li>
<li>Skip IP-based limitations</li>
<li>Mine with boosted rewards</li>
</ul>The faucet can display status messages to users on the frontend. Status messages can come from internal events (e.g. low balance warnings) or from external files.
faucetStatus:
json: "faucet-status.json" # path to JSON status file
yaml: "faucet-status.yaml" # path to YAML status file
refresh: 10 # refresh interval in seconds (default: 10)The status file can contain a single message or an array of messages:
# single message
- level: "warn"
prio: 10
text: "The faucet is under maintenance."
# with filters (only show to specific users)
- level: "info"
prio: 5
text: "Mining at {hashrate} total hashrate."
filter:
session: true # only show to users with active sessions
country: "US" # only show to users from specific countries
hosting: true # only show to hosting IPs
proxy: true # only show to proxy IPs
lt_version: "2.0.0" # only show to clients older than this version
gt_hashrate: 1000 # only show when total hashrate exceeds thisThe {hashrate} placeholder is replaced with the current total hashrate.
There are two supported ways to manage funds in the faucet wallet.
You can keep all funds in the faucet wallet directly, which works fine for most setups. Or you can use the more complex but more secure automatic wallet refill function.
The automatic wallet refill feature allows the faucet to operate with a low-balance hot wallet that is refilled automatically when it runs low. A smart contract protects the majority of funds and limits the amount available to the hot wallet.
The idea is that even if the faucet server is compromised, the attacker can only steal funds on the hot wallet. The funds in the vault contract remain safe because the hot wallet is limited to a specific withdrawal amount per interval.
A custom Vault Contract can be used to secure the funds.
The contract limits the withdrawable balance to a specific amount per interval, configurable by the owner via the setAllowance(address addr, uint256 amount, uint256 interval) function.
The refill contract configuration also supports an overflow mechanism: when the hot wallet balance exceeds overflowBalance, excess funds are deposited back into the vault contract via the depositFn.
Function argument placeholders:
-
{walletAddr}- the faucet wallet address -
{amount}- the withdrawal/deposit amount -
{token}- the token contract address (for ERC20 mode)
ethRefillContract:
contract: "0xA5058fbcD09425e922E3E9e78D569aB84EdB88Eb" # vault contract address
abi: '[...]' # vault contract ABI (JSON string)
allowanceFn: "getAllowance" # function to check withdrawable amount
allowanceFnArgs: ["{walletAddr}"] # arguments for allowance call
withdrawFn: "withdraw" # function to call for withdrawals
withdrawFnArgs: ["{amount}"] # arguments for withdraw call
depositFn: "deposit" # function to call for overflow deposits (optional)
depositFnArgs: [] # arguments for deposit call
withdrawGasLimit: 300000 # gas limit for withdraw/deposit transactions
checkContractBalance: true # check contract balance before withdrawing
contractDustBalance: 1000000000000000000 # keep 1 ETH in the contract
triggerBalance: 1100000000000000000000 # refill when hot wallet balance falls below 1100 ETH
overflowBalance: 2000000000000000000000 # deposit back to vault when balance exceeds 2000 ETH (optional)
cooldownTime: 5430 # minimum time between refill calls: 1.5h + 30sec
requestAmount: 125000000000000000000 # amount per withdrawal: 125 ETH