-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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 >= 14
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 & extract powfaucet-server-all.zip
from the latest release and run run-faucet.bat
.
After doing the above commands, you should be able to access the faucet via http://localhost:8080
The faucet server process needs to run all the time, so I suggest running it in a screen
session or systemd
daemon.
When the faucet is started the first time, it creates a copy of the example configuration at faucet-config.yaml
. Some settings are individualized by random values for your security (faucetSecret
, ethWalletKey
).
Read through the configuration file to customize the faucet for your needs.
The configuration is also described more detailed in this wiki.
There are also distribution specific all-in-one executables available for windows and linux. These executables are basically standalone nodejs binaries with the faucet scripts and the static folder baked in. There is no local NodeJS installation required to run these executables.
This works fine for testing environments, but I recommend using a locally installed NodeJs for better performance.
To build the faucet from source, you need a machine with git & NodeJS >= 20
installed. The OS doesn't really matter. I'm testing on debian & windows, but I'm pretty sure others work too.
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 doing the above commands, you should be able to access the faucet via http://localhost:8080
The faucet server process needs to run all the time, so I suggest running it in a screen
session or systemd
daemon.
When the faucet is started the first time, it creates a copy of the example configuration at faucet-config.yaml
. Some settings are individualized by random values for your security (faucetSecret
, ethWalletKey
).
Read through the configuration file to customize the faucet for your needs.
The configuration is also described more detailed in this wiki.
I'm maintaining a docker image for this faucet: pk910/powfaucet
There are various images available:
-
v2-stable
: The latest v2.x.x release -
v2-latest
: That latestmaster
branch version (automatically built) -
v2.x.x
: Version specific images for all v2.x.x releases.
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 -u $UID:$GID -v $(pwd):/config pk910/powfaucet:v2-stable --datadir=/config --create-config
this shoould create a copy of the example config called
faucet-config.yaml
-
Edit
faucet-config.yaml
and prepend /config/ todatabase.file
,faucetLogFile
&faucetPidFile
(ensure they're persisted outside of the container and not lost on updates)nano faucet-config.yaml
database:
driver: "sqlite"
file: "/config/faucet-store.db"
faucetLogFile: "/config/faucet-events.log"
faucetPidFile: "/config/faucet-pid.txt"Also change
ethRpcHost
to use a more reliably 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):/config -p 8080:8080 -it pk910/powfaucet:v2-stable --datadir=/config
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
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.
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: /pow) 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
Since v2.2.3
the powfaucet UI can be embedded as a independent component to an existing website. This allows a wide range of customization as the faucet can be styled for custom needs.
You can find a example implementation that loads the demo1-instance as a component to a jsfiddle site here: 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>
I recommend to always load the faucet script & style from the faucet process instead of including these js/css files in your site to avoid incompatible versions after faucet upgrades.
For security, the faucet needs to explicitly allow embedding from certain origins via the configurable cors policies:
# allow external sites embedding this faucet
corsAllowOrigin:
- "https://fiddle.jshell.net"
Most configuration settings can be changed without needing to restart the faucet process.
This is helpful because restarting the process leads to a a high number of session re-connects and might even result in a loss of rewards when loosing connectivity in time-critical situations (eg. a few secs before session timeout).
To reload the configuration without restarting, you need to send a SIGUSR1
signal to the faucet process.
You can use the following command in combination with the faucetPidFile
configuration to reload the faucet 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 SIGURS1 signal - reloading faucet config
However, all session progress is restored from the database when the server restarts. Nothing is lost, so it's not super critical to restart the faucet process when you need to do it.
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 allows distributing standard ERC20 tokens instead of native network tokens.
To enable this functionality, you need to configure the following settings:
-
faucetCoinType
: Set this to 'erc20' -
faucetCoinContract
: Set this 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 requested via decimals
/ balanceOf
.
Be aware that most amount-related configuration options of the faucet are based on uint values ("wei") and are set to work with the native token and 18 decimal places.
For custom tokens with a different number of decimals, you probably need to change these settings to reflect the intended values for your token.
The default drop amount can be configured via the maxDropAmount
& minDropAmount
settings:
# min/max drop amount (max is the default if no module lowers it)
maxDropAmount: 1000000000000000000 # 1 ETH
minDropAmount: 10000000000000000 # 0.01 ETH
By 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 V2 version of this faucet has a modular protection system.
Even if the name of this project is still "PoW"-Faucet, the PoW protection is a optional part of the faucet just like other modules.
There are various modules available, that can be enabled independendly to fit your protection needs.
Add captcha protection to fornt 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: false
Limits 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 immediatly if no module introduces a long running task (eg. 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 (sessions in 'running' state at the same time for the same 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 address
This 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: false
This 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: true
This 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 gets applied
# limits the rewards linearly according to the faucet balance (100% >= targetBalance, 0% <= spareFundsAmount)
dynamicRestriction:
targetBalance: 1100000000000000000000 # no restriction with faucet balance > 1100 ETH
This 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 will ensure that the average mining reward will not exceed the specified limit over long term. In short term the reward for one day might exceed the limit by the specifies boundaries, but the algorithm will then be more restrictive on the next day (negative outflow balance), so in average it will meet the limit again.
The status of the faucet-outflow
module is displayed on the faucet status page, so the faucet operator can keep track of the internal outflow balance and the applied reward reduction.
modules:
faucet-outflow:
# enable / disable faucet outflow protection
enabled: true
# limit outflow to 1000ETH per day
amount: 100000000000 # 1000 ETH
duration: 86400 # 1 day
# outflow balance limits
lowerLimit: -50000000000 # -500 ETH (when outflow balance gets smaller or equal to this, the reward will get closer/equal to 0%)
upperLimit: 50000000000 # 500 ETH (used as buffer from less-active periods for activity spikes)
This module adds the ability to authenticate with a github account before session start.
Besides of strict barriers, the authentication can also be made optional to apply reward factors based on github profile stats.
The github module needs a github api key & secret to handle the oauth authentication flow.
To register a github application, head over to the Github Developer Settings and register a "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 within the github app settings matches to subdomains as well.
So for all my faucets running under xy-faucet.pk910.de, I can use the same github app with the callback url set to https://pk910.de/api/githubCallback
. That URL doesn't need to work, its just used as a pattern. When the authentication workflow is triggered from a valid subdomain, the oauth flow will automatically redirect to the correct subdomain on completion.
Also see github documentation on this.
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
# github account checks
checks:
- required: true # easiest cheack - 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 day
This module fetches IP related information form an external API and allows defining restrictions based on that.
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, you need to ensure the request/response formats match the format provided by ip-api.com.
The information gathered and used for the following optional restrictions look 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 that information, several optional restrictions can be applied:
- Basic property restrictions
This was the first per-session restriction mechanism and easily restricts the rewards based on country or Proxy/Hosting setting.\ - Regex-based restrictions This allows restricting the reward via regular expressions for any of the gathered information.
- Dynamically loaded regex-based restrictions
This is quite similar to the Regex-based mechanism, but loads the restrictions from another file. The contents from that file gets refreshed periodically, so it can be updated dynamically via an external process.
I'm heavily using this to add additional "proprietary" protections like a abusive use from IP-Range detection and stuff on my goerli / sepolia instances. These parts are closed source because sharing their code and detection rules would prevent them from being effective.
The format of that 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 3 restriction classes can be configured individually and can be left out entirely if not needed. All configured restriction classes are processed in order of the list 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)
ipInfoCacheTime: 86400 # 1 day
# require valid ip info (throw error and fail session if lookup failed)
ipInfoRequired: false
# Restriction Classes
# Basic property restrictions
ipRestrictedRewardShare:
# 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
ipInfoMatchRestrictedReward:
"^.*Tencent Cloud.*$": 1 # 1% if info matches pattern
"^.*UCLOUD.*$": 1
"^.*Server Hosting.*$": 1
"^.*SCloud.*$": 1
# Dynamically loaded regex-based restrictions
restrictionsFile:
yaml: "./restrictions.yaml"
refresh: 30 # reload file every 30 sec
This module checks the target wallet on mainnet whenever a session is started.
There are several restrictions that can be enforced:
-
minBalance
: Minimum amount of funds the user needs to hold in his mainnet wallet to be allowed to use the faucet. -
minTxCount
: Minimum number of transactions the user needs to have sent from his mainnet wallet to be allowed to use the faucet. -
minErc20Balances
: List of minimal ERC20 token balances the user needs to hold in his mainnet wallet. (sincev2.2.4
) Each entry includes the token contract address (address
), the number of decimals (decimals
), thename
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 minimal erc20 token balances on mainnet wallet
minErc20Balances:
- name: "WETH"
address: "0x94373a4919B3240D86eA41593D5eBa789FEF3848" # WETH9 on holesky
decimals: 18
minBalance: 10000000000000000 # 0.01 WETH
This module fetches the gitcoin passport for a target address when a session is started.
The gitcoin passport gets scored by the configured score matrix. Each stamp can be given a individual score value. Afterwards, the score value is used to select a reward factor that then gets applied to all session rewards.
The module can be used to increase the rewards for more legitimate users in mining scenarios, or lower the rewards for less legitimate users in other scenarios.
You need to gather an API Key from the Gitcoin Passport Scorer Dashboard to load passports automatically.
To allow optional manual submission & verification of the passport JSON, you need to specify the public key of the stamp signer for verification.
For gitcoin passport, that's currently did:key:z6MkghvGHLobLEdj1bgRLhS4LPGJAvbMA1tn2zcRyqmYU5LC
modules:
passport:
enabled: true
scorerApiKey: "" # Gitcoin Passport Scorer API Key
trustedIssuers:
- "did:key:z6MkghvGHLobLEdj1bgRLhS4LPGJAvbMA1tn2zcRyqmYU5LC" # Gitcoin Passport
passportCachePath: "passport-cache"
refreshCooldown: 300 # allow refreshing every 5mins
cacheTime: 86400 # cache passports for 1 day (does not affect manual refreshing)
stampDeduplicationTime: 604800 # allow reusing a stamp in another passport after 7 days
# 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": 1
This module introduces the mining requirement, this faucet is originally known for.
When the module is enabled, the session rewards are not dropped immediately. Instead, they need to be collected by providing some processing power for mining in the browser.
The module is highly configurable to control all aspects of the mining session.
The most important settings are the following:
-
powShareReward
: The reward a session gets for sending in a eligible hash -
powSessionTimeout
: The maximum time a user may mine on the faucet.
Keep in mind that abusive users might use un-owned devices for mining. So don't make this too long and eg. use captchas to enforce some user interaction. -
powDifficulty
: This is the difficulty of the mining algorithm. See Eligable Hashes for more details. -
powHashrateSoftLimit
/powHashrateHardLimit
This controls the max allowed hashrate that can be used to mine on the faucet. The soft limit is enforced client side only, and might be slightly inaccurate with clock drifts. To prevent abusive users from working around that client side limitation, the hard limit is checked on server side. It should be slightly higher than the soft limit to avoid unwanted nonce rejections due to client side inaccuracies.
Take a look into Eligible Hashes & Hash Verification Process for more details about the other configuration settings.
All other settings default to the values specified in the configuration summary below and can be left out entirely to make the config more 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 to server) 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 left out to keep the config more readable.
## these settings default to the values specified below.
# penalty for not responding to a verification request (percent of powShareReward)
# shouldn't be too high as this can happen regularly in case of connection loss or so
verifyMinerMissPenaltyPerc: 10 # 10% of powShareReward
# reward for responding to a verification request in time (percent of powShareReward)
# some extra reward for slow miners
# comment out to disable rewards for verification requests
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: 30
Hashes that are eligible for a reward need to start with a specific number of 0
-bits. This requirement is called difficulty and can be configured via the powDifficulty
setting within the pow
module.
It is set to 11
by default, which seems reasonable for small testnets and test instances.
For higher activity (> 500 sessions) I strongly suggest increasing the difficulty to reduce traffic and server side processing time.
A lower difficulty is generally more user-friendly, because more hashes are found and the mining balance increases more frequently. But the lower the difficulty is, the higher the traffic & processing and validation work-load on server side will be.
A difficulty of 11
means, that on average 1 of 2^11 hashes are eligible for a reward.
With a average hashrate of ~300H/s that would lead to about 1 eligible hash every 7 secs for every 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 avoid malicious users from sending in random hashes that do not meet the specified criteria, all hashes need to be verified somehow. This can lead to a extremely high verification-load on the faucet server, which is not really helpful. To avoid that, the faucet is able to redistribute the verification-work for these hashes back to other randomly selected miners. These randomly selected miners ("verifiers") receive the hash and do the computation work to verify the hash is valid.
The redistribution feature is optional, but enabled by default. For security, it will only be active in when a specified minimum number of sessions is actively mining.
The security of the validation redistribution relies on the random selection of multiple verifiers. So the miner does not know if his result gets redistributed to a honest verifier or a malicious one. There should always be 2 verifiers for each hash. If the validity-results returned from the selected verifiers differ, the hash is checked again locally.
Invalid hashes or incorrect verifications always lead to a immediate session termination and loss of all collected funds. Due to that, it's not super critical if a group of malicious users manages to get a few invalid hashes validated. If they repeatedly send in invalid hashes, a invalid hash will be redistributed to a honest validator at some time. The attacker does not know where his hashes get redistributed to, but when an invalid hash reaches a honest verifier, the invalidity is detected and the attacker looses all his rewards.
The redistribution process is highly configurable via the verify*
settings. I suggest looking through these options as they're well described in the example config. However, the configured defaults should just work fine :)
The rewards that accumulates during mining is a combination of two reward portions:
- Reward for eligible hashes
- Reward for verifying hashes from other miners
The reward for eligible hashes can be configured via the powShareReward
option.
It is also the base value (100%) for all other reward / penalty options.
The reward for verifications can be configured via the verifyMinerRewardPerc
option and penalties for not doing those verifications via the verifyMinerMissPenaltyPerc
option.
Both settings are percental values based on powShareReward
. So setting verifyMinerRewardPerc
to eg. 15
means 15% of powShareReward
are rewarded for doing a verification.
The verification reward mostly benefits slow miners that don't find many eligible hashes themselves. But it shouldn't be too high to avoid farming these rewards without actually mining. I'm using a reward of 10% and a penalty of 15% on goerli & sepolia, which works well for a long time now :)
As described above, there should always be at least 2 verifiers for each hash to avoid malicious verifier behavior. The number of verifiers can be configured via the verifyMinerIndividuals
setting. Keep in mind that the reward specified by verifyMinerRewardPerc
will be paid to each verifier, so the total reward for every eligible hash from a operator perspective is powShareReward + verifyMinerIndividuals * (powShareReward / 100 * verifyMinerRewardPerc)
Since v2.3.0
the hashpower in the faucet can be used to vantygen keyless contract deployments as commonly used for EIPs.
The algorithm is called nickminer
(derived from "Nicks method") and can be used like any other mining algorithm.
There are however some extra steps needed to make the mining results usable for real onchain deployments. Here are the steps needed to generate a keyless deployment with a signature mined from the faucet. You'll need golang installed on your system.
-
Install dependencies (nick tool)
go install github.com/pk910/nick@latest
-
Prepare Transaction:
nick build --initcode="0x60425000"
Replace
0x60425000
with 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: 0x0282df0e7d57b72642348f062eec81b29f003d
In 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
&sigS
values 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 allows defining restrictions for recurring users.
These restrictions are based on IP and/or ETH wallet address. There can be multiple restrictions defined for different amounts / durations.
Keep in mind that session data gets removed from the database after the time defined by the global sessionCleanup
setting. The restrictions specified in this module can only aggregate the numbers from that time frame.
modules:
recurring-limits:
# enable / disable recurring limits protection
enabled: true
limits: # array of individual limits, which all need to be passed
- 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 # 5 days
byAddrOnly: true
There are two supported ways to manage funds in the faucet wallet.
You can keep it all in the faucet wallet, which should theoretically work fine. 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 wallet that can be refilled automatically when it gets empty. A smart contract is used to protect the majority of funds and limit the funds available to the hot wallet to a specific amount.
The general idea is, that even when the faucet server gets hacked or there is a critical bug somewhere in the faucet code, the attacker can only steal the funds on the hot wallet. The funds in the vault contract are still safe in that situation because the hot wallet is limited to a specific amount.
For my sepolia / goerli instances, I'm using a custom Vault Contract to secure the funds.
The contract basically just limits the withdrawable balance to a specific amount per interval, which can be configured by the owner via the setAllowance(address addr, uint256 amount, uint256 interval)
function.
The faucet can be configured to request funds from that contract automatically via these settings:
ethRefillContract:
contract: "0xA5058fbcD09425e922E3E9e78D569aB84EdB88Eb" # contract address
abi: '[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getAllowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]'
allowanceFn: "getAllowance" # function to get the withdrawable amount
allowanceFnArgs: [] # function args for getAllowance callwithdrawFnArgs
withdrawFn: "withdraw" # function to call for withdrawals
allowanceFnArgs: ["{amount}"] # function args for withdraw call
withdrawGasLimit: 300000 # gas limit for calls
checkContractBalance: true # check contract balance to avoid withdrawing more than the balance
contractDustBalance: 1000000000000000000 # keep 1 ETH in the contract
triggerBalance: 1100000000000000000000 # trigger withdrawal when hot wallet balance falls below 1100 ETH
cooldownTime: 5430 # minimum time between withdrawal calls: 1.5h + 30sec
requestAmount: 125000000000000000000 # amount to withdraw with each withdraw call: 125 ETH