Skip to main content

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

1

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 });
2

Delegator Uses Key

Delegator uses the key to get whitelisted
await nftPassport.methods.useReferralKey(key).send({ from: delegator });
3

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
key
bytes32
required
Unique key hash. Generate using keccak256 of a random string or nonce.
// 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
expiresInBlocks
uint256
required
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
maxUsage
uint256
required
Maximum number of uses (0 = unlimited)
expiresInBlocks
uint256
Blocks until expiration (0 = never expires)
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
// 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)
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

// 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);

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