Trading API Integration Guide
This guide provides comprehensive documentation for integrating with the Uniswap Trading API, including schema definitions, validation requirements, error handling, and best practices.
Table of Contents
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.
- Never Empty: The
datafield must be a non-empty hex string (not""or"0x") - Always Validate: Check
dataexists before broadcasting - Revert Prevention: Empty
datacauses 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
1. 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();
2. 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;
}
3. Submit to /swap
When 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
signatureandpermitData - If NOT using Permit2: Omit both fields entirely (don't set to
null) - Never mix: Don't provide one without the other
4. 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) |
Error Response Format
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
datafield: 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
1. Quote Freshness
Quotes are time-sensitive due to price volatility:
- Refresh quotes if more than 30 seconds old before broadcasting
- Use
deadlineparameter 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
}