Documentation Index Fetch the complete documentation index at: https://mintlify.com/snapshot-labs/sx-monorepo/llms.txt
Use this file to discover all available pages before exploring further.
Voting strategies determine how much voting power each user has when participating in governance. Strategies can read token balances, check whitelists, verify cross-chain proofs, or implement custom logic.
What are Voting Strategies?
A voting strategy is a contract that:
Validates user eligibility - Checks if a user can vote or propose
Calculates voting power - Returns the user’s voting weight
Provides parameters - Generates proof data needed for the calculation
Each space can configure multiple voting strategies, allowing users to choose which strategy to use when voting.
Strategy Interface
All strategies implement a common interface:
interface Strategy {
type : string ;
// Generate parameters for voting/proposing
getParams (
call : 'propose' | 'vote' ,
strategyConfig : StrategyConfig ,
signerAddress : string ,
metadata : Record < string , any > | null ,
data : Propose | Vote ,
clientConfig : ClientConfig
) : Promise < string >;
// Calculate voting power
getVotingPower (
strategyAddress : string ,
voterAddress : string ,
metadata : Record < string , any > | null ,
block : number | null ,
params : string ,
provider : Provider
) : Promise < bigint >;
}
EVM Strategies
Snapshot X supports multiple strategies for EVM-based spaces:
Vanilla Strategy
The simplest strategy - gives everyone 1 vote.
function createVanillaStrategy () : Strategy {
return {
type: 'vanilla' ,
async getParams () : Promise < string > {
return '0x00' ;
},
async getVotingPower () : Promise < bigint > {
return 1 n ;
}
};
}
Use case : One person, one vote governance where all members have equal power.
Token Balance Strategies
OpenZeppelin Votes (ozVotes)
Reads voting power from ERC20Votes or ERC721Votes tokens:
function createOzVotesStrategy () : Strategy {
return {
type: 'ozVotes' ,
async getParams () : Promise < string > {
return '0x00' ;
},
async getVotingPower (
strategyAddress : string ,
voterAddress : string ,
metadata : Record < string , any > | null ,
block : number | null ,
params : string ,
provider : Provider
) : Promise < bigint > {
const votesContract = new Contract ( params , IVotes , provider );
const votingPower = await votesContract . getVotes ( voterAddress , {
blockTag: block ?? 'latest'
});
return BigInt ( votingPower . toString ());
}
};
}
Configuration : The params field contains the token contract address.
Use case : Token-weighted voting using OpenZeppelin’s Votes extension.
Compound (comp)
Reads voting power from Compound-style governance tokens:
function createCompStrategy () : Strategy {
return {
type: 'comp' ,
async getParams () : Promise < string > {
return '0x00' ;
},
async getVotingPower (
strategyAddress : string ,
voterAddress : string ,
metadata : Record < string , any > | null ,
block : number | null ,
params : string ,
provider : Provider
) : Promise < bigint > {
const compContract = new Contract ( params , ICompAbi , provider );
const votingPower = await compContract . getCurrentVotes ( voterAddress , {
blockTag: block ?? 'latest'
});
return BigInt ( votingPower . toString ());
}
};
}
Configuration : The params field contains the COMP token address.
Use case : Token-weighted voting using Compound’s voting token interface.
Whitelist Strategy (merkleWhitelist)
Allows only whitelisted addresses with predefined voting power:
type Entry = {
address : string ;
votingPower : string ;
};
function createMerkleWhitelist () : Strategy {
return {
type: 'whitelist' ,
async getParams (
call : 'propose' | 'vote' ,
strategyConfig : StrategyConfig ,
signerAddress : string ,
metadata : Record < string , any > | null ,
data : Propose | Vote ,
clientConfig : ClientConfig
) : Promise < string > {
const tree : Entry [] = metadata ?. tree ;
if ( ! tree ) throw new Error ( 'Invalid metadata. Missing tree' );
const voterIndex = tree . findIndex (
entry => entry . address . toLowerCase () === signerAddress . toLowerCase ()
);
if ( voterIndex === - 1 ) {
throw new Error ( 'Signer is not in whitelist' );
}
const whitelist = tree . map (
entry => [ entry . address , BigInt ( entry . votingPower )] as [ string , bigint ]
);
const merkleTree = StandardMerkleTree . of ( whitelist , [ 'address' , 'uint96' ]);
const proof = merkleTree . getProof ( voterIndex );
const abiCoder = new AbiCoder ();
return abiCoder . encode (
[ 'bytes32[]' , 'tuple(address, uint96)' ],
[ proof , whitelist [ voterIndex ]]
);
},
async getVotingPower (
strategyAddress : string ,
voterAddress : string ,
metadata : Record < string , any > | null
) : Promise < bigint > {
const tree : Entry [] = metadata ?. tree ;
if ( ! tree ) return 0 n ;
const match = tree . find (
entry => entry . address . toLowerCase () === voterAddress . toLowerCase ()
);
return match ? BigInt ( match . votingPower ) : 0 n ;
}
};
}
Configuration :
params: Merkle root hash
metadata: Array of { address, votingPower } entries
Use case : Curated member lists with custom voting power per member.
Starknet Strategies
Starknet spaces support additional strategies for cross-chain governance:
EVM Slot Value (evmSlotValue)
Reads storage slots from Ethereum contracts and proves them on Starknet:
function createEvmSlotValueStrategy () : Strategy {
return {
type: 'evmSlotValue' ,
async getParams (
call : 'propose' | 'vote' ,
signerAddress : string ,
address : string ,
index : number ,
params : string ,
metadata : Record < string , any > | null ,
envelope : Envelope < Vote >,
clientConfig : ClientConfig
) : Promise < string []> {
if ( call === 'propose' ) throw new Error ( 'Not supported for proposing' );
const { contractAddress , slotIndex } = metadata ;
const { starkProvider , ethUrl , networkConfig } = clientConfig ;
// Get L1 block number cached for this proposal
const spaceContract = new Contract (
SpaceAbi ,
envelope . data . space ,
starkProvider
);
const proposalStruct = await spaceContract . call ( 'proposals' , [
envelope . data . proposal
]);
const startTimestamp = proposalStruct . start_timestamp ;
const contract = new Contract ( EVMSlotValue , address , starkProvider );
const l1BlockNumber = await contract . cached_timestamps ( startTimestamp );
// Generate storage proof from Ethereum
const provider = new StaticJsonRpcProvider (
ethUrl ,
networkConfig . herodotusAccumulatesChainId
);
const proof = await provider . send ( 'eth_getProof' , [
contractAddress ,
[ getSlotKey ( signerAddress , slotIndex )],
`0x ${ l1BlockNumber . toString ( 16 ) } `
]);
return CallData . compile ({
storageProof: proof . storageProof [ 0 ]. proof
});
},
async getVotingPower (
strategyAddress : string ,
voterAddress : string ,
metadata : Record < string , any > | null ,
timestamp : number | null ,
params : string [],
clientConfig : ClientConfig
) : Promise < bigint > {
if ( ! metadata || voterAddress . length !== 42 ) return 0 n ;
const { contractAddress , slotIndex } = metadata ;
const { starkProvider , ethUrl , networkConfig } = clientConfig ;
if ( ! timestamp ) {
// Read current value from Ethereum
const provider = new StaticJsonRpcProvider (
ethUrl ,
networkConfig . herodotusAccumulatesChainId
);
const storage = await provider . getStorageAt (
contractAddress ,
getSlotKey ( voterAddress , slotIndex )
);
return BigInt ( storage );
}
// Read proven value from Starknet strategy contract
const contract = new Contract (
EVMSlotValue ,
strategyAddress ,
starkProvider
);
const l1BlockNumber = await contract . cached_timestamps ( timestamp );
return await contract . get_voting_power (
timestamp ,
getUserAddressEnum ( 'ETHEREUM' , voterAddress ),
params ,
CallData . compile ({ storageProof })
);
}
};
}
Configuration :
params: Comma-separated config values
metadata: { contractAddress, slotIndex }
Use case : Allow Ethereum token holders to vote on Starknet without bridging tokens.
ERC20 Votes (erc20Votes)
Reads voting power from ERC20Votes tokens on Starknet.
Use case : Token-weighted voting using Starknet tokens.
OZ Votes Storage Proof (ozVotesStorageProof)
Proves OpenZeppelin Votes balances from Ethereum to Starknet using storage proofs.
Configuration : params: { trace: 208 | 224 } for different proof types.
Use case : Cross-chain voting with cryptographic proofs instead of bridges.
Offchain Strategies
Offchain spaces can use custom validation strategies:
Only Members
Only allows space members to vote:
function createOnlyMembersStrategy () : Strategy {
// Validates against space member list
}
Remote VP
Delegates voting power calculation to Snapshot’s scoring API:
function createRemoteVpStrategy () : Strategy {
// Calls Snapshot API for voting power
}
Remote Validate
Delegates validation to external services:
function createRemoteValidateStrategy ( name : string ) : Strategy {
// Supports: 'any', 'basic', 'passport-gated', 'arbitrum', 'karma-eas-attestation'
}
Using Strategies
When creating a space, configure voting strategies:
const { address , txId } = await client . deploySpace ({
signer ,
params: {
// ... other params
votingStrategies: [
{
addr: '0x...' , // ozVotes strategy address
params: '0x...' // Token contract address
},
{
addr: '0x...' , // whitelist strategy address
params: '0x...' // Merkle root
}
],
votingStrategiesMetadata: [
'' , // No metadata for token strategy
'ipfs://...' // Whitelist metadata
]
}
});
When voting, users select which strategy to use:
await client . vote ({
signer ,
envelope: {
data: {
space: '0x...' ,
proposal: 1 ,
choice: Choice . For ,
authenticator: '0x...' ,
strategies: [
{
index: 0 , // Use first strategy (token voting)
address: '0x...' ,
params: '0x...' ,
metadata: null
}
],
metadataUri: ''
}
}
});
Strategy Resolution
Strategies are resolved from network configuration:
function getStrategy (
address : string ,
networkConfig : NetworkConfig
) : Strategy | null {
const strategy = networkConfig . strategies [ address ];
if ( ! strategy ) return null ;
if ( strategy . type === 'vanilla' ) return createVanillaStrategy ();
if ( strategy . type === 'comp' ) return createCompStrategy ();
if ( strategy . type === 'ozVotes' ) return createOzVotesStrategy ();
if ( strategy . type === 'whitelist' ) return createMerkleWhitelist ();
if ( strategy . type === 'evmSlotValue' ) return createEvmSlotValueStrategy ();
// ...
return null ;
}
Spaces Learn about governance spaces
Authenticators Understand user authentication
Creating Proposals Create your first proposal
Custom Strategies Build custom voting strategies