# Curve Voting Library

Source: [https://docs.curve.finance/developer/curve-dao/governance/voting-library](https://docs.curve.finance/developer/curve-dao/governance/voting-library)

The `curve-voting-lib` is a Python toolkit for creating and simulating Curve DAO governance votes. Built on [titanoboa](https://github.com/vyperlang/titanoboa) (a Vyper/EVM execution framework), it replaces the deprecated `curve-dao` package.

GitHub

Source code for `curve-voting-lib` can be found on [GitHub](https://github.com/curvefi/curve-voting-lib).

* * *

<a id="overview"></a>

## Overview

The library uses a **context manager** pattern (`vote()`) that automatically captures contract interactions and constructs [Aragon EVM scripts](https://hack.aragon.org/docs/aragonos-ref#evmscripts). Every vote is **simulation-first**: before going live, the library creates the vote using the Convex voter proxy (the largest veCRV holder), votes yes, time-travels past the voting period, and executes — verifying the entire lifecycle on a forked mainnet.

Key features:

-   **Context manager voting** — `vote()` monkey-patches titanoboa's `prepare_calldata` to capture contract calls
-   **Automatic simulation** — every vote is simulated before going live
-   **IPFS integration** — vote descriptions are pinned via Pinata with local caching
-   **Cross-chain support** — `xvote()` context manager for cross-chain governance across 20+ chains

* * *

<a id="installation"></a>

## Installation

```bash
git clone https://github.com/curvefi/curve-voting-lib.git
cd curve-voting-lib
uv sync
uv run python -m pip install -e .
```

<a id="environment-variables"></a>

### Environment Variables

| Variable | Description |
| --- | --- |
| `RPC_URL` | Ethereum RPC endpoint URL |
| `PINATA_JWT` | Pinata API JWT token for IPFS pinning |

* * *

<a id="dao-types"></a>

## DAO Types

Curve has two DAO types with different quorum and support requirements:

| DAO Type | Quorum | Support | Use Case |
| --- | --- | --- | --- |
| `OWNERSHIP` | 30% | 51% | Critical changes (ownership transfers, etc.) |
| `PARAMETER` | 15% | 60% | Parameter adjustments (pool fees, etc.) |

Each DAO type is a `DAOParameters` dataclass containing the agent, voting contract, token, and quorum:

```python
from voting import OWNERSHIP, PARAMETER

# OWNERSHIP
# agent:  0x40907540d8a6C65c637785e8f8B742ae6b0b9968
# voting: 0xE478de485ad2fe566d49342Cbd03E49ed7DB3356
# token:  0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2
# quorum: 30

# PARAMETER
# agent:  0x4eeb3ba4f221ca16ed4a0cc7254e2e32df948c5f
# voting: 0xbcff8b0b9419b9a88c44546519b1e909cf330399
# token:  0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2
# quorum: 15
```

* * *

<a id="core-api"></a>

## Core API

<a id="vote-context-manager"></a>

### `vote()` Context Manager

The primary interface. Any mutable contract call made inside the `with vote(...)` block is automatically captured and bundled into an Aragon EVM script.

```python
from voting import vote, OWNERSHIP

with vote(
    OWNERSHIP,
    "Description of what this vote does",
    live_env=None,  # None = simulation only, BrowserEnv() or CustomEnv() for live
):
    # All contract calls here are captured as vote actions
    contract.do_something(value)
    assert contract.something() == value  # assertions work too
```

The context manager also acts as a **prank** (the caller is set to the DAO agent) and an **anchor** (state changes are reverted after the block). On exit, it:

1.  Prepares the EVM script from captured actions
2.  Creates the vote using the Convex voter proxy (`0x989AEB4D175E16225E39E87D0D97A3360524AD80`)
3.  Votes yes, time-travels past the voting period, and executes
4.  If `live_env` is set, pins the description to IPFS and creates the vote on-chain

<a id="vote_test-context-manager"></a>

### `vote_test()` Context Manager

Use `vote_test()` inside a `vote()` block to run assertions without capturing them as vote actions:

```python
with vote(OWNERSHIP, "Set pool implementation"):
    factory.set_pool_implementation(new_impl, impl_hash)

    with vote_test():
        # These calls are NOT captured as vote actions
        assert factory.pool_implementations(impl_hash) == new_impl
```

<a id="live-voting-environments"></a>

### Live Voting Environments

For creating actual on-chain votes (not just simulations):

```python
from voting import vote, OWNERSHIP, BrowserEnv, CustomEnv

# Using a browser wallet (Rabby, MetaMask, etc.)
with vote(OWNERSHIP, "Description", live_env=BrowserEnv()):
    contract.do_something()

# Using a custom account
from eth_account import Account
env = CustomEnv(rpc="https://ethereum-rpc.publicnode.com/", account=Account.from_key("0x..."))
with vote(OWNERSHIP, "Description", live_env=env):
    contract.do_something()
```

* * *

<a id="usage-example"></a>

## Usage Example

This example sets a new pool implementation on the TwoCrypto-NG factory:

```python
import os
import boa
from voting import vote, abi, OWNERSHIP, BrowserEnv
from eth_utils import keccak

RPC_URL = os.getenv("RPC_URL")
boa.fork(RPC_URL)
factory = abi.twocrypto_ng_mainnet_factory.at("0x98EE851a00abeE0d95D08cF4CA2BdCE32aeaAF7F")

with vote(
    OWNERSHIP,
    "[twocrypto] Add implementations for donations-enabled pools",
    live=BrowserEnv(),
):
    factory.set_pool_implementation(
        donations_pool := "0xbab4CA419DF4e9ED96435823990C64deAD976a9F",
        donations_hash := int.from_bytes(keccak(b"donations"), "big"),
    )

    assert factory.pool_implementations(donations_hash) == donations_pool
```

Scripts can be run using:

```bash
uv run scripts/gauges/add_gauge.py
uv run scripts/twocrypto-ng/set_implementation.py
```

* * *

<a id="cross-chain-governance-xvote"></a>

## Cross-chain Governance (`xvote()`)

The `xvote()` context manager nests inside `vote()` to create cross-chain governance proposals. It forks the target chain, captures messages, and broadcasts them through the appropriate broadcaster contract.

```python
from voting import vote, xvote, OWNERSHIP
from voting.xgov.chains import FRAXTAL

with vote(OWNERSHIP, description="[Fraxtal] Set things"):
    with xvote(FRAXTAL, "https://rpc.frax.com"):
        things.set()
```

<a id="supported-chains"></a>

### Supported Chains

The library supports 20+ chains including Arbitrum, Optimism, Base, Fraxtal, Mantle, Polygon, Gnosis, Avalanche, BSC, Fantom, Sonic, Kava, Celo, Aurora, Moonbeam, X Layer, Taiko, Ink, Etherlink, XDC, Corn, Plume, Hyperliquid, and TAC.

Each chain uses one of several broadcaster types:

| Broadcaster Type | Chains |
| --- | --- |
| Arbitrum | Arbitrum |
| Optimism | Optimism, Fraxtal, Base, Mantle |
| Polygon zkEVM | X Layer |
| Taiko | Taiko |
| Storage Proofs | All other chains (Gnosis, Polygon, BSC, Avalanche, Fantom, Sonic, Kava, Celo, etc.) |

For more details on the cross-chain governance infrastructure, see the [Cross-chain Governance](https://docs.curve.finance/developer/curve-dao/governance/x-gov/overview.md) section.

* * *

<a id="pre-built-abis"></a>

## Pre-built ABIs

The `abi` module provides pre-configured contract interfaces for common Curve contracts:

```python
from voting import abi

# Available ABIs include:
abi.aragon_agent          # Aragon Agent contract
abi.voting                # Aragon Voting contract
abi.twocrypto_ng_mainnet_factory  # TwoCrypto-NG Factory
# ... and more
```

These are loaded using `boa.loads_abi()` and can be attached to deployed addresses with `.at()`.
