v4quote
Exact-integer AMM quote math for Python: Uniswap v2 / v3 / v4 (including some v4 hooks), Algebra Integral,
LFJ Liquidity Book, OpenZeppelin ERC-4626 vaults and Metric OMM pools. Given a
pool's state at a block, v4quote returns the same amounts as the protocol's own quoter or swap(), to the wei,
including the cases where the contract reverts. It also includes a thin web3.py layer that loads pool state at a
pinned block (Multicall3, StateView, tick-bitmap words) and an optional Rust backend that returns identical results.
- Exact, not approximate. Python integers mirror the Solidity arithmetic (rounding, overflow checks,
uncheckedblocks). The swap path never uses floats, andtests/unit/test_no_float.pyenforces this. - Refuses instead of guessing. A pool feature that is not modelled raises
UnsupportedPool(for example a swap-altering v4 hook, or an LB hook outside the allowlist). A swap that walks past the ticks or bins you loaded raisesInsufficientState. A contract revert raisesMathRevert. - Pure functions over frozen state dataclasses. You can snapshot state, serialise it to JSON losslessly and replay quotes offline.
Supported pools
| Module | Pools |
|---|---|
v4quote.amm.uniswap_v2 |
Uniswap v2 and x*y=k forks with a parameterised fee (getAmountOut / getAmountIn) |
v4quote.amm.v3 |
Uniswap v3 TickMath / SqrtPriceMath / SwapMath / swap loop with the word-by-word tick-bitmap walk. Exact forks: PancakeSwap v3, Pharaoh (Ramses) V3 |
v4quote.amm.v3.pool_address |
CREATE2 pool addresses (parameterised deployer, init-code hash and key type) |
v4quote.amm.uniswap_v4 |
Uniswap v4 pools without swap-altering hooks (v4 SwapMath, LP fee + protocol fee). Optional: fee-only hooks (hook_fee) and modelled return-delta hooks (rd_hook) |
v4quote.amm.v4_rd_hooks |
Return-delta hook models: output_bps (PonsV2MemeHook), quote_bps (PangeaFeeHook) |
v4quote.amm.algebra_integral |
Algebra Integral (v1.2) pools: v3 math over the linked-list tick table, fee = fee() / the SwapFee log |
v4quote.amm.liquidity_book |
LFJ Liquidity Book v2.1 / v2.2 and Pharaoh DLMM (getSwapOut, getSwapIn, swap), with an allowlist of reviewed LB hooks |
v4quote.amm.erc4626 |
OpenZeppelin v5 ERC-4626 vault treated as an asset <-> shares pool |
v4quote.amm.metric_omm |
Metric OMM pools (oracle-priced bins): exact-output quotes; exact-input quotes that are refused (UnsupportedPool) when the pool's in-bin rounding could change the result. Loader and quoter wrappers in v4quote.chain.metric |
v4quote.routing |
best_route: exact-input route over the states above, up to 2 hops and a 2-way split, with gas units per hop |
v4quote.chain |
State loaders at a pinned block, Multicall3, quoter wrappers, eth_getLogs scanner, RPC from env with URL masking |
v4quote.amm.serialization |
Lossless JSON (de)serialisation of every pool state |
v4quote.amm.native |
Optional Rust backend (native/, module v4quote_native) for v2 / v3 / v4 / ERC-4626 exact-input quotes |
Verification
Each claim below is backed by a test in this repository.
Offline (no network). Run all of them with uv run pytest -m "not onchain".
| Claim | Test |
|---|---|
| v3 TickMath / SqrtPriceMath / SwapMath equal the Uniswap v3-core and v4-core test vectors (incl. 28 + 13 TickMath snapshot vectors) | tests/unit/test_v3_math.py |
v3 swap loop reproduces all 240 cases of v3-core's UniswapV3Pool.swaps.spec.ts snapshot |
tests/unit/test_v3_pool.py |
Algebra Integral: 20 exact-input swap sequences (1-2 pools, 2 hops) recorded on chain with eth_simulateV1 (Robinhood Chain, block 100117724): 19 equal the model to the wei, 1 is refused for insufficient loaded state (never a wrong number) |
tests/unit/test_algebra_integral.py |
| ERC-4626 (OZ v5) previews / deposit / redeem, incl. values read from Gnosis sDAI | tests/unit/test_erc4626.py |
| Liquidity Book math, hook rules and fee parameters | tests/unit/test_liquidity_book.py, tests/unit/test_lb_loader_*.py |
| v4 return-delta hook models follow the hooks' contract arithmetic. These are offline tests only: there are no V4Quoter vectors for these hooks yet | tests/unit/test_v4_rd_hooks.py |
The Rust backend is bit-exact with Python: 4,200 randomized v3 quotes and 2,600 v4 quotes, 0 fallbacks (counts printed with -s) |
tests/unit/test_native_equivalence.py, tests/unit/test_native_v4.py |
| Serialisation round-trips every state kind | tests/unit/test_serialization.py |
Metric OMM: 1,760 quoteHypothetical* calls on 10 Robinhood Chain pools (eth_call on a fork of block 102703478, stock / WETH / 9- and 18-decimal pairs, both directions, exact input and output): 880/880 exact-output quotes equal to the unit, 589 exact-input quotes equal, 15 revert alike, 276 exact-input quotes refused, 0 different. Price validation (asymmetry bound) on 88 provider quotes matches the pool |
tests/unit/test_metric_omm.py |
On-chain (marker onchain). These compare the model with the protocol's own contracts at one pinned block. Each
chain's tests run only when <CHAIN>_RPC_URL is set and are skipped otherwise. Some of them need an archive
endpoint, and rate-limited public endpoints can fail with HTTP 429.
| Claim | Test | Env |
|---|---|---|
v2 / v3 / v4 / LB quotes equal Router02, QuoterV2, V4Quoter and getSwapOut / getSwapIn / LBQuoter (Monad, Avalanche pools in tests/fixtures/pools.yaml) |
tests/onchain/test_quotes.py |
MONAD_RPC_URL, AVALANCHE_RPC_URL |
Real historical LB swap() calls (hooks executed) replay to the wei |
tests/onchain/test_lb_swap_replay.py |
AVALANCHE_RPC_URL |
| Fixture pools / hooks are what the official factories and StateView return | tests/onchain/test_address_links.py, tests/onchain/test_routing_replay.py |
MONAD_RPC_URL, AVALANCHE_RPC_URL |
| ERC-4626 (sDAI) matches the deployed vault's previews on many blocks | tests/onchain/test_erc4626_onchain.py |
GNOSIS_RPC_URL |
| PulseX v1 / v2 pairs and a Uniswap v3 copy match their routers, pair K checks and QuoterV2 | tests/onchain/test_pulsechain_onchain.py |
PULSECHAIN_RPC_URL |
Metric OMM pools (10 on Robinhood Chain) match MetricOmmSwapQuoter.quoteHypothetical* priced with the provider quote read at the same block: every quote equal to the unit, reverting alike, or refused by the model |
tests/onchain/test_metric_onchain.py |
ROBINHOOD_RPC_URL |
uv run pytest -m "not onchain" # offline suite
AVALANCHE_RPC_URL=... uv run pytest -m onchain # on-chain comparisons for one chain
Install
Requires Python >= 3.11.
pip install v4quote
The PyPI package is pure Python. The optional Rust backend gives the same results faster; it is built from source and needs Python >= 3.12 and a Rust toolchain >= 1.90:
git clone https://github.com/m6xcom/v4quote && cd v4quote
uv sync --extra dev # pure Python
uv sync --extra dev --extra native # + Rust backend (builds ./native with maturin)
You can also use pip: pip install -e ".[dev]". To add the Rust backend, also run pip install ./native.
The Rust backend is used when it imports, was built from the current native/ sources (a build fingerprint
is checked), and V4QUOTE_NATIVE is not 0. Otherwise every call runs the Python code, and the results are the
same. v4quote.amm.native.describe() tells you which backend is active and why. The native path for v4 pools is
opt-in: call native.enable_v4().
Logging: v4quote.chain.rpc.get_web3 / web3_from_url raise the web3.providers, web3._utils.http_session_manager,
urllib3.connectionpool and urllib3 loggers to INFO (when unset or lower) and add a redaction filter, because their
DEBUG records contain the endpoint URL, which often embeds an API key. A level you set to INFO or higher is kept.
Quickstart: quote a v4 pool and check it against V4Quoter
examples/quickstart_v4.py:
from v4quote.amm import uniswap_v4 as v4
from v4quote.chain.loaders import load_v4_pool
from v4quote.chain.quoters import v4_quoter_exact_input_single
from v4quote.chain.rpc import get_web3
# Avalanche C-Chain, Uniswap v4: native AVAX / USDC, fee 500 pips, tickSpacing 10, no hooks
STATE_VIEW = "0xc3c9e198c735a4b97e3e683f391ccbdd60b69286"
V4_QUOTER = "0xbe40675bb704506a3c2ccfb762dcfd1e979845c2"
USDC = "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"
KEY = v4.PoolKey(v4.ZERO_ADDRESS, USDC, 500, 10)
w3 = get_web3("avalanche") # reads AVALANCHE_RPC_URL; the URL never appears in errors or logs
block = w3.eth.block_number - 2
state = load_v4_pool(w3, STATE_VIEW, KEY, block, words_each_side=16)
amount_in = 25 * 10**18 # 25 AVAX
ours = v4.quote_exact_input(state, True, amount_in).amount_out # AVAX -> USDC, computed locally
chain = v4_quoter_exact_input_single(w3, V4_QUOTER, KEY, True, amount_in, block)
assert ours == chain
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc uv run python examples/quickstart_v4.py
When the swap walks past the loaded tick-bitmap words, the quote raises InsufficientState instead of returning a
wrong number. Reload with a larger words_each_side (or None for the full bitmap). After that, state is a plain
frozen dataclass: quote it as often as you like, apply a swap with v4.apply_swap, or store it with
v4quote.amm.serialization.
Quote a Metric pool
A Metric pool prices from its provider's bid / ask / reference and holds liquidity in bins around the reference.
load_metric_pool reads every bin, the pool's position and the provider quote at one block; the same raw quote can be
passed to the pool's hypothetical quoter, so both sides price identical state.
from v4quote.amm import metric_omm as mo
from v4quote.chain.metric import load_metric_pool, metric_quoter_exact_output
from v4quote.chain.rpc import get_web3
from v4quote.errors import UnsupportedPool
POOL = "0x1E8Fc442AD3606719F7016DED6C17B56C3cA7386" # WETH/USDG on Robinhood Chain (chainId 4663)
w3 = get_web3("robinhood") # reads ROBINHOOD_RPC_URL
block = w3.eth.block_number - 2
state = load_metric_pool(w3, POOL, block) # UnreadableState if the provider gives no quote (stale feed, closed market)
ours = mo.quote_exact_output(state, False, 10**17) # buy 0.1 WETH: USDG in, to the unit
prices = (state.bid_x64, state.ask_x64, state.reference_x64)
assert (ours.amount_in, ours.amount_out) == metric_quoter_exact_output(w3, POOL, False, 10**17, prices, block)
try:
sold = mo.quote_exact_input(state, True, 10**17) # sell 0.1 WETH for USDG
except UnsupportedPool:
sold = None # the pool's own in-bin rounding could change the last unit: ask the quoter instead
ROBINHOOD_RPC_URL=https://rpc.mainnet.chain.robinhood.com uv run python examples/quickstart_metric.py
Prices in a Metric state are the provider's raw quote. The public API's bidAdj / askAdj and depth already include
the spread and fees and cannot reproduce the pool to the unit, so the model is loaded from chain state;
v4quote.chain.metric also has helpers for the API's pool list and status (check_api_quotable). Swap extensions
(StopLoss, velocity guards, allowlists) can only accept or reject a swap, so they never change a quoted amount, but
they can reject it, exactly as they would make the quoter revert.
Adding a hook model
A v4 pool whose hook changes swap amounts is rejected (UnsupportedPool) until the hook has an exact model. To add
one:
-
Read the verified source. Model only hooks whose swap path is pure arithmetic over known per-pool terms (for example a fee in bps of one leg). Note which callbacks run (
beforeSwap/afterSwap), which leg they take from, and the exact rounding. -
Describe the model. Add a kind to
v4quote.amm.v4_rd_hooks.RdHook:covers(flags): the hook permission flags this kind accounts for;input_fee/output_fee: the take before and after the pool swap, using the contract's integer arithmetic;check_post_tick, if the hook reverts on the post-swap tick.
uniswap_v4.quote_exact_inputand the native hop apply these around the pool swap. Anything the model cannot reproduce must raiseMathRevertorUnsupportedPool, never approximate. -
Write offline tests for the arithmetic: edges, rounding, reverts. Use
tests/unit/test_v4_rd_hooks.pyas the pattern. -
Prove it against V4Quoter. Add an
onchaintest that:- loads the pool at a pinned block with
load_v4_pool; - sets
rd_hookfrom the hook's on-chain per-pool terms; - asserts
v4.quote_exact_input(state, zfo, amount).amount_out == v4_quoter_exact_input_single(...)for both directions and several sizes, including one that crosses initialised ticks.
Pin the hook's code hash in the test so that a redeployment fails loudly.
- loads the pool at a pinned block with
Fee-only hooks (which only override the LP fee) do not need a new kind. Resolve the fee the hook will apply and pass
hook_fee=(fee_zero_for_one, fee_one_for_zero) in the state, then verify it against V4Quoter in the same way.
Layout
src/v4quote/ Python package (amm/, chain/, math/, routing.py, routing_pools.py, errors.py)
native/ optional Rust crate v4quote-native (pyo3 + maturin), bit-exact port of the hot quotes
tests/unit/ offline tests; fixtures in tests/fixtures/
tests/onchain/ comparisons against deployed contracts (marker onchain)
examples/ runnable examples
License
Copyright (C) 2026 m6xcom
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
version (SPDX: GPL-2.0-or-later). It is distributed WITHOUT ANY WARRANTY; see LICENSE for the full text.
Acknowledgements
The swap math is an independent Python / Rust port of the arithmetic of the Uniswap v2-core, v3-core and v4-core
contracts, and of the Algebra Integral, LFJ Liquidity Book and OpenZeppelin ERC-4626 contracts, written to reproduce
their results exactly. The Metric OMM model is written from Metric's public documentation, its MIT-licensed interfaces
and lens contracts, and quoter measurements; it contains no code from Metric's BUSL-licensed pool contracts. Test vectors in tests/fixtures/uniswap_v3_*.json and in tests/unit/test_v3_math.py come
from the test suites of Uniswap v3-core and v4-core. This project is not affiliated with or endorsed by Uniswap Labs
or the other protocols' teams, including Metric.
Metadata
Release files for v4quote 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| v4quote-0.1.0.tar.gz | 460.1 kB | With provenance | Details for v4quote-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| v4quote-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for v4quote-0.1.0-py3-none-any.whl |
Total release size: 582.9 kB
Release files / v4quote-0.1.0.tar.gz
| Download URL | v4quote-0.1.0.tar.gz |
|---|---|
| Size | 460.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
daa785f4698ee3552e779777654ce7882f92f5da9da3ff471147e28c7b717426
|
|
BLAKE2b-256 checksum How to use checksums |
80267d2c0bb55b40e39f141184bcbf2400676f0a3f158c9c990ea083920edf2d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / v4quote-0.1.0-py3-none-any.whl
| Download URL | v4quote-0.1.0-py3-none-any.whl |
|---|---|
| Size | 122.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
74699200510230f28d521cb2a17733790268f67d6f08aa7d0a8d80071aacfbd9
|
|
BLAKE2b-256 checksum How to use checksums |
323dc1059f69c370b8eb3eb3b1d4573a0705afe876e692f4a24ce3215622dcda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logSecurity for v4quote 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for v4quote-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.