Authentication
All requests require an API key:
curl -X POST https://trade.api.uniswap.org/v1/quote \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tokenIn":"0x...","tokenOut":"0x...","amount":"1000000",...}'
Basic Quote Request
const response = await fetch('https://trade.api.uniswap.org/v1/quote', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
tokenIn: '0x0000000000000000000000000000000000000000', // ETH
tokenOut: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
tokenInChainId: 1,
tokenOutChainId: 1,
type: 'EXACT_INPUT',
amount: '1000000000000000000', // 1 ETH in wei
swapper: '0x...', // User's wallet address
slippageTolerance: 0.5 // 0.5%
})
});
const quote = await response.json();
Architecture
Client-Side Responsibilities
The Trading API is a quote and transaction building service. Your application handles:
- Balance Checks: Verify token balances before requesting quotes
- Allowance Management: Check and request token approvals (ERC-20
approve or Permit2)
- Nonce Management: Track transaction nonces for the user’s wallet
- Gas Estimation: Verify gas estimates before broadcasting
- Transaction Broadcasting: Sign and submit transactions via your RPC provider
- Transaction Monitoring: Track confirmations and handle reverts
Required Infrastructure
Your integration must include:
- RPC Provider: Connection to blockchain nodes (Infura, Alchemy, or self-hosted)
- Web3 Library: ethers.js, viem, or web3.js for transaction signing
- Wallet Integration: Uniswap Wallet, MetaMask, or similar for user signing
Data Flow
User Request
|
Your Application
|-- Check balances (via your RPC)
|-- Request quote (Trading API)
|-- Build transaction (Trading API response)
|-- Check allowances (via your RPC)
|-- Get user signature (Wallet)
|-- Manage nonce (your tracking)
+-- Broadcast transaction (via your RPC)
|
Blockchain
Available Endpoints
| Endpoint | Description |
|---|
| POST /quote | Generate a quote for a token swap |
| POST /swap | Convert a quote into an unsigned transaction |
| POST /check_approval | Check if token approval is required |
| POST /swap_5792 | Generate batch transactions for EIP-5792 |
| POST /swap_7702 | Generate transaction with EIP-7702 delegation |
| Cross-Chain Plans | Multi-step cross-chain swap endpoints |
Routing Types
The API returns different quote types based on the optimal routing strategy:
| Value | Type | Description |
|---|
| 0 | CLASSIC | Standard AMM swap through Uniswap pools |
| 1 | DUTCH_LIMIT | Dutch auction order (UniswapX) |
| 2 | DUTCH_V2 | Dutch auction V2 |
| 3 | LIMIT_ORDER | Limit order |
| 4 | WRAP | ETH to WETH wrap |
| 5 | UNWRAP | WETH to ETH unwrap |
| 6 | BRIDGE | Cross-chain bridge |
| 7 | PRIORITY | MEV-protected priority order |
| 8 | DUTCH_V3 | Dutch auction V3 |
| 9 | QUICKROUTE | Fast approximation quote |
| 10 | CHAINED | Multi-step cross-chain swap |
API Endpoints
POST /quote
Generate a quote for a token swap.
Request Body:
interface QuoteRequest {
// Required
tokenIn: string; // Token address to swap from
tokenOut: string; // Token address to swap to
tokenInChainId: number; // Chain ID for input token
tokenOutChainId: number; // Chain ID for output token
type: 'EXACT_INPUT' | 'EXACT_OUTPUT';
amount: string; // Amount in token's smallest unit (wei)
swapper: string; // User's wallet address
// Slippage (choose one, mutually exclusive)
slippageTolerance?: number; // Manual slippage as percentage (e.g., 0.5 = 0.5%)
autoSlippage?: 'DEFAULT'; // Automatic slippage calculation
// Optional
protocols?: Protocol[]; // Limit to specific protocols
routingPreference?: RoutingPreference;
urgency?: 'urgent' | 'normal';
permitAmount?: 'FULL' | 'EXACT';
gasStrategies?: GasStrategy[];
}
Response:
interface QuoteResponse {
requestId: string;
routing: SwapType; // See SwapType enum
// Quote (one of, depends on routing type)
classicQuote?: ClassicQuote;
bridgeQuote?: BridgeQuote;
dutchLimitQuote?: DutchLimitQuote;
dutchLimitV2Quote?: DutchLimitV2Quote;
dutchLimitV3Quote?: DutchLimitV3Quote;
wrapUnwrapQuote?: WrapUnwrapQuote;
priorityQuote?: PriorityQuote;
chainedQuote?: ChainedQuote;
// Permit data (if permit requested)
permitSingleData?: PermitSingleData;
permitTransferFromData?: PermitTransferFromData;
permitTransaction?: TransactionRequest;
permitGasFee?: string;
}
POST /swap
Convert a quote into an unsigned transaction ready for signing.
Request Body:
interface SwapRequest {
// Quote (required)
quote: ClassicQuote | WrapUnwrapQuote | BridgeQuote;
// Permit2 signature (optional, but both required if either present)
signature?: string; // EIP-712 signature for Permit2
permitData?: PermitSingleData; // Required if signature provided
// Optional parameters
safetyMode?: 'RELAXED' | 'SAFE';
deadline?: number; // Unix timestamp
simulateTransaction?: boolean;
refreshGasPrice?: boolean;
urgency?: 'urgent' | 'normal';
gasStrategies?: GasStrategy[];
}
The signature and permitData fields must either both be present or both be omitted. See Permit2 Flow for details.
Response:
interface SwapResponse {
requestId: string;
swap: TransactionRequest; // Transaction to sign and broadcast
gasFee?: string;
gasEstimates?: GasEstimate[];
txFailureReasons?: TransactionFailureReason[];
}
POST /check_approval
Check if token approval is required before swapping.
Request Body:
interface CheckApprovalRequest {
walletAddress: string;
token: string;
amount: string;
chainId: number;
urgency?: 'urgent' | 'normal';
includeGasInfo?: boolean;
tokenOut?: string;
tokenOutChainId?: number;
}
Response:
interface CheckApprovalResponse {
requestId: string;
approval: TransactionRequest; // Approval transaction (if needed)
cancel: TransactionRequest; // Cancel approval transaction
gasFee?: string;
cancelGasFee?: string;
gasEstimates?: GasEstimate[];
}
POST /swap_5792
Generate a batch of transactions for EIP-5792 wallet_sendCalls.
Request Body:
interface Swap5792Request {
classicQuote?: ClassicQuote;
wrapUnwrapQuote?: WrapUnwrapQuote;
bridgeQuote?: BridgeQuote;
permitData?: PermitSingleData;
deadline: number; // Unix timestamp (required)
urgency?: 'urgent' | 'normal';
}
Response:
interface Swap5792Response {
requestId: string;
from: string;
chainId: number;
calls: TransactionRequest5792[];
gasFee?: string;
gasEstimates?: GasEstimate[];
}
interface TransactionRequest5792 {
to: string;
data: string;
value: string;
}
POST /swap_7702
Generate a transaction with EIP-7702 delegation.
Request Body:
interface Swap7702Request {
classicQuote?: ClassicQuote;
wrapUnwrapQuote?: WrapUnwrapQuote;
bridgeQuote?: BridgeQuote;
smartContractDelegationAddress: string; // Required
permitData?: PermitSingleData;
deadline?: number;
urgency?: 'urgent' | 'normal';
gasStrategies?: GasStrategy[];
}
Cross-Chain Plans
For cross-chain swaps (where tokenInChainId !== tokenOutChainId):
- POST /plan - Create a plan from a CHAINED quote
- GET /plan/:planId - Check plan status
- PATCH /plan/:planId - Update plan with transaction hashes/signatures
// Example: Create cross-chain plan
const quoteResponse = await fetch('/quote', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'x-chained-actions-enabled': 'true', // Required header
'Content-Type': 'application/json'
},
body: JSON.stringify({
tokenIn: '0x...',
tokenInChainId: 1, // Ethereum
tokenOut: '0x...',
tokenOutChainId: 8453, // Base
// ... other params
})
});
Schema Reference
TransactionRequest
The TransactionRequest object returned by /swap contains all fields needed to broadcast a transaction:
interface TransactionRequest {
to: string; // Contract address to call
from: string; // User's wallet address
data: string; // Encoded function call (hex string)
value: string; // Native token amount (wei)
chainId: number;
gasLimit?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
gasPrice?: string; // Legacy gas price
}
Critical Field: data
The data field contains the encoded contract call and must always be present in swap transactions.
Validation Requirements
- Never Empty: The
data field must be a non-empty hex string (not "" or "0x")
- Always Validate: Check
data exists before broadcasting
- Revert Prevention: Empty
data causes on-chain transaction reverts
Validation Example:
function validateTransaction(tx: TransactionRequest): void {
// Critical validation
if (!tx.data || tx.data === '' || tx.data === '0x') {
throw new Error('Transaction data is empty - invalid swap transaction');
}
if (!tx.to || !tx.from) {
throw new Error('Missing required transaction fields');
}
// Verify valid hex
if (!/^0x[0-9a-fA-F]*$/.test(tx.data)) {
throw new Error('Transaction data is not valid hex');
}
}
// Use before broadcasting
const swapResponse = await fetch('/swap', {...});
const {swap} = await swapResponse.json();
validateTransaction(swap); // Validate before signing
const signedTx = await wallet.signTransaction(swap);
const txHash = await provider.sendTransaction(signedTx);
PermitSingleData
For Permit2-based approvals:
interface PermitSingleData {
domain: TypedDataDomain;
values: PermitSingle;
types: Record<string, TypedDataField[]>;
}
interface PermitSingle {
details: {
token: string;
amount: string;
expiration: string;
nonce: string;
};
spender: string;
sigDeadline: string;
}
Permit2 Flow
Permit2 enables gasless token approvals via EIP-712 signatures.
When to Use Permit2
Use Permit2 when:
- Swapping ERC-20 tokens (not native tokens)
- User hasn’t approved the token for Permit2
- You want to minimize transaction count (1 tx instead of 2)
Implementation Steps
Get Quote with Permit Data
const quoteResponse = await fetch('/quote', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
tokenIn: '0x...',
tokenOut: '0x...',
amount: '1000000',
type: 'EXACT_INPUT',
swapper: '0x...',
tokenInChainId: 1,
tokenOutChainId: 1,
permitAmount: 'EXACT', // or 'FULL'
slippageTolerance: 0.5
})
});
const {quote} = await quoteResponse.json();
Sign the Permit
let signature: string | undefined;
let permitData: PermitSingleData | undefined;
if (quote.permitData) {
signature = await wallet._signTypedData(
quote.permitData.domain,
quote.permitData.types,
quote.permitData.values
);
permitData = quote.permitData;
}
Submit to /swap
Critical Field RequirementsWhen including a Permit2 signature, both signature AND permitData must be provided:
// CORRECT - Both fields provided
const swapRequest = {
classicQuote: quote,
signature: signature,
permitData: permitData
};
// WRONG - Missing permitData
const swapRequest = {
classicQuote: quote,
signature: signature // Will fail validation
};
// WRONG - Null permitData
const swapRequest = {
classicQuote: quote,
signature: signature,
permitData: null // API rejects null, omit field instead
};
// CORRECT - No permit (both fields omitted)
const swapRequest = {
classicQuote: quote
// signature and permitData omitted entirely
};
Field Omission Rules:
- If using Permit2: Include both
signature and permitData
- If NOT using Permit2: Omit both fields entirely (don’t set to
null)
- Never mix: Don’t provide one without the other
Broadcast Transaction
const {swap} = await swapResponse.json();
validateTransaction(swap);
const signedTx = await wallet.signTransaction(swap);
const txReceipt = await provider.sendTransaction(signedTx);
Permit2 Error Messages
| Error | Cause | Solution |
|---|
"permitData" must be of type object | Field set to null | Omit field entirely |
signature provided without permitData | Missing permitData | Include both or neither |
Invalid permit signature | Wrong data signed or expired | Re-request quote and sign fresh |
Error Handling
HTTP Status Codes
| Code | Meaning |
|---|
| 200 | Request succeeded |
| 400 | Invalid request (validation error) |
| 401 | Invalid API key |
| 429 | Rate limit exceeded |
| 500 | API error (retry with backoff) |
| 503 | Temporary unavailability (retry) |
interface ErrorResponse {
error: string;
message: string;
details?: Record<string, any>;
}
Common Errors
NO_QUOTES_AVAILABLE
Cause: No route found for the requested swap
Solutions:
- Verify token addresses are correct and on the specified chain
- Check sufficient liquidity exists for the trade size
- Try different protocols or routing preferences
- Reduce trade size
INSUFFICIENT_RESERVES
Cause: Liquidity insufficient for the requested amount
Solutions:
- Reduce trade amount
- Split into multiple smaller trades
- Try alternative routing
VALIDATION_ERROR
Cause: Invalid request parameters
Solutions:
- Check all required fields are present
- Verify addresses are valid checksummed addresses
- Ensure amount is in correct units (wei, not ether)
- Validate chain IDs are supported
Transaction Revert Scenarios
If a transaction reverts on-chain:
- Check
data field: Verify it’s not empty
- Verify token balance: User has sufficient tokens at broadcast time
- Check allowance: Token approval is sufficient (if not using Permit2)
- Check slippage: Price moved beyond slippage tolerance
- Check deadline: Quote expired before broadcast
- Nonce collision: Another transaction used the same nonce
Recommended Client-Side Checks:
async function validateBeforeBroadcast(
tx: TransactionRequest,
provider: Provider,
token: string,
amount: string
): Promise<void> {
// 1. Validate transaction structure
if (!tx.data || tx.data === '' || tx.data === '0x') {
throw new Error('Invalid transaction: empty data field');
}
// 2. Check native balance
const balance = await provider.getBalance(tx.from);
if (balance.lt(tx.value)) {
throw new Error('Insufficient native token balance');
}
// 3. Check ERC-20 balance (if applicable)
if (token !== NATIVE_TOKEN_ADDRESS) {
const tokenContract = new Contract(token, ERC20_ABI, provider);
const tokenBalance = await tokenContract.balanceOf(tx.from);
if (tokenBalance.lt(amount)) {
throw new Error('Insufficient token balance');
}
}
// 4. Simulate transaction (optional but recommended)
try {
await provider.call(tx);
} catch (error) {
throw new Error(`Transaction simulation failed: ${error.message}`);
}
}
Best Practices
Quote Freshness
Quotes are time-sensitive due to price volatility:
- Refresh quotes if more than 30 seconds old before broadcasting
- Use
deadline parameter to prevent execution of stale quotes
- Monitor price impact and warn users of significant changes
const QUOTE_EXPIRY_MS = 30000; // 30 seconds
const quoteTimestamp = Date.now();
// ... user reviews and signs ...
if (Date.now() - quoteTimestamp > QUOTE_EXPIRY_MS) {
quote = await fetchQuote(params); // Fetch fresh quote
}
Transaction Validation
Always validate transaction payloads before broadcasting:
function validateSwapTransaction(tx: TransactionRequest): void {
if (!tx.data || tx.data === '' || tx.data === '0x') {
throw new Error('Transaction data is empty');
}
if (!tx.to || !isAddress(tx.to)) {
throw new Error('Invalid recipient address');
}
if (!tx.from || !isAddress(tx.from)) {
throw new Error('Invalid sender address');
}
if (tx.maxFeePerGas && tx.gasPrice) {
throw new Error('Cannot set both maxFeePerGas and gasPrice');
}
if (tx.value && BigNumber.from(tx.value).lt(0)) {
throw new Error('Invalid transaction value');
}
}
Gas Management
The API provides gas estimates, but clients should:
- Apply gas buffer: Add 10-20% to estimated gas limit
- Update gas prices: Use
refreshGasPrice: true for fresh estimates
- Handle gas spikes: Warn users when gas is unusually high
- EIP-1559 vs Legacy: Use EIP-1559 on supported chains
function applyGasBuffer(tx: TransactionRequest): TransactionRequest {
if (tx.gasLimit) {
const buffered = BigNumber.from(tx.gasLimit)
.mul(120) // 120%
.div(100);
return {...tx, gasLimit: buffered.toString()};
}
return tx;
}
Slippage Configuration
Balance protection vs execution success:
| Setting | Slippage | Use Case |
|---|
| Conservative | 0.1-0.5% | Stable pairs, low volatility |
| Moderate | 0.5-1% | Most swaps |
| Aggressive | 1-5% | Large trades, volatile markets, low liquidity |
Error Recovery
Implement retry logic with exponential backoff:
async function fetchQuoteWithRetry(
params: QuoteRequest,
maxRetries = 3
): Promise<QuoteResponse> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('/quote', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
});
if (!response.ok) {
if (response.status === 429) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
throw new Error('Max retries exceeded');
}
Monitoring & Logging
Track key metrics for production reliability:
interface SwapMetrics {
quoteLatency: number;
swapLatency: number;
quoteFreshness: number;
gasEstimateAccuracy: number;
revertRate: number;
}
function logSwapAttempt(
params: QuoteRequest,
quote: QuoteResponse,
txHash?: string,
error?: Error
): void {
const metrics = {
timestamp: Date.now(),
chainId: params.tokenInChainId,
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
amount: params.amount,
routing: quote.routing,
txHash,
success: !!txHash && !error,
error: error?.message
};
analytics.track('swap_attempt', metrics);
}
Troubleshooting
Quote Issues
Problem: No quotes returned
- Verify token addresses are valid for the specified chains
- Confirm liquidity exists for the trading pair
- Ensure the amount is within reasonable bounds
- Verify chain IDs are supported
- Check that specified protocols (if any) are available on the target chain
Problem: Quote price seems incorrect
- Verify the amount is in the correct units (wei, not ether)
- Confirm token decimals match the on-chain contract
- Review slippage tolerance for appropriateness
- Evaluate price impact relative to trade size
Transaction Issues
Problem: Transaction reverts on-chain
- Verify the
data field is not empty (see Transaction Validation)
- Confirm the token balance is sufficient at broadcast time
- Ensure token approval is in place (if not using Permit2)
- Check that the quote has not expired
- Review slippage tolerance and adjust (tighten or loosen) as needed
- Verify gas limit is sufficient
- Confirm nonce has no collisions with pending transactions
Problem: Transaction takes too long to confirm
- Use
refreshGasPrice: true to get competitive gas pricing
- Check network congestion on the target chain
- Verify the transaction nonce is not blocked by a pending transaction
API Issues
Problem: 429 Rate Limit Exceeded
Solution: Implement exponential backoff and request caching:
const cache = new Map<string, {data: any, timestamp: number}>();
async function fetchWithCache(
url: string,
params: any,
cacheDuration = 30000
): Promise<any> {
const cacheKey = `${url}:${JSON.stringify(params)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheDuration) {
return cached.data;
}
const data = await fetchWithRetry(url, params);
cache.set(cacheKey, {data, timestamp: Date.now()});
return data;
}
Problem: Inconsistent responses
Solution: Include requestId in all related requests for debugging:
const requestId = generateUUID();
const quote = await fetchQuote({...params, requestId});
const swap = await fetchSwap({classicQuote: quote, requestId});
Integration Issues
Problem: Empty data field in transaction
Solution:
- Add validation before broadcasting
- Log the full response for debugging
- Contact support with
requestId if persistent
const {swap} = await swapResponse.json();
if (!swap.data || swap.data === '' || swap.data === '0x') {
console.error('Invalid swap response:', {
requestId: swap.requestId,
swap
});
throw new Error('Empty transaction data - please contact support');
}
Problem: Permit2 validation errors
Solution: Follow exact field requirements:
const swapRequest: SwapRequest = {
classicQuote: quote
};
// Only add permit fields if both are present
if (signature && permitData) {
swapRequest.signature = signature;
swapRequest.permitData = permitData;
}
// Otherwise omit entirely (don't set to null)
Limitations
- UniswapX v2: Mainnet, Arbitrum, Base, Unichain only
- UniswapX v3: Arbitrum only
- L2 UniswapX minimum: 300 USDC equivalent
- Native token swaps: No UniswapX for native token input or wrapping
Support
For support, reach out to [email protected]
When reporting issues, include:
- Request ID from API response
- Full request/response payloads (sanitize sensitive data)
- Chain ID and transaction hash (if applicable)
- Timestamp of the request