mcpField
L
lp-integration

lp-integration

github.com/uniswap开发236446 次安装2 天前更新
代码仓库

关于 lp-integration

LP Integration 是一项技能,介绍如何通过 Uniswap LP API 为前端、后端和机器人集成流动性提供功能。它涵盖 V2、V3 和 V4 的授权检查与交易构建;签名和广播仍由应用负责。

主要功能

  1. 01检查代币授权和 permit 数据
  2. 02创建 V2、V3、V4 流动性头寸
  3. 03增加或减少现有流动性
  4. 04领取 V3 和 V4 交易费用
  5. 05读取池状态并构建未签名交易

适用场景

  1. 01为应用集成流动性提供功能
  2. 02创建 V3 或 V4 集中流动性头寸
  3. 03在流动性操作前检查授权
  4. 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_approvalV2、V3、V4
创建集中流动性仓位/lp/createV3、V4
创建全范围(经典)仓位/lp/create_classicV2
向现有仓位添加流动性/lp/increaseV2、V3、V4
移除一定比例的流动性/lp/decreaseV2、V3、V4
收取累计的交易手续费/lp/claim_feesV3、V4
读取实时池状态/lp/pool_infoV2、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 必须与现有头寸中的顺序一致
nftTokenIdv3/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 的整数
nftTokenIdv3/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 IDtokenIdnftTokenIdv3NftTokenId(整数)
概念/lp/create/lp/increase
v4 permit 载荷batchPermitDatav4BatchPermitData

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验证错误修正请求字段(见下方常见错误)
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 之间。

有关重试/退避、请求缓存、监控和完整的广播前检查清单,请参阅高级模式参考。

支持的链

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 的可用性或支持的链文档确认当前支持的链集合。

其他资源

评论

支持 Markdown