Skip to content

Functions

DISCLAIMER // NFA // DYOR

This analysis is based on decompiled bytecode and observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Proxy Address 0x2a9848c39fff51eb184326d65f1238cc36764069 (etherscan)
Implementation Address 0x435dc93869553123513148e5df4231c21dfca965 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-03-29 (Updated)

Function Summary

CATEGORY NUM FUNCTIONS
Initialization 1
User Functions 7
Operator Functions 8
Admin Functions 14+
View Functions 20+

Implementation Upgrade (2026-03-27)

The following 7 functions were added in the upgrade to implementation 0x435dc9...ca965:
VERSION(), requestPrioritySnapshot() (0x8bd94199), feeRecipient() (0x407c8855),
snapshotFee() (0x5d2e4c1c), prioritySnapshotFee() (0xc27762b5),
setFeeRecipient(address) (0x3c85102a), setPrioritySnapshotFee(uint256) (0xeb209249).


Initialization Function

initialize(address tokenAddress)

One-time initialization of the contract with the external token address.

ATTRIBUTE VALUE
Location Implementation:243-258
Parameters tokenAddress
Access Anyone (but only works once)
FLAG OBSERVATION
Can only be called once (prevents re-initialization)
First caller becomes admin
No protection on who can initialize (should be called immediately after deployment)
Token address cannot be changed after initialization
STEP ACTION
1 Validate not already initialized
2 Validate token address is not zero
3 Store token address
4 Set initialized flag to 1
5 Set caller as admin
6 Initialize pause states to 0
7 Set minimum balance requirement to 0
8 Set snapshot interval to 3600 (1 hour)
9 Set last snapshot time to block.timestamp
10 Emit initialization event
VARIABLE CHANGE
tokenAddress Set to provided token address
initialized Set to 1
adminAddress Set to msg.sender
minBalance Set to 0
snapshotInterval Set to 3600
lastSnapshotTime Set to block.timestamp
CONDITION REVERT MESSAGE
Already initialized "Already initialized"
Token address is zero "Invalid XCL token"
// Reconstructed from bytecode
function initialize(address tokenAddress) external {
    require(initialized == 0, "Already initialized");
    require(tokenAddress != address(0), "Invalid XCL token");

    tokenAddress = tokenAddress;
    initialized = 1;
    adminAddress = msg.sender;
    paused = 0;
    registrationOpen = 0;
    minBalanceRequired = 0;
    snapshotInterval = 3600;
    lastSnapshotTime = block.timestamp;

    emit Initialized(tokenAddress, msg.sender);
}

User Functions

register()

Registers the caller to participate in rewards distribution based on their token holdings.

ATTRIBUTE VALUE
Location Implementation:431-458
Selector N/A (decompiled)
Access External - Any eligible address
FLAG OBSERVATION
Registration is a one-time action per address
Initial reward debt prevents claiming past rewards
User's token balance is snapshot at registration time
External token balance query could fail if token contract is malicious
CONDITION REQUIREMENT
Contract state Not paused
Registration Must be open
Caller status Not blacklisted
Caller status Not already registered
Token balance >= minimum requirement
STEP ACTION
1 Check all preconditions
2 Query caller's token balance from external contract
3 Mark caller as registered
4 Add caller to participants array
5 Store caller's index in mapping
6 Increment participantCount
7 Store caller's balance snapshot
8 Record registration timestamp
9 Add balance to total tracked
10 Calculate initial reward debt
VARIABLE CHANGE
isRegistered[caller] Set to 1
participantIndex[caller] Set to index in array
participants Caller added to array
participantCount Incremented
userBalance[caller] Set to token balance
lastUpdate[caller] Set to block.timestamp
totalTrackedBalance Increased by caller's balance
rewardDebt[caller] Set to balance * rewardsPerShare / 10^18
CONDITION REVERT MESSAGE
Contract paused "Contract is paused"
Registration closed "Registration closed"
Caller blacklisted "Address blacklisted"
Already registered "Already registered"
Insufficient balance "Insufficient XCL balance"
External call fails Depends on token contract
// Reconstructed from bytecode
function register() external {
    require(paused == 0, "Contract is paused");
    require(registrationOpen == 1, "Registration closed");
    require(blacklisted[msg.sender] == 0, "Address blacklisted");
    require(isRegistered[msg.sender] == 0, "Already registered");

    uint256 balance = IERC20(tokenAddress).balanceOf(msg.sender);
    require(balance >= minBalanceRequired, "Insufficient XCL balance");

    isRegistered[msg.sender] = 1;
    participants.push(msg.sender);
    participantIndex[msg.sender] = participants.length - 1;
    participantCount += 1;

    userBalance[msg.sender] = balance;
    lastUpdate[msg.sender] = block.timestamp;
    totalTrackedBalance += balance;
    rewardDebt[msg.sender] = (balance * rewardsPerShare) / 1e18;

    emit Registered(msg.sender, balance);
}
flowchart TD
    Start([register called])

    Start --> Check1{Paused?}
    Check1 -->|Yes| Revert1[Revert: Contract is paused]
    Check1 -->|No| Check2{Registration Open?}

    Check2 -->|No| Revert2[Revert: Registration closed]
    Check2 -->|Yes| Check3{Blacklisted?}

    Check3 -->|Yes| Revert3[Revert: Address blacklisted]
    Check3 -->|No| Check4{Already Registered?}

    Check4 -->|Yes| Revert4[Revert: Already registered]
    Check4 -->|No| ExtCall[Query Token Balance]

    ExtCall --> Check5{Balance >= Minimum?}
    Check5 -->|No| Revert5[Revert: Insufficient balance]
    Check5 -->|Yes| Process[Register User]

    Process --> UpdateState[Update Storage]
    UpdateState --> CalcDebt[Calculate Reward Debt]
    CalcDebt --> EmitEvent[Emit Registration Event]
    EmitEvent --> Success([Registration Complete])

    style Start fill:#e1f5ff
    style Success fill:#e1ffe1
    style Revert1 fill:#ffe1e1
    style Revert2 fill:#ffe1e1
    style Revert3 fill:#ffe1e1
    style Revert4 fill:#ffe1e1
    style Revert5 fill:#ffe1e1

claimRewards()

Allows registered users to claim their accumulated ETH rewards.

ATTRIBUTE VALUE
Location Implementation:568-599
Access External - Registered users
FLAG OBSERVATION
Uses rewards-per-share accounting
Multiple safety checks ensure solvency
ETH transfer uses raw call
State updated after external call (reentrancy risk)
Caller must be able to receive ETH
CONDITION REQUIREMENT
Contract state Not paused
Caller status Not blacklisted
Caller rewards Not individually paused
Caller status Is registered
Pending rewards > 0
Contract balance Sufficient ETH
Reward pool Sufficient balance
STEP ACTION
1 Validate all preconditions
2 Calculate pending rewards
3 Decrease reward pool by pending amount
4 Increase totalClaimed[caller]
5 Increase totalDistributed
6 Update reward debt
7 Transfer ETH to caller
8 Emit claim event
VARIABLE CHANGE
rewardPool Decreased by claimed amount
totalClaimed[caller] Increased by claimed amount
totalDistributed Increased by claimed amount
rewardDebt[caller] Updated to current rewardsPerShare * balance / 10^18
CONDITION REVERT MESSAGE
Contract paused "Contract is paused"
Caller blacklisted "Address blacklisted"
Rewards paused "Rewards paused for this address"
Not registered "Not registered"
No rewards "No rewards to claim"
Low ETH balance "Insufficient contract balance"
Low reward pool "Insufficient reward pool"
Transfer failed "ETH transfer failed"
// Reconstructed from bytecode
function claimRewards() external {
    require(paused == 0, "Contract is paused");
    require(blacklisted[msg.sender] == 0, "Address blacklisted");
    require(userRewardsPaused[msg.sender] == 0, "Rewards paused for this address");
    require(isRegistered[msg.sender] == 1, "Not registered");

    uint256 pending = (userBalance[msg.sender] * rewardsPerShare / 1e18)
                      - rewardDebt[msg.sender];
    require(pending > 0, "No rewards to claim");
    require(address(this).balance >= pending, "Insufficient contract balance");
    require(rewardPool >= pending, "Insufficient reward pool");

    rewardPool -= pending;
    totalClaimed[msg.sender] += pending;
    totalDistributed += pending;
    rewardDebt[msg.sender] = (userBalance[msg.sender] * rewardsPerShare) / 1e18;

    (bool success, ) = msg.sender.call{value: pending}("");
    require(success, "ETH transfer failed");

    emit RewardsClaimed(msg.sender, pending);
}
sequenceDiagram
    participant User
    participant Contract

    User->>Contract: claimRewards()
    Note over Contract: Validate preconditions

    alt Any check fails
        Contract-->>User: Revert with error
    else All checks pass
        Contract->>Contract: Calculate pending rewards
        Note over Contract: balance * rewardsPerShare / 1e18<br/>minus rewardDebt

        Contract->>Contract: Update state

        Contract->>User: Transfer ETH

        alt Transfer succeeds
            Contract->>Contract: Emit claim event
            Contract-->>User: Return amount claimed
        else Transfer fails
            Contract-->>User: Revert: ETH transfer failed
        end
    end

deregister()

Allows users to unregister from the rewards system, claiming any pending rewards first.

ATTRIBUTE VALUE
Location Implementation:640-678
Access External - Registered users
FLAG OBSERVATION
Attempts to claim rewards automatically
Does not revert if reward claim fails (allows exit)
Uses array compaction to avoid gaps
Fully clears user state
No cooldown or penalty for deregistering
CONDITION REQUIREMENT
Contract state Not paused
Caller status Is registered
STEP ACTION
1 Check preconditions
2 If pending rewards exist and sufficient funds, claim them
3 Decrease total tracked balance by user's balance
4 If user is not last in array, compact array
5 Remove last array element
6 Clear all user state
7 Decrement participantCount
8 Emit deregistration event
VARIABLE CHANGE
totalTrackedBalance Decreased by user's balance
participants User removed, array compacted
participantCount Decremented
isRegistered[caller] Cleared
userBalance[caller] Cleared
lastUpdate[caller] Cleared
rewardDebt[caller] Cleared
participantIndex[caller] Cleared
CONDITION REVERT MESSAGE
Contract paused "Contract is paused"
Not registered "Not registered"
ETH transfer fails "ETH transfer failed" (during reward claim)
// Reconstructed from bytecode
function deregister() external {
    require(paused == 0, "Contract is paused");
    require(isRegistered[msg.sender] == 1, "Not registered");

    // Attempt to claim pending rewards (does not revert on failure)
    uint256 pending = (userBalance[msg.sender] * rewardsPerShare / 1e18)
                      - rewardDebt[msg.sender];
    if (pending > 0 && rewardPool >= pending && address(this).balance >= pending) {
        rewardPool -= pending;
        totalClaimed[msg.sender] += pending;
        totalDistributed += pending;
        (bool success, ) = msg.sender.call{value: pending}("");
        require(success, "ETH transfer failed");
    }

    totalTrackedBalance -= userBalance[msg.sender];

    // Array compaction: move last element to removed user's position
    uint256 idx = participantIndex[msg.sender];
    uint256 lastIdx = participants.length - 1;
    if (idx != lastIdx) {
        address lastParticipant = participants[lastIdx];
        participants[idx] = lastParticipant;
        participantIndex[lastParticipant] = idx;
    }
    participants.pop();

    // Clear all user state
    isRegistered[msg.sender] = 0;
    userBalance[msg.sender] = 0;
    lastUpdate[msg.sender] = 0;
    rewardDebt[msg.sender] = 0;
    participantIndex[msg.sender] = 0;
    participantCount -= 1;

    emit Deregistered(msg.sender);
}
flowchart TD
    Start([deregister called])

    Start --> Check1{Paused?}
    Check1 -->|Yes| Revert1[Revert: Contract is paused]
    Check1 -->|No| Check2{Registered?}

    Check2 -->|No| Revert2[Revert: Not registered]
    Check2 -->|Yes| Check3{Has Pending Rewards?}

    Check3 -->|Yes| Check4{Sufficient Funds?}
    Check4 -->|Yes| ClaimRewards[Claim Pending Rewards]
    Check4 -->|No| SkipClaim[Skip Reward Claim]
    Check3 -->|No| SkipClaim

    ClaimRewards --> UpdateTotal[Decrease Total Balance]
    SkipClaim --> UpdateTotal

    UpdateTotal --> Check5{Last in Array?}
    Check5 -->|Yes| RemoveLast[Remove from Array End]
    Check5 -->|No| Compact[Move Last Participant<br/>to User's Position]

    Compact --> RemoveLast
    RemoveLast --> ClearState[Clear User State]

    ClearState --> DecrementCount[Decrement Participant Count]
    DecrementCount --> EmitEvent[Emit Deregistration Event]
    EmitEvent --> Success([Deregistration Complete])

    style Start fill:#e1f5ff
    style Success fill:#e1ffe1
    style Revert1 fill:#ffe1e1
    style Revert2 fill:#ffe1e1

updateBalanceSnapshot()

Allows users to manually update their token balance snapshot, claiming any pending rewards first.

ATTRIBUTE VALUE
Location Implementation:601-638
Access External - Registered users
FLAG OBSERVATION
Rate-limited by snapshot interval
Automatically claims pending rewards from old balance
Essential for users whose token balance changes
Relies on external token contract being accurate
CONDITION REQUIREMENT
Contract state Not paused
Caller status Is registered
Time elapsed Snapshot interval elapsed since last update
STEP ACTION
1 Check preconditions
2 Query current token balance from external contract
3 If balance changed from stored balance
4 - If pending rewards exist, claim them
5 - Update stored balance to new balance
6 - Update timestamp
7 - Adjust total tracked balance
8 - Recalculate reward debt
CONDITION REVERT MESSAGE
Contract paused "Contract is paused"
Not registered "Not registered"
Too soon "Snapshot interval not elapsed"
External call fails Depends on token contract
// Reconstructed from bytecode
function updateBalanceSnapshot() external {
    require(paused == 0, "Contract is paused");
    require(isRegistered[msg.sender] == 1, "Not registered");
    require(
        block.timestamp >= lastUpdate[msg.sender] + snapshotInterval,
        "Snapshot interval not elapsed"
    );

    uint256 newBalance = IERC20(tokenAddress).balanceOf(msg.sender);
    uint256 oldBalance = userBalance[msg.sender];

    if (newBalance != oldBalance) {
        // Claim pending rewards based on old balance
        uint256 pending = (oldBalance * rewardsPerShare / 1e18)
                          - rewardDebt[msg.sender];
        if (pending > 0 && rewardPool >= pending && address(this).balance >= pending) {
            rewardPool -= pending;
            totalClaimed[msg.sender] += pending;
            totalDistributed += pending;
            (bool success, ) = msg.sender.call{value: pending}("");
            require(success, "ETH transfer failed");
        }

        // Update balance and recalculate
        userBalance[msg.sender] = newBalance;
        lastUpdate[msg.sender] = block.timestamp;
        totalTrackedBalance = totalTrackedBalance - oldBalance + newBalance;
        rewardDebt[msg.sender] = (newBalance * rewardsPerShare) / 1e18;
    }
}

requestPrioritySnapshot() (Added v1.0.0)

Allows any user to pay ETH to trigger an immediate balance snapshot, bypassing the normal snapshot interval cooldown.

ATTRIBUTE VALUE
Selector 0x8bd94199
Parameters None
Access External - Payable - Anyone
FLAG OBSERVATION
ETH payment flows to fee recipient EOA, not the reward pool
Fee recipient is a separate address from admin/operator
Bypasses the normal snapshot interval rate limit
Provides users a way to update their balance without waiting
CONDITION REQUIREMENT
msg.value Must equal priority snapshot fee (currently 0.005 ETH)
Fee configured Priority snapshot fee must be > 0
Fee recipient Must be configured (non-zero address)
STEP ACTION
1 Validate msg.value matches priority snapshot fee
2 Validate fee is configured
3 Forward ETH to fee recipient address
4 Trigger balance snapshot for caller (or global)
CONDITION REVERT MESSAGE
Wrong amount "Insufficient priority snapshot f..."
Fee not configured "Priority snapshot fee not config..."
Fee exceeds limit "Fee exceeds maximum (1000 ETH)"
// Reconstructed from bytecode
function requestPrioritySnapshot() external payable {
    require(prioritySnapshotFee > 0, "Priority snapshot fee not configured");
    require(prioritySnapshotFee <= 1000 ether, "Fee exceeds maximum (1000 ETH)");
    require(msg.value == prioritySnapshotFee, "Insufficient priority snapshot fee");
    require(feeRecipient != address(0), "Fee recipient not configured");

    // Forward fee to recipient
    (bool success, ) = feeRecipient.call{value: msg.value}("");
    require(success, "Fee transfer failed");

    // Trigger balance snapshot for caller (bypasses interval)
    if (isRegistered[msg.sender] == 1) {
        uint256 newBalance = IERC20(tokenAddress).balanceOf(msg.sender);
        uint256 oldBalance = userBalance[msg.sender];

        if (newBalance != oldBalance) {
            userBalance[msg.sender] = newBalance;
            lastUpdate[msg.sender] = block.timestamp;
            totalTrackedBalance = totalTrackedBalance - oldBalance + newBalance;
            rewardDebt[msg.sender] = (newBalance * rewardsPerShare) / 1e18;
        }
    }

    emit PrioritySnapshotRequested(msg.sender, msg.value);
}

canRegister(address sender)

Checks if an address is eligible to register and returns status message.

ATTRIBUTE VALUE
Location Implementation:375-395
Access Public (view)
CONDITION REQUIREMENT
Contract state Not paused
Registration Is open
Address status Not blacklisted
Address status Not already registered
Token balance >= minimum requirement
TYPE DESCRIPTION
bool Can register
Multiple strings Status/error description
// Reconstructed from bytecode
function canRegister(address sender) public view returns (bool, string memory) {
    if (paused != 0) return (false, "Contract is paused");
    if (registrationOpen != 1) return (false, "Registration closed");
    if (blacklisted[sender] != 0) return (false, "Address blacklisted");
    if (isRegistered[sender] != 0) return (false, "Already registered");

    uint256 balance = IERC20(tokenAddress).balanceOf(sender);
    if (balance < minBalanceRequired) return (false, "Insufficient XCL balance");

    return (true, "Eligible");
}

Operator Functions

depositRewards()

Deposits ETH into the reward pool for distribution to participants.

ATTRIBUTE VALUE
Location Implementation:210-221
Access Admin or Operator
Payable Yes
FLAG OBSERVATION
ETH sent is immediately available for distribution
Rewards distributed proportionally to token holdings
If no participants, ETH accumulates without updating rewardsPerShare
No cap on deposit amount
Critical function for system operation
STEP ACTION
1 Validate caller has permission
2 Validate ETH amount > 0
3 Add ETH to reward pool
4 If participants exist, increase rewardsPerShare
5 Emit deposit event
VARIABLE CHANGE
rewardPool Increased by msg.value
rewardsPerShare Increased by (10^18 * depositAmount) / totalTrackedBalance
// Reconstructed from bytecode
function depositRewards() external payable {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    require(msg.value > 0, "Amount must be positive");

    rewardPool += msg.value;

    if (totalTrackedBalance > 0) {
        rewardsPerShare += (msg.value * 1e18) / totalTrackedBalance;
    }

    emit RewardsDeposited(msg.sender, msg.value);
}
sequenceDiagram
    participant Operator
    participant Contract

    Operator->>Contract: depositRewards() + ETH
    Note over Contract: Check operator permission

    alt Not operator
        Contract-->>Operator: Revert: Not operator
    else Is operator
        alt Amount <= 0
            Contract-->>Operator: Revert: Amount must be positive
        else Amount > 0
            Contract->>Contract: Increase reward pool

            alt Participants exist
                Contract->>Contract: Calculate rewards per share
                Contract->>Contract: Increase rewardsPerShare
            else No participants
                Note over Contract: ETH stored but not<br/>distributed yet
            end

            Contract->>Contract: Emit deposit event
            Contract-->>Operator: Success
        end
    end

pause() / unpause()

Controls global pause state of the contract.

ATTRIBUTE VALUE
Location Implementation:137-153
Access Admin or Operator
FUNCTION ACTION
pause() Sets paused flag to 1
unpause() Sets paused flag to 0
// Reconstructed from bytecode
function pause() external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    paused = 1;
    emit Paused(msg.sender);
}

function unpause() external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    paused = 0;
    emit Unpaused(msg.sender);
}

openRegistration() / closeRegistration()

Controls whether new users can register.

ATTRIBUTE VALUE
Location Implementation:155-181
Access Admin or Operator
// Reconstructed from bytecode
function openRegistration() external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    registrationOpen = 1;
    emit RegistrationOpened(msg.sender);
}

function closeRegistration() external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    registrationOpen = 0;
    emit RegistrationClosed(msg.sender);
}

blacklistAddress(address)

Adds an address to the blacklist, preventing registration and claims.

ATTRIBUTE VALUE
Location Implementation:273-284
Access Admin or Operator
FLAG OBSERVATION
Does not automatically deregister if user already registered
Prevents future registrations and claims
Separate from pause functionality (address-specific)
// Reconstructed from bytecode
function blacklistAddress(address target) external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    require(target != address(0), "Invalid address");
    blacklisted[target] = 1;
    emit Blacklisted(target);
}

pauseUserRewards(address)

Pauses rewards for a specific registered user.

ATTRIBUTE VALUE
Location Implementation:286-297
Access Admin or Operator
// Reconstructed from bytecode
function pauseUserRewards(address target) external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    require(isRegistered[target] == 1, "Not registered");
    userRewardsPaused[target] = 1;
    emit UserRewardsPaused(target);
}

forceDeregister(address, bool)

Forcibly deregisters a user, optionally claiming their rewards first.

ATTRIBUTE VALUE
Location Implementation:680-727
Access Admin or Operator
FLAG OBSERVATION
Admin/operator can remove users without their consent
Can choose whether to claim user's rewards first
Clears pause status when removing
// Reconstructed from bytecode
function forceDeregister(address target, bool claimFirst) external {
    require(
        msg.sender == adminAddress || operators[msg.sender] == 1,
        "Not operator"
    );
    require(isRegistered[target] == 1, "Not registered");

    if (claimFirst) {
        uint256 pending = (userBalance[target] * rewardsPerShare / 1e18)
                          - rewardDebt[target];
        if (pending > 0 && rewardPool >= pending && address(this).balance >= pending) {
            rewardPool -= pending;
            totalClaimed[target] += pending;
            totalDistributed += pending;
            (bool success, ) = target.call{value: pending}("");
            require(success, "ETH transfer failed");
        }
    }

    totalTrackedBalance -= userBalance[target];

    // Array compaction
    uint256 idx = participantIndex[target];
    uint256 lastIdx = participants.length - 1;
    if (idx != lastIdx) {
        address lastParticipant = participants[lastIdx];
        participants[idx] = lastParticipant;
        participantIndex[lastParticipant] = idx;
    }
    participants.pop();

    // Clear all user state
    isRegistered[target] = 0;
    userBalance[target] = 0;
    lastUpdate[target] = 0;
    rewardDebt[target] = 0;
    participantIndex[target] = 0;
    userRewardsPaused[target] = 0;
    participantCount -= 1;

    emit ForceDeregistered(target, claimFirst);
}

globalBalanceSnapshot()

Updates token balance snapshots for all registered participants.

ATTRIBUTE VALUE
Location Implementation:491-529
Access Anyone
FLAG OBSERVATION
Can be called by anyone (public utility function)
Rate-limited by interval to prevent spam
Gas cost scales linearly with participant count
Critical for maintaining accurate distribution
// Reconstructed from bytecode
function globalBalanceSnapshot() external {
    require(
        block.timestamp >= lastSnapshotTime + snapshotInterval,
        "Interval not elapsed"
    );

    uint256 newTotal = 0;

    for (uint256 i = 0; i < participants.length; i++) {
        address participant = participants[i];
        uint256 oldBalance = userBalance[participant];
        uint256 newBalance = IERC20(tokenAddress).balanceOf(participant);

        if (newBalance != oldBalance) {
            userBalance[participant] = newBalance;
            emit BalanceUpdated(participant, oldBalance, newBalance);
        }

        newTotal += newBalance;
    }

    totalTrackedBalance = newTotal;
    lastSnapshotTime = block.timestamp;

    emit GlobalSnapshotCompleted(participants.length, newTotal);
}
flowchart TD
    Start([globalSnapshot called])

    Start --> Check1{Interval Elapsed?}
    Check1 -->|No| Revert1[Revert: Interval not elapsed]
    Check1 -->|Yes| Init[Initialize: idx=0, sum=0]

    Init --> Loop{idx < participants.length?}
    Loop -->|No| UpdateTotal[Update Total Tracked Balance]
    Loop -->|Yes| QueryBalance[Query participant balance]

    QueryBalance --> Check2{Balance Changed?}
    Check2 -->|No| AddToSum[Add old balance to sum]
    Check2 -->|Yes| UpdateUser[Update user balance]

    UpdateUser --> EmitUserEvent[Emit balance change event]
    EmitUserEvent --> AddNewToSum[Add new balance to sum]

    AddToSum --> Increment[idx++]
    AddNewToSum --> Increment
    Increment --> Loop

    UpdateTotal --> UpdateTime[Update last snapshot time]
    UpdateTime --> EmitGlobal[Emit global snapshot event]
    EmitGlobal --> Success([Snapshot Complete])

    style Start fill:#e1f5ff
    style Success fill:#e1ffe1
    style Revert1 fill:#ffe1e1

Admin Functions

addOperator(address newOperator)

Grants operator role to a new address.

ATTRIBUTE VALUE
Location Implementation:260-271
Access Admin only
CONDITION REQUIREMENT
Caller Is admin
Address Not zero
Status Not already an operator
// Reconstructed from bytecode
function addOperator(address newOperator) external {
    require(msg.sender == adminAddress, "Not admin");
    require(newOperator != address(0), "Invalid address");
    require(operators[newOperator] == 0, "Already an operator");

    operators[newOperator] = 1;
    operatorCount += 1;

    emit OperatorAdded(newOperator);
}

queueAdminTransfer(address) / acceptAdminTransfer()

Two-step admin transfer with 24-hour timelock.

ATTRIBUTE VALUE
Access Admin (queue) / Pending Admin (accept)
Timelock 24 hours
FLAG OBSERVATION
Two-step process prevents accidental transfers
Pending admin must actively accept
24-hour delay provides warning
// Reconstructed from bytecode
function queueAdminTransfer(address newAdmin) external {
    require(msg.sender == adminAddress, "Not admin");
    require(newAdmin != address(0), "Invalid address");

    bytes32 key = keccak256(abi.encodePacked("adminTransfer", newAdmin));
    timelockQueue[key] = block.timestamp + 24 hours;
    pendingAdminAddress = newAdmin;

    emit AdminTransferQueued(newAdmin, block.timestamp + 24 hours);
}

function acceptAdminTransfer() external {
    require(msg.sender == pendingAdminAddress, "Not pending admin");

    bytes32 key = keccak256(abi.encodePacked("adminTransfer", msg.sender));
    require(timelockQueue[key] != 0, "No pending transfer");
    require(block.timestamp >= timelockQueue[key], "Timelock not elapsed");

    adminAddress = msg.sender;
    pendingAdminAddress = address(0);
    timelockQueue[key] = 0;

    emit AdminTransferred(msg.sender);
}

queueOperatorRemoval(address) / executeOperatorRemoval(address, uint256)

Queues and executes operator removal with 24-hour timelock.

ATTRIBUTE VALUE
Access Admin only
Timelock 24 hours
// Reconstructed from bytecode
function queueOperatorRemoval(address operator) external {
    require(msg.sender == adminAddress, "Not admin");
    require(operators[operator] == 1, "Not an operator");

    bytes32 key = keccak256(abi.encodePacked("removeOperator", operator));
    timelockQueue[key] = block.timestamp + 24 hours;

    emit OperatorRemovalQueued(operator, block.timestamp + 24 hours);
}

function executeOperatorRemoval(address operator, uint256 queuedAt) external {
    require(msg.sender == adminAddress, "Not admin");

    bytes32 key = keccak256(abi.encodePacked("removeOperator", operator));
    require(timelockQueue[key] != 0, "Not queued");
    require(block.timestamp >= timelockQueue[key], "Timelock not elapsed");

    operators[operator] = 0;
    operatorCount -= 1;
    timelockQueue[key] = 0;

    emit OperatorRemoved(operator);
}

queueParameterChange(uint256) / executeParameterChange(uint256, uint256)

Queues and executes minimum balance requirement change with timelock.

ATTRIBUTE VALUE
Access Admin only
Timelock 24 hours
// Reconstructed from bytecode
function queueParameterChange(uint256 newMinBalance) external {
    require(msg.sender == adminAddress, "Not admin");

    bytes32 key = keccak256(abi.encodePacked("paramChange", newMinBalance));
    timelockQueue[key] = block.timestamp + 24 hours;

    emit ParameterChangeQueued(newMinBalance, block.timestamp + 24 hours);
}

function executeParameterChange(uint256 newMinBalance, uint256 queuedAt) external {
    require(msg.sender == adminAddress, "Not admin");

    bytes32 key = keccak256(abi.encodePacked("paramChange", newMinBalance));
    require(timelockQueue[key] != 0, "Not queued");
    require(block.timestamp >= timelockQueue[key], "Timelock not elapsed");

    minBalanceRequired = newMinBalance;
    timelockQueue[key] = 0;

    emit ParameterChanged(newMinBalance);
}

queueEmergencyWithdrawal(...) / executeEmergencyWithdrawal(...)

Queues and executes emergency withdrawal of ETH or ERC20 tokens.

ATTRIBUTE VALUE
Location Implementation:350-360 (queue), 531-566 (execute)
Access Admin only
Timelock 24 hours
FLAG OBSERVATION
Critical emergency function to recover funds
Can withdraw any ERC20 token (including the tracked token)
Can withdraw ETH from reward pool
24-hour timelock provides warning period
Could be used to drain the contract
// Reconstructed from bytecode
function queueEmergencyWithdrawal(
    address token,
    address recipient,
    uint256 amount
) external {
    require(msg.sender == adminAddress, "Not admin");

    bytes32 key = keccak256(
        abi.encodePacked("emergencyWithdraw", token, recipient, amount)
    );
    timelockQueue[key] = block.timestamp + 24 hours;

    emit EmergencyWithdrawalQueued(token, recipient, amount, block.timestamp + 24 hours);
}

function executeEmergencyWithdrawal(
    address token,
    address recipient,
    uint256 amount
) external {
    require(msg.sender == adminAddress, "Not admin");

    bytes32 key = keccak256(
        abi.encodePacked("emergencyWithdraw", token, recipient, amount)
    );
    require(timelockQueue[key] != 0, "Not queued");
    require(block.timestamp >= timelockQueue[key], "Timelock not elapsed");

    timelockQueue[key] = 0;

    if (token == address(0)) {
        // Withdraw ETH
        rewardPool -= amount;
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "ETH transfer failed");
    } else {
        // Withdraw ERC20
        IERC20(token).transfer(recipient, amount);
    }

    emit EmergencyWithdrawalExecuted(token, recipient, amount);
}

manualBalanceUpdate(address, uint256)

Allows admin to manually set a user's tracked token balance.

ATTRIBUTE VALUE
Location Implementation:362-373
Access Admin only
FLAG OBSERVATION
Powerful admin function that can manipulate reward distribution
Bypasses external token contract query
Could be used to fix discrepancies or manipulate system
No validation that new balance matches actual token balance
// Reconstructed from bytecode
function manualBalanceUpdate(address target, uint256 newBalance) external {
    require(msg.sender == adminAddress, "Not admin");
    require(isRegistered[target] == 1, "Not registered");

    uint256 oldBalance = userBalance[target];
    userBalance[target] = newBalance;
    totalTrackedBalance = totalTrackedBalance - oldBalance + newBalance;
    rewardDebt[target] = (newBalance * rewardsPerShare) / 1e18;

    emit ManualBalanceUpdate(target, oldBalance, newBalance);
}

cancelQueuedAction(bytes32)

Cancels a queued timelock action.

ATTRIBUTE VALUE
Location Implementation:192-200
Access Admin only
// Reconstructed from bytecode
function cancelQueuedAction(bytes32 key) external {
    require(msg.sender == adminAddress, "Not admin");
    require(timelockQueue[key] != 0, "Action not queued");

    timelockQueue[key] = 0;

    emit ActionCancelled(key);
}

removeFromBlacklist(address)

Removes an address from the blacklist.

ATTRIBUTE VALUE
Location Implementation:233-241
Access Admin only
// Reconstructed from bytecode
function removeFromBlacklist(address target) external {
    require(msg.sender == adminAddress, "Not admin");
    require(blacklisted[target] == 1, "Not blacklisted");

    blacklisted[target] = 0;

    emit RemovedFromBlacklist(target);
}

unpauseUserRewards(address)

Unpauses rewards for a specific user.

ATTRIBUTE VALUE
Location Implementation:223-231
Access Admin only
// Reconstructed from bytecode
function unpauseUserRewards(address target) external {
    require(msg.sender == adminAddress, "Not admin");
    require(userRewardsPaused[target] == 1, "Not paused");

    userRewardsPaused[target] = 0;

    emit UserRewardsUnpaused(target);
}

setFeeRecipient(address) (Added v1.0.0)

Sets the address that receives priority snapshot fee payments.

ATTRIBUTE VALUE
Selector 0x3c85102a
Access Admin only
FLAG OBSERVATION
Can redirect all fee revenue to any address
No timelock protection observed on this function
Currently set to EOA 0xe1f3fbb1...37264a84
// Reconstructed from bytecode
function setFeeRecipient(address newRecipient) external {
    require(msg.sender == adminAddress, "Not admin");
    require(newRecipient != address(0), "Invalid address");

    feeRecipient = newRecipient;

    emit FeeRecipientUpdated(newRecipient);
}

setPrioritySnapshotFee(uint256) (Added v1.0.0)

Sets the ETH amount required for priority snapshot requests.

ATTRIBUTE VALUE
Selector 0xeb209249
Access Admin only
FLAG OBSERVATION
Maximum fee appears to be capped at 1000 ETH based on error string
Currently set to 0.005 ETH
// Reconstructed from bytecode
function setPrioritySnapshotFee(uint256 newFee) external {
    require(msg.sender == adminAddress, "Not admin");
    require(newFee <= 1000 ether, "Fee exceeds maximum (1000 ETH)");

    prioritySnapshotFee = newFee;

    emit PrioritySnapshotFeeUpdated(newFee);
}

View Functions

Contract State Functions

FUNCTION RETURNS PURPOSE
initialized() bool Whether contract has been initialized
admin() address Current admin address
pendingAdmin() address Pending admin during transfer
paused() bool Whether contract is globally paused
rewardPool() uint256 Total ETH available for rewards
totalDistributed() uint256 Cumulative ETH distributed
participantCount() uint256 Number of registered participants

User State Functions

FUNCTION RETURNS PURPOSE
isRegistered(address) bool Whether address is registered
blacklisted(address) bool Whether address is blacklisted
operators(address) bool Whether address has operator role
totalClaimed(address) uint256 Total rewards claimed by address
participants(uint256) address Participant at specific index

Statistics Functions

FUNCTION RETURNS PURPOSE
getStats() Multiple Comprehensive contract statistics
pendingRewards(address) uint256 Pending reward amount for address
userStats(address) Multiple Share percentage, tracked balance, current balance
getParticipants(uint256, uint256) address[], uint256[] Paginated list of participants and balances

Functions Added in v1.0.0 Upgrade

FUNCTION RETURNS PURPOSE
VERSION() string Implementation version ("1.0.0")
feeRecipient() (0x407c8855) address Address receiving priority snapshot fees
snapshotFee() (0x5d2e4c1c) uint256 Base snapshot fee (currently 0.001 ETH)
prioritySnapshotFee() (0xc27762b5) uint256 Priority snapshot fee (currently 0.005 ETH)