mcpField
V
v4-sdk-integration

v4-sdk-integration

github.com/uniswap開発236734 回インストール2 日前に更新
リポジトリ

v4-sdk-integration について

Uniswap v4 SDK のアプリ層統合を支援する、開発者向けスキルです。見積もりのシミュレーション、プール状態の読み取り、Permit2 承認、Universal Router と PositionManager によるポジション管理を扱います。

主な機能

  1. 01シングルホップとマルチホップのスワップ
  2. 02正確な入力・出力額の見積もり
  3. 03StateView によるプール状態の読み取り
  4. 04Permit2 承認フロー
  5. 05流動性ポジションのライフサイクル管理

活用シーン

  1. 01v4 トークンスワップ画面の構築
  2. 02シミュレーションによる取引見積もりの表示
  3. 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 の選択表

項目v3v4
スワップの実行SwapRouter を直接使用Universal Router が必要(V4Planner)
プールのアーキテクチャプールごとに 1 つのコントラクトSingleton PoolManager
プール状態の読み取りプールコントラクトを直接使用StateView コントラクト
ネイティブ ETHWETH にラップネイティブ対応(Ether.onChain(chainId))
ポジション NFTNonfungiblePositionManagerPositionManager + multicall
手数料の回収明示的な collect()ポジションの変更時に自動
ポジションの検出オンチェーン列挙オフチェーンのイベントインデックス化
トークンの承認直接 approvePermit2 が必要
コントラクトアドレスすべてのチェーンで同一チェーンごとに異なる — デプロイ情報で確認してください

コアコントラクト(チェーンごと)

アドレスは https://docs.uniswap.org/contracts/v4/deployments で確認してください。チェーンごとに異なります。

コントラクト用途
PoolManagerシングルトンプールの状態
Universal Routerスワップ実行のエントリポイント
Quoterオフチェーンでの見積もりシミュレーション(eth_call。トランザクションではありません)
StateViewプール状態の読み取り(getSlot0、getLiquidity)
PositionManagerLP ポジションのライフサイクル
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() を使用してください。手動で組み立ててはいけません。

リンク


関連スキル

  • swap-integration — Trading API と v3 中心のスワップ統合(v4 SDK を直接使用するものではありません)
  • uniswap-hooks — Solidity のフックコントラクト生成(アプリケーション層の SDK ではありません)

コメント