китаев.tech

Цена токена из Uniswap V3 через Quoter

Как получить spot-цену токена через Uniswap V3 QuoterV2, вызвать quoteExactInputSingle в ethers v6 и правильно интерпретировать результат.

Quoter возвращает цену через симуляцию swap

Uniswap V3 Quoter показывает, сколько токенов вернёт swap прямо сейчас.

Это не оракул и не гарантированная цена исполнения. Это симуляция конкретного маршрута, размера сделки и fee tier на текущем состоянии пула.


Получаем цену WETH в USDC

Создайте проект:

bash
mkdir uniswap-v3-price-example
cd uniswap-v3-price-example

npm init -y
npm pkg set type="module"
npm install ethers tsx
mkdir src

Создайте файл src/uniswap-v3-price.ts:

typescript
import { ethers } from "ethers";

const RPC_URL = "https://ethereum-rpc.publicnode.com";

const QUOTER_V2_ADDRESS = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e";
const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

const WETH_DECIMALS = 18;
const USDC_DECIMALS = 6;
const FEE_005_PERCENT = 500;

const QUOTER_V2_ABI = [
  "function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) external returns (uint256 amountOut,uint160 sqrtPriceX96After,uint32 initializedTicksCrossed,uint256 gasEstimate)",
];

const provider = new ethers.JsonRpcProvider(RPC_URL);

const quoter = new ethers.Contract(
  QUOTER_V2_ADDRESS,
  QUOTER_V2_ABI,
  provider,
);

const amountIn = ethers.parseUnits("1", WETH_DECIMALS);

const [amountOut, sqrtPriceX96After, initializedTicksCrossed, gasEstimate] =
  await quoter.quoteExactInputSingle.staticCall({
    tokenIn: WETH_ADDRESS,
    tokenOut: USDC_ADDRESS,
    amountIn,
    fee: FEE_005_PERCENT,
    sqrtPriceLimitX96: 0,
  });

const price = ethers.formatUnits(amountOut, USDC_DECIMALS);

console.log(`1 WETH ~= ${price} USDC`);
console.log(`sqrtPriceX96 after quote: ${sqrtPriceX96After.toString()}`);
console.log(`Initialized ticks crossed: ${initializedTicksCrossed.toString()}`);
console.log(`Estimated gas: ${gasEstimate.toString()}`);

Запустите:

bash
npx tsx src/uniswap-v3-price.ts

Скрипт выведет текущую оценку:

text
1 WETH ~= 3542.184203 USDC
sqrtPriceX96 after quote: 4714432799985203108285716
Initialized ticks crossed: 1
Estimated gas: 94121

Числа будут отличаться: цена и состояние пула меняются в каждом блоке.

Как это работает

QUOTER_V2_ADDRESS — адрес Uniswap V3 QuoterV2 в Ethereum mainnet.

WETH_ADDRESS и USDC_ADDRESS — реальные mainnet-адреса токенов.

FEE_005_PERCENT — fee tier 500, то есть пул с комиссией 0.05%. Для другой пары может быть ликвиднее tier 3000 или 10000.

amountIn — входной объём сделки. Цена зависит от размера: quote для 1 WETH и 1000 WETH даст разную эффективную цену из-за price impact.

quoteExactInputSingle.staticCall() — локально симулирует вызов Quoter. Транзакция не отправляется, gas не списывается.

sqrtPriceLimitX96: 0 — без ограничения цены. Для обычного чтения quote этого достаточно.

amountOut — количество USDC, которое вернул бы swap WETH → USDC при текущем состоянии пула.

Почему это не oracle

Quoter берёт текущую цену из конкретного Uniswap V3 пула.

Такую цену можно временно сдвинуть большим swap-ом, особенно если пул неглубокий или сделка большая. Поэтому Quoter подходит для UI, предпросмотра swap и off-chain расчётов, но не для on-chain решений о залоге, ликвидации или mint.

Если контракту нужна цена для финансового решения, используйте TWAP, Chainlink или отдельный oracle design.

Частые ошибки

Использовать Quoter как защищённый оракул → Quoter возвращает spot-quote. Для критичной логики нужна защита от манипуляции цены.

Забыть про decimals → WETH имеет 18 decimals, USDC имеет 6. Форматируйте amountOut через decimals токена, который получаете.

Выбрать неправильный fee tier → один и тот же токен-пар может иметь несколько пулов. Проверяйте, где есть реальная ликвидность: 500, 3000 или 10000.

Вызвать Quoter как обычную транзакцию → используйте staticCall. Quoter нужен для симуляции, а не для изменения состояния.


Что дальше

  • Oracle manipulation — почему spot-цену AMM нельзя использовать как безопасный оракул
  • ERC-20: approve и allowance — как пользователь разрешает router списывать токены перед swap
  • Batch и Multicall — как объединять несколько чтений и вызовов в один flow

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.