A CLI tool to interact with Monad's staking contract and execute operations by interacting with it.
- Features
- Prerequisites
- Installation
- Configuration
- Validator Onboarding
- Usage
- TUI Mode
- Troubleshooting
- Example Workflow
- Adding/Registering a Validator
- Delegating stake to a Validator
- Undelegating stake from a Validator
- Withdrawing pending undelegations using withdrawal IDs
- Claiming rewards
- Compounding rewards
- Changing Validator commission
- Querying staking state on the chain
- Python 3.8 or higher
- Git
- Access to a Monad RPC endpoint
- Private key with sufficient funds for transactions
git clone https://github.com/monad-developers/staking-sdk-cli.git
cd staking-sdk-cli
python -m venv cli-venv
source cli-venv/bin/activate # On Windows: cli-venv\Scripts\activate
pip install .
Note: To make changes to SDK code and have it reflect, use the
-e
flag withpip
.
Make sure you are in a virtual environment with all dependencies installed as given in steps above to make a build successfully.
pip install pyinstaller
- Make sure you are in the correct directory
cd staking-cli
- Run the pyinstaller command to create a spec
pyinstaller --additional-hooks-dir=./hooks --name monad-staking-cli --onedir --clean --noconfirm main.py
- Create an executable
pyinstaller --noconfirm monad-staking-cli.spec
The distribution will be available at dist/monad-staking-cli/monad-staking-cli
. The folder dist/monad-staking-cli
can be archived and distributed.
cd dist/monad-staking-cli/
./monad-staking-cli --help
Create a file named config.toml
in your preferred location (e.g., home directory or project root):
Note: Default config path is
./config.toml
. Create it in the same directory asmain.py
or an executable to avoid passing the config flags for every command.
# Main application settings
title = "staking_cli"
rpc_url = "https://your-monad-rpc-endpoint.com"
chain_id = 30143
contract_address = "0x0000000000000000000000000000000000001000"
# Log levels: debug, info, warning, error
log_level = "info"
[staking]
# IMPORTANT: Replace with your actual private key (without 0x prefix)
funded_address_private_key = "0xYOUR_PRIVATE_KEY_HERE"
[colors]
border = "white"
main = "red"
primary_text = "cyan"
secondary_text = "green"
highlight = "yellow"
- Never commit your private key to version control
- Keep your config.toml file secure and private
- Use a funded address with sufficient balance for gas fees
⚠️ Usemonad-keystore
to extract private keys and verify the derived keys with their respective public keys!⚠️
🛑 CAUTION: don't commit your
config.toml
file accidently! 🛑
- Setup staking cli according to the installation instructions.
- Create a
config.toml
file for the staking cli. Refer toconfig.toml
example. - Get a Funded Address and populate the
config.toml
with its private key. - Make sure you have enough tokens in your wallet - minimum stake: 1,000,000 MON to register and sufficient gas to execute the transactions.
- Choose between cli or tui mode and execute the
add-validator
workflow as described below. - Follow the debug and troubleshooting steps below in case of unexpected behaviour or general issues.
⚠️ Due to changes in keystore versions over upgrades make sure you follow the method below to get your private keys! DO NOT USE ANY KEYS IN BACKUP FILES
- Extract your SECP and BLS keys from keystores using the
monad-keystore
binary (v0.11.2). Use the keystore files at/home/monad/monad-bft/config/id-{bls,secp}
.
source /home/monad/.env
monad-keystore recover --password "$KEYSTORE_PASSWORD" --keystore-path /home/monad/monad-bft/config/id-secp --key-type secp
source /home/monad/.env
monad-keystore recover --password "$KEYSTORE_PASSWORD" --keystore-path /home/monad/monad-bft/config/id-bls --key-type bls
- Use the command below and fill in the values carefully before executing the command, if the keys are wrong the funds will be deducted!
python main.py add-validator \
--secp-privkey "{{ SECP privkey from id-secp }}" \
--bls-privkey "{{ BLS privkey from id-bls }}" \
--auth-address "{{ auth-address (YOU MUST CONTROL THIS ADDRESS) }}" \
--amount 1_000_000 \
--config-path ~/config.toml
Authorized address is the address that will have control over validator operations on-chain. This address can be different from the funded address. Make sure you have control over the authorized address which you provide.
⚠️ Make sure you verify keys before entering yes in the prompt
[13:49:49] INFO SECP Pubkey: 03bbf692002bda53050f22289d4da8fe0bec8b81a6b0d4f641760....
INFO BLS Pubkey: 985d3f7052ac5ad586592ba1a240b0260b5351a9c3973a471fff79....
Do the derived public keys match? (make sure that the private keys were recovered using monad-keystore) [y/n] (n): y
[13:49:52] INFO Tx status: 1
INFO Tx hash: e11114c8e6dd1dc5e0cde400ce5014dab257....
Transaction status: 0 means the transaction failed, check debug steps to get the trace.
- Run the staking cli in tui-mode:
python main.py tui --config-path /path/to/config.toml
- In the
Main Menu
choose option 1 to Add Validator
- Fill in the prompts with the appropriate values
- Verify all the values before executing the transaction
-
Make sure the transaction exited with status 1
-
Make sure you got a validator-id after command runs
INFO Validator Created! ID: 1, Delegator: 0xF88.....
- Make sure you are part of the execution set (Only after 50 million stake has been given)
python main.py query validator-set --type execution --config-path ~/config.toml | grep {{ SECP PUBKEY }}
- Check validator information. After running above command you will get the validator-id of your validator
19: 03bbf6...
19 is the validator id in this case, it can be used to perform other operations like delegate, undelegate, claim, compound etc.
- Fetch validator info
python main.py query validator --validator-id 1 --config-path ~/config.toml
- Verify all values match in the output
- Check transaction status, if 0 the transaction has failed
- For a failed transaction you need to obtain the trace by:
- Getting the tx data:
curl --location 'https://your-monad-rpc-url' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc":"2.0",
"method":"eth_getTransactionByHash",
"params":[
"0xe57ada....{enter the hash of the failed tx}..."
],
"id":1
}'
- Use the data from the above tx to make an
eth_call
tx:
curl --location 'https://your-monad-rpc-url' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"from": "0xf88c... {{ FILL FROM TX DATA }}",
"to": "0x0000000000000000000000000000000000001000 {{ FILL FROM TX DATA }}",
"gas": "0xc350",
"gasPrice": "0xbdfd63e00",
"value": "{{ FILL FROM TX DATA }}",
"data": "0xf145204c0000000000 {{ FILL FROM TX DATA }}.... "
}, "latest"],
"id":1
}'
The above would give you the trace response like:
{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "insufficient balance"
},
"id": 1
}
- In the case you can't debug from the trace, post it on discord or open a ticket.
cd staking-cli
Register a new validator on the network.
Requirements:
- Minimum stake to join register validator: 1,000,000 MON
- Valid SECP256k1 private key (64 hex chars, no 0x prefix)
- Valid BLS private key (64 hex chars, with 0x prefix)
- Make sure the
auth-address
is an address you control and intend to perform validator operations with. This can be the same as the funded address. You can provide another address here to decouple staking and operations.
python main.py add-validator \
--secp-privkey "{{ VALIDATOR PRIVATE SECP KEY }}" \
--bls-privkey "{{ VALIDATOR PRIVATE BLS KEY }}" \
--auth-address "{{ AN ADDRESS THAT YOU CONTROL }}" \
--amount 1000000 \
--config-path ~/config.toml
Expected Output:
INFO SECP Pubkey: 02a1b2c3d4e5f6789...
INFO BLS Pubkey: b1a2b3c4d5e6f789...
INFO Tx status: 1
INFO Tx hash: 0x1234567890abcdef...
Delegate MON tokens to a validator.
python main.py delegate \
--validator-id 1 \
--amount 1000 \
--config-path ~/config.toml
Expected Output:
INFO Tx status: 1
INFO Tx hash: 0xabcdef1234567890...
Create a withdrawal request to undelegate tokens.
python main.py undelegate \
--validator-id 1 \
--withdrawal-id 0 \
--amount 500 \
--config-path ~/config.toml
Withdraw tokens from a completed undelegation request.
python main.py withdraw \
--validator-id 1 \
--withdrawal-id 0 \
--config-path ~/config.toml
Note: Withdrawals can only be processed after the required waiting period (typically 2 epochs).
Claim accumulated staking rewards.
python main.py claim-rewards \
--validator-id 1 \
--config-path ~/config.toml
Automatically restake rewards as additional delegation.
python main.py compound-rewards \
--validator-id 1 \
--config-path ~/config.toml
Update the commission for a Validator. Commission is specified as percentage (0.0 to 100.0).
python main.py change-commission \
--validator-id 1 \
--commission 5.0 \
--config-path ~/config.toml
Expected Output:
INFO Validator ID: 1
INFO Current commission: 10.0%
INFO New commission: 5.0%
INFO Tx status: 1
INFO Tx hash: 0xabcdef1234567890...
INFO Commission successfully changed from 10.0% to 5.0% for validator 1
Note: Only the Validator's authorized address can change the commission.
python main.py query validator --validator-id 1 --config-path ~/config.toml
python main.py query delegator \
--validator-id 1 \
--delegator-address 0x742d35C... \
--config-path ~/config.toml
python main.py query withdrawal-request \
--validator-id 1 \
--delegator-address 0x742d35C... \
--withdrawal-id 0 \
--config-path ~/config.toml
# Options: consensus, execution, snapshot
python main.py query validator-set --type consensus --config-path ~/config.toml
python main.py query delegators --validator-id 1 --config-path ~/config.toml
python main.py query delegations \
--delegator-address 0x742d35C... \
--config-path ~/config.toml
python main.py query epoch --config-path ~/config.toml
python main.py add-validator --help
python main.py delegate --help
# etc.
Interactive Terminal User Interface mode for easier navigation.
The TUI provides a menu-driven interface with options:
- Add Validator
- Delegate
- Undelegate
- Withdraw
- Claim Rewards
- Compound
- Change Commission
- Query
- Exit
python main.py tui --config-path ~/config.toml
- All amounts are specified in MON units (not wei)
- Example:
--amount 1000
= 1,000 MON tokens - The CLI automatically converts MON to wei for blockchain transactions
- SECP Private Key: 64 hexadecimal characters, no
0x
prefix- Example:
a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789abcdef
- Example:
- BLS Private Key: 64 hexadecimal characters, with
0x
prefix- Example:
0x1a2b3c4d5e6f7890123456789abcdef0123456789abcdef0123456789abcdef
- Example:
- Validator registration: ~2M gas
- Delegation operations: ~1M gas
- The CLI automatically sets appropriate gas limits
If transactions fail with status 0
:
- Check gas limits: Validator registration requires higher gas
- Verify amounts: Ensure minimum requirements are met
- Check network: Confirm RPC endpoint is accessible
- Validate keys: Ensure proper key formats
- "Invalid Validator ID": Use
query validator-set
to see available validators - "Cannot withdraw yet": Wait for the required epoch delay
- "Insufficient funds": Ensure your address has enough MON for the operation
- "Key validation failed": Check key format and length
- Verify your RPC URL is correct and accessible
- Check if the network is experiencing downtime
- Ensure your internet connection is stable
- Setup: Create
config.toml
with your details - Query: Check current validator set and epoch
- Delegate: Start with a small delegation to test
- Monitor: Query your delegation status
- Manage: Claim rewards, compound, or undelegate as needed