Staking Contract Guide

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 initiateOperatorTransfer on the ValidatorWallet, then completeOperatorTransfer after the operator-transfer delay (or cancelOperatorTransfer)

Post-Join Steps:

  1. Save the validatorWallet address
  2. Start your validator node using the operator key
  3. Monitor that validatorPrime(validatorWallet) executes each epoch
  4. 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 activated

Withdrawing 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 calls validatorWallet.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

RuleNormal Epochs (2+)Epoch 0
Validator minimum stake42,000 GENNo minimum to join
Delegator minimum stake42 GENNo minimum to join
Activation delay+2 epochsActive in epoch 2
validatorPrime requiredYes, each epochNot required
Share calculationBased on existing ratio1:1 (shares = input)
Transaction processingYesNo (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 0

Key behaviors:

  1. No minimum stake: Validators can join with any non-zero amount
  2. Registered for epoch 2: Stakes are recorded and will become active when epoch 2 begins
  3. No priming required: validatorPrime() is not needed during epoch 0
  4. 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 2

Key behaviors:

  1. No minimum delegation: Any non-zero amount accepted
  2. Registered for epoch 2: Delegation is recorded and will become active when epoch 2 begins
  3. 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 ACTIVE

Epoch 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() required

Share 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:

  1. Epoch 0 stakes that meet minimums become active - validators need 42,000 GEN, delegators need 42 GEN
  2. Normal minimum requirements apply for new joins/deposits
  3. +2 epoch activation delay applies to all new deposits
  4. validatorPrime() becomes mandatory for validators to remain in the selection pool
  5. 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 active

Scenario 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

  1. Ensure node health: Your validator node automatically calls validatorPrime()
  2. Use separate keys: Cold wallet for owner, hot key for operator
  3. Monitor priming: Verify deposit activation occurs as expected
  4. Monitor quarantine status: Check if your validator is quarantined
  5. Maintain high uptime: Downtime leads to idleness strikes
  6. Set up backup monitoring: Alert if priming fails

For Delegators

  1. Diversify: Spread stake across multiple validators
  2. Select carefully: Choose validators with good uptime records
  3. Verify priming: Ensure validators are consistently primed
  4. Consider smaller validators: They often provide higher returns
  5. Monitor health: Regularly check validator status
  6. Use shares for exits: Calculate percentages, not absolute amounts

For Both

  1. Account for unbonding: Plan for the 7-epoch wait when exiting
  2. Monitor epochs: Track current epoch for claim eligibility
  3. Calculate exits carefully: Always use share percentages
  4. Understand compounding: Rewards automatically increase stake-per-share
  5. Watch governance: Parameter changes can affect staking dynamics

Troubleshooting

ErrorCauseSolution
ValidatorBelowMinimumStakeStake below 42,000 GENDeposit at least 42,000 GEN when joining
InvalidOperatorAddressZero operator, or deposit not sent through a joined ValidatorWalletRegister a valid operator key; deposit through validatorWallet.validatorDeposit()
OperatorAlreadyAssignedOperator key in use by another owner's validatorChoose a different operator key
ValidatorMayNotDepositZeroValueZero deposit amountDeposit a non-zero amount
ValidatorSlashInProgressA slash against the validator is being settledWait for priming to settle the slash, then retry
ValidatorExitExceedsSharesExiting more shares than ownedCheck shares with validatorSelfSharesOf()
DelegatorExitWouldBeBelowMinimumPartial exit would leave less than the minimum delegationExit all shares or keep at least the minimum
UnauthorizedDelegatorClaimClaim sent by an address other than the delegatorSend delegatorClaim() from the delegator's address
Validator not selectedNode not primingEnsure node runs and calls validatorPrime()
Stake not activatingPriming not occurringMonitor node for automatic validatorPrime() calls
Claim returns 0Unbonding not complete, withdrawal not yet priced, or slashing not yet settledVerify the unbonding period has passed with genStaking.epoch() and that the validator has been primed
Delegation not earningValidator issuesCheck if validator is quarantined/banned; consider switching

Related Resources