Цена токена из Uniswap V3 через Quoter
Как получить spot-цену токена через Uniswap V3 QuoterV2, вызвать quoteExactInputSingle в ethers v6 и правильно интерпретировать результат.
Quoter возвращает цену через симуляцию swap
Uniswap V3 Quoter показывает, сколько токенов вернёт swap прямо сейчас.
Это не оракул и не гарантированная цена исполнения. Это симуляция конкретного маршрута, размера сделки и fee tier на текущем состоянии пула.
Получаем цену WETH в USDC
Создайте проект:
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:
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()}`);Запустите:
npx tsx src/uniswap-v3-price.tsСкрипт выведет текущую оценку:
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