UniswapX Integration
UniswapX is fundamentally different architecture for executing swaps compared to Uniswap V2/V3. Instead of direct AMM pool interaction — intent-based system: user signs an intent to perform a swap, filler (professional market maker or arbitrage bot) executes it optimally. This changes not only UX but the entire technical integration scheme.
How intent-based execution works
Order flow
Traditional Uniswap V3 swap: user → transaction → Router → Pool → execution. Every step on-chain, user pays gas, MEV extracted by sandwich bots.
UniswapX order flow:
- User signs
SignedOrder(off-chain, no gas) - Order published to UniswapX order stream (open dutch auction)
- Fillers compete for order execution
- Winning filler executes transaction on-chain (filler pays gas)
- Filler receives spread between auction price and actual execution price
For end user: no gas fees (or significantly reduced), MEV protection (sandwich impossible — no open order in mempool), better price through filler competition.
Dutch Auction mechanics
UniswapX uses Dutch Auction for price discovery. Order initial price set favorably for fillers (large spread), shifts toward user over time. Filler accepting order first at acceptable price — wins.
Order parameters determine auction curve:
-
inputAmount— what user gives -
outputs[].startAmount— minimum output at auction start (favorable for fillers) -
outputs[].endAmount— minimum output at auction end (favorable for user) -
deadline— when order expires
Optimal auction curve depends on asset volatility and expected execution time. For highly liquid pairs (ETH/USDC) — aggressive curve with fast convergence. For illiquid pairs — softer curve.
Integration via UniswapX SDK
Order creation and signing
import { DutchOrderBuilder, NonceManager, PERMIT2_ADDRESS } from "@uniswap/uniswapx-sdk";
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
// Get nonce via NonceManager
const nonceManager = new NonceManager(provider, chainId, PERMIT2_ADDRESS);
const nonce = await nonceManager.useNonce(wallet.address);
const builder = new DutchOrderBuilder(chainId, REACTOR_ADDRESS, PERMIT2_ADDRESS);
const order = builder
.deadline(Math.floor(Date.now() / 1000) + 300) // 5 minutes
.decayStartTime(Math.floor(Date.now() / 1000))
.decayEndTime(Math.floor(Date.now() / 1000) + 180) // auction 3 minutes
.nonce(nonce)
.input({
token: WETH_ADDRESS,
startAmount: ethers.parseEther("1"),
endAmount: ethers.parseEther("1"), // input doesn't change
})
.output({
token: USDC_ADDRESS,
startAmount: ethers.parseUnits("3150", 6), // favorable for filler
endAmount: ethers.parseUnits("3180", 6), // user target price
recipient: wallet.address,
})
.build();
// Sign via Permit2
const { domain, types, values } = order.permitData();
const signature = await wallet.signTypedData(domain, types, values);
const signedOrder = { order: order.serialize(), sig: signature };
Permit2: why it matters
UniswapX uses Permit2 (EIP-712 signature for permissions) instead of standard ERC-20 approve. This enables batch approvals, time-limited permissions and signature without on-chain transaction. User approves PERMIT2_ADDRESS once per token, then only off-chain signatures.
For dApp integration: check Permit2 approval existence on first interaction, request approve if missing. Once per token — not per swap.
Sending order to API
// Send to UniswapX order stream
const response = await fetch("https://api.uniswap.org/v2/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(signedOrder),
});
const { hash } = await response.json();
// Monitor status
const statusResponse = await fetch(`https://api.uniswap.org/v2/orders?orderHash=${hash}`);
const { orderStatus } = await statusResponse.json();
// orderStatus: "open" | "filled" | "cancelled" | "expired"
Tracking execution
UniswapX API polling — simple approach. For real-time updates: subscribe to Fill(orderHash, filler, swapper, nonce) events from Reactor contract via WebSocket or The Graph.
Filler-side integration
If the goal is to become a filler (order executor) for income:
Filler architecture: Service monitors open orders via UniswapX API, evaluates profitability of each order (current price vs auction price), executes via execute() or executeBatch() on Reactor contract.
Liquidity sources for filler:
- Own inventory (pre-funded tokens)
- Flash swap via Uniswap V3 (atomically: get from pool → send to user → return to pool)
- Routing via Jupiter/1inch for finding better execution price
Filler reactor contract:
contract UniswapXFiller is IReactorCallback {
function reactorCallback(
ResolvedOrder[] calldata resolvedOrders,
bytes calldata callbackData
) external override {
// Tokens already transferred from swapper to this contract
// Execute routing via Uniswap V3 or other source
// Return required output tokens to Reactor
}
}
Supported networks
| Network | Reactor address | Status |
|---|---|---|
| Ethereum mainnet | ExclusiveDutchOrderReactor | Production |
| Polygon | ExclusiveDutchOrderReactor | Production |
| Arbitrum | ExclusiveDutchOrderReactor | Production |
| Optimism | ExclusiveDutchOrderReactor | Production |
| Base | ExclusiveDutchOrderReactor | Production |
For cross-chain swaps — UniswapX with cross-chain routing (experimental, based on Across Protocol).
Development process
Analytics (1-2 days). Define task: integrator (add UniswapX to existing dApp) or filler (earn on order execution).
Development (3-5 days).
- For integrator: SDK integration, Permit2 flow, UI components, order tracking.
- For filler: filler contract, order monitoring service, routing logic, profit calculation.
Testing. UniswapX provides testnet deployment (Sepolia). Test complete flow: signing → submission → monitoring → execution.
Timeline estimates
Basic UniswapX integration into existing dApp (order creation, Permit2, tracking) — 3-5 days. Filler bot with routing logic and flash swap — 1-2 weeks. Cost calculated individually.







