The OpenZeppelin Governor client enables interaction with governance systems built on OpenZeppelin’s Governor contracts. It supports both v4 and v5 of the OpenZeppelin Governor standard, with gasless voting through signatures or direct transaction-based voting.
Installation
npm install @snapshot-labs/sx
Overview
OpenZeppelin Governor is a flexible and modular on-chain governance protocol used by many DAOs. The client supports both major versions:
Governor v4 : Original version with basic ballot signing
Governor v5 : Enhanced version with nonce-based signatures and additional parameters
The OpenZeppelin Governor client currently supports voting only . Proposal creation and execution must be done through direct contract interaction or other tools.
Client Types
The OpenZeppelin Governor client comes in two implementations:
OpenZeppelinEthereumSig : Signature-based voting (gasless)
OpenZeppelinEthereumTx : Transaction-based voting (direct)
OpenZeppelinEthereumSig (Signature-based)
The signature-based client allows users to vote using EIP-712 signatures, which can be submitted by relayers for gasless voting.
Initialization
import { clients } from '@snapshot-labs/sx' ;
const client = new clients . OpenZeppelinEthereumSig ({
chainId: 1 // Ethereum mainnet
});
Configuration
Chain ID for the network (1 for Ethereum mainnet, 11155111 for Sepolia)
Methods
vote()
Create a signed vote message that can be submitted to the OpenZeppelin Governor contract.
Vote (Governor v5)
Vote (Governor v4)
Vote with Reason (v5)
Vote Abstain
import { Wallet } from '@ethersproject/wallet' ;
const signer = new Wallet ( privateKey );
const envelope = await client . vote ({
signer ,
authenticatorType: 'OpenZeppelinAuthenticatorSignatureV5' ,
data: {
spaceId: '0x...' , // Governor contract address
proposalId: '42' , // Proposal ID (string for v5)
choice: 1 // 1 = For
}
});
// The envelope contains signature data that can be submitted
// to the Governor contract's castVoteBySig function
console . log ( 'Signature:' , envelope . signatureData . signature );
signer
Signer & TypedDataSigner
required
Ethers.js signer with EIP-712 signing capabilities
authenticatorType
OpenZeppelinAuthenticator
required
Version-specific authenticator type:
'OpenZeppelinAuthenticatorSignatureV4' for Governor v4
'OpenZeppelinAuthenticatorSignatureV5' for Governor v5
Vote data object OpenZeppelin Governor contract address
Proposal ID (string format for compatibility with large numbers)
Vote choice:
0 = Against
1 = For
2 = Abstain
Optional reason for the vote (only supported in v5)
Returns an envelope containing the signature data and vote data EIP-712 signature that can be submitted to castVoteBySig
EIP-712 domain with governor name, version, and chain ID
Signed message (structure varies by version)
Version Differences
Ballot Structure type EIP712BallotV4 = {
proposalId : string ;
support : number ;
};
Features
Simple ballot with proposalId and support
No nonce or voter address in signature
No built-in reason support in ballot
Ballot Structure type EIP712BallotV5 = {
voter : string ; // Voter address
proposalId : string ;
support : number ;
nonce : number ; // Prevents replay attacks
};
type EIP712ExtendedBallotV5 = {
voter : string ;
proposalId : string ;
support : number ;
nonce : number ;
reason : string ; // Vote reason
params : string ; // Additional parameters
};
Features
Includes voter address and nonce for enhanced security
Built-in support for vote reasons
Additional params field for extensibility
Automatic nonce fetching from contract
Submitting Signed Votes
After creating a signed vote, it must be submitted to the Governor contract:
Submit v5 Vote
Submit v5 Vote with Reason
Submit v4 Vote (Legacy)
import { Contract } from '@ethersproject/contracts' ;
const governorAbi = [
'function castVoteBySig(uint256 proposalId, uint8 support, address voter, bytes signature)'
];
const governor = new Contract (
envelope . data . spaceId ,
governorAbi ,
provider
);
const tx = await governor . castVoteBySig (
envelope . data . proposalId ,
envelope . data . choice ,
envelope . signatureData . address ,
envelope . signatureData . signature
);
await tx . wait ();
console . log ( 'Vote submitted:' , tx . hash );
OpenZeppelinEthereumTx (Transaction-based)
The transaction-based client allows direct voting through contract transactions.
Initialization
import { clients } from '@snapshot-labs/sx' ;
import { JsonRpcProvider } from '@ethersproject/providers' ;
const provider = new JsonRpcProvider ( 'https://eth.llamarpc.com' );
const client = new clients . OpenZeppelinEthereumTx ({
provider
});
Methods
vote()
Submit a vote transaction directly to the Governor contract.
Vote Transaction (v5)
Vote with Reason (v5)
import { Wallet } from '@ethersproject/wallet' ;
const signer = new Wallet ( privateKey , provider );
const tx = await client . vote ({
signer ,
data: {
spaceId: '0x...' , // Governor contract address
proposalId: '42' ,
choice: 1 // 1 = For
}
});
await tx . wait ();
console . log ( 'Vote cast:' , tx . hash );
Ethers.js signer connected to a provider
Vote data object (same structure as signature-based client)
Returns an ethers.js transaction response that can be awaited
Type Definitions
Vote
type Vote = {
spaceId : string ; // OpenZeppelin Governor contract address
proposalId : string ; // Proposal ID
choice : 0 | 1 | 2 ; // 0=Against, 1=For, 2=Abstain
reason ?: string ; // Optional vote reason (v5 only)
};
EIP712 Ballot Types
// Governor v4
type EIP712BallotV4 = {
proposalId : string ;
support : number ;
};
// Governor v5
type EIP712BallotV5 = {
voter : string ;
proposalId : string ;
support : number ;
nonce : number ;
};
// Governor v5 Extended (with reason)
type EIP712ExtendedBallotV5 = {
voter : string ;
proposalId : string ;
support : number ;
nonce : number ;
reason : string ;
params : string ;
};
SignatureData
type SignatureData = {
authenticatorType : OpenZeppelinAuthenticator ;
address : string ;
signature : string ;
domain : TypedDataDomain ;
types : Record < string , TypedDataField []>;
message : Record < string , any >;
};
type OpenZeppelinAuthenticator =
| 'OpenZeppelinAuthenticatorSignatureV4'
| 'OpenZeppelinAuthenticatorSignatureV5' ;
Governor Contract Detection
The client automatically fetches governor metadata for EIP-712 signing:
Automatic Metadata Detection
// The client queries both name() and version() from the contract
const [ name , version ] = await Promise . all ([
governorContract . name (),
governorContract . version ()
]);
// e.g., name: "MyDAO Governor", version: "1"
// Used in EIP-712 domain:
const domain = {
name: name ,
version: version ,
chainId: 1 ,
verifyingContract: '0x...'
};
Complete Example
Here’s a full example of voting on an OpenZeppelin Governor v5 proposal:
import { clients } from '@snapshot-labs/sx' ;
import { Wallet } from '@ethersproject/wallet' ;
import { JsonRpcProvider } from '@ethersproject/providers' ;
import { Contract } from '@ethersproject/contracts' ;
// 1. Initialize client
const client = new clients . OpenZeppelinEthereumSig ({
chainId: 1
});
// 2. Create signer
const provider = new JsonRpcProvider ( 'https://eth.llamarpc.com' );
const signer = new Wallet ( process . env . PRIVATE_KEY , provider );
// 3. Generate signed vote
const envelope = await client . vote ({
signer ,
authenticatorType: 'OpenZeppelinAuthenticatorSignatureV5' ,
data: {
spaceId: '0x...' , // Governor address
proposalId: '42' ,
choice: 1 ,
reason: 'Strong support for this initiative'
}
});
// 4. Submit vote to Governor contract
const governorAbi = [
'function castVoteWithReasonAndParamsBySig(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature)'
];
const governor = new Contract (
envelope . data . spaceId ,
governorAbi ,
provider
);
const tx = await governor . castVoteWithReasonAndParamsBySig (
envelope . data . proposalId ,
envelope . data . choice ,
envelope . signatureData . address ,
envelope . data . reason || '' ,
'0x' , // params
envelope . signatureData . signature
);
await tx . wait ();
console . log ( 'Vote successfully cast:' , tx . hash );
Migration from v4 to v5
If you’re upgrading from Governor v4 to v5:
Update Authenticator Type
Change from 'OpenZeppelinAuthenticatorSignatureV4' to 'OpenZeppelinAuthenticatorSignatureV5'
Update Contract Calls
Use the new v5 signature format (voter address instead of v,r,s)
Add Reason Support
Take advantage of built-in reason parameter in v5 ballots
Handle Nonces
The client automatically fetches nonces - no changes needed
Source Code
View the complete implementation: