Overview
The NFTPassport contract manages the whitelist system for Fenines Network. Validators create referral keys to invite delegators, establishing a permissioned staking system with built-in attribution tracking.
Contract Address : 0x0000000000000000000000000000000000001001 (System Contract)
Core Concepts
Referral Keys Unique cryptographic keys created by validators to invite delegators
Whitelist Delegator must be whitelisted to a validator before staking
Multi-Use Codes Optional promo-code style keys with usage limits
Direct Invites Validators can directly whitelist addresses without keys
Workflow
Validator Creates Key
Validator generates a referral key (one-time or multi-use) const key = web3 . utils . keccak256 ( 'my-unique-referral-code' );
await nftPassport . methods . createReferralKey ( key ). send ({ from: validator });
Delegator Uses Key
Delegator uses the key to get whitelisted await nftPassport . methods . useReferralKey ( key ). send ({ from: delegator });
Delegator Can Stake
Now delegator is whitelisted and can stake to that validator await systemContract . methods . stakeToValidator ( validator ). send ({
from: delegator ,
value: stakeAmount
});
Validator Functions
createReferralKey
Create a one-time use referral key.
function createReferralKey ( bytes32 key ) external
Unique key hash. Generate using keccak256 of a random string or nonce.
Generate & Create
Using Helper
// Generate key from random string
const randomCode = 'my-referral-' + Date . now () + Math . random ();
const key = web3 . utils . keccak256 ( randomCode );
await nftPassport . methods . createReferralKey ( key ). send ({
from: validatorAddress
});
// Share the code (NOT the hash) with delegator
console . log ( 'Share this code:' , randomCode );
Effects:
Creates key with isActive = true
Key can be used once
Added to validator’s key list
Increments validatorActiveKeyCount
createReferralKeyWithExpiry
Create a one-time key with expiration.
function createReferralKeyWithExpiry (
bytes32 key ,
uint256 expiresInBlocks
) external
Number of blocks until expiration (e.g., 28800 = ~24 hours at 3s blocks)
const key = web3 . utils . keccak256 ( 'limited-time-offer' );
const blocksPerDay = 28800 ; // ~24 hours
await nftPassport . methods . createReferralKeyWithExpiry (
key ,
blocksPerDay * 7 // Expires in 7 days
). send ({ from: validatorAddress });
createMultiUseKey
Create a multi-use referral code (like a promo code).
function createMultiUseKey (
bytes32 key ,
uint256 maxUsage ,
uint256 expiresInBlocks
) external
Maximum number of uses (0 = unlimited)
Blocks until expiration (0 = never expires)
Multi-Use Code
Limited Campaign
const promoCode = 'FENINE100' ;
const key = web3 . utils . keccak256 ( promoCode );
await nftPassport . methods . createMultiUseKey (
key ,
100 , // Max 100 uses
0 // Never expires
). send ({ from: validatorAddress });
// Share the promo code
console . log ( 'Promo code:' , promoCode );
Use Cases:
Public promo codes for marketing campaigns
Partner referral programs
Community growth initiatives
Event-specific invitations
revokeReferralKey
Deactivate a referral key.
function revokeReferralKey ( bytes32 key ) external
const key = web3 . utils . keccak256 ( 'compromised-key' );
await nftPassport . methods . revokeReferralKey ( key ). send ({
from: validatorAddress
});
Effects:
Sets isActive = false
Key can no longer be used
Decrements validatorActiveKeyCount
Does NOT affect already-whitelisted users
directInvite
Directly whitelist a delegator without requiring a key.
function directInvite ( address delegator ) external
await nftPassport . methods . directInvite ( delegatorAddress ). send ({
from: validatorAddress
});
Requirements:
Delegator cannot be self (validator)
Delegator not already whitelisted
Contract not paused
Use Cases:
VIP delegators
Partnership agreements
Manual whitelist management
Testing/development
batchDirectInvite
Whitelist multiple delegators at once.
function batchDirectInvite ( address [] calldata delegators ) external
const delegators = [
'0x1111...' ,
'0x2222...' ,
'0x3333...'
];
await nftPassport . methods . batchDirectInvite ( delegators ). send ({
from: validatorAddress ,
gas: 500000 // Increase gas for batch operation
});
Gas-efficient for whitelisting many addresses. Automatically skips invalid/duplicate addresses.
revokeWhitelist
Remove a delegator from whitelist.
function revokeWhitelist ( address delegator ) external
await nftPassport . methods . revokeWhitelist ( delegatorAddress ). send ({
from: validatorAddress
});
This does NOT automatically unstake the delegator. They must unstake manually from FenineSystem.
Delegator Functions
useReferralKey
Use a referral key to get whitelisted.
function useReferralKey ( bytes32 key ) external
Delegator Side
Check Before Using
// Validator shared the code (not the hash)
const code = 'my-referral-code-from-validator' ;
// Hash it
const key = web3 . utils . keccak256 ( code );
// Use it
await nftPassport . methods . useReferralKey ( key ). send ({
from: delegatorAddress
});
// Now can stake
const systemContract = new web3 . eth . Contract ( systemABI , systemAddress );
await systemContract . methods . stakeToValidator ( validatorAddress ). send ({
from: delegatorAddress ,
value: web3 . utils . toWei ( '1000' , 'ether' )
});
Requirements:
Key must exist
Key must be active
Key not expired (if expiry set)
Not already whitelisted to this validator
Usage limit not reached (for multi-use keys)
Effects:
Increments usageCount
One-time keys: deactivated after use
Delegator added to whitelist
Sets delegatorValidator[delegator] = validator
exitFromValidator
Self-remove from validator’s whitelist.
function exitFromValidator ( address validator ) external
// Delegator unstakes first (from FenineSystem)
await systemContract . methods . unstakeDelegator ( validatorAddress ). send ({
from: delegatorAddress
});
// Then exit whitelist
await nftPassport . methods . exitFromValidator ( validatorAddress ). send ({
from: delegatorAddress
});
Best practice: Unstake from FenineSystem first, then exit whitelist. Cannot re-enter without new referral key.
View Functions
isWhitelisted
Check if a delegator is whitelisted to a validator.
function isWhitelisted ( address delegator , address validator )
external view returns ( bool )
const canStake = await nftPassport . methods . isWhitelisted (
delegatorAddress ,
validatorAddress
). call ();
if ( canStake ) {
console . log ( '✓ Can stake to this validator' );
} else {
console . log ( '✗ Need referral key first' );
}
This function is called internally by FenineSystem.stakeToValidator() to verify permission.
getKeyInfo
Get detailed information about a referral key.
function getKeyInfo ( bytes32 key ) external view returns (
address validator ,
bool isActive ,
bool isMultiUse ,
uint256 usageCount ,
uint256 maxUsage ,
uint256 createdAt ,
uint256 expiresAt ,
bool isExpired ,
bool isUsable
)
const code = 'PROMO2024' ;
const key = web3 . utils . keccak256 ( code );
const info = await nftPassport . methods . getKeyInfo ( key ). call ();
console . log ({
validator: info . validator ,
isActive: info . isActive ,
isMultiUse: info . isMultiUse ,
usageCount: info . usageCount + '/' + ( info . maxUsage || '∞' ),
createdBlock: info . createdAt ,
expiresBlock: info . expiresAt || 'Never' ,
isExpired: info . isExpired ,
canUseNow: info . isUsable
});
getValidatorKeys
Get all referral keys created by a validator.
function getValidatorKeys ( address validator )
external view returns ( bytes32 [] memory )
const keys = await nftPassport . methods . getValidatorKeys ( validatorAddress ). call ();
console . log ( 'Total keys created:' , keys . length );
// Get details for each
for ( const key of keys ) {
const info = await nftPassport . methods . getKeyInfo ( key ). call ();
console . log ({
key: key ,
active: info . isActive ,
used: info . usageCount + '/' + ( info . maxUsage || '∞' )
});
}
getValidatorStats
Get validator’s referral statistics.
function getValidatorStats ( address validator ) external view returns (
uint256 totalKeys ,
uint256 activeKeys ,
uint256 whitelistCount
)
const stats = await nftPassport . methods . getValidatorStats ( validatorAddress ). call ();
console . log ({
totalKeysCreated: stats . totalKeys ,
activeKeys: stats . activeKeys ,
whitelistedDelegators: stats . whitelistCount
});
getWhitelistedBy
Get which validator whitelisted a delegator.
function getWhitelistedBy ( address delegator )
external view returns ( address validator )
const validator = await nftPassport . methods . getWhitelistedBy (
delegatorAddress
). call ();
console . log ( 'Whitelisted by:' , validator );
Helper Functions
hashReferralCode
Generate key hash from human-readable string.
function hashReferralCode ( string calldata code )
external pure returns ( bytes32 )
On-Chain Hash
Off-Chain Hash (Same Result)
const hash = await nftPassport . methods . hashReferralCode ( 'MY-CODE-2024' ). call ();
console . log ( 'Hash:' , hash );
generateKey
Generate deterministic key from validator address + nonce.
function generateKey ( address validator , uint256 nonce )
external pure returns ( bytes32 )
const nonce = Date . now ();
const key = await nftPassport . methods . generateKey (
validatorAddress ,
nonce
). call ();
console . log ( 'Generated key:' , key );
Events
event ReferralKeyCreated (
address indexed validator ,
bytes32 indexed keyHash ,
bool isMultiUse ,
uint256 maxUsage ,
uint256 expiresAt
)
event ReferralKeyUsed (
address indexed delegator ,
address indexed validator ,
bytes32 indexed keyHash
)
event ReferralKeyRevoked (
address indexed validator ,
bytes32 indexed keyHash
)
event DirectInvite (
address indexed validator ,
address indexed delegator
)
event WhitelistRevoked (
address indexed validator ,
address indexed delegator
)
event DelegatorExited (
address indexed delegator ,
address indexed validator
)
Integration Examples
Validator: Create Campaign
Delegator: Join with Code
List All Whitelisted Users
// Marketing campaign with multi-use promo code
const campaignCode = 'FENINE-LAUNCH-2024' ;
const key = web3 . utils . keccak256 ( campaignCode );
await nftPassport . methods . createMultiUseKey (
key ,
1000 , // 1000 uses
28800 * 30 // 30-day campaign
). send ({ from: validatorAddress });
console . log ( 'Campaign created! Share code:' , campaignCode );
// Monitor usage
setInterval ( async () => {
const info = await nftPassport . methods . getKeyInfo ( key ). call ();
const stats = await nftPassport . methods . getValidatorStats ( validatorAddress ). call ();
console . log ({
codeUsed: info . usageCount + '/1000' ,
totalWhitelisted: stats . whitelistCount ,
blocksRemaining: info . expiresAt - await web3 . eth . getBlockNumber ()
});
}, 60000 );
// Received promo code from validator
const promoCode = 'FENINE-LAUNCH-2024' ;
const validatorAddress = '0x1234...' ; // From validator's marketing
// Step 1: Hash the code
const key = web3 . utils . keccak256 ( promoCode );
// Step 2: Check if valid
const info = await nftPassport . methods . getKeyInfo ( key ). call ();
if ( ! info . isUsable ) {
console . log ( 'Code not valid:' , {
expired: info . isExpired ,
maxUsed: info . usageCount >= info . maxUsage
});
return ;
}
// Step 3: Use the key
await nftPassport . methods . useReferralKey ( key ). send ({
from: delegatorAddress
});
console . log ( '✓ Whitelisted to validator:' , validatorAddress );
// Step 4: Now can stake
const systemContract = new web3 . eth . Contract ( systemABI , systemAddress );
await systemContract . methods . stakeToValidator ( validatorAddress ). send ({
from: delegatorAddress ,
value: web3 . utils . toWei ( '1000' , 'ether' )
});
console . log ( '✓ Staked successfully!' );
const stats = await nftPassport . methods . getValidatorStats (
validatorAddress
). call ();
console . log ( 'Total whitelisted:' , stats . whitelistCount );
// Get delegator list from FenineSystem
const systemContract = new web3 . eth . Contract ( systemABI , systemAddress );
const stakers = await systemContract . methods . getValidatorStakers (
validatorAddress
). call ();
console . log ( 'Active stakers:' , stakers );
// Check each one
for ( const staker of stakers ) {
const isWhitelisted = await nftPassport . methods . isWhitelisted (
staker ,
validatorAddress
). call ();
const whitelistedBy = await nftPassport . methods . getWhitelistedBy (
staker
). call ();
console . log ({
delegator: staker ,
whitelisted: isWhitelisted ,
validator: whitelistedBy
});
}
Best Practices
Generate keys off-chain using keccak256 to save gas
Use descriptive codes for multi-use keys (SUMMER2024, not random hashes)
Store key-to-code mappings in your backend
Revoke compromised keys immediately
Set expiry on time-limited campaigns
Use one-time keys for VIP delegators
Use multi-use keys for public campaigns
Monitor key usage via getKeyInfo()
Track conversion: keys created → keys used → actual stakes
Revoke keys if abused
Save the original code, not the hash
Check key validity before using (getKeyInfo)
One delegator can be whitelisted to multiple validators
Exit cleanly: unstake first, then call exitFromValidator()
Keys are NOT secrets - they’re invitation tokens
Anyone with the code can use it (if multi-use or unused)
Validators should distribute keys via trusted channels
Monitor for unusual usage patterns
Admin Functions
These functions are restricted to contract admin for emergency use only.
setPaused
Pause/unpause the contract.
function setPaused ( bool _paused ) external onlyAdmin
Effects when paused:
Cannot create keys
Cannot use keys
Cannot direct invite
View functions still work
transferAdmin
Transfer admin role.
function transferAdmin ( address newAdmin ) external onlyAdmin
Next Steps
FenineSystem Contract Learn about staking and rewards
Tax Manager Reward taxation system