MemeFolioThresholdDistributor

0x202fad2e905077bfa3d658455b287b85eeb58dc5

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,270 bytes
ABI entries
141
67 read · 16 write
License
none

Contract information

Address
0x202fad2e905077bfa3d658455b287b85eeb58dc5
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x0718416c8c…35fb70b06a

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (67)

ADMIN_TRANSFER_DELAY()uint48
COMMUNITY_TOKEN_COUNT()uint256
CORE_TOKEN_COUNT()uint256
DEFAULT_ADMIN_ROLE()bytes32
DISTRIBUTION_INTERVAL()uint256
MAX_REWARD_TOKEN_COUNT()uint256
OPERATOR_ROLE()bytes32
PAUSER_ROLE()bytes32
activeCycleId()bytes32
cancelledCycleIds(bytes32)bool
communityController()address
communityTokenAt(uint256)address
completedCycleIds(bytes32)bool
coreTokenAt(uint256)address
cursor()uint256
cycleActive()bool
cyclePhase()uint8
cycleRewardTokenAt(uint256)address
cycleRewardTokenCount()uint256
defaultAdmin()address
defaultAdminDelay()uint48
defaultAdminDelayIncreaseWait()uint48
eligible()uint256
excludedAccountAt(uint256)address
excludedAccountCount()uint256
excludedFromRewards(address)bool
expectedHolderCount()uint256
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
isCoreRewardToken(address)bool
lastCompletedHolderCount()uint256
lastProcessedHolder()address
lastVerifiedHolder()address
maxDistributionBatchSize()uint256
minHolderBalance()uint256
nextDistribution()uint256
owner()address
paidHolderCount()uint256
paused()bool
payoutHolderSetHash()bytes32
pendingCommunityExecutableAt()uint256
pendingCommunityExpiresAt()uint256
pendingCommunityProposalId()bytes32
pendingCommunityTokenAt(uint256)address
pendingCoreExecutableAt()uint256
pendingCoreProposalId()bytes32
pendingCoreTokenAt(uint256)address
pendingDefaultAdmin()address, uint48
pendingDefaultAdminDelay()uint48, uint48
potAt(uint256)uint256
processedEligibleBalance()uint256
processedSourceEligibleBalance()uint256
rewardRegistry()address
rewardTokenAt(uint256)address
rewardTokenCount()uint256
snapshotBlock()uint256
snapshotBlockHash()bytes32
sourceEligible()uint256
sourceExpectedHolderCount()uint256
sourceHolderMinimum()uint256
sourceToken()address
supportsInterface(bytes4)bool
thresholdVerificationRequired()bool
treasury()address
usedCycleIds(bytes32)bool
verificationCursor()uint256
verifiedHolderSetHash()bytes32

Events (18)

BatchPaidCycleCancelledCycleStartedDefaultAdminDelayChangeCanceledDefaultAdminDelayChangeScheduledDefaultAdminTransferCanceledDefaultAdminTransferScheduledDistributionCompletedForeignTokenRescuedNativeRescuedPausedPayoutSkippedRoleAdminChangedRoleGrantedRoleRevokedSnapshotVerifiedUnpausedVerificationBatchProcessed

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "contract IMemeFolioThresholdSnapshotToken",
        "name": "sourceToken_",
        "type": "address"
      },
      {
        "internalType": "contract IMemeFolioRewardRegistry",
        "name": "rewardRegistry_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "minHolderBalance_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maxDistributionBatchSize_",
        "type": "uint256"
      },
      {
        "internalType": "address[]",
        "name": "excludedAccounts_",
        "type": "address[]"
      },
      {
        "internalType": "uint256",
        "name": "initialDistributionTimestamp_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "name": "AccessControlEnforcedDefaultAdminDelay",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AccessControlEnforcedDefaultAdminRules",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "defaultAdmin",
        "type": "address"
      }
    ],
    "name": "AccessControlInvalidDefaultAdmin",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "expected",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "actual",
        "type": "bytes32"
      }
    ],
    "name": "ActiveCycleMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maximum",
        "type": "uint256"
      }
    ],
    "name": "BatchTooLarge",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "holdersPaid",
        "type": "uint256"
      }
    ],
    "name": "CycleAlreadyPaid",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "CycleAlreadyUsed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CycleInProgress",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "processed",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      }
    ],
    "name": "EligibleBalanceExceeded",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "processed",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      }
    ],
    "name": "EligibleBalanceMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyBatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minimum",
        "type": "uint256"
      }
    ],
    "name": "HolderBelowMinimum",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "processed",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "expected",
        "type": "bytes32"
      }
    ],
    "name": "HolderSetHashMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidBatchSize",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "actual",
        "type": "uint256"
      }
    ],
    "name": "InvalidCursor",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidHolderCount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "previous",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "current",
        "type": "address"
      }
    ],
    "name": "InvalidHolderOrder",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "currentTimestamp",
        "type": "uint256"
      }
    ],
    "name": "InvalidInitialDistributionTimestamp",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      }
    ],
    "name": "InvalidRewardTokenCount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "snapshotBlock",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "currentBlock",
        "type": "uint256"
      }
    ],
    "name": "InvalidSnapshotBlock",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "sourceMinimum",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "rewardMinimum",
        "type": "uint256"
      }
    ],
    "name": "InvalidThreshold",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NativeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoCycle",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoDistributionPot",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoEligibleHolders",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "ProtectedToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SnapshotMetadataMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "nextAllowedTimestamp",
        "type": "uint256"
      }
    ],
    "name": "TooEarly",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "VerificationAlreadyComplete",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "VerificationIncomplete",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fromIndex",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toIndex",
        "type": "uint256"
      }
    ],
    "name": "BatchPaid",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "CycleCancelled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "snapshotBlock",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "snapshotBlockHash",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "sourceHolders",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "sourceEligibleBalance",
        "type": "uint256"
      }
    ],
    "name": "CycleStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "DefaultAdminDelayChangeCanceled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      },
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "effectSchedule",
        "type": "uint48"
      }
    ],
    "name": "DefaultAdminDelayChangeScheduled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "DefaultAdminTransferCanceled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "acceptSchedule",
        "type": "uint48"
      }
    ],
    "name": "DefaultAdminTransferScheduled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "frozenPot",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "holders",
        "type": "uint256"
      }
    ],
    "name": "DistributionCompleted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ForeignTokenRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "NativeRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "PayoutSkipped",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "rewardHolders",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "rewardEligibleBalance",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "holderSetHash",
        "type": "bytes32"
      }
    ],
    "name": "SnapshotVerified",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fromIndex",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toIndex",
        "type": "uint256"
      }
    ],
    "name": "VerificationBatchProcessed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ADMIN_TRANSFER_DELAY",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "COMMUNITY_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CORE_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DISTRIBUTION_INTERVAL",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_REWARD_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "OPERATOR_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PAUSER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "activeCycleId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      }
    ],
    "name": "beginDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelCurrentCycle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "cancelledCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "cancelled",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      }
    ],
    "name": "changeDefaultAdminDelay",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "communityController",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "communityTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "completedCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "completed",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "coreTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cursor",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cycleActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cyclePhase",
    "outputs": [
      {
        "internalType": "enum MemeFolioThresholdDistributor.CyclePhase",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "cycleRewardTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cycleRewardTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdmin",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdminDelay",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdminDelayIncreaseWait",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "expectedCursor",
        "type": "uint256"
      },
      {
        "internalType": "address[]",
        "name": "holders",
        "type": "address[]"
      }
    ],
    "name": "distributeBatch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eligible",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "excludedAccountAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "excludedAccountCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "excludedFromRewards",
    "outputs": [
      {
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "expectedHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isCoreRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastCompletedHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastProcessedHolder",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastVerifiedHolder",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxDistributionBatchSize",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minHolderBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextDistribution",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paidHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "payoutHolderSetHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityExecutableAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityExpiresAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityProposalId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "pendingCommunityTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCoreExecutableAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCoreProposalId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "pendingCoreTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingDefaultAdmin",
    "outputs": [
      {
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      },
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingDefaultAdminDelay",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      },
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "potAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "processedEligibleBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "processedSourceEligibleBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueForeignToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueNative",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardRegistry",
    "outputs": [
      {
        "internalType": "contract IMemeFolioRewardRegistry",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "rewardTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rollbackDefaultAdminDelay",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "snapshotBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "snapshotBlockHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sourceEligible",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sourceExpectedHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sourceHolderMinimum",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sourceToken",
    "outputs": [
      {
        "internalType": "contract IMemeFolioThresholdSnapshotToken",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "snapshotBlock_",
        "type": "uint256"
      },
      {
        "internalType": "bytes32",
        "name": "snapshotBlockHash_",
        "type": "bytes32"
      }
    ],
    "name": "startCycle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "thresholdVerificationRequired",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "usedCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "used",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "verificationCursor",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "verifiedHolderSetHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "expectedCursor",
        "type": "uint256"
      },
      {
        "internalType": "address[]",
        "name": "holders",
        "type": "address[]"
      }
    ],
    "name": "verifyBatch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {
    AccessControlDefaultAdminRules
} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IMemeFolioThresholdSnapshotToken is IERC20 {
    function minHolderBalance() external view returns (uint256);
    function getPastBalance(address account, uint256 blockNumber) external view returns (uint256);
    function getPastEligibleSupply(uint256 blockNumber) external view returns (uint256);
    function getPastEligibleHolderCount(uint256 blockNumber) external view returns (uint256);
}

interface IMemeFolioRewardRegistry {
    function rewardTokenCount() external view returns (uint256);
    function rewardTokenAt(uint256 index) external view returns (address);
    function coreTokenAt(uint256 index) external view returns (address);
    function communityTokenAt(uint256 index) external view returns (address);
    function isCoreRewardToken(address token) external view returns (bool);
    function communityController() external view returns (address);
    function pendingCoreProposalId() external view returns (bytes32);
    function pendingCoreExecutableAt() external view returns (uint256);
    function pendingCoreTokenAt(uint256 index) external view returns (address);
    function pendingCommunityProposalId() external view returns (bytes32);
    function pendingCommunityExecutableAt() external view returns (uint256);
    function pendingCommunityExpiresAt() external view returns (uint256);
    function pendingCommunityTokenAt(uint256 index) external view returns (address);
}

/// @title MemeFolioThresholdDistributor
/// @notice Preserves the token's complete historical-holder reconciliation while applying a higher
///         immutable reward threshold in a separate verification phase before any payout occurs.
/// @dev The existing governed distributor remains the reward-token registry so an in-flight voting
///      round and future community basket changes do not need to be migrated or restarted.
contract MemeFolioThresholdDistributor is
    AccessControlDefaultAdminRules,
    Pausable,
    ReentrancyGuard
{
    using SafeERC20 for IERC20;

    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    uint48 public constant ADMIN_TRANSFER_DELAY = 2 days;
    uint256 public constant DISTRIBUTION_INTERVAL = 15 minutes;
    uint256 public constant CORE_TOKEN_COUNT = 5;
    uint256 public constant COMMUNITY_TOKEN_COUNT = 3;
    uint256 public constant MAX_REWARD_TOKEN_COUNT = 8;

    enum CyclePhase {
        IDLE,
        VERIFYING,
        PAYING
    }

    IMemeFolioThresholdSnapshotToken public immutable sourceToken;
    IMemeFolioRewardRegistry public immutable rewardRegistry;
    address public immutable treasury;
    uint256 public immutable sourceHolderMinimum;
    uint256 public immutable minHolderBalance;
    uint256 public immutable maxDistributionBatchSize;

    address[] private _excludedAccounts;
    IERC20[] private _cycleRewardTokens;
    uint256[] private _pots;

    mapping(address account => bool excluded) public excludedFromRewards;
    mapping(bytes32 cycleId => bool used) public usedCycleIds;
    mapping(bytes32 cycleId => bool completed) public completedCycleIds;
    mapping(bytes32 cycleId => bool cancelled) public cancelledCycleIds;

    CyclePhase public cyclePhase;
    bytes32 public activeCycleId;
    uint256 public snapshotBlock;
    bytes32 public snapshotBlockHash;
    uint256 public sourceExpectedHolderCount;
    uint256 public sourceEligible;
    uint256 public verificationCursor;
    uint256 public processedSourceEligibleBalance;
    address public lastVerifiedHolder;
    uint256 public expectedHolderCount;
    uint256 public paidHolderCount;
    uint256 public eligible;
    uint256 public processedEligibleBalance;
    uint256 public cursor;
    uint256 public nextDistribution;
    address public lastProcessedHolder;
    bytes32 public verifiedHolderSetHash;
    bytes32 public payoutHolderSetHash;
    uint256 public lastCompletedHolderCount;
    bool public cycleActive;

    error ActiveCycleMismatch(bytes32 expected, bytes32 actual);
    error BatchTooLarge(uint256 supplied, uint256 maximum);
    error CycleAlreadyPaid(uint256 holdersPaid);
    error CycleAlreadyUsed(bytes32 cycleId);
    error CycleInProgress();
    error EmptyBatch();
    error EligibleBalanceExceeded(uint256 processed, uint256 expected);
    error EligibleBalanceMismatch(uint256 processed, uint256 expected);
    error HolderBelowMinimum(address holder, uint256 balance, uint256 minimum);
    error HolderSetHashMismatch(bytes32 processed, bytes32 expected);
    error InvalidAddress();
    error InvalidBatchSize();
    error InvalidCursor(uint256 expected, uint256 actual);
    error InvalidHolderCount();
    error InvalidHolderOrder(address previous, address current);
    error InvalidInitialDistributionTimestamp(uint256 supplied, uint256 currentTimestamp);
    error InvalidRewardTokenCount(uint256 supplied);
    error InvalidSnapshotBlock(uint256 snapshotBlock, uint256 currentBlock);
    error InvalidThreshold(uint256 sourceMinimum, uint256 rewardMinimum);
    error NativeTransferFailed();
    error NoCycle();
    error NoDistributionPot();
    error NoEligibleHolders();
    error ProtectedToken(address token);
    error SnapshotMetadataMismatch();
    error TooEarly(uint256 nextAllowedTimestamp);
    error VerificationIncomplete();
    error VerificationAlreadyComplete();

    event CycleStarted(
        bytes32 indexed cycleId,
        uint256 indexed snapshotBlock,
        bytes32 snapshotBlockHash,
        uint256 sourceHolders,
        uint256 sourceEligibleBalance
    );
    event VerificationBatchProcessed(bytes32 indexed cycleId, uint256 fromIndex, uint256 toIndex);
    event SnapshotVerified(
        bytes32 indexed cycleId,
        uint256 rewardHolders,
        uint256 rewardEligibleBalance,
        bytes32 holderSetHash
    );
    event BatchPaid(bytes32 indexed cycleId, uint256 fromIndex, uint256 toIndex);
    event PayoutSkipped(
        bytes32 indexed cycleId, address indexed token, address indexed holder, uint256 amount
    );
    event DistributionCompleted(
        bytes32 indexed cycleId, address indexed token, uint256 frozenPot, uint256 holders
    );
    event CycleCancelled(bytes32 indexed cycleId);
    event NativeRescued(address indexed treasury, uint256 amount);
    event ForeignTokenRescued(address indexed token, address indexed treasury, uint256 amount);

    constructor(
        address admin,
        address operator,
        address treasury_,
        IMemeFolioThresholdSnapshotToken sourceToken_,
        IMemeFolioRewardRegistry rewardRegistry_,
        uint256 minHolderBalance_,
        uint256 maxDistributionBatchSize_,
        address[] memory excludedAccounts_,
        uint256 initialDistributionTimestamp_
    ) AccessControlDefaultAdminRules(ADMIN_TRANSFER_DELAY, admin) {
        if (
            operator == address(0) || treasury_ == address(0) || address(sourceToken_) == address(0)
                || address(sourceToken_).code.length == 0 || address(rewardRegistry_) == address(0)
                || address(rewardRegistry_).code.length == 0
        ) revert InvalidAddress();
        if (maxDistributionBatchSize_ == 0 || maxDistributionBatchSize_ > 100) {
            revert InvalidBatchSize();
        }
        if (initialDistributionTimestamp_ != 0 && initialDistributionTimestamp_ < block.timestamp) {
            revert InvalidInitialDistributionTimestamp(
                initialDistributionTimestamp_, block.timestamp
            );
        }

        uint256 sourceMinimum = sourceToken_.minHolderBalance();
        if (sourceMinimum == 0 || minHolderBalance_ <= sourceMinimum) {
            revert InvalidThreshold(sourceMinimum, minHolderBalance_);
        }

        sourceToken = sourceToken_;
        rewardRegistry = rewardRegistry_;
        treasury = treasury_;
        sourceHolderMinimum = sourceMinimum;
        minHolderBalance = minHolderBalance_;
        maxDistributionBatchSize = maxDistributionBatchSize_;
        nextDistribution = initialDistributionTimestamp_;

        _exclude(admin);
        _exclude(operator);
        _exclude(treasury_);
        _exclude(address(sourceToken_));
        _exclude(address(rewardRegistry_));
        _exclude(address(this));
        for (uint256 i; i < excludedAccounts_.length; ++i) {
            _exclude(excludedAccounts_[i]);
        }

        _grantRole(OPERATOR_ROLE, operator);
        _grantRole(PAUSER_ROLE, admin);
    }

    receive() external payable { }

    function thresholdVerificationRequired() external pure returns (bool) {
        return true;
    }

    function communityController() external view returns (address) {
        return rewardRegistry.communityController();
    }

    function rewardTokenCount() external view returns (uint256) {
        return rewardRegistry.rewardTokenCount();
    }

    function rewardTokenAt(uint256 index) external view returns (address) {
        return rewardRegistry.rewardTokenAt(index);
    }

    function coreTokenAt(uint256 index) external view returns (address) {
        return rewardRegistry.coreTokenAt(index);
    }

    function communityTokenAt(uint256 index) external view returns (address) {
        return rewardRegistry.communityTokenAt(index);
    }

    function isCoreRewardToken(address token) external view returns (bool) {
        return rewardRegistry.isCoreRewardToken(token);
    }

    function pendingCoreProposalId() external view returns (bytes32) {
        return rewardRegistry.pendingCoreProposalId();
    }

    function pendingCoreExecutableAt() external view returns (uint256) {
        return rewardRegistry.pendingCoreExecutableAt();
    }

    function pendingCoreTokenAt(uint256 index) external view returns (address) {
        return rewardRegistry.pendingCoreTokenAt(index);
    }

    function pendingCommunityProposalId() external view returns (bytes32) {
        return rewardRegistry.pendingCommunityProposalId();
    }

    function pendingCommunityExecutableAt() external view returns (uint256) {
        return rewardRegistry.pendingCommunityExecutableAt();
    }

    function pendingCommunityExpiresAt() external view returns (uint256) {
        return rewardRegistry.pendingCommunityExpiresAt();
    }

    function pendingCommunityTokenAt(uint256 index) external view returns (address) {
        return rewardRegistry.pendingCommunityTokenAt(index);
    }

    function cycleRewardTokenCount() external view returns (uint256) {
        return _cycleRewardTokens.length;
    }

    function cycleRewardTokenAt(uint256 index) external view returns (address) {
        return address(_cycleRewardTokens[index]);
    }

    function potAt(uint256 index) external view returns (uint256) {
        return _pots[index];
    }

    function excludedAccountCount() external view returns (uint256) {
        return _excludedAccounts.length;
    }

    function excludedAccountAt(uint256 index) external view returns (address) {
        return _excludedAccounts[index];
    }

    function startCycle(bytes32 cycleId, uint256 snapshotBlock_, bytes32 snapshotBlockHash_)
        external
        onlyRole(OPERATOR_ROLE)
        whenNotPaused
        nonReentrant
    {
        if (cycleActive) revert CycleInProgress();
        if (block.timestamp < nextDistribution) revert TooEarly(nextDistribution);
        if (cycleId == bytes32(0) || snapshotBlockHash_ == bytes32(0)) {
            revert SnapshotMetadataMismatch();
        }
        if (
            snapshotBlock_ >= block.number || block.number - snapshotBlock_ > 256
                || blockhash(snapshotBlock_) != snapshotBlockHash_
        ) revert InvalidSnapshotBlock(snapshotBlock_, block.number);
        if (usedCycleIds[cycleId]) revert CycleAlreadyUsed(cycleId);

        uint256 holderCount = sourceToken.getPastEligibleHolderCount(snapshotBlock_);
        if (holderCount == 0) revert InvalidHolderCount();
        uint256 sourceEligible_ = sourceToken.getPastEligibleSupply(snapshotBlock_);

        uint256 rewardCount = rewardRegistry.rewardTokenCount();
        if (rewardCount != CORE_TOKEN_COUNT && rewardCount != MAX_REWARD_TOKEN_COUNT) {
            revert InvalidRewardTokenCount(rewardCount);
        }
        delete _cycleRewardTokens;
        delete _pots;
        bool hasPot;
        for (uint256 i; i < rewardCount; ++i) {
            address token = rewardRegistry.rewardTokenAt(i);
            _validateRewardToken(token, i);
            IERC20 reward = IERC20(token);
            _cycleRewardTokens.push(reward);
            uint256 pot = _safeBalanceOf(reward);
            _pots.push(pot);
            if (pot != 0) hasPot = true;
        }
        if (!hasPot) revert NoDistributionPot();

        for (uint256 i; i < _excludedAccounts.length; ++i) {
            (holderCount, sourceEligible_) = _subtractExcluded(
                _excludedAccounts[i], holderCount, sourceEligible_, snapshotBlock_
            );
        }
        for (uint256 i; i < rewardCount; ++i) {
            address token = address(_cycleRewardTokens[i]);
            if (!excludedFromRewards[token]) {
                (holderCount, sourceEligible_) =
                    _subtractExcluded(token, holderCount, sourceEligible_, snapshotBlock_);
            }
        }
        if (holderCount == 0 || sourceEligible_ == 0) revert NoEligibleHolders();

        usedCycleIds[cycleId] = true;
        activeCycleId = cycleId;
        snapshotBlock = snapshotBlock_;
        snapshotBlockHash = snapshotBlockHash_;
        sourceExpectedHolderCount = holderCount;
        sourceEligible = sourceEligible_;
        verificationCursor = 0;
        processedSourceEligibleBalance = 0;
        lastVerifiedHolder = address(0);
        expectedHolderCount = 0;
        paidHolderCount = 0;
        eligible = 0;
        processedEligibleBalance = 0;
        cursor = 0;
        lastProcessedHolder = address(0);
        verifiedHolderSetHash = bytes32(0);
        payoutHolderSetHash = bytes32(0);
        cyclePhase = CyclePhase.VERIFYING;
        cycleActive = true;
        nextDistribution = block.timestamp + DISTRIBUTION_INTERVAL;
        emit CycleStarted(cycleId, snapshotBlock_, snapshotBlockHash_, holderCount, sourceEligible_);
    }

    function verifyBatch(bytes32 cycleId, uint256 expectedCursor, address[] calldata holders)
        external
        onlyRole(OPERATOR_ROLE)
        whenNotPaused
        nonReentrant
    {
        if (!cycleActive) revert NoCycle();
        if (cyclePhase != CyclePhase.VERIFYING) revert VerificationAlreadyComplete();
        if (cycleId != activeCycleId) revert ActiveCycleMismatch(activeCycleId, cycleId);
        if (expectedCursor != verificationCursor) {
            revert InvalidCursor(verificationCursor, expectedCursor);
        }
        uint256 count = holders.length;
        if (count == 0) revert EmptyBatch();
        if (count > maxDistributionBatchSize) {
            revert BatchTooLarge(count, maxDistributionBatchSize);
        }
        uint256 end = verificationCursor + count;
        if (end > sourceExpectedHolderCount) revert InvalidHolderCount();

        address previous = lastVerifiedHolder;
        uint256 processedSource = processedSourceEligibleBalance;
        uint256 rewardHolderCount = expectedHolderCount;
        uint256 rewardEligibleBalance = eligible;
        bytes32 holderSetHash = verifiedHolderSetHash;
        for (uint256 i; i < count; ++i) {
            address holder = holders[i];
            if (holder <= previous) revert InvalidHolderOrder(previous, holder);
            if (_isExcludedForCycle(holder)) revert InvalidAddress();
            uint256 balance = sourceToken.getPastBalance(holder, snapshotBlock);
            if (balance < sourceHolderMinimum) {
                revert HolderBelowMinimum(holder, balance, sourceHolderMinimum);
            }
            previous = holder;
            processedSource += balance;
            if (processedSource > sourceEligible) {
                revert EligibleBalanceExceeded(processedSource, sourceEligible);
            }
            if (balance >= minHolderBalance) {
                ++rewardHolderCount;
                rewardEligibleBalance += balance;
                holderSetHash = keccak256(abi.encode(holderSetHash, holder, balance));
            }
        }

        uint256 from = verificationCursor;
        verificationCursor = end;
        processedSourceEligibleBalance = processedSource;
        lastVerifiedHolder = previous;
        expectedHolderCount = rewardHolderCount;
        eligible = rewardEligibleBalance;
        verifiedHolderSetHash = holderSetHash;
        emit VerificationBatchProcessed(cycleId, from, end);

        if (end == sourceExpectedHolderCount) {
            if (processedSource != sourceEligible) {
                revert EligibleBalanceMismatch(processedSource, sourceEligible);
            }
            if (rewardHolderCount == 0 || rewardEligibleBalance == 0) revert NoEligibleHolders();
            cyclePhase = CyclePhase.PAYING;
            emit SnapshotVerified(cycleId, rewardHolderCount, rewardEligibleBalance, holderSetHash);
        }
    }

    function distributeBatch(bytes32 cycleId, uint256 expectedCursor, address[] calldata holders)
        external
        onlyRole(OPERATOR_ROLE)
        whenNotPaused
        nonReentrant
    {
        if (!cycleActive) revert NoCycle();
        if (cyclePhase != CyclePhase.PAYING) revert VerificationIncomplete();
        if (cycleId != activeCycleId) revert ActiveCycleMismatch(activeCycleId, cycleId);
        if (expectedCursor != cursor) revert InvalidCursor(cursor, expectedCursor);
        uint256 count = holders.length;
        if (count == 0) revert EmptyBatch();
        if (count > maxDistributionBatchSize) {
            revert BatchTooLarge(count, maxDistributionBatchSize);
        }
        uint256 end = cursor + count;
        if (end > expectedHolderCount) revert InvalidHolderCount();

        address previous = lastProcessedHolder;
        uint256 processed = processedEligibleBalance;
        bytes32 holderSetHash = payoutHolderSetHash;
        for (uint256 i; i < count; ++i) {
            address holder = holders[i];
            if (holder <= previous) revert InvalidHolderOrder(previous, holder);
            if (_isExcludedForCycle(holder)) revert InvalidAddress();
            uint256 balance = sourceToken.getPastBalance(holder, snapshotBlock);
            if (balance < minHolderBalance) {
                revert HolderBelowMinimum(holder, balance, minHolderBalance);
            }
            previous = holder;
            processed += balance;
            if (processed > eligible) revert EligibleBalanceExceeded(processed, eligible);
            holderSetHash = keccak256(abi.encode(holderSetHash, holder, balance));
            _payHolder(cycleId, holder, balance, eligible);
        }

        uint256 from = cursor;
        cursor = end;
        paidHolderCount = end;
        processedEligibleBalance = processed;
        lastProcessedHolder = previous;
        payoutHolderSetHash = holderSetHash;
        emit BatchPaid(cycleId, from, end);

        if (end == expectedHolderCount) {
            if (processed != eligible) revert EligibleBalanceMismatch(processed, eligible);
            if (holderSetHash != verifiedHolderSetHash) {
                revert HolderSetHashMismatch(holderSetHash, verifiedHolderSetHash);
            }
            cycleActive = false;
            cyclePhase = CyclePhase.IDLE;
            completedCycleIds[cycleId] = true;
            activeCycleId = bytes32(0);
            lastCompletedHolderCount = expectedHolderCount;
            for (uint256 i; i < _pots.length; ++i) {
                emit DistributionCompleted(
                    cycleId, address(_cycleRewardTokens[i]), _pots[i], expectedHolderCount
                );
            }
        }
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _unpause();
    }

    function cancelCurrentCycle() external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused nonReentrant {
        bytes32 cycleId = activeCycleId;
        if (cycleId == bytes32(0)) revert NoCycle();
        if (cyclePhase == CyclePhase.PAYING && cursor != 0) revert CycleAlreadyPaid(cursor);
        cycleActive = false;
        cyclePhase = CyclePhase.IDLE;
        activeCycleId = bytes32(0);
        cancelledCycleIds[cycleId] = true;
        emit CycleCancelled(cycleId);
    }

    function rescueForeignToken(IERC20 token, uint256 amount)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        whenPaused
        nonReentrant
    {
        address tokenAddress = address(token);
        if (tokenAddress == address(0)) revert InvalidAddress();
        if (_isProtectedRewardToken(tokenAddress)) revert ProtectedToken(tokenAddress);
        token.safeTransfer(treasury, amount);
        emit ForeignTokenRescued(tokenAddress, treasury, amount);
    }

    function rescueNative(uint256 amount)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        whenPaused
        nonReentrant
    {
        (bool success,) = payable(treasury).call{ value: amount }("");
        if (!success) revert NativeTransferFailed();
        emit NativeRescued(treasury, amount);
    }

    function _subtractExcluded(
        address account,
        uint256 holderCount,
        uint256 sourceEligible_,
        uint256 snapshotBlock_
    ) private view returns (uint256, uint256) {
        uint256 balance = sourceToken.getPastBalance(account, snapshotBlock_);
        if (balance >= sourceHolderMinimum) return (holderCount - 1, sourceEligible_ - balance);
        return (holderCount, sourceEligible_);
    }

    function _validateRewardToken(address token, uint256 index) private view {
        if (
            token == address(0) || token == address(sourceToken) || token == address(this)
                || token.code.length == 0
        ) revert InvalidAddress();
        for (uint256 i; i < index; ++i) {
            if (address(_cycleRewardTokens[i]) == token) revert InvalidAddress();
        }
    }

    function _isExcludedForCycle(address account) private view returns (bool) {
        if (excludedFromRewards[account]) return true;
        for (uint256 i; i < _cycleRewardTokens.length; ++i) {
            if (address(_cycleRewardTokens[i]) == account) return true;
        }
        return false;
    }

    function _isProtectedRewardToken(address token) private view returns (bool) {
        for (uint256 i; i < _cycleRewardTokens.length; ++i) {
            if (address(_cycleRewardTokens[i]) == token) return true;
        }
        uint256 activeCount = rewardRegistry.rewardTokenCount();
        for (uint256 i; i < activeCount; ++i) {
            if (rewardRegistry.rewardTokenAt(i) == token) return true;
        }
        if (rewardRegistry.pendingCoreProposalId() != bytes32(0)) {
            for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
                if (rewardRegistry.pendingCoreTokenAt(i) == token) return true;
            }
        }
        if (rewardRegistry.pendingCommunityProposalId() != bytes32(0)) {
            for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
                if (rewardRegistry.pendingCommunityTokenAt(i) == token) return true;
            }
        }
        return false;
    }

    function _exclude(address account) private {
        if (account == address(0)) revert InvalidAddress();
        if (excludedFromRewards[account]) return;
        excludedFromRewards[account] = true;
        _excludedAccounts.push(account);
    }

    function _payHolder(bytes32 cycleId, address holder, uint256 balance, uint256 eligible_)
        private
    {
        for (uint256 tokenIndex; tokenIndex < _pots.length; ++tokenIndex) {
            uint256 amount = Math.mulDiv(_pots[tokenIndex], balance, eligible_);
            if (amount == 0) continue;
            IERC20 token = _cycleRewardTokens[tokenIndex];
            if (!token.trySafeTransfer(holder, amount)) {
                emit PayoutSkipped(cycleId, address(token), holder, amount);
            }
        }
    }

    function _safeBalanceOf(IERC20 token) private view returns (uint256 bal) {
        bytes memory payload = abi.encodeCall(IERC20.balanceOf, (address(this)));
        assembly ("memory-safe") {
            let success := staticcall(gas(), token, add(payload, 0x20), mload(payload), 0x00, 0x20)
            if and(success, iszero(lt(returndatasize(), 0x20))) { bal := mload(0x00) }
        }
    }
}
Chain explorer3104msChain node96ms