lp-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

LP Integration

LP 集成

Integrate Uniswap liquidity provisioning into frontends, backends, and bots using the Uniswap LP API.
The LP API is a transaction-building service. You send position parameters; the API fetches live pool state, computes the dependent token amount, and returns a fully-formed, unsigned transaction. Your application signs and broadcasts it. The API never holds keys, never moves funds, and never broadcasts.
使用 Uniswap LP API 将 Uniswap 流动性提供功能集成到前端、后端和机器人中。
LP API 是一项交易构建服务。你发送头寸参数,API 获取实时池状态,计算对应的代币数量,并返回完整的未签名交易。你的应用负责签名并广播该交易。API 永远不会持有密钥、转移资金或进行广播。

Prerequisites

前置条件

This skill assumes familiarity with viem basics (client setup, account management, contract interactions, transaction signing). Install the uniswap-viem plugin for comprehensive viem/wagmi guidance:
claude plugin add @uniswap/uniswap-viem
For token swaps (not liquidity), see the sibling swap-integration skill in this plugin.
本技能假设你熟悉 viem 基础知识(客户端设置、账户管理、合约交互、交易签名)。安装 uniswap-viem 插件以获取全面的 viem/wagmi 指导:
claude plugin add @uniswap/uniswap-viem
关于代币兑换(非流动性相关),请参阅本插件中的姊妹技能 swap-integration

Base URL

基础 URL

text
LP_API_BASE_URL = https://liquidity.api.uniswap.org
All LP endpoints are POST requests under the
/lp/
prefix (e.g.
https://liquidity.api.uniswap.org/lp/create
).
The LP API host is intentionally different from the swap Trading API. Liquidity provisioning lives at
https://liquidity.api.uniswap.org
(no
/v1
prefix), not the
https://trade-api.gateway.uniswap.org/v1
host used for swaps. Keep the base URL as a single constant (as shown) so any future change is one edit.
text
LP_API_BASE_URL = https://liquidity.api.uniswap.org
所有 LP 端点均为
/lp/
前缀下的 POST 请求(例如
https://liquidity.api.uniswap.org/lp/create
)。
LP API 的主机地址与兑换交易 API 有意不同。 流动性提供功能部署在
https://liquidity.api.uniswap.org
(无
/v1
前缀),而非用于兑换的
https://trade-api.gateway.uniswap.org/v1
主机。请将基础 URL 设为单个常量(如上所示),以便未来变更时只需修改一处。

Authentication

身份验证

Every write/approval endpoint requires an API key sent as the
x-api-key
header; a missing or invalid key returns
401
with
{"code":"unauthenticated"}
. (
/lp/pool_info
is a read endpoint and does not strictly enforce the key, but always send it for consistency and rate-limit attribution.)
text
Content-Type: application/json
Accept: application/json
x-api-key: <your-api-key>
Getting a key: Register at the Uniswap Developer Platform dashboard. The same key works across swapping and liquidity provisioning. Never hardcode the key; load it from an environment variable.
每个写入/授权端点都需要在
x-api-key
请求头中携带 API 密钥;缺失或无效的密钥将返回 401 状态码及
{"code":"unauthenticated"}
。(
/lp/pool_info
是读取端点,不强制要求密钥,但为了一致性和速率限制归因,请始终携带该密钥。)
text
Content-Type: application/json
Accept: application/json
x-api-key: <your-api-key>
获取密钥:Uniswap 开发者平台仪表盘 注册。同一密钥可同时用于兑换和流动性提供功能。切勿硬编码密钥,应从环境变量中加载。

Quick Decision Guide

快速决策指南

You want to...EndpointProtocols
Check/grant token approvals before any action
/lp/check_approval
V2, V3, V4
Create a concentrated-liquidity position
/lp/create
V3, V4
Create a full-range (classic) position
/lp/create_classic
V2
Add liquidity to an existing position
/lp/increase
V2, V3, V4
Remove a percentage of liquidity
/lp/decrease
V2, V3, V4
Collect accumulated trading fees
/lp/claim_fees
V3, V4
Read live pool state
/lp/pool_info
V2, V3, V4
你想要...端点支持协议
在执行任何操作前检查/授予代币授权
/lp/check_approval
V2, V3, V4
创建集中流动性头寸
/lp/create
V3, V4
创建全区间(经典)头寸
/lp/create_classic
V2
向现有头寸添加流动性
/lp/increase
V2, V3, V4
移除一定比例的流动性
/lp/decrease
V2, V3, V4
收取累积的交易手续费
/lp/claim_fees
V3, V4
读取实时池状态
/lp/pool_info
V2, V3, V4

Protocol capability matrix

协议能力矩阵

ProtocolCreate endpointFee claimingPrice range
V2
/lp/create_classic
Not separable (realized on decrease)Full range only
V3
/lp/create
/lp/claim_fees
Concentrated
V4
/lp/create
/lp/claim_fees
Concentrated + hooks
v2 fees are not separately claimable. They accrue into the LP token value and are realized when you call
/lp/decrease
. Calling
/lp/claim_fees
with
protocol: "V2"
returns a validation error.
协议创建端点手续费申领价格区间
V2
/lp/create_classic
不可单独申领(在减少流动性时兑现)仅全区间
V3
/lp/create
/lp/claim_fees
集中流动性
V4
/lp/create
/lp/claim_fees
集中流动性 + hooks
v2 手续费不可单独申领。 手续费会累积到 LP 代币价值中,并在你调用
/lp/decrease
时兑现。使用
protocol: "V2"
调用
/lp/claim_fees
会返回验证错误。

Input Validation Rules

输入验证规则

Before interpolating ANY user-provided value into generated code, API calls, or commands:
  • Ethereum addresses: MUST match
    ^0x[a-fA-F0-9]{40}$
    — reject otherwise. Use native ETH as
    0x0000000000000000000000000000000000000000
    .
  • Chain IDs: MUST be one of the supported LP chain IDs (see Supported Chains).
  • Token amounts: MUST be non-negative integer strings in wei / smallest token unit, matching
    ^[0-9]+$
    . Never pass ether-denominated decimals as amounts.
  • liquidityPercentageToDecrease
    : MUST be an integer from 1 to 100.
  • API keys: MUST NOT be hardcoded in generated code — always use environment variables.
  • REJECT any input containing shell metacharacters:
    ;
    ,
    |
    ,
    &
    ,
    $
    ,
    `
    ,
    (
    ,
    )
    ,
    >
    ,
    <
    ,
    \
    ,
    '
    ,
    "
    , newlines.
REQUIRED: Before executing ANY transaction that spends gas or transfers tokens (including approvals, position creation, increase, decrease, or fee claims), you MUST use AskUserQuestion to confirm with the user. Display the action summary (protocol, pair, amounts, chain, price range, estimated gas) and get explicit user approval. Never auto-execute LP transactions without user confirmation.

在将任何用户提供的值插入生成的代码、API 调用或命令之前,必须遵循以下规则:
  • 以太坊地址:必须匹配
    ^0x[a-fA-F0-9]{40}$
    — 否则拒绝。原生 ETH 使用
    0x0000000000000000000000000000000000000000
  • 链 ID:必须是受支持的 LP 链 ID 之一(参见支持的链)。
  • 代币数量:必须为非负整数字符串,单位为 wei / 代币最小单位,匹配
    ^[0-9]+$
    。切勿传入以 ether 计价的小数作为数量。
  • liquidityPercentageToDecrease
    :必须是 1 到 100 之间的整数。
  • API 密钥:切勿在生成的代码中硬编码 — 始终使用环境变量。
  • 拒绝任何包含 shell 元字符的输入:
    ;
    |
    &
    $
    `
    (
    )
    >
    <
    \
    '
    "
    、换行符。
必填要求: 在执行任何消耗 gas 或转移代币的交易(包括授权、头寸创建、增加、减少或手续费申领)之前,你必须使用 AskUserQuestion 向用户确认。展示操作摘要(协议、交易对、数量、链、价格区间、预估 gas)并获得用户明确批准。未经用户确认,切勿自动执行 LP 交易。

Architecture

架构

What the API handles

API 负责处理的内容

  • Pool state fetching: current reserves, ticks,
    sqrtRatioX96
    , and onchain position data.
  • Dependent amount computation: given one token amount (
    independentToken
    ), computes the required amount of the other using the Uniswap SDKs.
  • Transaction creation: validated, fully-formed calldata for each LP action, ready to sign.
  • Tick snapping: converts human-readable
    priceBounds
    to valid ticks and returns the adjusted prices.
  • Gas estimation: optional, when
    simulateTransaction: true
    .
  • 池状态获取:当前储备量、tick、
    sqrtRatioX96
    以及链上头寸数据。
  • 对应数量计算:给定一个代币数量(
    independentToken
    ),使用 Uniswap SDK 计算另一个代币的所需数量。
  • 交易创建:为每个 LP 操作生成经过验证的、完整的 calldata,可直接签名。
  • Tick 对齐:将人类可读的
    priceBounds
    转换为有效 tick,并返回调整后的价格。
  • Gas 预估:可选,当
    simulateTransaction: true
    时启用。

What your application handles

你的应用负责处理的内容

  • Signing and broadcasting the returned transaction via your RPC provider.
  • Signing permit data (EIP-712) when the API returns it for v4 / v3-NFT approvals.
  • Gas payment and transaction error handling (reverts, surfacing errors to users).
  • 通过你的 RPC 提供商签名并广播返回的交易。
  • 当 API 返回 v4 / v3-NFT 授权的 permit 数据时,签名 permit 数据(EIP-712)。
  • 支付 Gas 和交易错误处理(回滚、向用户展示错误)。

Data flow

数据流

text
User intent (create / increase / decrease / claim)
   |
Your application
   |-- POST /lp/check_approval        -> returns approval transactions and/or permit data
   |-- (execute approval txns; sign permit if returned)
   |-- POST /lp/create | increase | decrease | claim_fees
   |-- validate the returned transaction payload (non-empty data, valid addresses)
   |-- user signature (wallet)
   +-- broadcast (your RPC)
        |
   Blockchain
text
User intent (create / increase / decrease / claim)
   |
Your application
   |-- POST /lp/check_approval        -> returns approval transactions and/or permit data
   |-- (execute approval txns; sign permit if returned)
   |-- POST /lp/create | increase | decrease | claim_fees
   |-- validate the returned transaction payload (non-empty data, valid addresses)
   |-- user signature (wallet)
   +-- broadcast (your RPC)
        |
   Blockchain

Endpoint Reference

端点参考

All field names below come from the LP API OpenAPI contract. Where the public integration guide uses different names, the contract names here are authoritative.
以下所有字段名均来自 LP API OpenAPI 合约。若公开集成指南使用了不同的名称,以本处的合约名称为准。

POST /lp/check_approval

POST /lp/check_approval

Always call this first. Returns the approval transactions (and/or permit data) needed before an LP action. If the response
transactions
array is empty and no permit data is returned, all approvals are already in place — unless
kycRequiredWarnings
is non-empty, which means the pool contains a permissioned token and the wallet is not allowlisted. In that case the response is still
200
; render the KYC call-to-action from each warning's
kycUrl
instead of attempting the LP action.
Request
json
{
  "walletAddress": "0x...",
  "protocol": "V4",
  "chainId": 1,
  "lpTokens": [
    { "tokenAddress": "0x...", "amount": "1000000000000000000" },
    { "tokenAddress": "0x...", "amount": "500000000" }
  ],
  "action": "CREATE"
}
FieldRequiredNotes
walletAddress
YesThe position owner
protocol
Yes
V2
|
V3
|
V4
chainId
YesSupported chain ID
lpTokens
YesArray of
{ tokenAddress, amount }
to be spent
action
Yes
CREATE
|
INCREASE
|
DECREASE
|
MIGRATE
simulateTransaction
NoInclude gas estimates
includeGasInfo
NoInclude gas info on returned approvals
generatePermitAsTransaction
NoIf
true
, return permit as an executable transaction instead of typed data
urgency
No
NORMAL
|
FAST
|
URGENT
v3NftTokenId
Nov3 NFT id when approving a position-manager NFT
Response
ts
interface CheckApprovalResponse {
  requestId: string;
  transactions: ApprovalTransactionRequest[]; // sign each .transaction; empty (with kycRequiredWarnings also empty) = nothing to approve
  v4BatchPermitData?: NullablePermit; // v4: sign and pass into /lp/create or /lp/increase
  v3NftPermitData?: NullablePermit; // v3 NFT permit
  kycRequiredWarnings: KycRequiredWarning[]; // permissioned pools: non-empty when the wallet is NOT allowlisted. Always present (usually []).
}

interface ApprovalTransactionRequest {
  transaction: TransactionRequest; // the tx to sign and broadcast
  cancelApproval: boolean;
  action: 'CREATE' | 'INCREASE' | 'DECREASE' | 'MIGRATE';
  gasFee?: string;
}

interface KycRequiredWarning {
  kycUrl: string;
  tokenAddress: string;
  chainId: number;
}
The response field is
transactions
, not
approvals
.
Each element WRAPS a transaction. Sign
element.transaction
, never the element object itself. There are no top-level
token
/
spender
fields.
始终首先调用此端点。返回 LP 操作前所需的授权交易(和/或 permit 数据)。如果响应中的
transactions
数组为空且未返回 permit 数据,则所有授权均已完成 — 除非
kycRequiredWarnings
非空,这意味着池包含权限型代币且钱包不在白名单中。这种情况下响应仍为 200;你应展示每个警告中
kycUrl
指向的 KYC 行动号召,而非尝试执行 LP 操作。
请求
json
{
  "walletAddress": "0x...",
  "protocol": "V4",
  "chainId": 1,
  "lpTokens": [
    { "tokenAddress": "0x...", "amount": "1000000000000000000" },
    { "tokenAddress": "0x...", "amount": "500000000" }
  ],
  "action": "CREATE"
}
字段必填说明
walletAddress
头寸所有者
protocol
V2
|
V3
|
V4
chainId
受支持的链 ID
lpTokens
要花费的
{ tokenAddress, amount }
数组
action
CREATE
|
INCREASE
|
DECREASE
|
MIGRATE
simulateTransaction
包含 gas 预估
includeGasInfo
在返回的授权信息中包含 gas 信息
generatePermitAsTransaction
若为
true
,将 permit 作为可执行交易而非类型化数据返回
urgency
NORMAL
|
FAST
|
URGENT
v3NftTokenId
授权头寸管理器 NFT 时的 v3 NFT ID
响应
ts
interface CheckApprovalResponse {
  requestId: string;
  transactions: ApprovalTransactionRequest[]; // sign each .transaction; empty (with kycRequiredWarnings also empty) = nothing to approve
  v4BatchPermitData?: NullablePermit; // v4: sign and pass into /lp/create or /lp/increase
  v3NftPermitData?: NullablePermit; // v3 NFT permit
  kycRequiredWarnings: KycRequiredWarning[]; // permissioned pools: non-empty when the wallet is NOT allowlisted. Always present (usually []).
}

interface ApprovalTransactionRequest {
  transaction: TransactionRequest; // the tx to sign and broadcast
  cancelApproval: boolean;
  action: 'CREATE' | 'INCREASE' | 'DECREASE' | 'MIGRATE';
  gasFee?: string;
}

interface KycRequiredWarning {
  kycUrl: string;
  tokenAddress: string;
  chainId: number;
}
响应字段名为
transactions
,而非
approvals
每个元素都包裹着一个交易。请对
element.transaction
签名,切勿对元素对象本身签名。响应没有顶层的
token
/
spender
字段。

POST /lp/create

POST /lp/create

Create a v3 or v4 concentrated-liquidity position. Specify a price range and one token amount; the API computes the other from live pool state.
Pool specification — provide exactly one of:
  • existingPool
    :
    { token0Address, token1Address, poolReference }
    where
    poolReference
    is the pool address (v3) or pool ID (v4).
  • newPool
    :
    { token0Address, token1Address, fee, tickSpacing, hooks?, initialPrice }
    where
    initialPrice
    is a
    sqrtRatioX96
    string (
    hooks
    is v4-only).
Price range — provide exactly one of:
  • priceBounds
    :
    { minPrice, maxPrice, quotedTokenAddress }
    minPrice
    /
    maxPrice
    are decimal price strings and
    quotedTokenAddress
    is required (it must equal
    token0Address
    or
    token1Address
    ).
    quotedTokenAddress
    sets which token the prices are denominated in; there is no default, and omitting it returns a
    400
    . The API snaps to valid ticks and returns the adjusted prices.
  • tickBounds
    :
    { tickLower, tickUpper }
    raw integers.
Request
json
{
  "walletAddress": "0x...",
  "chainId": 1,
  "protocol": "V3",
  "existingPool": {
    "token0Address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
    "token1Address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "poolReference": "0x3470447f3cecffac709d3e783a307790b0208d60"
  },
  "independentToken": {
    "tokenAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
    "amount": "198251669183062942"
  },
  "priceBounds": {
    "minPrice": "0.00000000000324",
    "maxPrice": "0.00000000000393",
    "quotedTokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
  },
  "simulateTransaction": false
}
FieldRequiredNotes
walletAddress
,
chainId
,
protocol
,
independentToken
Yes
independentToken
is
{ tokenAddress, amount }
existingPool
|
newPool
One requiredSee pool specification above
priceBounds
|
tickBounds
One requiredSee price range above
dependentToken
NoOverride the computed dependent amount
slippageTolerance
NoDecimal percent (e.g.
0.5
)
deadline
NoUnix seconds
simulateTransaction
NoInclude
gasFee
in response
urgency
No
NORMAL
|
FAST
|
URGENT
batchPermitData
+
signature
Nov4 permit from
/lp/check_approval
(note: create uses
batchPermitData
)
nativeTokenBalance
NoUsed when one side is native ETH
includeApprovalSimulation
NoInclude approval pre-calls in the gas simulation (only with
simulateTransaction: true
)
Response
ts
interface CreatePositionResponse {
  requestId: string;
  token0: LPToken; // { tokenAddress, amount }
  token1: LPToken;
  adjustedMinPrice: string; // show THIS to the user, not your input minPrice
  adjustedMaxPrice: string;
  tickLower: number;
  tickUpper: number;
  create: TransactionRequest;
  gasFee?: string; // present when simulateTransaction: true
  slippage?: number; // effective slippage the API applied (e.g. native-token v4 cases)
}
Display
adjustedMinPrice
/
adjustedMaxPrice
(the tick-snapped values), not the original
priceBounds
you sent.
创建 v3 或 v4 集中流动性头寸。指定价格区间和一个代币数量;API 将根据实时池状态计算另一个代币的数量。
池规范 — 提供以下其中一项:
  • existingPool
    { token0Address, token1Address, poolReference }
    ,其中
    poolReference
    是池地址(v3)或池 ID(v4)。
  • newPool
    { token0Address, token1Address, fee, tickSpacing, hooks?, initialPrice }
    ,其中
    initialPrice
    sqrtRatioX96
    字符串(
    hooks
    仅 v4 支持)。
价格区间 — 提供以下其中一项:
  • priceBounds
    { minPrice, maxPrice, quotedTokenAddress }
    minPrice
    /
    maxPrice
    是小数价格字符串,且
    quotedTokenAddress
    必填(必须等于
    token0Address
    token1Address
    )。
    quotedTokenAddress
    指定价格以哪种代币计价;无默认值,省略该字段将返回 400 错误。API 会将价格对齐到有效 tick 并返回调整后的价格。
  • tickBounds
    { tickLower, tickUpper }
    原始整数。
请求
json
{
  "walletAddress": "0x...",
  "chainId": 1,
  "protocol": "V3",
  "existingPool": {
    "token0Address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
    "token1Address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "poolReference": "0x3470447f3cecffac709d3e783a307790b0208d60"
  },
  "independentToken": {
    "tokenAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
    "amount": "198251669183062942"
  },
  "priceBounds": {
    "minPrice": "0.00000000000324",
    "maxPrice": "0.00000000000393",
    "quotedTokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
  },
  "simulateTransaction": false
}
字段必填说明
walletAddress
,
chainId
,
protocol
,
independentToken
independentToken
{ tokenAddress, amount }
existingPool
|
newPool
二选一必填参见上方池规范
priceBounds
|
tickBounds
二选一必填参见上方价格区间
dependentToken
覆盖计算得出的对应代币数量
slippageTolerance
滑点容忍度,百分比小数(例如
0.5
deadline
Unix 时间戳(秒)
simulateTransaction
在响应中包含
gasFee
urgency
NORMAL
|
FAST
|
URGENT
batchPermitData
+
signature
来自
/lp/check_approval
的 v4 permit(注意:create 接口使用
batchPermitData
nativeTokenBalance
当一侧是原生 ETH 时使用
includeApprovalSimulation
在 gas 模拟中包含授权预调用(仅当
simulateTransaction: true
时有效)
响应
ts
interface CreatePositionResponse {
  requestId: string;
  token0: LPToken; // { tokenAddress, amount }
  token1: LPToken;
  adjustedMinPrice: string; // show THIS to the user, not your input minPrice
  adjustedMaxPrice: string;
  tickLower: number;
  tickUpper: number;
  create: TransactionRequest;
  gasFee?: string; // present when simulateTransaction: true
  slippage?: number; // effective slippage the API applied (e.g. native-token v4 cases)
}
请向用户展示
adjustedMinPrice
/
adjustedMaxPrice
(tick 对齐后的值),而非你发送的原始
priceBounds

POST /lp/create_classic

POST /lp/create_classic

Create a v2 (full-range) position. Provide one
independentToken
amount; the API computes the
dependentToken
from current pair reserves.
Request
json
{
  "walletAddress": "0x...",
  "poolParameters": {
    "token0Address": "0xc02fe7317d4eb8753a02c35fe019786854a92001",
    "token1Address": "0x0000000000000000000000000000000000000000",
    "chainId": 130
  },
  "independentToken": {
    "tokenAddress": "0xc02fe7317d4eb8753a02c35fe019786854a92001",
    "amount": "1000000000000000"
  },
  "simulateTransaction": false
}
FieldRequiredNotes
walletAddress
Yes
poolParameters
Yes
{ token0Address, token1Address, chainId }
independentToken
Yes
{ tokenAddress, amount }
dependentToken
NoIf omitted, computed from reserves. Required when creating a brand-new pool.
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
,
includeApprovalSimulation
No
Response:
{ requestId, independentToken, dependentToken, create: TransactionRequest, gasFee? }
. Note the create_classic response names the tokens
independentToken
/
dependentToken
(not
token0
/
token1
).
创建 v2(全区间)头寸。提供一个
independentToken
数量;API 将根据当前交易对储备量计算
dependentToken
的数量。
请求
json
{
  "walletAddress": "0x...",
  "poolParameters": {
    "token0Address": "0xc02fe7317d4eb8753a02c35fe019786854a92001",
    "token1Address": "0x0000000000000000000000000000000000000000",
    "chainId": 130
  },
  "independentToken": {
    "tokenAddress": "0xc02fe7317d4eb8753a02c35fe019786854a92001",
    "amount": "1000000000000000"
  },
  "simulateTransaction": false
}
字段必填说明
walletAddress
poolParameters
{ token0Address, token1Address, chainId }
independentToken
{ tokenAddress, amount }
dependentToken
若省略,则根据储备量计算。创建全新池时为必填。
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
,
includeApprovalSimulation
响应
{ requestId, independentToken, dependentToken, create: TransactionRequest, gasFee? }
。注意 create_classic 响应中的代币命名为
independentToken
/
dependentToken
(而非
token0
/
token1
)。

POST /lp/increase

POST /lp/increase

Add liquidity to an existing v2/v3/v4 position. Provide one token amount; the API computes the other.
Request
json
{
  "walletAddress": "0x...",
  "chainId": 130,
  "protocol": "V4",
  "token0Address": "0x0000000000000000000000000000000000000000",
  "token1Address": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
  "nftTokenId": "1833079",
  "independentToken": {
    "tokenAddress": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
    "amount": "8223"
  },
  "simulateTransaction": false
}
FieldRequiredNotes
walletAddress
,
chainId
,
protocol
,
token0Address
,
token1Address
,
independentToken
Yes
token0Address
/
token1Address
must match the order in the existing position
nftTokenId
v3/v4NFT id identifying the position
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
No
v4BatchPermitData
+
signature
Nov4 permit from
/lp/check_approval
(note: increase uses
v4BatchPermitData
, create uses
batchPermitData
)
nativeTokenBalance
,
includeApprovalSimulation
,
permissioned
No
nativeTokenBalance
when one side is native ETH;
permissioned
routes to the permissioned PositionManager (KYC-gated pools)
Response:
{ requestId, token0, token1, increase: TransactionRequest, gasFee?, slippage? }
.
Native ETH: use
0x0000000000000000000000000000000000000000
. The API generates a multicall including
refundETH
to return any excess.
向现有 v2/v3/v4 头寸添加流动性。提供一个代币数量;API 计算另一个的数量。
请求
json
{
  "walletAddress": "0x...",
  "chainId": 130,
  "protocol": "V4",
  "token0Address": "0x0000000000000000000000000000000000000000",
  "token1Address": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
  "nftTokenId": "1833079",
  "independentToken": {
    "tokenAddress": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
    "amount": "8223"
  },
  "simulateTransaction": false
}
字段必填说明
walletAddress
,
chainId
,
protocol
,
token0Address
,
token1Address
,
independentToken
token0Address
/
token1Address
必须与现有头寸中的顺序一致
nftTokenId
v3/v4 必填标识头寸的 NFT ID
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
v4BatchPermitData
+
signature
来自
/lp/check_approval
的 v4 permit(注意:increase 接口使用
v4BatchPermitData
,create 接口使用
batchPermitData
nativeTokenBalance
,
includeApprovalSimulation
,
permissioned
当一侧是原生 ETH 时使用
nativeTokenBalance
permissioned
用于路由到权限型 PositionManager(KYC 门控池)
响应
{ requestId, token0, token1, increase: TransactionRequest, gasFee?, slippage? }
原生 ETH:使用
0x0000000000000000000000000000000000000000
。API 会生成包含
refundETH
的 multicall 以退还多余的 ETH。

POST /lp/decrease

POST /lp/decrease

Remove a percentage of liquidity from a v2/v3/v4 position.
Request
json
{
  "walletAddress": "0x...",
  "chainId": 130,
  "protocol": "V4",
  "token0Address": "0x0000000000000000000000000000000000000000",
  "token1Address": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
  "liquidityPercentageToDecrease": 25,
  "nftTokenId": "1833079",
  "simulateTransaction": false
}
FieldRequiredNotes
walletAddress
,
chainId
,
protocol
,
token0Address
,
token1Address
,
liquidityPercentageToDecrease
YesPercentage is an integer 1-100
nftTokenId
v3/v4NFT id identifying the position
withdrawAsWeth
NoApplies to V2 and V3 (ignored on V4). Default /
true
keeps WETH;
false
unwraps WETH to native ETH.
permissioned
NoRoutes to the permissioned PositionManager (KYC-gated pools).
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
No
Response:
{ requestId, token0, token1, decrease: TransactionRequest, gasFee? }
.
v3 fee collection on decrease: for v3, the returned
decrease
calldata bundles uncollected fees into the withdrawal automatically, so you do not need to call
/lp/claim_fees
separately. Note the response
token0
/
token1
amounts reflect only the pro-rata liquidity removed — the swept fees are encoded in the calldata, not added to those response amounts.
从 v2/v3/v4 头寸中移除一定比例的流动性。
请求
json
{
  "walletAddress": "0x...",
  "chainId": 130,
  "protocol": "V4",
  "token0Address": "0x0000000000000000000000000000000000000000",
  "token1Address": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
  "liquidityPercentageToDecrease": 25,
  "nftTokenId": "1833079",
  "simulateTransaction": false
}
字段必填说明
walletAddress
,
chainId
,
protocol
,
token0Address
,
token1Address
,
liquidityPercentageToDecrease
百分比为 1-100 的整数
nftTokenId
v3/v4 必填标识头寸的 NFT ID
withdrawAsWeth
适用于 V2 和 V3(V4 上忽略)。默认/
true
保留 WETH;
false
将 WETH 解包为原生 ETH。
permissioned
路由到权限型 PositionManager(KYC 门控池)。
slippageTolerance
,
deadline
,
simulateTransaction
,
urgency
响应
{ requestId, token0, token1, decrease: TransactionRequest, gasFee? }
v3 减少流动性时的手续费收取:对于 v3,返回的
decrease
calldata 会自动将未收取的手续费打包到提款中,因此你无需单独调用
/lp/claim_fees
。注意,响应中的
token0
/
token1
数量仅反映按比例移除的流动性 — 归集的手续费编码在 calldata 中,不会添加到这些响应数量里。

POST /lp/claim_fees

POST /lp/claim_fees

Collect accumulated trading fees from a v3 or v4 position. Not available for v2.
Request
json
{
  "protocol": "V4",
  "walletAddress": "0x...",
  "chainId": 130,
  "tokenId": "1833079",
  "simulateTransaction": false
}
FieldRequiredNotes
protocol
,
walletAddress
,
chainId
,
tokenId
Yes
protocol
of
V2
returns a validation error. Note: claim uses
tokenId
(not
nftTokenId
).
collectAsWeth
Nov3 only. If
false
, unwraps WETH to native ETH.
permissioned
NoRoutes to the permissioned PositionManager (KYC-gated pools).
simulateTransaction
No
Response:
{ requestId, token0, token1, claim: TransactionRequest, gasFee? }
.
从 v3 或 v4 头寸收取累积的交易手续费。v2 不支持。
请求
json
{
  "protocol": "V4",
  "walletAddress": "0x...",
  "chainId": 130,
  "tokenId": "1833079",
  "simulateTransaction": false
}
字段必填说明
protocol
,
walletAddress
,
chainId
,
tokenId
protocol
V2
时返回验证错误。注意:claim 接口使用
tokenId
(而非
nftTokenId
)。
collectAsWeth
仅 v3 支持。若为
false
,将 WETH 解包为原生 ETH。
permissioned
路由到权限型 PositionManager(KYC 门控池)。
simulateTransaction
响应
{ requestId, token0, token1, claim: TransactionRequest, gasFee? }

POST /lp/pool_info

POST /lp/pool_info

Read live pool state (reserves, tick,
sqrtRatioX96
, liquidity) for one or more pools.
Request:
{ protocol, poolParameters?, poolReferences?, chainId?, pageSize?, currentPage? }
(only
protocol
is strictly required; supply
poolParameters
{ tokenAddressA, tokenAddressB, fee?, tickSpacing?, hookAddress? }
or
poolReferences
to identify pools).
Response:
{ requestId, pools: PoolInformation[], pageSize, currentPage }
where each
PoolInformation
includes
poolReferenceIdentifier, poolProtocol, tokenAddressA, tokenAddressB, tickSpacing, fee, hookAddress, chainId, tokenAmountA, tokenAmountB, tokenDecimalsA, tokenDecimalsB, poolLiquidity, sqrtRatioX96, currentTick, token0Reserves, token1Reserves
(note: address/amount/decimals fields use the
A
/
B
suffix, but the two reserve fields use
0
/
1
). Optional fields (e.g. amounts,
hookAddress
, reserves) are omitted when not applicable to the pool.
读取一个或多个池的实时状态(储备量、tick、
sqrtRatioX96
、流动性)。
请求
{ protocol, poolParameters?, poolReferences?, chainId?, pageSize?, currentPage? }
(仅
protocol
为强制必填;提供
poolParameters
{ tokenAddressA, tokenAddressB, fee?, tickSpacing?, hookAddress? }
poolReferences
来标识池)。
响应
{ requestId, pools: PoolInformation[], pageSize, currentPage }
,其中每个
PoolInformation
包含
poolReferenceIdentifier, poolProtocol, tokenAddressA, tokenAddressB, tickSpacing, fee, hookAddress, chainId, tokenAmountA, tokenAmountB, tokenDecimalsA, tokenDecimalsB, poolLiquidity, sqrtRatioX96, currentTick, token0Reserves, token1Reserves
(注意:地址/数量/小数位字段使用
A
/
B
后缀,但两个储备字段使用
0
/
1
)。不适用于池的可选字段(例如数量、
hookAddress
、储备量)将被省略。

Field-name quirks to preserve

需要注意的字段名差异

The same concept is named differently across endpoints. Use the exact name per endpoint:
Concept
/lp/claim_fees
/lp/increase
,
/lp/decrease
/lp/check_approval
Position NFT id
tokenId
nftTokenId
v3NftTokenId
(integer)
Concept
/lp/create
/lp/increase
v4 permit payload
batchPermitData
v4BatchPermitData
同一概念在不同端点中的命名不同。请按端点使用确切的字段名:
概念
/lp/claim_fees
/lp/increase
,
/lp/decrease
/lp/check_approval
头寸 NFT ID
tokenId
nftTokenId
v3NftTokenId
(整数)
概念
/lp/create
/lp/increase
v4 permit 载荷
batchPermitData
v4BatchPermitData

Approval and Permit Flow

授权与 Permit 流程

Always call
/lp/check_approval
before any LP action, even when approvals were previously granted (allowances can be revoked or consumed).
在任何 LP 操作前始终调用
/lp/check_approval
,即使之前已授予授权(授权额度可能被撤销或消耗)。

Onchain approvals

链上授权

ts
const res = await fetch(`${LP_API_BASE_URL}/lp/check_approval`, {
  method: 'POST',
  headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify({ walletAddress, protocol: 'V4', chainId: 1, lpTokens, action: 'CREATE' }),
});
if (!res.ok) throw new Error(`check_approval failed: ${res.status}`);
const { transactions, v4BatchPermitData, kycRequiredWarnings } = await res.json();

// A permissioned pool gates LPing on KYC: a non-empty kycRequiredWarnings means render the
// KYC CTA (warning.kycUrl) and stop — do NOT treat empty transactions as "approved" here.
if (kycRequiredWarnings?.length)
  throw new Error('Wallet not allowlisted for this permissioned pool');

// transactions is an array of ApprovalTransactionRequest. Empty (and no KYC warnings) => already approved.
for (const approval of transactions) {
  validateLpTransaction(approval.transaction); // see Critical Notes
  const hash = await walletClient.sendTransaction(approval.transaction);
  await publicClient.waitForTransactionReceipt({ hash });
}
ts
const res = await fetch(`${LP_API_BASE_URL}/lp/check_approval`, {
  method: 'POST',
  headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify({ walletAddress, protocol: 'V4', chainId: 1, lpTokens, action: 'CREATE' }),
});
if (!res.ok) throw new Error(`check_approval failed: ${res.status}`);
const { transactions, v4BatchPermitData, kycRequiredWarnings } = await res.json();

// A permissioned pool gates LPing on KYC: a non-empty kycRequiredWarnings means render the
// KYC CTA (warning.kycUrl) and stop — do NOT treat empty transactions as "approved" here.
if (kycRequiredWarnings?.length)
  throw new Error('Wallet not allowlisted for this permissioned pool');

// transactions is an array of ApprovalTransactionRequest. Empty (and no KYC warnings) => already approved.
for (const approval of transactions) {
  validateLpTransaction(approval.transaction); // see Critical Notes
  const hash = await walletClient.sendTransaction(approval.transaction);
  await publicClient.waitForTransactionReceipt({ hash });
}

v4 permit (EIP-712 sign-and-return)

v4 permit(EIP-712 签名并返回)

For v4,
check_approval
returns a
v4BatchPermitData
(a gasless Permit2 batch approval) — often alongside onchain ERC-20 → Permit2 approval
transactions
, not instead of them. Execute any returned
transactions
first (Permit2 needs the ERC-20 allowance), then sign the permit offchain and pass it into the next call.
The permit payload is proto-encoded and is NOT directly viem-ready. Normalize two fields before signing — the un-normalized object is still what you send back to the API:
  • domain.chainId
    arrives as the chain enum-name string (e.g.
    "UNICHAIN"
    ), not a number — replace it with the numeric chain ID.
  • each
    types
    entry is wrapped as
    { fields: [...] }
    ; viem (and the EIP-712 spec) expect a bare array — unwrap
    .fields
    .
ts
let signature: string | undefined;
if (v4BatchPermitData) {
  // Normalize the proto-encoded permit into viem's TypedData shape (for signing only).
  const types = Object.fromEntries(
    Object.entries(v4BatchPermitData.types).map(([k, v]) => [k, (v as any).fields])
  );
  const domain = { ...v4BatchPermitData.domain, chainId }; // numeric chainId (e.g. 130), NOT "UNICHAIN"

  signature = await walletClient.signTypedData({
    domain,
    types,
    message: v4BatchPermitData.values,
    primaryType: 'PermitBatch',
  });
}

// Send the ORIGINAL (un-normalized) permit back to the API, with the signature.
// /lp/create (uses batchPermitData) ...
const createBody = { ...createParams, batchPermitData: v4BatchPermitData, signature };
// ... or /lp/increase (uses v4BatchPermitData)
const increaseBody = { ...increaseParams, v4BatchPermitData, signature };
For full typed implementations (v3 NFT permit,
EIP712Domain
edge cases, migration), see Advanced Patterns Reference.
对于 v4,
check_approval
会返回
v4BatchPermitData
(一种无 gas 的 Permit2 批量授权)— 通常链上 ERC-20 → Permit2 授权
transactions
一起返回,而非替代它们。先执行所有返回的
transactions
(Permit2 需要 ERC-20 授权额度),然后离线签名 permit 并将其传入下一个调用。
permit 载荷是 proto 编码的,不能直接在 viem 中使用。 签名前需要标准化两个字段 — 但发送回 API 时仍需使用未标准化的对象:
  • domain.chainId
    返回时是链的枚举名称字符串(例如
    "UNICHAIN"
    ),而非数字 — 将其替换为数字链 ID。
  • 每个
    types
    条目都被包裹为
    { fields: [...] }
    ;viem(以及 EIP-712 规范)期望裸数组 — 解包
    .fields
ts
let signature: string | undefined;
if (v4BatchPermitData) {
  // Normalize the proto-encoded permit into viem's TypedData shape (for signing only).
  const types = Object.fromEntries(
    Object.entries(v4BatchPermitData.types).map(([k, v]) => [k, (v as any).fields])
  );
  const domain = { ...v4BatchPermitData.domain, chainId }; // numeric chainId (e.g. 130), NOT "UNICHAIN"

  signature = await walletClient.signTypedData({
    domain,
    types,
    message: v4BatchPermitData.values,
    primaryType: 'PermitBatch',
  });
}

// Send the ORIGINAL (un-normalized) permit back to the API, with the signature.
// /lp/create (uses batchPermitData) ...
const createBody = { ...createParams, batchPermitData: v4BatchPermitData, signature };
// ... or /lp/increase (uses v4BatchPermitData)
const increaseBody = { ...increaseParams, v4BatchPermitData, signature };
有关完整的类型化实现(v3 NFT permit、
EIP712Domain
边缘情况、迁移),请参见高级模式参考

Critical Implementation Notes

关键实现注意事项

1. Sign the wrapped transaction, not the wrapper

1. 对被包裹的交易签名,而非包裹对象

/lp/check_approval
returns
transactions: ApprovalTransactionRequest[]
. Each element is
{ transaction, cancelApproval, action, gasFee? }
.
ts
// WRONG — signs the wrapper object, not a transaction
for (const a of transactions) await walletClient.sendTransaction(a);

// CORRECT
for (const a of transactions) await walletClient.sendTransaction(a.transaction);
/lp/check_approval
返回
transactions: ApprovalTransactionRequest[]
。每个元素为
{ transaction, cancelApproval, action, gasFee? }
ts
// WRONG — signs the wrapper object, not a transaction
for (const a of transactions) await walletClient.sendTransaction(a);

// CORRECT
for (const a of transactions) await walletClient.sendTransaction(a.transaction);

2. Use the contract's response field names

2. 使用合约定义的响应字段名

ts
// WRONG — these names come from the narrative guide, not the contract
const { approvals } = await checkApprovalRes.json();
const { minPrice, maxPrice } = createResponse;

// CORRECT
const { transactions } = await checkApprovalRes.json();
const { adjustedMinPrice, adjustedMaxPrice } = createResponse;
ts
// WRONG — these names come from the narrative guide, not the contract
const { approvals } = await checkApprovalRes.json();
const { minPrice, maxPrice } = createResponse;

// CORRECT
const { transactions } = await checkApprovalRes.json();
const { adjustedMinPrice, adjustedMaxPrice } = createResponse;

3. Never modify, always validate the
data
field

3. 切勿修改
data
字段,始终进行验证

The
create
/
increase
/
decrease
/
claim
field holds pre-validated calldata.
ts
function validateLpTransaction(tx: TransactionRequest): void {
  if (!tx.data || tx.data === '' || tx.data === '0x') throw new Error('Empty transaction data');
  if (!tx.to || !isAddress(tx.to)) throw new Error('Invalid recipient address');
  if (!tx.from || !isAddress(tx.from)) throw new Error('Invalid sender address');
  if (tx.maxFeePerGas && tx.gasPrice) throw new Error('Cannot set both maxFeePerGas and gasPrice');
}
Never edit the calldata — modifying it can cause reverts or loss of funds.
create
/
increase
/
decrease
/
claim
字段包含经过预验证的 calldata。
ts
function validateLpTransaction(tx: TransactionRequest): void {
  if (!tx.data || tx.data === '' || tx.data === '0x') throw new Error('Empty transaction data');
  if (!tx.to || !isAddress(tx.to)) throw new Error('Invalid recipient address');
  if (!tx.from || !isAddress(tx.from)) throw new Error('Invalid sender address');
  if (tx.maxFeePerGas && tx.gasPrice) throw new Error('Cannot set both maxFeePerGas and gasPrice');
}
切勿编辑 calldata — 修改它可能导致交易回滚或资金损失。

4. Transactions are time-sensitive

4. 交易具有时效性

Pool price moves. If the user takes more than ~30 seconds to review, refetch the transaction before broadcasting.
ts
const TX_EXPIRY_MS = 30_000;
const builtAt = Date.now();
// ... user reviews ...
if (Date.now() - builtAt > TX_EXPIRY_MS) lpTx = await refetchLpTransaction(params);
池价格会波动。如果用户审核时间超过约 30 秒,广播前请重新获取交易。
ts
const TX_EXPIRY_MS = 30_000;
const builtAt = Date.now();
// ... user reviews ...
if (Date.now() - builtAt > TX_EXPIRY_MS) lpTx = await refetchLpTransaction(params);

5. Amounts are wei strings

5. 数量为 wei 字符串

All
amount
fields are integer strings in the token's smallest unit. Convert with
parseUnits(value, decimals).toString()
— never send ether-denominated decimals.
所有
amount
字段均为代币最小单位的整数字符串。使用
parseUnits(value, decimals).toString()
进行转换 — 切勿发送以 ether 计价的小数。

6. Strip undefined optional fields

6. 移除未定义的可选字段

Send permit fields as a matched pair (
batchPermitData
+
signature
) or omit both. Do not send
signature: undefined
alongside a present permit, or vice versa.
permit 字段应成对发送(
batchPermitData
+
signature
),或都不发送。不要在 permit 存在的情况下发送
signature: undefined
,反之亦然。

Worked Example: Create a v3 Position (viem)

示例:使用 viem 创建 v3 头寸

ts
import { createWalletClient, createPublicClient, http, isAddress, parseUnits } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const LP_API_BASE_URL = 'https://liquidity.api.uniswap.org';
const API_KEY = process.env.UNISWAP_API_KEY!; // never hardcode

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(process.env.RPC_URL),
});
const publicClient = createPublicClient({ chain: mainnet, transport: http(process.env.RPC_URL) });

const headers = {
  'x-api-key': API_KEY,
  'Content-Type': 'application/json',
  Accept: 'application/json',
};

async function lpFetch(path: string, body: object) {
  const res = await fetch(`${LP_API_BASE_URL}${path}`, {
    method: 'POST',
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${path} failed: ${res.status} ${await res.text()}`);
  return res.json();
}

// Same checks as the canonical validateLpTransaction in "Critical Implementation Notes" above.
function validateLpTransaction(tx: any) {
  if (!tx || !tx.data || tx.data === '' || tx.data === '0x')
    throw new Error('Empty transaction data');
  if (!tx.to || !isAddress(tx.to)) throw new Error('Invalid recipient address');
  if (!tx.from || !isAddress(tx.from)) throw new Error('Invalid sender address');
  if (tx.maxFeePerGas && tx.gasPrice) throw new Error('Cannot set both maxFeePerGas and gasPrice');
}

async function createV3Position() {
  const protocol = 'V3';
  const chainId = 1;
  const token0Address = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; // UNI
  const token1Address = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; // USDT
  const independentToken = { tokenAddress: token0Address, amount: parseUnits('10', 18).toString() };

  // 1. Approvals
  const { transactions } = await lpFetch('/lp/check_approval', {
    walletAddress: account.address,
    protocol,
    chainId,
    lpTokens: [independentToken],
    action: 'CREATE',
  });
  for (const a of transactions) {
    validateLpTransaction(a.transaction);
    const hash = await walletClient.sendTransaction(a.transaction);
    await publicClient.waitForTransactionReceipt({ hash });
  }

  // 2. Build the create transaction
  const created = await lpFetch('/lp/create', {
    walletAddress: account.address,
    chainId,
    protocol,
    independentToken,
    existingPool: {
      token0Address,
      token1Address,
      poolReference: '0x3470447f3cecffac709d3e783a307790b0208d60',
    },
    priceBounds: {
      minPrice: '0.00000000000324',
      maxPrice: '0.00000000000393',
      quotedTokenAddress: token1Address,
    },
    simulateTransaction: true,
  });

  // 3. CONFIRM WITH USER before broadcasting (use AskUserQuestion in the skill flow):
  //    pair, amounts (created.token0 / created.token1), adjusted range
  //    (created.adjustedMinPrice / created.adjustedMaxPrice), created.gasFee
  validateLpTransaction(created.create);
  const hash = await walletClient.sendTransaction(created.create);
  return publicClient.waitForTransactionReceipt({ hash });
}
ts
import { createWalletClient, createPublicClient, http, isAddress, parseUnits } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const LP_API_BASE_URL = 'https://liquidity.api.uniswap.org';
const API_KEY = process.env.UNISWAP_API_KEY!; // never hardcode

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(process.env.RPC_URL),
});
const publicClient = createPublicClient({ chain: mainnet, transport: http(process.env.RPC_URL) });

const headers = {
  'x-api-key': API_KEY,
  'Content-Type': 'application/json',
  Accept: 'application/json',
};

async function lpFetch(path: string, body: object) {
  const res = await fetch(`${LP_API_BASE_URL}${path}`, {
    method: 'POST',
    headers,
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${path} failed: ${res.status} ${await res.text()}`);
  return res.json();
}

// Same checks as the canonical validateLpTransaction in "Critical Implementation Notes" above.
function validateLpTransaction(tx: any) {
  if (!tx || !tx.data || tx.data === '' || tx.data === '0x')
    throw new Error('Empty transaction data');
  if (!tx.to || !isAddress(tx.to)) throw new Error('Invalid recipient address');
  if (!tx.from || !isAddress(tx.from)) throw new Error('Invalid sender address');
  if (tx.maxFeePerGas && tx.gasPrice) throw new Error('Cannot set both maxFeePerGas and gasPrice');
}

async function createV3Position() {
  const protocol = 'V3';
  const chainId = 1;
  const token0Address = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; // UNI
  const token1Address = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; // USDT
  const independentToken = { tokenAddress: token0Address, amount: parseUnits('10', 18).toString() };

  // 1. Approvals
  const { transactions } = await lpFetch('/lp/check_approval', {
    walletAddress: account.address,
    protocol,
    chainId,
    lpTokens: [independentToken],
    action: 'CREATE',
  });
  for (const a of transactions) {
    validateLpTransaction(a.transaction);
    const hash = await walletClient.sendTransaction(a.transaction);
    await publicClient.waitForTransactionReceipt({ hash });
  }

  // 2. Build the create transaction
  const created = await lpFetch('/lp/create', {
    walletAddress: account.address,
    chainId,
    protocol,
    independentToken,
    existingPool: {
      token0Address,
      token1Address,
      poolReference: '0x3470447f3cecffac709d3e783a307790b0208d60',
    },
    priceBounds: {
      minPrice: '0.00000000000324',
      maxPrice: '0.00000000000393',
      quotedTokenAddress: token1Address,
    },
    simulateTransaction: true,
  });

  // 3. CONFIRM WITH USER before broadcasting (use AskUserQuestion in the skill flow):
  //    pair, amounts (created.token0 / created.token1), adjusted range
  //    (created.adjustedMinPrice / created.adjustedMaxPrice), created.gasFee
  validateLpTransaction(created.create);
  const hash = await walletClient.sendTransaction(created.create);
  return publicClient.waitForTransactionReceipt({ hash });
}

Companion SDKs

配套 SDK

The API returns ready-to-sign transactions, so a protocol SDK is not required. For client-side pool/position math, tick conversions, and validation, developers commonly pair the LP API with:
  • @uniswap/sdk-core
    — chains,
    Token
    ,
    CurrencyAmount
    ,
    Percent
    ,
    Price
    (foundational).
  • @uniswap/v3-sdk
    — v3 tick math (
    TickMath
    ,
    nearestUsableTick
    ),
    Pool
    ,
    Position
    .
  • @uniswap/v4-sdk
    — v4 pool keys, hooks-aware
    Position
    modeling.
  • @uniswap/v2-sdk
    — v2 pair/liquidity math.
  • viem
    (or ethers) — sign and broadcast the returned
    { to, data, value }
    , and sign EIP-712 permit data.
API 返回可直接签名的交易,因此无需协议 SDK。对于客户端池/头寸计算、tick 转换和验证,开发者通常将 LP API 与以下工具搭配使用:
  • @uniswap/sdk-core
    — 链、
    Token
    CurrencyAmount
    Percent
    Price
    (基础库)。
  • @uniswap/v3-sdk
    — v3 tick 计算(
    TickMath
    nearestUsableTick
    )、
    Pool
    Position
  • @uniswap/v4-sdk
    — v4 池密钥、支持 hooks 的
    Position
    建模。
  • @uniswap/v2-sdk
    — v2 交易对/流动性计算。
  • viem
    (或 ethers)— 签名并广播返回的
    { to, data, value }
    ,以及签名 EIP-712 permit 数据。

Error Handling

错误处理

CodeMeaningAction
400Validation errorFix request fields (see common errors below)
401Invalid API keyCheck the
x-api-key
header
429Rate limitedExponential backoff; cache repeated reads
500API errorRetry with backoff
503Temporary unavailabilityRetry
Error body (Connect protocol):
{ code: string, message: string, details?: Array<{ type: string; value: string }> }
— e.g.
{"code":"invalid_argument","message":"RequestValidationError: ...","details":[...]}
.
code
is a Connect error-code string (
invalid_argument
,
unauthenticated
,
failed_precondition
,
internal
, …), not an HTTP number, and there is no top-level
error
field. Read
body.code
/
body.message
, not
body.error
.
Common errors
  • v2 fee claim attempt: calling
    /lp/claim_fees
    with
    protocol: "V2"
    . Use
    /lp/decrease
    to realize v2 fees.
  • Pool not found: verify token addresses, the
    poolReference
    , and the chain; or use
    newPool
    to initialize.
  • Insufficient liquidity: the computed dependent amount exceeds balances; reduce
    independentToken.amount
    or widen the range.
  • Validation error: ensure all required fields for the chosen
    protocol
    , checksummed addresses, wei-denominated amounts, and
    liquidityPercentageToDecrease
    in 1-100.
For retry/backoff, request caching, monitoring, and the full pre-broadcast checklist, see Advanced Patterns Reference.
状态码含义处理方式
400验证错误修复请求字段(参见下方常见错误)
401API 密钥无效检查
x-api-key
请求头
429速率受限指数退避;缓存重复的读取请求
500API 错误退避重试
503临时不可用重试
错误体(Connect 协议):
{ code: string, message: string, details?: Array<{ type: string; value: string }> }
— 例如
{"code":"invalid_argument","message":"RequestValidationError: ...","details":[...]}
code
是 Connect 错误码字符串(
invalid_argument
unauthenticated
failed_precondition
internal
等),不是 HTTP 状态码,且没有顶层
error
字段。请读取
body.code
/
body.message
,而非
body.error
常见错误
  • 尝试申领 v2 手续费:使用
    protocol: "V2"
    调用
    /lp/claim_fees
    。请使用
    /lp/decrease
    来兑现 v2 手续费。
  • 池未找到:验证代币地址、
    poolReference
    和链;或使用
    newPool
    初始化。
  • 流动性不足:计算出的对应代币数量超过余额;减少
    independentToken.amount
    或扩大价格区间。
  • 验证错误:确保所选
    protocol
    的所有必填字段齐全、地址为校验和格式、数量以 wei 计价、
    liquidityPercentageToDecrease
    在 1-100 之间。
有关重试/退避、请求缓存、监控以及完整的广播前检查清单,请参见高级模式参考

Supported Chains

支持的链

The LP API supports a fixed set of chain IDs. Validate
chainId
against this set before sending:
text
1, 10, 56, 130, 137, 143, 196, 324, 480, 1868, 4217, 4326, 4663, 5042,
8453, 10143, 42161, 42220, 43114, 59144, 81457, 7777777, 1301, 84532, 11155111
(Mainnet 1, Optimism 10, BNB 56, Unichain 130, Polygon 137, Base 8453, Arbitrum 42161, Avalanche 43114, Linea 59144, Blast 81457, Zora 7777777, Sepolia 11155111, and others.) Confirm the live set against
/lp/pool_info
availability or the supported chains docs.
LP API 支持固定的一组链 ID。发送请求前请验证
chainId
是否在该集合中:
text
1, 10, 56, 130, 137, 143, 196, 324, 480, 1868, 4217, 4326, 4663, 5042,
8453, 10143, 42161, 42220, 43114, 59144, 81457, 7777777, 1301, 84532, 11155111
(主网 1、Optimism 10、BNB 56、Unichain 130、Polygon 137、Base 8453、Arbitrum 42161、Avalanche 43114、Linea 59144、Blast 81457、Zora 7777777、Sepolia 11155111 等。)请通过
/lp/pool_info
可用性或支持的链文档确认最新的支持列表。

Additional Resources

更多资源