Skip to main content
The Governor Bravo client enables interaction with governance systems built on Compound’s Governor Bravo protocol. It supports both gasless voting through signatures and direct transaction-based voting.

Installation

npm install @snapshot-labs/sx

Overview

Governor Bravo is a widely-used on-chain governance protocol originally developed by Compound. Many DAOs use Governor Bravo or its variants for on-chain governance.
The Governor Bravo client currently supports voting only. Proposal creation and execution must be done through direct contract interaction or other tools.

Client Types

The Governor Bravo client comes in two implementations:
  • GovernorBravoEthereumSig: Signature-based voting (gasless)
  • GovernorBravoEthereumTx: Transaction-based voting (direct)

GovernorBravoEthereumSig (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.GovernorBravoEthereumSig({
  chainId: 1 // Ethereum mainnet
});

Configuration

chainId
number
required
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 Governor Bravo contract.
import { Wallet } from '@ethersproject/wallet';

const signer = new Wallet(privateKey);

const envelope = await client.vote({
  signer,
  authenticatorType: 'GovernorBravoAuthenticatorSignature',
  data: {
    spaceId: '0x...', // Governor contract address
    proposalId: 42,   // Proposal ID
    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
'GovernorBravoAuthenticatorSignature'
required
Must be set to ‘GovernorBravoAuthenticatorSignature’
data
Vote
required
Vote data object
envelope
Envelope<Vote>
Returns an envelope containing the signature data and vote data

Submitting Signed Votes

After creating a signed vote, it must be submitted to the Governor Bravo contract:
import { Contract } from '@ethersproject/contracts';

const governorAbi = [
  'function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s)'
];

const governor = new Contract(
  envelope.data.spaceId,
  governorAbi,
  provider
);

// Extract signature components
const sig = envelope.signatureData.signature;
const r = sig.slice(0, 66);
const s = '0x' + sig.slice(66, 130);
const v = parseInt(sig.slice(130, 132), 16);

// Submit the vote
const tx = await governor.castVoteBySig(
  envelope.data.proposalId,
  envelope.data.choice,
  v,
  r,
  s
);

await tx.wait();
console.log('Vote submitted:', tx.hash);

GovernorBravoEthereumTx (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.GovernorBravoEthereumTx({
  provider
});

Methods

vote()

Submit a vote transaction directly to the Governor Bravo contract.
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);
signer
Signer
required
Ethers.js signer connected to a provider
data
Vote
required
Vote data object (same structure as signature-based client)
transaction
TransactionResponse
Returns an ethers.js transaction response that can be awaited

Type Definitions

Vote

type Vote = {
  spaceId: string;      // Governor Bravo contract address
  proposalId: number;   // Proposal ID
  choice: 0 | 1 | 2;    // 0=Against, 1=For, 2=Abstain
  reason?: string;      // Optional vote reason
};

EIP712Ballot

type EIP712Ballot = {
  proposalId: number;
  support: number; // Same as choice
};

type EIP712BallotWithReason = {
  proposalId: number;
  support: number;
  reason: string;
};

SignatureData

type SignatureData = {
  authenticatorType: 'GovernorBravoAuthenticatorSignature';
  address: string;
  signature: string;
  domain: TypedDataDomain;
  types: Record<string, TypedDataField[]>;
  message: Record<string, any>;
};

Governor Contract Compatibility

The client automatically detects the governor contract name for EIP-712 signing:
// The client queries the governor contract's name() function
const name = await governorContract.name();
// e.g., "Compound Governor Bravo"

// Used in EIP-712 domain:
const domain = {
  name: name,
  chainId: 1,
  verifyingContract: '0x...'
};
For contracts without a name() function, custom names can be configured in the client source code.

Complete Example

Here’s a full example of voting on a Governor Bravo 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.GovernorBravoEthereumSig({
  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: 'GovernorBravoAuthenticatorSignature',
  data: {
    spaceId: '0x...', // Governor address
    proposalId: 42,
    choice: 1,
    reason: 'Fully support this initiative'
  }
});

// 4. Submit vote to Governor contract
const governorAbi = [
  'function castVoteWithReasonBySig(uint256 proposalId, uint8 support, string reason, uint8 v, bytes32 r, bytes32 s)'
];

const governor = new Contract(
  envelope.data.spaceId,
  governorAbi,
  provider
);

const sig = envelope.signatureData.signature;
const r = sig.slice(0, 66);
const s = '0x' + sig.slice(66, 130);
const v = parseInt(sig.slice(130, 132), 16);

const tx = await governor.castVoteWithReasonBySig(
  envelope.data.proposalId,
  envelope.data.choice,
  envelope.data.reason || '',
  v,
  r,
  s
);

await tx.wait();
console.log('Vote successfully cast:', tx.hash);

Source Code

View the complete implementation:

Build docs developers (and LLMs) love