Quotes & swaps
Quotes and Swaps
This guide shows how to get price quotes and execute swaps using the unified SDK (@whetstone-research/doppler-sdk-alpha) across Uniswap V2, V3, and V4 (including Doppler dynamic auctions).
Quoting uses the SDK
Quoterfor V2/V3/V4.Executing swaps uses the Uniswap Universal Router. For convenience, we show examples with the
doppler-routerhelpers used in the demo app.
Setup
import { DopplerSDK, Quoter, getAddresses, DYNAMIC_FEE_FLAG } from '@whetstone-research/doppler-sdk'
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem'
import { base } from 'viem/chains'
const publicClient = createPublicClient({ chain: base, transport: http(rpcUrl) })
const walletClient = createWalletClient({ chain: base, transport: http(rpcUrl), account })
const sdk = new DopplerSDK({ publicClient, walletClient, chainId: base.id })
const quoter = new Quoter(publicClient, base.id)
const addresses = getAddresses(base.id)Quoting
V3: Exact Input (Single Pool)
const { amountOut, sqrtPriceX96After } = await quoter.quoteExactInputV3({
tokenIn: tokenInAddress,
tokenOut: tokenOutAddress,
amountIn: parseUnits('1.0', inDecimals),
fee: 3000, // 0.3%
sqrtPriceLimitX96: 0n, // optional
})V3: Exact Output (Single Pool)
const { amountIn } = await quoter.quoteExactOutputV3({
tokenIn: tokenInAddress,
tokenOut: tokenOutAddress,
amountOut: parseUnits('100', outDecimals),
fee: 3000,
})V2: Exact Input (Path)
// Simple 2-hop path (tokenIn -> tokenOut). Multi-hop supported.
const amounts = await quoter.quoteExactInputV2({
amountIn: parseUnits('1.0', inDecimals),
path: [tokenInAddress, tokenOutAddress],
})
const amountOut = amounts[amounts.length - 1]V4 (Dynamic Auctions): Exact Input
For Doppler V4 dynamic auctions, build a poolKey and determine direction with zeroForOne:
import { Address } from 'viem'
// Sort to get currency0/currency1 as in V4 (lexicographically ascending)
const [currency0, currency1] = [baseToken as Address, quoteToken as Address]
.sort((a, b) => (a.toLowerCase() < b.toLowerCase() ? -1 : 1))
const poolKey = {
currency0,
currency1,
fee: DYNAMIC_FEE_FLAG, // Doppler dynamic auctions use the dynamic fee flag
tickSpacing: 8, // Typical for Doppler auctions; use actual value if known
hooks: hookAddress as Address,
}
// Direction: swap 0->1 when tokenIn is currency0
const zeroForOne = (tokenInAddress.toLowerCase() === currency0.toLowerCase())
const { amountOut } = await quoter.quoteExactInputV4({
poolKey,
zeroForOne,
exactAmount: parseUnits('1.0', inDecimals),
hookData: '0x', // usually empty for Doppler swaps
})Notes:
Use your pool’s actual
tickSpacingif available from the indexer or hook config.hookDatais typically0xfor Doppler swaps.
Executing Swaps (Universal Router)
The SDK exposes addresses for the Uniswap Universal Router via getAddresses(chainId). To build inputs, the miniapp uses doppler-router helpers; you can do the same or craft bytes manually.
Install helpers:
npm install doppler-routerV4 Dynamic Auction: Swap Exact In Single
import { CommandBuilder, V4ActionBuilder, V4ActionType } from 'doppler-router'
import { zeroAddress, maxUint256, parseUnits } from 'viem'
// 1) Build poolKey and zeroForOne as in the quoting example
const [currency0, currency1] = [baseToken as Address, quoteToken as Address]
.sort((a, b) => (a.toLowerCase() < b.toLowerCase() ? -1 : 1))
const poolKey = { currency0, currency1, fee: DYNAMIC_FEE_FLAG, tickSpacing: 8, hooks: hookAddress as Address }
const zeroForOne = (tokenInAddress.toLowerCase() === currency0.toLowerCase())
const amountIn = parseUnits('1.0', inDecimals)
const minAmountOut = 0n // add slippage logic as needed
// 2) Build V4 swap actions
const actionBuilder = new V4ActionBuilder()
const [actions, params] = actionBuilder
.addSwapExactInSingle(poolKey, zeroForOne, amountIn, minAmountOut, '0x')
// Settle and take ensures outputs are transferred correctly
.addAction(V4ActionType.SETTLE_ALL, [zeroForOne ? poolKey.currency0 : poolKey.currency1, maxUint256])
.addAction(V4ActionType.TAKE_ALL, [zeroForOne ? poolKey.currency1 : poolKey.currency0, 0])
.build()
// 3) Encode Universal Router command
const [commands, inputs] = new CommandBuilder().addV4Swap(actions, params).build()
// 4) Minimal Universal Router ABI with execute()
const universalRouterAbi = [
{
type: 'function',
name: 'execute',
stateMutability: 'payable',
inputs: [
{ name: 'commands', type: 'bytes' },
{ name: 'inputs', type: 'bytes[]' },
],
outputs: [],
},
] as const
// 5) Execute
const txHash = await walletClient.writeContract({
address: addresses.universalRouter,
abi: universalRouterAbi,
functionName: 'execute',
args: [commands, inputs],
// Send ETH when swapping from native currency (currency0 usually WETH/native)
value: zeroForOne ? amountIn : 0n,
})
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })Tips:
For ERC20 inputs, ensure allowance (Permit2 or token approve). See
doppler-routergetPermitSignaturehelper.Use a non‑zero
minAmountOutbased on a prior quote and desired slippage.
V3 and V2 Swaps
The Universal Router also supports V3/V2 swaps. You can:
Use the
CommandBuilderto add V3/V2 swap commands similarly, orCall the respective pool routers directly (outside the scope of this doc).
The unified SDK’s Quoter covers price discovery for all of V2/V3/V4 regardless of which path you choose for execution.
End‑to‑End Pattern (Demo application)
The doppler-demo-app demonstrates:
Building V4
poolKeyandzeroForOnefrom base/quote tokensQuoting via
quoter.quoteExactInputV4Executing via Universal Router using
doppler-routerbuildersView it's code here: https://github.com/whetstoneresearch/doppler-demo-app
Look at src/pages/PoolDetails.tsx for a complete reference implementation.
Last updated