Skip to content

Eligibility Parameters

BoardConfig controls who is allowed to propose and support initiatives via two separate fields:

  • proposerRequirements — gates who can call proposeInitiative / proposeInitiativeWithLock
  • supporterRequirements — gates who can call supportInitiative

Both use the same ParticipantRequirements struct, and both are immutable after deployment.

struct ParticipantRequirements {
    address token;              // Token used for eligibility checks
    uint256 minBalance;         // Minimum current balance required (in wei)
    uint256 minHoldingDuration; // Minimum blocks tokens must be held (requires IVotes)
    uint256 minLockAmount;      // Minimum tokens that must be locked per proposal/support
}

Proposer Requirement Modes

Eligibility mode is inferred from the field values — there is no explicit enum. The three modes are:

Mode 1 — No Requirement (Open)

Anyone can propose regardless of token balance.

// Set minBalance and minHoldingDuration to 0
proposerRequirements: IAuthorizer.ParticipantRequirements({
    token: address(underlyingToken), // still required (cannot be address(0))
    minBalance: 0,
    minHoldingDuration: 0,
    minLockAmount: 0
})

When to use: Fully open boards where any address can submit initiatives. Permissionless governance experiments, public suggestion boards, etc.


Mode 2 — Minimum Balance

Proposer must hold at least minBalance tokens at proposal time.

// Set minBalance > 0, leave minHoldingDuration as 0
proposerRequirements: IAuthorizer.ParticipantRequirements({
    token: address(governanceToken),
    minBalance: 100_000e18,   // e.g. 100,000 tokens
    minHoldingDuration: 0,
    minLockAmount: 0
})

The contract checks IERC20(token).balanceOf(proposer) >= minBalance at the time of the call.

When to use: Boards where token-holding alignment is a lightweight signal, but historical snapshot proofs aren't required. Works with any ERC20 token.


Mode 3 — Minimum Balance Held for Duration

Proposer must have held at least minBalance tokens continuously for at least minHoldingDuration blocks before the proposal.

// Set both minBalance > 0 and minHoldingDuration > 0
proposerRequirements: IAuthorizer.ParticipantRequirements({
    token: address(governanceToken),
    minBalance: 50_000e18,      // must currently hold ≥ 50,000 tokens
    minHoldingDuration: 7200,   // must have held them for ≥ 7,200 blocks (~24h on Ethereum mainnet)
    minLockAmount: 0
})

The contract calls IVotes(token).getPastVotes(proposer, block.number - minHoldingDuration) and checks that the historical balance is also ≥ minBalance.

⚠️ Requires a Governor-style token (IVotes / ERC20Votes)

minHoldingDuration only works with tokens that implement OpenZeppelin's IVotes interface (e.g. ERC20Votes, ERC721Votes). These tokens record checkpoint snapshots on every transfer/delegation, enabling historical balance queries.

Plain ERC20 tokens do not support this — the contract will revert with Signals_TokenHasNoCheckpointSupport if getPastVotes is called on a non-IVotes token. If you configure minHoldingDuration > 0, your token address must be IVotes-compatible.

When to use: Higher-stakes boards where you want to prevent last-minute token accumulation to game proposal rights. Common in DAO governance to require "skin in the game" for a minimum period.


minLockAmount — Minimum Lock Per Proposal

minLockAmount can be combined with any of the above modes. When set, proposeInitiativeWithLock requires the caller to lock at least minLockAmount tokens alongside their proposal. Calling proposeInitiative (without a lock) when minLockAmount > 0 will still revert.

proposerRequirements: IAuthorizer.ParticipantRequirements({
    token: address(underlyingToken),
    minBalance: 50_000e18,
    minHoldingDuration: 0,
    minLockAmount: 10_000e18  // proposer must lock at least 10,000 tokens
})

Constraint: minLockAmount must be ≤ minBalance.


Constraints Summary

FieldConstraint
tokenCannot be address(0)
minHoldingDuration > 0Requires minBalance > 0 and an IVotes-compatible token
minLockAmountMust be ≤ minBalance

Violating these at initialization will revert with Signals_InvalidArguments.


Error Reference

ErrorCause
Signals_InsufficientTokensCurrent balance < minBalance
Signals_InsufficientTokenDurationHistorical balance < minBalance at block.number - minHoldingDuration
Signals_TokenHasNoCheckpointSupportToken doesn't implement IVotes; getPastVotes reverted
Signals_InsufficientLockAmountLock amount < minLockAmount
Signals_InvalidArgumentsInvalid configuration at initialization (e.g. minHoldingDuration > 0 with minBalance == 0)

Reading Requirements On-Chain

// Check proposer requirements
IAuthorizer.ParticipantRequirements memory reqs = signals.getProposerRequirements();
 
// Check supporter requirements
IAuthorizer.ParticipantRequirements memory reqs = signals.getParticipantRequirements();
 
// Check whether a specific account can propose with a given lock amount
bool canPropose = signals.accountCanPropose(account, lockAmount);
 
// Check whether a specific account can support with a given lock amount
bool canSupport = signals.accountCanSupport(account, lockAmount);