Multisig membership & eligibility
The Multisig Plugin's member list reuses the shared Addresslist, a checkpointed set of addresses (the plugin adds no membership storage of its own). isMember calls isListed. Two additional rules govern its use: the minApprovals invariant and the two-speed eligibility model.
Managing members
Members are added and removed with addAddresses / removeAddresses, gated by UPDATE_MULTISIG_SETTINGS_PERMISSION_ID (held by the DAO, so membership changes go through governance). Because they touch the same permission and interact with minApprovals, membership and the threshold are really one coupled setting.
minApprovals and the member count
One invariant holds at all times: 1 ≤ minApprovals ≤ member count. A multisig can never require zero approvals (that would auto-pass everything), nor more approvals than it has members (that would make passing impossible). It's enforced from both directions:
updateMultisigSettingsrejects aminApprovalsbelow 1 or above the current member count.removeAddressesrejects a removal that would drop the member count below the currentminApprovals.
Because minApprovals is an absolute count, not a percentage, changing the member list changes the share of members needed to pass a proposal. Adding members keeps the same number of approvals needed (a smaller share of the group, so relatively easier to pass), while removing members makes each remaining approval weigh more (relatively harder). That's why only removal is threshold-constrained: adding can never breach an absolute floor.
Shrinking the multisig takes two actions, in order. Each function checks against the current onchain state, not against other actions queued in the same proposal. So to remove members and lower the threshold below where it is now, the proposal's actions must be: (1)
updateMultisigSettingsto lowerminApprovalsfirst, then (2)removeAddresses. The reverse order reverts, because the removal is validated against the old, higher threshold. Preserve this order when scripting membership rotations.
Two-speed eligibility
Who may create a proposal and who may approve one are judged at different times.
- Approving is snapshot-based. A proposal fixes a snapshot at the block before creation, and approval eligibility is checked against membership at that snapshot (
isListedAtBlock), never against current membership. A member removed after a proposal was created can still approve that already-open proposal, they were listed at its snapshot, and removal never re-checks. Ejecting a compromised or departed signer does not stop them pushing through proposals that were already open when you removed them; only proposals created after the removal exclude them. (Symmetrically, a member added after creation cannot approve that proposal.) The snapshot is the prior block, not the current one, so nobody can flip membership in the same block they create a proposal to rig who's eligible. - Creating is live. Whether you may open a proposal is checked against current membership at call time (see below).
The reason for the split: approvals must be counted against a stable electorate for the whole life of a proposal, but "may I even start one" is naturally a here-and-now question.
Who can create vs. who can approve
Creation eligibility is enforced by a small permission condition, ListedCheckCondition, wired onto CREATE_PROPOSAL_PERMISSION_ID (which is granted broadly to any address). The condition simply asks: is onlyListed on, and if so, is the caller currently listed? This is the same pattern as the Token Voting Plugin's VotingPowerCondition, a broad grant narrowed dynamically by a condition.
Because the condition reads onlyListed live, it doubles as a useful switch:
onlyListed = falsedecouples proposing from passing. Turn it off and anyone can create a proposal, while approving and passing it stays restricted to the member list. This lets the public submit proposals for members to decide. Open proposal creation does not mean open governance: the approvals are still members-only.
Keep in mind
minApprovalsnever exceeds the member count. To shrink the multisig below the current threshold, lowerminApprovalsbefore removing members in the same proposal.- Removing a member doesn't lock them out of open proposals. Their eligibility was frozen at each open proposal's snapshot; only proposals created after the removal exclude them. After ejecting a signer, check what's still open, they can still approve it. (Conversely, a member added later can't approve an already-open proposal.)
- No double approval. Each member's approval counts once; approving again reverts.
See also
- Multisig Plugin — the approval model and lifecycle.
- Membership and the address list — the shared, checkpointed
Addresslistthis builds on. - Permission conditions — how
ListedCheckConditiongates creation.