Skip to main content
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

networkConfig
EvmNetworkConfig
required
Network-specific configuration including chain ID and contract addresses
manaUrl
string
required
URL of the Mana relayer service for submitting signed transactions
whitelistServerUrl
string
required
URL of the whitelist server for strategy validation
provider
Provider
required
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
data
Propose
required
Proposal data object
envelope
Envelope<Propose>
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);
data.choice
Choice
required
Vote choice: 1 (For), 2 (Against), or 3 (Abstain)
data.proposal
number
required
Proposal ID to vote on

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.
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.
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:

Build docs developers (and LLMs) love