Staking Contract Guide
The Staking Contract Guide explains how developers can interact directly with GenLayer staking smart contracts for consensus-level staking operations. It includes Solidity code examples for both validator and delegator operations. The examples use genStaking for the Staking contract on GenLayer Chain and validatorWallet for a validator's ValidatorWallet contract.
To understand the concepts behind staking, including the epoch system, shares vs stake, validator selection weights, and reward distribution, see Staking in GenLayer.
For CLI-based staking operations (recommended for most users), see the GenLayer CLI Staking Commands. The CLI and genlayer-js also build the operator key proof that joining requires.
Stake is denominated in GEN, the native token of GenLayer Chain, so staking calls send GEN as msg.value. The minimums, unbonding periods, and epoch durations below are current defaults that governance can change. Read the minimums from the Staking contract with validatorMinStake() and delegatorMinStake() instead of hardcoding them.
Validator Operations
Joining as a Validator
Requirements:
- Minimum stake: 42,000 GEN tokens
- Must run a validator node for consensus participation
- An operator key whose possession you can prove
- A single address can create multiple validators
The caller becomes the validator owner. The operator is derived from the public key you register, and the possession proof shows that you control that key:
uint256[2] memory operatorPubKey = [pubKeyX, pubKeyY]; // operator public key
bytes memory possessionProof = ...; // signature by the operator key over its possession message
address validatorWallet = genStaking.validatorJoin{value: 42000 ether}(
operatorPubKey,
possessionProof
);Use your own key to operate the node yourself, or a separate hot key for cold/hot separation (recommended). The possession message is bound to the wallet factory and the caller; generate the key and proof with the GenLayer CLI (genlayer staking validator-join or the wizard) or with validatorJoin({ amount, registration }) in genlayer-js.
Save the returned ValidatorWallet address - you'll need it for all future operations.
Important Notes:
- Deposits activate 2 epochs after joining
validatorPrime()is automatically called by your validator node each epoch- Monitor priming for proper activation within 2 epochs
- The operator cannot be the zero address, and cannot already operate another owner's validator
- To rotate the operator later, the owner calls
initiateOperatorTransferon the ValidatorWallet, thencompleteOperatorTransferafter the operator-transfer delay (orcancelOperatorTransfer)
Post-Join Steps:
- Save the
validatorWalletaddress - Start your validator node using the operator key
- Monitor that
validatorPrime(validatorWallet)executes each epoch - Wait 2 epochs and verify activation
Depositing as a Validator
validatorWallet.validatorDeposit{value: 10000 ether}();Key Points:
- Call through the ValidatorWallet contract only; only the wallet owner can call it
- Deposits follow the epoch +2 activation rule
- Your validator node automatically calls
validatorPrime()to commit the deposit - No minimum for additional deposits
- Cannot deposit zero value
- Deposits revert while a slash against the validator is being settled
Activation Timeline:
Epoch N: validatorDeposit() called
Epoch N+1: validatorPrime() → deposit staged
Epoch N+2: validatorPrime() → deposit activatedWithdrawing as a Validator
Step 1: Calculate Shares
uint256 currentShares = genStaking.validatorSelfSharesOf(validatorWallet);
uint256 sharesToExit = (currentShares * 30) / 100; // Exit 30%genStaking.validatorView(validatorWallet) returns a ValidatorView struct with the validator's stake (vStake, dStake), pool shares (vShares, dShares), pending operations, and ban and priming state.
Step 2: Initiate Exit
validatorWallet.validatorExit(sharesToExit);You must specify shares, not token amounts. Cannot exit more shares than owned.
Important Notes:
- Exited shares stop counting toward stake and selection weight immediately
- The withdrawal is priced at the validator's next priming; it still receives its share of rewards for the epochs it served and stays exposed to slashing for them
- Tokens are locked for 7 epochs before claiming
- Each exit creates a separate withdrawal
Unbonding Timeline:
Epoch 10: validatorExit() called → unbonding starts
Epochs 11-16: Locked period
Epoch 17: earliest validatorClaim()Claiming Withdrawn Funds as a Validator
The owner claims through the ValidatorWallet, which collects the matured withdrawals and forwards them to the owner:
validatorWallet.validatorClaim();Important Notes:
- Must wait 7 full epochs after calling
validatorExit() - A withdrawal is claimable only after it has been priced and any slashing for the epochs it served has been settled; until then a claim pays nothing
- Multiple exits can be claimed in a single call
genStaking.validatorClaim(validatorWallet)is permissionless, but it pays the ValidatorWallet, not the owner; the owner still callsvalidatorWallet.validatorClaim()to receive the funds
Checking Claim Availability:
uint256 exitEpoch = 10;
uint256 claimableAtEpoch = exitEpoch + 7; // current default unbonding period
if (genStaking.epoch() >= claimableAtEpoch) {
validatorWallet.validatorClaim();
}Delegator Operations
Joining as a Delegator
Requirements:
- Minimum delegation: 42 GEN per validator
- Target validator must be a registered validator
- Can delegate to multiple validators
Basic Delegation:
address validatorWallet = 0x...;
genStaking.delegatorJoin{value: 1000 ether}(validatorWallet);Multi-Validator Delegation:
genStaking.delegatorJoin{value: 500 ether}(validator1);
genStaking.delegatorJoin{value: 500 ether}(validator2);
genStaking.delegatorJoin{value: 500 ether}(validator3);Important Notes:
- Follows the epoch +2 activation rule
- The validator's node automatically calls
validatorPrime()to commit your delegation - Delegating to a validator that is below its own minimum stake is allowed, but that validator is not selected until it meets the minimum
- Choose validators with good uptime
- Smaller validators often provide higher returns
- Cannot delegate zero value
Delegation Strategy Tips:
- Diversify across multiple validators
- Check validator performance and uptime
- Monitor validator quarantine/ban status
Depositing as a Delegator
To add to an existing delegation, call delegatorJoin() again for the same validator:
genStaking.delegatorJoin{value: 500 ether}(validatorWallet);Important Notes:
- Follows the epoch +2 activation rule
- The validator's node automatically calls
validatorPrime() - Rewards automatically compound
Withdrawing as a Delegator
Step 1: Calculate Shares
uint256 currentShares = genStaking.sharesOf(msg.sender, validatorWallet);
uint256 sharesToExit = (currentShares * 50) / 100; // Exit 50%Step 2: Initiate Exit
genStaking.delegatorExit(validatorWallet, sharesToExit);You must specify shares, not token amounts.
Important Notes:
- Can exit partial amounts, but a partial exit cannot leave the delegation below the minimum; exit all shares instead
- Exited shares stop counting toward stake immediately; the withdrawal is priced at the validator's next priming
- Tokens are locked for 7 epochs
- Exit each validator separately if delegating to multiple
Unbonding Timeline:
Epoch 5: delegatorExit() called
Epochs 6-11: Locked period
Epoch 12: earliest delegatorClaim()Claiming Withdrawn Funds as a Delegator
uint256 tokensReceived = genStaking.delegatorClaim(
msg.sender, // must be the delegator
validatorWallet
);Important Notes:
- Must wait 7 full epochs after calling
delegatorExit() - Only the delegator can claim; calls from any other address revert with
UnauthorizedDelegatorClaim - Tokens are sent to the delegator's address
- A withdrawal is claimable only after it has been priced and any slashing for the epochs it served has been settled
- Claim from each validator separately if delegated to multiple
Multi-Validator Claim Example:
genStaking.delegatorClaim(msg.sender, validator1);
genStaking.delegatorClaim(msg.sender, validator2);
genStaking.delegatorClaim(msg.sender, validator3);Genesis Epoch 0
Epoch 0 is the genesis bootstrapping period with special rules designed to facilitate network launch. The normal staking rules are relaxed to allow rapid network bootstrapping.
What is Epoch 0?
Epoch 0 is the bootstrapping period before the network becomes operational. During epoch 0:
- No transactions are processed - the network accepts Intelligent Contract transactions only from epoch 2
- No consensus occurs - validators are not yet participating
- Stakes are registered and prepared for activation in epoch 2
- The epoch lasts at least its configured minimum duration (14 days by default; later epochs last at least 1 day)
Important: When epoch 0 ends, the network normally moves directly to epoch 2 (epoch 1 is skipped). The skip requires the inflation for epoch 2 to have been delivered from Ethereum; otherwise the network waits in epoch 1 until it arrives. Validators and delegators who stake in epoch 0 become active in epoch 2.
Special Rules for Epoch 0
| Rule | Normal Epochs (2+) | Epoch 0 |
|---|---|---|
| Validator minimum stake | 42,000 GEN | No minimum to join |
| Delegator minimum stake | 42 GEN | No minimum to join |
| Activation delay | +2 epochs | Active in epoch 2 |
| validatorPrime required | Yes, each epoch | Not required |
| Share calculation | Based on existing ratio | 1:1 (shares = input) |
| Transaction processing | Yes | No (bootstrapping only) |
Activation requires meeting minimums: While you can join with any amount during epoch 0, your stake will only be activated in epoch 2 if it meets the minimum requirements (42,000 GEN for validators, 42 GEN for delegators). Stakes below the minimum remain registered but inactive.
Validators in Epoch 0
Joining:
// In epoch 0, any non-zero amount is accepted (no 42,000 GEN minimum)
address validatorWallet = genStaking.validatorJoin{value: 1000 ether}(
operatorPubKey,
possessionProof
);
// Stake is registered for activation in epoch 2
// No need to call validatorPrime() in epoch 0Key behaviors:
- No minimum stake: Validators can join with any non-zero amount
- Registered for epoch 2: Stakes are recorded and will become active when epoch 2 begins
- No priming required:
validatorPrime()is not needed during epoch 0 - No consensus participation: Validators do not process transactions in epoch 0
Do validators need to take any action in epoch 0 to be active in epoch 2?
No. Validators who join in epoch 0:
- Have their stake registered during epoch 0
- Become active automatically in epoch 2 only if they have at least 42,000 GEN staked
- Must start calling
validatorPrime()in epoch 2 for continued participation in epoch 4+
Critical: Validators who joined in epoch 0 with less than 42,000 GEN will not be active in epoch 2. They must deposit additional funds to meet the minimum requirement before epoch 2 begins.
Delegators in Epoch 0
Joining:
// In epoch 0, any non-zero amount is accepted (no 42 GEN minimum)
genStaking.delegatorJoin{value: 10 ether}(validatorWallet);
// Delegation is registered for activation in epoch 2Key behaviors:
- No minimum delegation: Any non-zero amount accepted
- Registered for epoch 2: Delegation is recorded and will become active when epoch 2 begins
- No rewards in epoch 0: Since no transactions are processed, no rewards are earned during epoch 0
Is a delegation made in epoch 0 active in epoch 2?
Yes. Delegations made in epoch 0 become active in epoch 2. Unlike normal epochs where you wait +2 epochs, epoch 0 delegations activate as soon as the network becomes operational.
Activation Timeline Comparison
Normal Epochs (2+):
Epoch N: validatorJoin() or delegatorJoin() called
Epoch N+1: validatorPrime() stages the deposit
Epoch N+2: validatorPrime() activates the deposit → NOW ACTIVEEpoch 0 (Bootstrapping):
Epoch 0: validatorJoin() or delegatorJoin() called → stake registered (not yet active)
No transactions processed, no consensus
Epoch 2: Stakes become active (if minimum met), network operational, validatorPrime() requiredShare Calculation in Epoch 0
In epoch 0, shares are calculated at a 1:1 ratio with the input amount:
// Epoch 0: 1,000 GEN deposit = 1,000 shares (1:1 ratio)
genStaking.delegatorJoin{value: 1000 ether}(validatorWallet);
// Result: delegator receives exactly 1,000 shares
// Normal epochs: shares depend on pool ratio
// new_shares = deposit_amount × (total_shares / total_stake)This is because there's no existing stake pool to calculate a ratio against. Starting from epoch 2, shares are calculated based on the current stake-to-share ratio.
Transitioning from Epoch 0 to Epoch 2
When the network advances from epoch 0 to epoch 2:
- Epoch 0 stakes that meet minimums become active - validators need 42,000 GEN, delegators need 42 GEN
- Normal minimum requirements apply for new joins/deposits
- +2 epoch activation delay applies to all new deposits
- validatorPrime() becomes mandatory for validators to remain in the selection pool
- Existing validators must ensure their nodes begin calling
validatorPrime()in epoch 2
FAQ: Epoch 0 Special Cases
Q: Can I join as a validator with less than 42,000 GEN in epoch 0? A: Yes, any non-zero amount is accepted during epoch 0. However, you will not be active in epoch 2 unless you have at least 42,000 GEN staked by then.
Q: If I delegate in epoch 0, when does it become active? A: In epoch 2. Unlike normal epochs with a +2 delay, epoch 0 delegations activate when the network becomes operational.
Q: Do I need to call validatorPrime() in epoch 0? A: No. Priming is not required during epoch 0. Your node should start calling it automatically when epoch 2 begins.
Q: Will my epoch 0 stake still be active after epoch 0 ends? A: Yes, if you meet the minimum requirements. Stakes from epoch 0 carry forward and remain active in all subsequent epochs.
Q: What happens to my stake if I joined in epoch 0 but my node doesn't call validatorPrime() in epoch 2? A: You'll be excluded from validator selection in epoch 4, but your stake remains. Once priming resumes, you'll be eligible for selection again.
Common Scenarios
Scenario 1: Becoming a Validator
address validatorWallet = genStaking.validatorJoin{value: 42000 ether}(
operatorPubKey,
possessionProof
);
// Start validator node with the operator key
// Monitor: Epoch N joined, N+1 staged, N+2 activeScenario 2: Delegating to Multiple Validators
genStaking.delegatorJoin{value: 1000 ether}(validator1);
genStaking.delegatorJoin{value: 2000 ether}(validator2);
genStaking.delegatorJoin{value: 1500 ether}(validator3);
// Later, exit from one validator
uint256 shares = genStaking.sharesOf(msg.sender, validator1);
genStaking.delegatorExit(validator1, shares);
// After 7 epochs:
genStaking.delegatorClaim(msg.sender, validator1);Scenario 3: Partial Validator Exit
uint256 currentShares = genStaking.validatorSelfSharesOf(validatorWallet);
uint256 sharesToExit = (currentShares * 25) / 100; // Exit 25%
validatorWallet.validatorExit(sharesToExit);
// After 7 epochs:
validatorWallet.validatorClaim();Best Practices
For Validators
- Ensure node health: Your validator node automatically calls
validatorPrime() - Use separate keys: Cold wallet for owner, hot key for operator
- Monitor priming: Verify deposit activation occurs as expected
- Monitor quarantine status: Check if your validator is quarantined
- Maintain high uptime: Downtime leads to idleness strikes
- Set up backup monitoring: Alert if priming fails
For Delegators
- Diversify: Spread stake across multiple validators
- Select carefully: Choose validators with good uptime records
- Verify priming: Ensure validators are consistently primed
- Consider smaller validators: They often provide higher returns
- Monitor health: Regularly check validator status
- Use shares for exits: Calculate percentages, not absolute amounts
For Both
- Account for unbonding: Plan for the 7-epoch wait when exiting
- Monitor epochs: Track current epoch for claim eligibility
- Calculate exits carefully: Always use share percentages
- Understand compounding: Rewards automatically increase stake-per-share
- Watch governance: Parameter changes can affect staking dynamics
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
ValidatorBelowMinimumStake | Stake below 42,000 GEN | Deposit at least 42,000 GEN when joining |
InvalidOperatorAddress | Zero operator, or deposit not sent through a joined ValidatorWallet | Register a valid operator key; deposit through validatorWallet.validatorDeposit() |
OperatorAlreadyAssigned | Operator key in use by another owner's validator | Choose a different operator key |
ValidatorMayNotDepositZeroValue | Zero deposit amount | Deposit a non-zero amount |
ValidatorSlashInProgress | A slash against the validator is being settled | Wait for priming to settle the slash, then retry |
ValidatorExitExceedsShares | Exiting more shares than owned | Check shares with validatorSelfSharesOf() |
DelegatorExitWouldBeBelowMinimum | Partial exit would leave less than the minimum delegation | Exit all shares or keep at least the minimum |
UnauthorizedDelegatorClaim | Claim sent by an address other than the delegator | Send delegatorClaim() from the delegator's address |
| Validator not selected | Node not priming | Ensure node runs and calls validatorPrime() |
| Stake not activating | Priming not occurring | Monitor node for automatic validatorPrime() calls |
| Claim returns 0 | Unbonding not complete, withdrawal not yet priced, or slashing not yet settled | Verify the unbonding period has passed with genStaking.epoch() and that the validator has been primed |
| Delegation not earning | Validator issues | Check if validator is quarantined/banned; consider switching |
Related Resources
- Staking Concepts - How staking works in GenLayer
- Unstaking - Unstaking process details
- Slashing - Slashing penalties and conditions
- GenLayer CLI Staking Commands - CLI reference for staking
- genlayer-js staking methods - SDK reference for staking