Skip to main content

langchain-uniswap-v2

CI PyPI

LangChain tools for Uniswap V2 (and Uniswap-V2-shaped forks, e.g. PancakeSwap): live swap quotes, liquidity previews, LP token balances, and execution plans for swaps, approvals, and add/remove liquidity — ready for an EOA to sign or a smart-contract wallet to batch, on any EVM chain, given just an RPC URL and contract addresses.

LangChain's existing blockchain integrations are Coinbase's CDP AgentKit (execution-oriented — transfers, trades, deployments — tied to Coinbase's own wallet infrastructure) and the Compass DeFi Toolkit (a paid third-party API that returns unsigned transactions across several protocols, including Uniswap). Neither is a free, permissionless toolkit that works against any EVM chain given just an RPC URL — no API key, no third-party service, no wallet required. This package fills that specific gap for the Uniswap V2 AMM shape.

Scope

This package never holds a private key, a signer, or any wallet state, and it never signs or submits a transaction. Read tools (quotes, liquidity previews, balances) only make RPC calls. Write tools (approvals, swaps, add/remove liquidity) build and return an execution plan — an ordered list of account-agnostic contract calls, ready for an EOA to sign or a smart-contract wallet to batch. See Execution modes and Write tools below.

Install

pip install langchain-uniswap-v2

For local development (editable install + test dependencies):

pip install -e ".[dev]"

Quick start

from langchain_uniswap_v2 import UniswapV2Toolkit

# Any Uniswap-V2-shaped DEX, on any chain, via explicit addresses:
toolkit = UniswapV2Toolkit(
    rpc_url="https://eth.llamarpc.com",
    router_address="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
    native_wrapped_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",  # WETH
)

# Or, for a chain already in the built-in registry, just pass a chain_id:
toolkit = UniswapV2Toolkit.for_chain(chain_id=1)  # mainnet
# toolkit = UniswapV2Toolkit.for_chain(chain_id=56)  # bsc (PancakeSwap)

tools = toolkit.get_tools()
# -> [get_quote_in, get_quote_out, get_pool_quote, get_lp_amounts,
#     get_liquidity_token_balance, is_token_balance_sufficient,
#     is_native_balance_sufficient, is_derived_token_input_sufficient,
#     is_derived_native_input_sufficient, is_liquidity_sufficient,
#     is_liquidity_sufficient_eth, is_liquidity_removal_sufficient,
#     approve_token,
#     swap_exact_tokens_for_tokens, swap_tokens_for_exact_tokens,
#     swap_exact_eth_for_tokens, swap_eth_for_exact_tokens,
#     swap_exact_tokens_for_eth, swap_tokens_for_exact_eth,
#     add_liquidity, add_liquidity_eth,
#     remove_liquidity, remove_liquidity_eth]

# Pass tools directly into an agent:
#   agent = create_agent(model, tools=tools)

# Or call a tool directly:
result = tools[1].invoke(
    {  # get_quote_out
        "token_in": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",  # WETH
        "token_out": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",  # USDC
        "amount_in": 1,
    }
)
# -> {"amount_in": 1.0, "amount_out": <live price>, "path": [...]}

UniswapV2Toolkit.for_chain(...) accepts an optional rpc_url= override — the registry's default is a free public endpoint, fine for prototyping but rate-limited. Pass your own (Alchemy, Infura, etc.) for production use.

A second example, for a smart-contract wallet consuming calls directly instead of signing EOA transactions:

scw_toolkit = UniswapV2Toolkit.for_chain(chain_id=1, tx_mode="calls")
scw_tools = {t.name: t for t in scw_toolkit.get_tools()}

plan = scw_tools["swap_exact_tokens_for_tokens"].invoke(
    {
        "token_in": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",  # WETH
        "token_out": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",  # USDC
        "amount_in": 1,
        "from_address": "0xYourSmartAccount...",
    }
)
# plan["transactions"] is None -- zero nonce/gas/fee RPC calls were made.
executions = [(c["to"], c["value"], bytes.fromhex(c["data"][2:])) for c in plan["calls"]]
# feed `executions` into your account's own batch executor, e.g. an
# ERC-7579 execute(mode, abi.encode(executions)) call.

Execution modes

Every write tool returns a plan, not a bare transaction:

{
    "calls": [  # always present, ordered -- execute all of them
        {"to": "0x...", "value": 0, "data": "0x...", "role": "approve", "description": "..."},
        {
            "to": "0x...router",
            "value": 0,
            "data": "0x...",
            "role": "swap",
            "description": "...",
        },
    ],
    "transactions": [...] | None,  # EOA mode only; same calls, signable, nonces assigned
    "chain_id": 1,
    "summary": {...},  # amounts in whole units, safe to show a user
}
  • tx_mode="eoa" (the default) also renders plan["transactions"]: the same calls as unsigned, signable transactions with sequential nonces already assigned. Sign and broadcast them in order.
  • tx_mode="calls" returns calls only (transactions is None) and makes zero nonce/gas/fee RPC calls — for a smart-contract wallet's own batch executor.

calls is the smallest primitive both audiences agree on: an EOA transaction is (to, value, data) plus nonce/gas/fees; an ERC-7579 Execution is exactly (to, value, data). Execute every call in a plan, in order — if your account supports batching, execute them atomically in one transaction. That atomicity is what makes the approve → action → reset sequence safe for accounts that reject standing approvals.

gas_estimated caveat (EOA mode): a call whose prerequisite approval is earlier in the same plan can't be simulated yet — the allowance isn't on-chain until that earlier call is mined. For those, the transaction's gas field falls back to a static per-role default (DEFAULT_GAS) and gas_estimated is set to False, flagging that the limit wasn't derived from a live simulation and should be sanity-checked before broadcast. Only the first call in a plan has its revert treated as fatal — if that one fails to estimate, the tool raises ToolException instead of returning a plan that would fail on-chain. gas_estimated is metadata, not a transaction field — pop it (or move it into a parallel list) before signing.

Constructor options, all keyword-only:

Param Default Meaning
tx_mode "eoa" "eoa" also renders transactions. "calls" returns calls only.
estimate_gas True EOA mode only. False uses default_gas for every call, skipping live eth_estimateGas.
gas_buffer 1.25 Multiplier applied to a successful gas estimate.
default_gas None Per-role fallback gas limits, merged over the module's DEFAULT_GAS.
reset_residual_approvals None Append a trailing zero-approval where needed (see Write tools). Defaults to True in "calls" mode, False in "eoa" mode.
preflight True Run the matching balance-sufficiency check before building, raising ToolException (naming the shortfall) if it fails.

UniswapV2Toolkit.for_chain(chain_id, rpc_url=None, **kwargs) forwards **kwargs to the constructor, so e.g. UniswapV2Toolkit.for_chain(1, tx_mode="calls", preflight=False) works.

Tools

All tools take plain contract addresses as arguments — no ticker→address registry is assumed.

Read tools

Tool Purpose
get_quote_in(token_in, token_out, amount_out) How much token_in is needed to receive an exact amount_out of token_out.
get_quote_out(token_in, token_out, amount_in) How much token_out will be received for an exact amount_in of token_in.
get_pool_quote(token_a, token_b, amount_a) Proportional token_b deposit required to match a given token_a deposit, before adding liquidity.
get_lp_amounts(token_a, token_b, lp_amount) Expected token amounts redeemable for a given amount of LP tokens, before removing liquidity.
get_liquidity_token_balance(owner_address, token_a, token_b) An address's LP token balance for a given pair.

For a token_in/token_out pair, a direct pool is used when one exists and has liquidity; otherwise, if native_wrapped_address is configured, the quote routes through it (2-hop → 3-hop path), matching how Uniswap V2 pools are typically seeded.

Balance-sufficiency checks answer "does this address hold enough" before a write tool would actually be called, given an explicit owner_address — useful for an agent to check before spending gas building/submitting a transaction that would just revert. Every write tool below already runs its matching check automatically before building (see preflight above); these tools are for checking ahead of time or with preflight=False. Each mirrors the balance requirement of one or two write tools below:

Tool Checks balance for
is_token_balance_sufficient(token_address, amount, owner_address) An exact ERC20 amount — e.g. before approve_token or an exact-input swap.
is_native_balance_sufficient(amount, owner_address) An exact native-asset amount.
is_derived_token_input_sufficient(token_in, token_out, amount_out, owner_address, slippage_bps=50) swap_tokens_for_exact_tokens / swap_tokens_for_exact_eth — required input is derived from a live quote plus slippage.
is_derived_native_input_sufficient(token_out, amount_out, owner_address, slippage_bps=50) swap_eth_for_exact_tokens.
is_liquidity_sufficient(token_a, amount_a, token_b, owner_address) add_liquidity — both token amounts, the second derived from live reserves.
is_liquidity_sufficient_eth(token, amount_token, owner_address) add_liquidity_eth.
is_liquidity_removal_sufficient(token_a, token_b, lp_amount, owner_address) remove_liquidity / remove_liquidity_eth — LP token balance.

Write tools (execution plans)

Every write tool below includes its own approval call(s) in the plan automatically, sized to what that call actually needs — you no longer need to call approve_token first. approve_token remains available for explicit/manual control (e.g. granting an allowance outside of any swap or deposit flow, or to a spender other than this toolkit's router), and takes an unlimited=True option for the standard effectively-unlimited approval instead of passing an arbitrary large amount.

Tool Approves Reset needed
approve_token(token_address, spender_address, from_address, amount=0, unlimited=False) is itself the approval
swap_exact_tokens_for_tokens / swap_exact_tokens_for_eth token_in, the exact amount sold no — exact pull
swap_tokens_for_exact_tokens / swap_tokens_for_exact_eth token_in, the derived max yes, when the router pulls less
swap_exact_eth_for_tokens / swap_eth_for_exact_tokens — (native value, no approval needed)
add_liquidity / add_liquidity_eth the token(s) deposited yes — pool ratio may consume less
remove_liquidity / remove_liquidity_eth the pair's own LP token, the exact amount burned no — exact pull

"Reset needed" means: when the router may pull less than it was approved for, the plan appends a trailing approve(spender, 0) call — but only when reset_residual_approvals is enabled (see Execution modes). Tools that always pull their exact approved amount never append one, regardless of that setting, since there is nothing left to clear.

All amount-based write tools also take slippage_bps (default 50 = 0.5%) and derive amountOutMin/amountInMax/equivalent from a live on-chain quote, and deadline_secs (default 600) for the plan's on-chain expiry — both are always explicit, never silently applied. Every write tool also takes an optional recipient (defaults to from_address) for the output of the call — the account executing the plan and the account receiving the result don't have to be the same address — and an optional nonce: the starting nonce for the plan in EOA mode (later calls increment from it automatically), ignored entirely in "calls" mode.

"ETH" in tool/parameter names is a generic internal label for the chain's native asset (ETH, BNB, etc.) — it works identically on every supported network.

Supported chains (built-in registry)

All addresses are Uniswap Labs' own official V2 redeployments, except BSC, which deliberately uses PancakeSwap — see the note below.

chain_id name native_token
1 mainnet ETH
11155111 sepolia ETH
130 unichain ETH
42161 arbitrum ETH
43114 avalanche AVAX
56 bsc (PancakeSwap V2) BNB
8453 base ETH
10 optimism ETH
137 polygon POL
7777777 zora ETH
480 worldchain ETH
143 monad MON
196 x-layer OKB

Why BSC uses PancakeSwap, not Uniswap Labs' own BSC redeployment: the official Uniswap Labs BSC contracts exist and respond to calls, but were live-tested and found to have near-zero liquidity (returning wildly incorrect quotes). PancakeSwap has the actual liquidity on BSC, so the registry points there instead.

For any chain not listed here, instantiate UniswapV2Toolkit(...) directly with explicit router_address / factory_address / native_wrapped_address.

Migration (0.2.0 → 0.3.0)

Read tools are unchanged. Write tools now return an execution plan instead of a bare transaction dict:

0.2.0 0.3.0
tx = tool.invoke(...) → bare tx dict plan = tool.invoke(...) → plan dict
sign(tx) for tx in plan["transactions"]: sign(tx)
caller calls approve_token separately approval is plan["calls"][0], already sized
caller manages nonce collisions nonces pre-assigned across the plan
caller re-quotes to describe the swap plan["summary"]
build raises when allowance missing builds fine; gas_estimated: False flags fallbacks

Minimal EOA diff:

-tx = tools["swap_exact_tokens_for_tokens"].invoke({...})
-signed = acct.sign_transaction(tx)
-w3.eth.send_raw_transaction(signed.raw_transaction)
+plan = tools["swap_exact_tokens_for_tokens"].invoke({...})
+for tx in plan["transactions"]:
+    tx.pop("gas_estimated", None)
+    signed = acct.sign_transaction(tx)
+    w3.eth.send_raw_transaction(signed.raw_transaction)

Development

pip install -e ".[dev]"
pytest
ruff check .
ruff format --check .

The test suite mocks all contract calls (see tests/web3_mocks.py) — it makes no live network calls, so it runs fast and doesn't depend on any RPC endpoint being up.

langchain_uniswap_v2/abis.py is machine-generated from the original ABI JSON and verified byte-for-byte against it — it's excluded from Ruff so lint/format never churns it.

CI (.github/workflows/ci.yml) runs ruff check/ruff format --check and the test suite (Python 3.10–3.12) on every push and pull request.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_uniswap_v2-0.3.0.tar.gz (35.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_uniswap_v2-0.3.0-py3-none-any.whl (29.4 kB view details)

Uploaded Python 3

File details

Details for the file langchain_uniswap_v2-0.3.0.tar.gz.

File metadata

  • Download URL: langchain_uniswap_v2-0.3.0.tar.gz
  • Upload date:
  • Size: 35.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for langchain_uniswap_v2-0.3.0.tar.gz
Algorithm Hash digest
SHA256 13507fd4476ce7e10328187d356b74528e74ab0d355811b312cbacfa5e980a6c
MD5 20a82d21490ab0f6383bfdcdc7b48627
BLAKE2b-256 09765844a6dbe29a8912dd5082c711f9acd05c231ed9c6b5470cd05fcf4bdcd5

See more details on using hashes here.

File details

Details for the file langchain_uniswap_v2-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for langchain_uniswap_v2-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 04f57b9ac989711ee13020fc3c1d2f1c83f40b6a611a6854b27b0a984f5fe853
MD5 35809ae9dd310779ec2e8cefda54a91f
BLAKE2b-256 8cd157af37f782e7a6d5c4a6e824f4f20fc5df17980e457e47cf5a600fe22590

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page