v4-sdk-integration について
Uniswap v4 SDK のアプリ層統合を支援する、開発者向けスキルです。見積もりのシミュレーション、プール状態の読み取り、Permit2 承認、Universal Router と PositionManager によるポジション管理を扱います。
主な機能
- 01シングルホップとマルチホップのスワップ
- 02正確な入力・出力額の見積もり
- 03StateView によるプール状態の読み取り
- 04Permit2 承認フロー
- 05流動性ポジションのライフサイクル管理
活用シーン
- 01v4 トークンスワップ画面の構築
- 02シミュレーションによる取引見積もりの表示
- 03流動性ポジションの追加・削除・手数料回収
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uniswap/ uniswap-ai --skill v4-sdk-integrationインストール後、Claude Code で/v4-sdk-integrationを入力して読み込まれたか確認します。
詳細
Uniswap v4 SDK の統合
スワップ、見積もり、流動性向けのアプリケーション層 SDK です。Solidity のフックコントラクトには
uniswap-hooksスキルを使用してください。Trading API または v3 中心のスワップには、swap-integrationスキルを使用してください。
使用する場面
- トークンスワップ UI(シングルホップまたはマルチホップ)
- 取引実行前の見積もり/価格表示
- 流動性ポジションの管理(追加/削除/回収)
- プール状態の読み取り(価格、ティック、流動性)
パッケージ
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdk
v4 と v3 の選択表
| 項目 | v3 | v4 |
|---|---|---|
| スワップの実行 | SwapRouter を直接使用 | Universal Router が必要(V4Planner) |
| プールのアーキテクチャ | プールごとに 1 つのコントラクト | Singleton PoolManager |
| プール状態の読み取り | プールコントラクトを直接使用 | StateView コントラクト |
| ネイティブ ETH | WETH にラップ | ネイティブ対応(Ether.onChain(chainId)) |
| ポジション NFT | NonfungiblePositionManager | PositionManager + multicall |
| 手数料の回収 | 明示的な collect() | ポジションの変更時に自動 |
| ポジションの検出 | オンチェーン列挙 | オフチェーンのイベントインデックス化 |
| トークンの承認 | 直接 approve | Permit2 が必要 |
| コントラクトアドレス | すべてのチェーンで同一 | チェーンごとに異なる — デプロイ情報で確認してください |
コアコントラクト(チェーンごと)
アドレスは https://docs.uniswap.org/contracts/v4/deployments で確認してください。チェーンごとに異なります。
| コントラクト | 用途 |
|---|---|
| PoolManager | シングルトンプールの状態 |
| Universal Router | スワップ実行のエントリポイント |
| Quoter | オフチェーンでの見積もりシミュレーション(eth_call。トランザクションではありません) |
| StateView | プール状態の読み取り(getSlot0、getLiquidity) |
| PositionManager | LP ポジションのライフサイクル |
| Permit2 | トークン承認レイヤー(全チェーンで同一:0x000000000022D473030F116dDEE9F6B43aC78BA3) |
スワップのパターン(Universal Router)
すべてのスワップで、V4Planner -> RoutePlanner -> Universal Router の execute() を使用します。
シングルホップ(入力額指定):
import { Actions, V4Planner } from '@uniswap/v4-sdk';
import { CommandType, RoutePlanner } from '@uniswap/universal-router-sdk';
const v4Planner = new V4Planner();
v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [swapConfig]);
v4Planner.addAction(Actions.SETTLE_ALL, [inputCurrency, amountIn]);
v4Planner.addAction(Actions.TAKE_ALL, [outputCurrency, amountOutMinimum]);
const routePlanner = new RoutePlanner();
routePlanner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params]);
const deadline = Math.floor(Date.now() / 1000) + 3600;
// Note: universalRouter.execute() is pseudocode for the viem call pattern.
// With viem, use: walletClient.writeContract({ address: UNIVERSAL_ROUTER_ADDRESS, abi: universalRouterAbi, functionName: 'execute', args: [routePlanner.commands, [v4Planner.finalize()], deadline], ...txOptions })
await universalRouter.execute(routePlanner.commands, [v4Planner.finalize()], deadline, txOptions);
マルチホップ(入力額指定):
import { Actions, V4Planner, encodeMultihopExactInPath } from '@uniswap/v4-sdk';
import { CommandType, RoutePlanner } from '@uniswap/universal-router-sdk';
const v4Planner = new V4Planner();
// Build multi-hop path: tokenA -> tokenB -> tokenC
const path = encodeMultihopExactInPath([poolKeyAB, poolKeyBC], tokenA);
v4Planner.addAction(Actions.SWAP_EXACT_IN, [{ path, amountIn, amountOutMinimum }]);
// SETTLE_ALL uses first pool's input currency; TAKE_ALL uses last pool's output currency
v4Planner.addAction(Actions.SETTLE_ALL, [tokenA, amountIn]);
v4Planner.addAction(Actions.TAKE_ALL, [tokenC, amountOutMinimum]);
const routePlanner = new RoutePlanner();
routePlanner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params]);
const deadline = Math.floor(Date.now() / 1000) + 3600;
// Note: universalRouter.execute() is pseudocode for the viem call pattern.
// With viem, use: walletClient.writeContract({ address: UNIVERSAL_ROUTER_ADDRESS, abi: universalRouterAbi, functionName: 'execute', args: [routePlanner.commands, [v4Planner.finalize()], deadline], ...txOptions })
await universalRouter.execute(routePlanner.commands, [v4Planner.finalize()], deadline, txOptions);
SwapConfig(SwapExactInSingle):
const swapConfig = {
poolKey: { currency0, currency1, fee, tickSpacing, hooks },
zeroForOne,
amountIn,
amountOutMinimum,
hookData: '0x00',
};
見積もりのパターン
Quoter は view 関数ではありません。その理由は Uniswap 独自のガイドで説明されています。v4 の Quoter コントラクトは「必要なデータを返すために、リバートされることを前提とした状態変更呼び出しを利用します」
(https://developers.uniswap.org/docs/sdks/v4/guides/swapping/quoting)。実際のトランザクションとして送信すると、ガスを無駄にするだけでなく、値をコードに返すこともできません。
ライブラリに関係なく、ルールは同じです。eth_call で見積もりをシミュレーションしてください。トランザクションとして送信してはいけません。 シミュレーションの方法はライブラリごとに異なるため、ガイドで見かけたものではなく、自分のスタックに合う方法を選んでください。
quoteExactInputSingle は 2 つの値((uint256 amountOut, uint256 gasEstimate))を返すため、分割代入してください。amountOutMinimum に渡すのは amountOut だけです。タプル全体を渡すとバグになります。
// viem — the stack the rest of this skill assumes.
// simulateContract, not readContract: the quote method is nonpayable, not view.
const {
result: [amountOut, gasEstimate],
} = await publicClient.simulateContract({
address: QUOTER_ADDRESS,
abi: quoterAbi,
functionName: 'quoteExactInputSingle',
args: [{ poolKey, zeroForOne, exactAmount: amountIn, hookData: '0x00' }],
});
// ethers v6 — `.staticCall` hangs off the method itself
const [amountOut, gasEstimate] = await quoterContract.quoteExactInputSingle.staticCall({
poolKey,
zeroForOne,
exactAmount: amountIn,
hookData: '0x00',
});
// ethers v5 only — `callStatic` was removed in v6
const [amountOut, gasEstimate] = await quoterContract.callStatic.quoteExactInputSingle({
poolKey,
zeroForOne,
exactAmount: amountIn,
hookData: '0x00',
});
利用できる 4 つのメソッド:
quoteExactInputSingle— シングルホップ、入力額を指定quoteExactInput— マルチホップ、入力額を指定quoteExactOutputSingle— シングルホップ、出力額を指定quoteExactOutput— マルチホップ、出力額を指定
プール状態の読み取り(StateView)
import { Pool } from '@uniswap/v4-sdk';
const poolId = Pool.getPoolId(currency0, currency1, fee, tickSpacing, hooks);
const [slot0, liquidity] = await Promise.all([
stateViewContract.getSlot0(poolId),
stateViewContract.getLiquidity(poolId),
]);
// slot0 → { sqrtPriceX96, tick, protocolFee, lpFee }
ERC20 承認フロー(Permit2)
ERC20 のスワップには、トークンから Permit2、次に Permit2 から Universal Router への 2 回の承認が必要です。
// Step 1: Approve Permit2 on the token contract
await erc20Contract.approve(PERMIT2_ADDRESS, MaxUint256);
// Step 2: Approve Universal Router on Permit2
await permit2Contract.approve(tokenAddress, UNIVERSAL_ROUTER_ADDRESS, MAX_UINT160, deadline);
ネイティブ ETH のスワップでは、どちらの承認も不要です。代わりにトランザクションオプションで value を渡します。
ポジション管理(PositionManager)
すべての操作で PositionManager.multicall() を使用します。
| 操作 | SDK メソッド |
|---|---|
| 流動性を追加 | V4PositionManager.addCallParameters(position, options) |
| 流動性を削除 | V4PositionManager.removeCallParameters(position, options) |
| 手数料を回収 | V4PositionManager.collectCallParameters(options) |
| ポジションを作成 | V4PositionManager.createCallParameters(position, options) |
const { calldata, value } = V4PositionManager.addCallParameters(position, {
slippageTolerance: new Percent(50, 10_000),
deadline: deadline.toString(),
tokenId: tokenId.toString(),
useNative: token0.isNative ? Ether.onChain(chainId) : undefined,
batchPermit,
hookData: '0x',
});
await walletClient.writeContract({
address: POSITION_MANAGER_ADDRESS,
functionName: 'multicall',
args: [[calldata]],
value: BigInt(value),
});
厳守事項
- スワップで PoolManager を直接呼び出してはいけません。必ず Universal Router を経由してください。
- コントラクトアドレスがチェーン間で同じだと想定してはいけません。デプロイ情報ページで確認してください。
- Quoter をトランザクションとして送信してはいけません(ガス代が高く、値を返すこともできません)。必ず
eth_callでシミュレーションしてください。viem のsimulateContract、ethers v6 の.staticCall、ethers v5 のcallStaticを使用します。 - ERC20 のスワップで Permit2 を省略してはいけません。Universal Router への直接の
approveは機能しません。 - スワップと LP 操作では、必ず期限を設定してください。
- v4 プールのコンテキストでネイティブ ETH を扱う場合は、WETH ではなく
Ether.onChain(chainId)を必ず使用してください。 - プール識別子の計算には必ず
Pool.getPoolId()を使用してください。手動で組み立ててはいけません。
リンク
- SDK の概要:https://docs.uniswap.org/sdk/v4/overview
- スワップガイド:https://docs.uniswap.org/sdk/v4/guides/swaps/quoting
- 流動性ガイド:https://docs.uniswap.org/sdk/v4/guides/liquidity/add-remove-liquidity
- プールデータ:https://docs.uniswap.org/sdk/v4/guides/advanced/pool-data
- デプロイ情報:https://docs.uniswap.org/contracts/v4/deployments
- npm:https://www.npmjs.com/package/@uniswap/v4-sdk
関連スキル
swap-integration— Trading API と v3 中心のスワップ統合(v4 SDK を直接使用するものではありません)uniswap-hooks— Solidity のフックコントラクト生成(アプリケーション層の SDK ではありません)