The EVM client provides methods to interact with Snapshot X governance spaces on Ethereum and EVM-compatible networks. It offers two implementations: signature-based (gasless) and transaction-based.
Installation
npm install @snapshot-labs/sx
Client Types
The EVM client comes in two flavors:
EthereumSig : Signature-based client for gasless voting via Mana relayer
EthereumTx : Transaction-based client for direct on-chain interactions
EthereumSig (Signature-based)
The signature-based client allows users to create signed messages that are relayed to the blockchain, enabling gasless governance actions.
Initialization
import { clients } from '@snapshot-labs/sx' ;
import { JsonRpcProvider } from '@ethersproject/providers' ;
const client = new clients . EvmEthereumSig ({
networkConfig: {
eip712ChainId: 1 , // Ethereum mainnet
// ... other network config
},
manaUrl: 'https://mana.snapshot.org' ,
whitelistServerUrl: 'https://whitelist.snapshot.org' ,
provider: new JsonRpcProvider ( 'https://eth.llamarpc.com' )
});
Configuration
Network-specific configuration including chain ID and contract addresses EIP-712 chain identifier for signature domain
URL of the Mana relayer service for submitting signed transactions
URL of the whitelist server for strategy validation
Ethers.js provider for reading blockchain state
Methods
propose()
Create a new governance proposal with signature-based authentication.
import { Wallet } from '@ethersproject/wallet' ;
const signer = new Wallet ( privateKey );
const envelope = await client . propose ({
signer ,
data: {
space: '0x...' , // Space contract address
authenticator: '0x...' , // Authenticator contract
strategies: [
{
index: 0 ,
address: '0x...' ,
params: '0x' ,
metadata: {}
}
],
executionStrategy: {
addr: '0x...' ,
params: '0x'
},
metadataUri: 'ipfs://...' // Proposal metadata
}
});
// Send the signed envelope to Mana
const receipt = await client . send ( envelope );
signer
Signer & TypedDataSigner
required
Ethers.js signer with EIP-712 signing capabilities
Proposal data object Authenticator contract address
Array of proposal validation strategies
Execution strategy with address and parameters
IPFS URI pointing to proposal metadata (title, body, etc.)
Returns an envelope containing the signature data and proposal data
vote()
Cast a vote on a proposal using a signed message.
const envelope = await client . vote ({
signer ,
data: {
space: '0x...' ,
authenticator: '0x...' ,
strategies: [
{
index: 0 ,
address: '0x...' ,
params: '0x'
}
],
proposal: 1 , // Proposal ID
choice: 1 , // 1 = For, 2 = Against, 3 = Abstain
metadataUri: '' // Optional vote metadata
}
});
const receipt = await client . send ( envelope );
Vote choice: 1 (For), 2 (Against), or 3 (Abstain)
updateProposal()
Update an existing proposal’s execution strategy or metadata.
const envelope = await client . updateProposal ({
signer ,
data: {
space: '0x...' ,
proposal: 1 ,
authenticator: '0x...' ,
executionStrategy: {
addr: '0x...' ,
params: '0x'
},
metadataUri: 'ipfs://...'
}
});
const receipt = await client . send ( envelope );
send()
Send a signed envelope to the Mana relayer for on-chain execution.
const result = await client . send ( envelope );
console . log ( 'Transaction hash:' , result );
EthereumTx (Transaction-based)
The transaction-based client allows direct on-chain interactions, requiring users to pay gas fees.
Initialization
import { clients } from '@snapshot-labs/sx' ;
const client = new clients . EvmEthereumTx ({
networkConfig: {
eip712ChainId: 1 ,
proxyFactory: '0x...' ,
masterSpace: '0x...' ,
executionStrategiesImplementations: {
SimpleQuorumAvatar: '0x...' ,
SimpleQuorumTimelock: '0x...'
},
maxPriorityFeePerGas: 2000000000 // 2 gwei
},
whitelistServerUrl: 'https://whitelist.snapshot.org' ,
provider: new JsonRpcProvider ( 'https://eth.llamarpc.com' )
});
Space Deployment
deploySpace()
Deploy a new governance space.
const { txId , address } = await client . deploySpace ({
signer ,
params: {
controller: signer . address ,
votingDelay: 0 ,
minVotingDuration: 3600 , // 1 hour
maxVotingDuration: 604800 , // 1 week
proposalValidationStrategy: {
addr: '0x...' ,
params: '0x'
},
proposalValidationStrategyMetadataUri: '' ,
daoUri: '' ,
metadataUri: 'ipfs://...' ,
authenticators: [ '0x...' ],
votingStrategies: [
{ addr: '0x...' , params: '0x' }
],
votingStrategiesMetadata: [ '' ]
},
saltNonce: '0x...' // Optional, random if not provided
});
console . log ( 'Space deployed at:' , address );
console . log ( 'Transaction:' , txId );
deployAvatarExecution()
Deploy an Avatar execution strategy (Safe integration).
const { txId , address } = await client . deployAvatarExecution ({
signer ,
params: {
controller: signer . address ,
target: '0x...' , // Safe address
spaces: [ '0x...' ], // Authorized spaces
quorum: BigInt ( '1000000000000000000' ) // 1 token
}
});
deployTimelockExecution()
Deploy a Timelock execution strategy.
Deploy Timelock Execution
const { txId , address } = await client . deployTimelockExecution ({
signer ,
params: {
controller: signer . address ,
vetoGuardian: '0x...' ,
spaces: [ '0x...' ],
timelockDelay: BigInt ( 86400 ), // 24 hours
quorum: BigInt ( '1000000000000000000' )
}
});
Governance Actions
propose()
Submit a proposal transaction directly to the blockchain.
Submit Proposal Transaction
const tx = await client . propose ({
signer ,
envelope: {
data: {
space: '0x...' ,
authenticator: '0x...' ,
strategies: [ ... ],
executionStrategy: { addr: '0x...' , params: '0x' },
metadataUri: 'ipfs://...'
}
}
});
await tx . wait ();
vote()
Submit a vote transaction.
const tx = await client . vote ({
signer ,
envelope: {
data: {
space: '0x...' ,
authenticator: '0x...' ,
strategies: [ ... ],
proposal: 1 ,
choice: 1 ,
metadataUri: ''
}
}
});
await tx . wait ();
execute()
Execute a passed proposal.
const tx = await client . execute ({
signer ,
space: '0x...' ,
proposal: 1 ,
executionParams: '0x...'
});
await tx . wait ();
cancel()
Cancel a proposal (only by proposal author).
const tx = await client . cancel ({
signer ,
space: '0x...' ,
proposal: 1
});
await tx . wait ();
Space Management
updateSettings()
Update space configuration.
const tx = await client . updateSettings ({
signer ,
space: '0x...' ,
settings: {
minVotingDuration: 7200 ,
maxVotingDuration: 1209600 ,
metadataUri: 'ipfs://...' ,
votingStrategiesToAdd: [
{ addr: '0x...' , params: '0x' }
],
authenticatorsToAdd: [ '0x...' ]
}
});
await tx . wait ();
transferOwnership()
Transfer space ownership.
const tx = await client . transferOwnership ({
signer ,
space: '0x...' ,
owner: '0x...' // New owner address
});
await tx . wait ();
Type Definitions
StrategyConfig
type StrategyConfig = {
index : number ; // Strategy index in the space
address : string ; // Strategy contract address
params : string ; // ABI-encoded parameters
metadata ?: Record < string , any >; // Optional metadata
};
AddressConfig
type AddressConfig = {
addr : string ; // Contract address
params : string ; // ABI-encoded parameters
};
Envelope
type Envelope < T > = {
signatureData ?: SignatureData ;
data : T ;
};
Source Code
View the complete implementation: