Voting Challenges
Voting challenges require a weighted quorum threshold to be met through votes from specified voters. This enables multi-signature-like approval where multiple parties must vote to approve transfers, with each voter having a configurable weight. Votes are stored separately and can be updated.
The system requires a minimum percentage of total voter weight to vote "yes" before a transfer can be approved. Each voter has a weight, and each vote allocates a percentage (0-100%) to "yes" with the remainder going to "no".
Structure
Challenge Fields
proposalId
string
Unique identifier for tracking votes
quorumThreshold
string
Percentage (0-100) of total possible weight that must vote "yes"
voters
Voter[]
List of voters with addresses and weights
uri
string
Optional metadata URI
customData
string
Optional custom data
resetAfterExecution
boolean
If true, all votes are cleared after a successful transfer uses this challenge
delayAfterQuorum
string (Uint, ms)
Mandatory wait period (in milliseconds) after quorum is reached before the transfer can execute
Vote Fields
proposalId
string
Proposal ID this vote is for
voter
string
Address of the voter casting the vote
yesWeight
string
Percentage (0-100) allocated to "yes"; remainder goes to "no"
Voting Process
Cast Vote: Voters use
MsgCastVotewithyesWeight(0-100%)Store Vote: Votes stored with key:
collectionId-approverAddress-approvalLevel-approvalId-proposalId-voterAddressUpdate Vote: Casting a new vote with same parameters overwrites the previous vote
Calculate Threshold: On transfer attempt:
Retrieve all votes for the proposal
Calculate each voter's yes contribution:
(voterWeight Γ yesWeight) / 100Sum all yes contributions
Calculate percentage:
(totalYesWeight Γ 100) / totalPossibleWeightCompare to
quorumThreshold
Weighted Voting
Each voter has a configurable weight:
Example:
Total possible weight: 350
Quorum threshold: 50% of 350 = 175 weight must vote "yes"
Partial Votes
Voters can allocate a percentage to "yes" with the remainder going to "no":
yesWeight
Yes %
No %
100
100%
0%
70
70%
30%
50
50%
50%
0
0%
100%
Threshold Calculation
Important: The threshold is calculated as a percentage of total possible weight (all voters), not just voters who have cast votes. Non-voting voters count as 0% yes.
Example:
Voter A: weight 100, votes 100% yes β contributes 100 yes weight
Voter B: weight 200, votes 50% yes β contributes 100 yes weight
Voter C: weight 50, doesn't vote β contributes 0 yes weight
Total possible weight: 350
Quorum threshold: 50%
Calculation:
Total yes weight: 100 + 100 + 0 = 200
Percentage: (200 Γ 100) / 350 = 57.14%
Result: 57.14% β₯ 50% β Challenge satisfied
Implementation Details
Vote Key Format
Votes are stored with key: collectionId-approverAddress-approvalLevel-approvalId-proposalId-voterAddress
This scopes votes to specific collection, approver, approval level, approval ID, proposal ID, and voter.
Vote Verification
When checking if a challenge is satisfied:
Retrieve all votes for voters in the challenge
For each voter:
If vote exists: calculate yes contribution
(voterWeight Γ yesWeight) / 100If no vote: contribution is 0
Sum all yes contributions
Calculate percentage:
(totalYesWeight Γ 100) / totalPossibleWeightCompare to
quorumThreshold
Type Definitions
Cast votes using MsgCastVote.
Vote Reset Behavior
When resetAfterExecution is set to true, all votes for the challenge are cleared after a transfer successfully uses the challenge. This makes the challenge reusable without needing to change the proposalId.
Use cases:
Vault withdrawals β a multi-sig vault that requires fresh approval for every withdrawal. After each approved transfer, votes reset and signers must re-approve the next one.
Recurring multi-sig β any scenario where the same set of voters needs to repeatedly approve transfers, such as a treasury that pays out monthly.
Without resetAfterExecution, votes persist indefinitely. Once quorum is reached, any subsequent transfer matching the approval would also pass the challenge (assuming votes are not manually changed). Setting this flag ensures each transfer requires a new round of voting.
Quorum Delay
The delayAfterQuorum field specifies a mandatory waiting period (in milliseconds) between when quorum is reached and when the transfer can actually execute. The chain records the timestamp when quorum is first reached; any transfer attempt before the delay elapses will be rejected.
Use cases:
Vault timelocks β require a 24-hour (86400000 ms) delay after quorum so that stakeholders have time to review and potentially revoke votes before execution.
Safety delays β prevent rushed execution of high-value transfers by enforcing a cooling-off period after quorum.
Governance windows β give dissenting voters time to change their votes or escalate concerns before the transfer proceeds.
If delayAfterQuorum is not set or is "0", the transfer can execute immediately once quorum is met.
Use Cases
Multi-Signature Approval
Require unanimous approval from multiple parties:
Requires all three voters to vote 100% yes.
Weighted Governance
Different stakeholders have different voting power:
Requires 66% of total weight (1056 out of 1600) to vote yes.
Flexible Approval
Lower threshold for partial approval:
Allows approval with 30% of total weight voting yes.
Error Conditions
The system fails if:
Voter is not in the challenge's voters list
yesWeightis greater than 100Percentage of yes votes is less than the quorum threshold
proposalIddoesn't match any voting challenge in the approvalChallenge has no voters or all voters have zero weight
Security Considerations
Proposal ID Management
Changing the proposalId resets the vote tracker. Use unique proposal IDs for each challenge to prevent vote overlap. Useful for rotating challenge configurations or creating new voting periods, but ensure old votes cannot be maliciously reused.
Vote Tampering Prevention
Votes are stored on-chain and cannot be tampered with. Votes can be updated by the voter, are scoped to specific contexts (preventing cross-context reuse), and the system validates voters are in the challenge's voters list.
Abstention Handling
Non-voting voters are treated as 0% yes (100% no). Set realistic thresholds that account for expected participation. High thresholds with many voters may be difficult to meet if voters abstain.
Weight Distribution
Weight distribution affects security. If one voter has most of the weight, they can control approvals. Distribute weights appropriately to prevent single points of failure. Use equal weights for multi-signature scenarios.
Use the uri and customData fields to provide context about what voters are approving. Monitor vote tallies to understand approval status.
Last updated