关于 lp-integration
LP Integration 是一项技能,介绍如何通过 Uniswap LP API 为前端、后端和机器人集成流动性提供功能。它涵盖 V2、V3 和 V4 的授权检查与交易构建;签名和广播仍由应用负责。
主要功能
- 01检查代币授权和 permit 数据
- 02创建 V2、V3、V4 流动性头寸
- 03增加或减少现有流动性
- 04领取 V3 和 V4 交易费用
- 05读取池状态并构建未签名交易
适用场景
- 01为应用集成流动性提供功能
- 02创建 V3 或 V4 集中流动性头寸
- 03在流动性操作前检查授权
- 04为流动性机器人构建交易
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uniswap/ uniswap-ai --skill lp-integration安装后在 Claude Code 中输入/lp-integration确认已加载。
附带文件references/advanced-patterns.md
详细介绍
LP 集成
使用 Uniswap LP API,将 Uniswap 流动性提供功能集成到前端、后端和机器人中。
LP API 是一项构建交易的服务。你发送仓位参数后,API 会获取实时池状态、计算依赖的代币数量,并返回构建完整但未经签名的交易。你的应用会为其签名并广播。API 不会持有密钥、转移资金或广播交易。
前置条件
本文档假设你熟悉 viem 基础知识(客户端设置、账户管理、合约交互和交易签名)。安装 uniswap-viem 插件,以获取全面的 viem/wagmi 使用指南:claude plugin add @uniswap/uniswap-viem
如需进行代币兑换(而非流动性操作),请参阅此插件中的同级 swap-integration 技能。
基础 URL
LP_API_BASE_URL = https://liquidity.api.uniswap.org
所有 LP 端点均为 /lp/ 前缀下的 POST 请求(例如 https://liquidity.api.uniswap.org/lp/create)。
LP API 主机与兑换 Trading API 的主机特意不同。 流动性提供服务位于
https://liquidity.api.uniswap.org(没有/v1前缀),而非兑换所用的https://trade-api.gateway.uniswap.org/v1主机。请将基础 URL 保持为一个常量(如图所示),以便将来需要更改时只需修改一处。
身份验证
每个写入/授权端点都需要通过 x-api-key 标头发送 API 密钥;缺少密钥或密钥无效时会返回 401 和 {"code":"unauthenticated"}。(/lp/pool_info 是读取端点,不会严格校验密钥,但为保持一致并进行速率限制归因,请始终发送密钥。)
Content-Type: application/json
Accept: application/json
x-api-key: <your-api-key>
获取密钥: 在 Uniswap Developer Platform dashboard 注册。同一个密钥可用于兑换和流动性提供。切勿将密钥硬编码;请从环境变量中读取。
快速决策指南
| 你想要…… | 端点 | 协议 |
|---|---|---|
| 在执行任何操作前检查/授予代币授权 | /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 |
协议能力矩阵
| 协议 | 创建端点 | 手续费收取 | 价格范围 |
|---|---|---|---|
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会返回验证错误。
输入验证规则
在将任何用户提供的值插入生成的代码、API 调用或命令之前:
- Ethereum 地址:必须匹配
^0x[a-fA-F0-9]{40}$,否则拒绝。原生 ETH 使用0x0000000000000000000000000000000000000000。 - Chain ID:必须是受支持的 LP chain ID 之一(参见支持的链)。
- Token 数量:必须是以 wei / 最小 token 单位表示的非负整数字符串,并匹配
^[0-9]+$。切勿将以 ether 为单位的小数作为数量传入。 liquidityPercentageToDecrease:必须是 1 到 100 之间的整数。- API 密钥:不得硬编码在生成的代码中,始终使用环境变量。
- 拒绝任何包含 shell 元字符的输入:
;、|、&、$、`、(、)、>、<、\、'、"、换行符。
**必须:**在执行任何会消耗 gas 或转移 token 的交易之前(包括授权、创建头寸、增加、减少或领取费用),必须使用 AskUserQuestion 向用户确认。显示操作摘要(协议、交易对、数量、链、价格范围、预估 gas),并获得用户明确批准。未经用户确认,切勿自动执行 LP 交易。
架构
API 处理的内容
- 获取池状态:当前储备量、刻度、
sqrtRatioX96和链上头寸数据。 - 计算依赖数量:给定一个 token 数量(
independentToken),使用 Uniswap SDK 计算另一个 token 所需的数量。 - 创建交易:为每项 LP 操作生成经过验证且完整的 calldata,可供签名。
- 刻度对齐:将人类可读的
priceBounds转换为有效刻度,并返回调整后的价格。 - Gas 估算:可选,仅在
simulateTransaction: true时执行。
应用程序处理的内容
- 通过 RPC 提供商对返回的交易进行签名和广播。
- 在 API 针对 v4 / v3-NFT 授权返回签名许可数据时,对其进行签名(EIP-712)。
- 处理 Gas 付款和交易错误(回滚、向用户显示错误)。
数据流
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
端点参考
以下所有字段名称均来自 LP API OpenAPI 合约。如果公开集成指南使用了不同名称,以此处的合约名称为准。
POST /lp/check_approval
务必先调用此接口。它会返回执行 LP 操作前所需的授权交易(和/或许可数据)。如果响应中的 transactions 数组为空,且未返回许可数据,则表示所有授权均已就绪——除非 kycRequiredWarnings 非空,这表示该资金池包含受限代币,且钱包不在允许列表中。此时响应仍为 200;请根据每条警告中的 kycUrl 显示 KYC 行动号召,而不要尝试执行 LP 操作。
请求
{
"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,则以可执行交易而非类型化数据的形式返回许可 |
urgency | 否 | NORMAL | FAST | URGENT |
v3NftTokenId | 否 | 授权仓位管理器 NFT 时使用的 V3 NFT ID |
响应
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
创建 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 会将价格调整到有效刻度,并返回调整后的价格。tickBounds:{ tickLower, tickUpper }原始整数。
请求
{
"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 时使用) |
响应
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(按刻度调整后的值),而不是你发送的原始priceBounds。
POST /lp/create_classic
创建 v2(全范围)头寸。提供一个 independentToken 数量;API 根据当前交易对储备量计算 dependentToken。
请求
{
"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
向现有 v2/v3/v4 头寸添加流动性。提供一个代币数量;API 会计算另一个代币的数量。
请求
{
"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,以退还任何多余金额。
POST /lp/decrease
从 v2/v3/v4 仓位中移除一定百分比的流动性。
请求
{
"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调用数据会自动将未收取的手续费一并打包到提取操作中,因此无需单独调用/lp/claim_fees。请注意,响应中的token0/token1数量仅反映按比例移除的流动性;已收取的手续费编码在调用数据中,不会计入这些响应数量。
POST /lp/claim_fees
收取 v3 或 v4 仓位中累积的交易手续费。v2 不支持此操作。
请求
{
"protocol": "V4",
"walletAddress": "0x...",
"chainId": 130,
"tokenId": "1833079",
"simulateTransaction": false
}
| 字段 | 必填 | 说明 |
|---|---|---|
protocol、walletAddress、chainId、tokenId | 是 | protocol 为 V2 时会返回验证错误。注意:收取手续费时使用 tokenId(而非 nftTokenId)。 |
collectAsWeth | 否 | 仅适用于 v3。若为 false,则将 WETH 解包为原生 ETH。 |
permissioned | 否 | 路由到需经过许可的 PositionManager(适用于受 KYC 限制的池)。 |
simulateTransaction | 否 |
响应:{ requestId, token0, token1, claim: TransactionRequest, gasFee? }。
POST /lp/pool_info
读取一个或多个池的实时状态(储备、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、储备)将被省略。
需保留的字段名称差异
同一概念在不同端点中的名称可能不同。请使用各端点对应的准确名称:
| 概念 | /lp/claim_fees | /lp/increase、/lp/decrease | /lp/check_approval |
|---|---|---|---|
| Position NFT ID | tokenId | nftTokenId | v3NftTokenId(整数) |
| 概念 | /lp/create | /lp/increase |
|---|---|---|
| v4 permit 载荷 | batchPermitData | v4BatchPermitData |
Approval 和 Permit 流程
执行任何 LP 操作前,都应先调用 /lp/check_approval,即使之前已授予 approval(allowance 可能会被撤销或消耗)。
链上 approval
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 签名并返回)
对于 v4,check_approval 会返回 v4BatchPermitData(免 gas 的 Permit2 批量 approval)——通常还会同时返回链上 ERC-20 → Permit2 approval transactions,而不是用它们取而代之。先执行返回的任何 transactions(Permit2 需要 ERC-20 allowance),然后在链下签署 permit,并将其传入下一次调用。
permit 载荷经过 proto 编码,不能直接用于 viem。 签名前需规范化两个字段——但传回 API 的仍应是未经规范化的对象:
domain.chainId返回的是链的枚举名称字符串(例如"UNICHAIN"),而不是数字——请将其替换为数字链 ID。- 每个
types条目都包装在{ fields: [...] }中;viem(以及 EIP-712 规范)要求使用裸数组——请解包.fields。
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 边缘情况、迁移),请参阅 高级模式参考。
关键实现注意事项
1. 签署包装后的交易,而非包装器
/lp/check_approval 返回 transactions: ApprovalTransactionRequest[]。每个元素都是 { transaction, cancelApproval, action, gasFee? }。
// 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. 使用合约的响应字段名称
// 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. 切勿修改 data 字段,务必验证
create/increase/decrease/claim 字段包含经过预先验证的 calldata。
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. 交易有时效性
池价格会变动。如果用户审核超过约 30 秒,请在广播交易前重新获取交易数据。
const TX_EXPIRY_MS = 30_000;
const builtAt = Date.now();
// ... user reviews ...
if (Date.now() - builtAt > TX_EXPIRY_MS) lpTx = await refetchLpTransaction(params);
5. 金额是 wei 字符串
所有 amount 字段都是以代币最小单位表示的整数字符串。使用 parseUnits(value, decimals).toString() 进行转换——切勿发送以 ether 为单位的小数。
6. 移除未定义的可选字段
将 permit 字段成对发送(batchPermitData + signature),或同时省略两者。不要在存在 permit 时发送 signature: undefined,反之亦然。
示例:创建 v3 Position(viem)
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 });
}
配套 SDK
该 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 数据进行签名。
错误处理
| 代码 | 含义 | 操作 |
|---|---|---|
| 400 | 验证错误 | 修正请求字段(见下方常见错误) |
| 401 | API 密钥无效 | 检查 x-api-key 标头 |
| 429 | 请求过多 | 指数退避;缓存重复读取 |
| 500 | API 错误 | 采用退避策略重试 |
| 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 之间。
有关重试/退避、请求缓存、监控和完整的广播前检查清单,请参阅高级模式参考。
支持的链
LP API 支持一组固定的链 ID。发送前,请根据此集合验证 chainId:
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 等。)请通过 /lp/pool_info 的可用性或支持的链文档确认当前支持的链集合。
其他资源
- LP API:入门指南 — 官方概念概述
- LP API:集成指南 — 官方指南
- Uniswap Developer Platform 仪表板 — 获取 API 密钥
- swap-integration — 用于通过 Trading API 兑换代币的配套技能
- 高级模式参考 — permit 深入解析、迁移、NFT 仓位注意事项、可靠性