Overview
The FenineSystem contract is the heart of Fenines Network’s FPoS (Finality Proof of Stake) consensus. It manages validator registration, staking, delegation, and reward distribution through a unique proximity-based system.
Contract Address : 0x0000000000000000000000000000000000001000 (System Contract)
Architecture
NOT_EXIST → CREATED → STAKED → VALIDATED → UNSTAKED → CREATED
↓ ↓ ↓
Register Stake Activated
NOT_EXIST : Address has never registered
CREATED : Registered with metadata, not yet staked
STAKED : Staked ≥10,000 FEN, waiting for epoch activation
VALIDATED : Active validator, earning rewards
UNSTAKED : Initiated unstake, in lock period
Fenines implements an 8-level proximity (referral) system where:
When a delegator claims rewards, a portion goes to their uplines (previous stakers to the same validator)
Default distribution: 30% of rewards distributed across 8 levels
Per-level allocation: [7%, 5%, 4%, 3.5%, 3%, 2.5%, 2.5%, 2.5%]
Remainder split 50/50 between claimer and validator
Anti-Sybil: Minimum stake requirements increase per level
Epoch Length : 200 blocks (~10 minutes at 3s block time)
System Transactions : Executed automatically at block % 200 == 0
updateValidatorCandidates() - Activate STAKED → VALIDATED
distributeBlockReward() - Distribute accumulated rewards
Validator Set Updates : Only at epoch boundaries
Constants
Constant Value Description MAX_VALIDATORS101 Maximum active validators MIN_VA_STAKE10,000 FEN Minimum validator self-stake MIN_DC_STAKE1,000 FEN Minimum delegator stake VA_LOCK_PERIOD50,000 blocks ~7 days unstake lock DC_LOCK_PERIOD25,000 blocks ~3.5 days unstake lock DEFAULT_COMMISSION500 (5%) Default commission rate MAX_COMMISSION1000 (10%) Maximum commission rate BLOCK_EPOCH200 Blocks per epoch PROXIMITY_DEPTH8 Proximity levels
Validator Functions
registerValidator
Register as a validator (expression of intent).
function registerValidator (
address payable rewardAddress ,
string calldata moniker ,
string calldata phoneNumber ,
string calldata details ,
uint256 commissionRate
) external returns ( bool )
Address to receive rewards (can differ from msg.sender)
Validator name/identifier (1-128 characters)
Contact phone number (optional)
Additional metadata (optional)
Commission in basis points (500-1000 = 5-10%)
const tx = await systemContract . methods . registerValidator (
rewardAddress ,
'MyValidator' ,
'+1234567890' ,
'Best validator in town' ,
500 // 5% commission
). send ({ from: validatorAddress });
You must register before staking. Status transitions: NOT_EXIST → CREATED
stakeValidator
Stake as validator (minimum 10,000 FEN).
function stakeValidator () external payable returns ( bool )
const stakeAmount = web3 . utils . toWei ( '10000' , 'ether' );
const tx = await systemContract . methods . stakeValidator (). send ({
from: validatorAddress ,
value: stakeAmount
});
Requirements:
Status must be CREATED or UNSTAKED
msg.value >= 10,000 FEN
Contract not paused
Effects:
Adds to selfStake and totalStake
Status → STAKED
Added to validatorCandidates array
Will be activated at next epoch
addValidatorStake
Add more stake to existing validator (top-up).
function addValidatorStake () external payable returns ( bool )
const additionalStake = web3 . utils . toWei ( '5000' , 'ether' );
await systemContract . methods . addValidatorStake (). send ({
from: validatorAddress ,
value: additionalStake
});
Requirements:
Status must be STAKED or VALIDATED
msg.value > 0
Effects:
Increases selfStake and totalStake
Does NOT reset stake age or status
unstakeValidator
Initiate validator unstake process.
function unstakeValidator () external returns ( bool )
await systemContract . methods . unstakeValidator (). send ({
from: validatorAddress
});
Requirements:
Status must be STAKED or VALIDATED
Not already unstaking
Effects:
Status → UNSTAKED
Sets unstakeBlock = current block
Removed from activeValidatorSet
Removed from validatorCandidates
Lock period: 50,000 blocks (~7 days)
withdrawValidatorStake
Withdraw staked FEN after lock period.
function withdrawValidatorStake () external returns ( bool )
await systemContract . methods . withdrawValidatorStake (). send ({
from: validatorAddress
});
Requirements:
unstakeBlock > 0 (unstake initiated)
block.number >= unstakeBlock + 50000 (lock period passed)
selfStake > 0
Effects:
Transfers selfStake to validator
Resets selfStake = 0
Status → CREATED
Can re-stake later
claimValidatorReward
Claim accumulated validator rewards.
function claimValidatorReward () external returns ( bool )
// Check rewards first
const info = await systemContract . methods . getValidatorInfo ( validatorAddress ). call ();
console . log ( 'Claimable:' , web3 . utils . fromWei ( info . claimableReward , 'ether' ), 'FEN' );
// Claim
await systemContract . methods . claimValidatorReward (). send ({
from: validatorAddress
});
Requirements:
Status must be VALIDATED
claimableReward > 0
Effects:
Transfers rewards to rewardAddress
Applies tax via TaxManager
Resets claimableReward = 0
Tax Flow:
Gross Reward
├─> Tax (burn + dev)
└─> Net → rewardAddress
Update validator information.
function updateValidatorMetadata (
string calldata moniker ,
string calldata phoneNumber ,
string calldata details
) external returns ( bool )
await systemContract . methods . updateValidatorMetadata (
'NewValidatorName' ,
'+9876543210' ,
'Updated description'
). send ({ from: validatorAddress });
updateCommissionRate
Update commission rate (only before activation).
function updateCommissionRate ( uint256 newRate ) external returns ( bool )
await systemContract . methods . updateCommissionRate ( 750 ). send ({
from: validatorAddress
}); // Set to 7.5%
Can only update when status is STAKED (not yet activated). Commission is locked after activation.
Delegator Functions
stakeToValidator
Stake FEN to a validator as a delegator.
function stakeToValidator ( address vaAddr ) external payable returns ( bool )
Validator address to stake to
const validatorAddress = '0x1234...' ;
const stakeAmount = web3 . utils . toWei ( '1000' , 'ether' );
await systemContract . methods . stakeToValidator ( validatorAddress ). send ({
from: delegatorAddress ,
value: stakeAmount
});
Requirements:
Validator status must be VALIDATED
msg.value >= 1,000 FEN
Not currently unstaking
Whitelisted via NFTPassport contract
Effects:
First-time: Added to validator’s stakers array (proximity position)
Increases stakeAmount and validator’s totalStake
Starts earning rewards
Proximity Position : Your position in the stakers array determines proximity rewards. Earlier stakers are deeper in the chain and receive rewards from later stakers.
addDelegatorStake
Add more stake to existing delegation.
function addDelegatorStake ( address vaAddr ) external payable returns ( bool )
await systemContract . methods . addDelegatorStake ( validatorAddress ). send ({
from: delegatorAddress ,
value: web3 . utils . toWei ( '500' , 'ether' )
});
unstakeDelegator
Initiate delegator unstake process.
function unstakeDelegator ( address vaAddr ) external returns ( bool )
await systemContract . methods . unstakeDelegator ( validatorAddress ). send ({
from: delegatorAddress
});
Requirements:
stakeAmount > 0
Not already unstaking
Effects:
Removed from stakers array (proximity chain)
Status → UNSTAKING
Sets unstakeBlock = current block
Lock period: 25,000 blocks (~3.5 days)
Pending rewards preserved
withdrawDelegatorStake
Withdraw delegated stake after lock period.
function withdrawDelegatorStake ( address vaAddr ) external returns ( bool )
// Check if lock period passed
const info = await systemContract . methods . getDelegatorInfo (
delegatorAddress ,
validatorAddress
). call ();
const currentBlock = await web3 . eth . getBlockNumber ();
const lockPeriod = 25000 ;
if ( currentBlock >= info . unstakeBlock + lockPeriod ) {
await systemContract . methods . withdrawDelegatorStake ( validatorAddress ). send ({
from: delegatorAddress
});
}
claimDelegatorReward
Claim rewards with proximity distribution.
function claimDelegatorReward ( address vaAddr ) external returns ( bool )
// Estimate rewards first
const estimate = await systemContract . methods . getEstimatedDelegatorReward (
delegatorAddress ,
validatorAddress
). call ();
console . log ( 'Pending:' , web3 . utils . fromWei ( estimate . pending , 'ether' ));
console . log ( 'After proximity:' , web3 . utils . fromWei ( estimate . afterProximity , 'ether' ));
console . log ( 'After tax:' , web3 . utils . fromWei ( estimate . afterTax , 'ether' ));
// Claim
await systemContract . methods . claimDelegatorReward ( validatorAddress ). send ({
from: delegatorAddress
});
Reward Flow:
Pending Reward (100%)
├─> Proximity Distribution (30% default)
│ ├─> Level 1 upline (7%)
│ ├─> Level 2 upline (5%)
│ └─> ... 8 levels
├─> Residual split (if uplines ineligible)
│ ├─> 50% to claimer (bonus)
│ └─> 50% to validator
└─> Net to Claimer (70% + bonuses)
├─> Tax (burn + dev)
└─> Final Amount
View Functions
getActiveValidators
Get list of active validators.
function getActiveValidators () external view returns ( address [] memory )
const validators = await systemContract . methods . getActiveValidators (). call ();
console . log ( 'Active validators:' , validators );
getValidatorInfo
Get detailed validator information.
function getValidatorInfo ( address vaAddr ) external view returns (
VAStatus status ,
uint256 selfStake ,
uint256 totalStake ,
uint256 commissionRate ,
uint256 claimableReward ,
uint256 stakerCount
)
const info = await systemContract . methods . getValidatorInfo ( validatorAddress ). call ();
console . log ({
status: [ 'NOT_EXIST' , 'CREATED' , 'STAKED' , 'VALIDATED' , 'UNSTAKED' ][ info . status ],
selfStake: web3 . utils . fromWei ( info . selfStake , 'ether' ),
totalStake: web3 . utils . fromWei ( info . totalStake , 'ether' ),
commission: info . commissionRate / 100 + '%' ,
claimable: web3 . utils . fromWei ( info . claimableReward , 'ether' ),
delegators: info . stakerCount
});
getDelegatorInfo
Get delegator stake information.
function getDelegatorInfo ( address dcAddr , address vaAddr ) external view returns (
DCStatus status ,
uint256 stakeAmount ,
uint256 pendingRewards ,
uint256 joinedAt ,
uint256 stakerIndex
)
getEstimatedDelegatorReward
Calculate estimated rewards (preview before claiming).
function getEstimatedDelegatorReward ( address dcAddr , address vaAddr )
external view returns (
uint256 pending ,
uint256 afterProximity ,
uint256 afterTax
)
getProximityConfig
Get current proximity distribution configuration.
function getProximityConfig () external view returns (
uint16 totalBps ,
uint8 depth ,
uint16 [ 8 ] memory rewardBps
)
const config = await systemContract . methods . getProximityConfig (). call ();
console . log ( 'Total proximity:' , config . totalBps / 100 , '%' );
console . log ( 'Levels:' , config . depth );
console . log ( 'Distribution:' , config . rewardBps . map ( bps => bps / 100 + '%' ));
getCurrentEpoch
Get current epoch number.
function getCurrentEpoch () external view returns ( uint256 )
getNextEpochReward
Get pending reward pool for next epoch.
function getNextEpochReward () external view returns ( uint256 )
Admin Functions
Admin functions are restricted to the contract admin (set during genesis). These should only be used for emergency situations or governance-approved changes.
setProximityConfig
Update proximity reward distribution.
function setProximityConfig (
uint16 newTotalBps ,
uint16 [ 8 ] calldata newRewardBps
) external onlyAdmin returns ( bool )
Requirements:
Only admin can call
newTotalBps <= 8700 (87% max)
Array must sum to newTotalBps
pause / unpause
Emergency pause/unpause contract (inherited from EmergencyControl).
function pause () external onlyAdmin
function unpause () external onlyAdmin
changeAdmin
Transfer admin role (inherited from EmergencyControl, with 7-day timelock).
function changeAdmin ( address newAdmin ) external onlyAdmin
Events
event ValidatorRegistered ( address indexed va , string moniker , uint256 timestamp )
event ValidatorStaked ( address indexed va , uint256 amount , uint256 totalStake , uint256 timestamp )
event ValidatorActivated ( address indexed va , uint256 indexed epochNumber )
event ValidatorUnstaked ( address indexed va , uint256 amount , uint256 unlockBlock )
event ValidatorWithdrawn ( address indexed va , uint256 amount )
event ValidatorRewardClaimed ( address indexed va , uint256 gross , uint256 net , uint256 tax )
event MetadataUpdated ( address indexed va )
event CommissionUpdated ( address indexed va , uint256 oldRate , uint256 newRate )
event DelegatorStaked ( address indexed dc , address indexed va , uint256 amount , uint256 totalStake , uint256 timestamp )
event DelegatorUnstaked ( address indexed dc , address indexed va , uint256 unlockBlock )
event DelegatorWithdrawn ( address indexed dc , address indexed va , uint256 amount )
event DelegatorRewardClaimed ( address indexed dc , address indexed va , uint256 gross , uint256 net , uint256 tax )
event ProximityRewardsApplied ( address indexed claimer , address indexed va , uint256 totalDistributed )
event ProximityRewardAllocated ( address indexed recipient , uint256 amount , uint8 level )
event RewardDistributed ( uint256 epochReward , uint256 validatorCount , uint256 indexed epochNumber , uint256 timestamp )
event ValidatorSetUpdated ( address [] validators , uint256 indexed epochNumber )
event EpochProcessed ( uint256 indexed blockNumber , address indexed caller , uint256 reward , bool success )
Integration Examples
Become a Validator
Delegate to Validator
Claim Rewards
// Step 1: Register
await systemContract . methods . registerValidator (
rewardAddress ,
'MyValidator' ,
'' ,
'Professional validator' ,
500
). send ({ from: validatorAddress });
// Step 2: Stake (minimum 10,000 FEN)
await systemContract . methods . stakeValidator (). send ({
from: validatorAddress ,
value: web3 . utils . toWei ( '10000' , 'ether' )
});
// Step 3: Wait for next epoch (automatic activation)
// Status will change: CREATED → STAKED → VALIDATED
// Step 4: Monitor rewards
setInterval ( async () => {
const info = await systemContract . methods . getValidatorInfo ( validatorAddress ). call ();
console . log ( 'Claimable:' , web3 . utils . fromWei ( info . claimableReward , 'ether' ));
}, 60000 ); // Check every minute
// Prerequisites: Get whitelisted via NFTPassport
const nftPassport = new web3 . eth . Contract ( nftPassportABI , nftPassportAddress );
// Check if whitelisted
const isWhitelisted = await nftPassport . methods . isWhitelisted (
delegatorAddress ,
validatorAddress
). call ();
if ( ! isWhitelisted ) {
console . log ( 'Need referral key from validator' );
return ;
}
// Stake to validator
await systemContract . methods . stakeToValidator ( validatorAddress ). send ({
from: delegatorAddress ,
value: web3 . utils . toWei ( '1000' , 'ether' )
});
// Monitor rewards
const estimate = await systemContract . methods . getEstimatedDelegatorReward (
delegatorAddress ,
validatorAddress
). call ();
console . log ( 'Estimated rewards after tax:' ,
web3 . utils . fromWei ( estimate . afterTax , 'ether' ), 'FEN'
);
// For Validators
const vaInfo = await systemContract . methods . getValidatorInfo ( validatorAddress ). call ();
if ( BigInt ( vaInfo . claimableReward ) > 0 ) {
await systemContract . methods . claimValidatorReward (). send ({
from: validatorAddress
});
}
// For Delegators
const dcEstimate = await systemContract . methods . getEstimatedDelegatorReward (
delegatorAddress ,
validatorAddress
). call ();
if ( BigInt ( dcEstimate . pending ) > 0 ) {
await systemContract . methods . claimDelegatorReward ( validatorAddress ). send ({
from: delegatorAddress
});
}
Security Considerations
All state-changing functions that transfer ETH use the nonReentrant modifier to prevent reentrancy attacks.
User functions: Anyone can call
System functions: Only block.coinbase (consensus layer)
Admin functions: Only contract admin with timelock
Minimum stake requirements per proximity level prevent sybil attacks
Stake age requirement (100 blocks minimum)
Unstaking addresses cannot receive proximity rewards
Pause functionality (inherited from EmergencyControl)
7-day timelock for admin changes
Emergency withdrawal capability with hooks
Next Steps
NFT Passport Contract Whitelist and referral system
Tax Manager Contract Reward tax and burn mechanism
Integration Examples Complete integration examples
Quick Reference Common queries and selectors