Generic ERC-1271 voting proxy for Snapshot voting without moving voting power.
For older multisigs that cannot upgrade to ERC-1271 or move their voting power,
VotingProxy lets the multisig approve exact vote hashes and recover the
associated data through a small companion contract.
Snapshot Score API strategy PR: snapshot-labs/score-api#1452
- 🔐 A multisig approves an exact Snapshot vote hash and associated data onchain.
- 🧾
VotingProxyexposes ERC-1271isValidSignature. - 🏭
VotingProxyFactoryrecords factory-created proxy sources. - 🧮 A Snapshot
voting-proxystrategy maps proxy voting power through the factory. - 📬 Snapshot counts the proxy vote with the owner's configured voting power.
interface IVotingProxy {
event Vote(bytes32 indexed hash, bytes data);
function owner() external view returns (address);
function source() external view returns (address);
function votes(bytes32 hash) external view returns (bytes memory data);
function vote(bytes32 hash, bytes calldata data) external;
function isValidSignature(bytes32 hash, bytes calldata sig) external view returns (bytes4);
}Minimal behavior:
owneris immutable and set in the constructor.source()returnsowner.vote(hash, data)is callable only byowner.- Ownership cannot be transferred or renounced.
datamust be non-empty.- An approved hash cannot be replaced.
votes(hash)returns the stored data bytes for retrieval.isValidSignature(hash, 0x)returns0x1626ba7eonly ifvotes(hash).length != 0.vote(hash, data)emitsVote(hash, data).
ownerapproves vote hashes.ownerprovides voting power throughsource().- The constructor
ownerargument sets the only owner. VotingProxyFactory.create()deploys a proxy formsg.sender.VotingProxyFactory.source(proxy)returns the registered source for factory-created proxies.
- Create Snapshot vote typed data with
from = VotingProxy. - Compute the Snapshot EIP-712 hash.
- Owner approves
VotingProxy.vote(hash, data)through its normal signing or multisig flow. - Later signers or submitters can read
votes(hash)to recover the stored data. - Submit the vote with
address = VotingProxyandsig = "0x". - Snapshot calls
isValidSignature(hash, 0x). - Snapshot strategy gives
VotingProxythe voting power ofVotingProxyFactory.source(VotingProxy).
voting-proxy is a factory-gated wrapper strategy:
- Run inner strategies for each voter normally.
- If a voter has
0VP, batch-callfactory.source(voter). - Run inner strategies for the source address.
- Return the source score under the original proxy voter.
- Dedup multiple voters that resolve to the same source.
Example:
{
"factory": "0x1111111111111111111111111111111111111111",
"strategies": [
{
"name": "erc20-balance-of",
"network": "1",
"params": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"decimals": 6
}
}
]
}If multiple voters resolve to the same source():
- Direct source voter wins if it has positive voting power.
- Otherwise the lowest proxy address wins deterministically.
- All other voters for that source return
0.
- The strategy should be marked
overriding: true. - Snapshot must treat the strategy as dependent on other addresses.
- The proxy must exist before the proposal snapshot block.
- The submitted Snapshot typed data must match the approved hash exactly.
- The contract stores data bytes for retrieval, but offchain tooling must verify those bytes match the approved hash.