Note: This is a Labrys fork of the Uniswap Routing API, customized for Immutable zkEVM testnet deployment. It provides V3-only routing functionality for a single chain.
This repository contains a routing API for the Uniswap V3 protocol, deployed as an AWS Lambda function that uses @Labrys-Group/smart-order-router to find the most efficient way to swap token A for token B.
- Single Chain Only: Supports only Immutable zkEVM Testnet (Chain ID: 13473)
- V3 Only: V2 and V4 protocol support has been removed
- Simplified Configuration: No Tenderly or Alchemy integrations required
- Custom Dependencies: Uses
@Labrys-Group/smart-order-routerpackage
This API exclusively supports:
- Immutable zkEVM Testnet (Chain ID:
13473)
This project uses packages from the @Labrys-Group GitHub package repository. You must authenticate before running npm install.
Create a GitHub Personal Access Token with read:packages scope and configure npm:
Option 1: Using Taskfile (Recommended)
task package:login-
Clone the repository
git clone git@github.com:Labrys-Group/uniswap-routing-api.git cd uniswap-routing-api -
Authenticate to GitHub Packages (see above)
-
Create environment configuration
cp .env.example .env
Then edit
.envand configure the required variables (see Environment Variables section below). -
Install dependencies and build
npm install npm run build
# GitHub personal access token for authentication to access @Labrys-Group packages
GITHUB_PACKAGE_TOKEN=ghp_your_token_here
# RPC endpoint for Immutable zkEVM testnet
ZK_EVM_TESTNET_RPC=https://rpc.testnet.immutable.com
# Your deployed V3 subgraph URL
V3_SUBGRAPH_URL=https://your-subgraph-url/graphql
# Application version
VERSION=1
# Node.js options for source maps
NODE_OPTIONS=--enable-source-maps
# AWS Region (for DynamoDB and Lambda)
AWS_REGION=ap-southeast-2For production deployments, DynamoDB caching is recommended:
# Routes cache tables
ROUTES_TABLE_NAME=RoutesDB
CACHED_ROUTES_TABLE_NAME=RouteCachingDB
ROUTES_CACHING_REQUEST_FLAG_TABLE_NAME=RoutesDbCacheReqFlagDB
# V3 pools cache table
CACHED_V3_POOLS_TABLE_NAME=V3PoolsCachingDB# Secret for enabling debug routing config
UNICORN_SECRET=your-random-secret-stringFor comprehensive deployment using Terraform, refer to the Terraform Deployment Guide. This guide covers:
- Complete infrastructure setup (Lambda, DynamoDB, API Gateway)
- IAM roles and permissions
- Environment configuration
- Monitoring and logging
This repository includes automated deployment via GitHub Actions:
Workflow: .github/workflows/deploy.yml
Required GitHub Secrets:
AWS_ROLE_ARN- IAM role ARN for deploymentAWS_REGION- AWS region (e.g., ap-southeast-2)LAMBDA_FUNCTION_NAME- Name of your Lambda function
Required GitHub Variables:
UNI_GRAPHQL_ENDPOINT- Your V3 subgraph URLUNI_GRAPHQL_HEADER_ORIGIN- GraphQL header origin
The workflow automatically builds and deploys on push to the main branch.
The deployed Lambda function uses:
- Runtime: Node.js 18.x
- Handler:
lib/handlers/index.quoteHandler - Memory: 5120 MB
- Timeout: 30 seconds
POST /quote
curl -X POST 'https://your-api-gateway-url/quote' \
-H 'Content-Type: application/json' \
-d '{
"tokenInAddress": "0x...",
"tokenInChainId": 13473,
"tokenOutAddress": "0x...",
"tokenOutChainId": 13473,
"amount": "1000000000000000000",
"type": "exactIn",
"recipient": "0x...",
"slippageTolerance": "0.5",
"deadline": 1234567890
}'tokenInChainIdandtokenOutChainIdmust be13473(Immutable zkEVM Testnet)amountis in wei (for exactIn) or token units (for exactOut)typecan beexactInorexactOutslippageToleranceis a percentage (e.g., "0.5" = 0.5%)
{
"methodParameters": {
"calldata": "0x...",
"value": "0x00",
"to": "0x..."
},
"blockNumber": "12345678",
"amount": "1000000000000000000",
"amountDecimals": "1.0",
"quote": "999000000000000000",
"quoteDecimals": "0.999",
"quoteGasAdjusted": "998000000000000000",
"quoteGasAdjustedDecimals": "0.998",
"gasUseEstimateQuote": "1000000000000000",
"gasUseEstimateQuoteDecimals": "0.001",
"gasUseEstimate": "150000",
"gasUseEstimateUSD": "0.50",
"gasPriceWei": "1000000000",
"route": [...]
}npm run test:unitWatch mode:
npm run test:unit:watchIntegration tests run against a local DynamoDB instance (requires JDK 8):
npm run test:integE2E tests fetch quotes from your deployed API and execute swaps on a Hardhat fork:
-
Update
.envwith your API URL and archive node:UNISWAP_ROUTING_API='https://your-api-gateway-url' ARCHIVE_NODE_RPC='https://your-archive-node-rpc'
-
Run the tests:
npm run test:e2e
Note: Test files may not be present in all forks. Check for the existence of test directories before running.
npm run build- Build the TypeScript codenpm run build:clean- Clean and rebuildnpm run lint- Run ESLintnpm run lint:fix- Auto-fix linting issuesnpm run test:unit- Run unit testsnpm run test:integ- Run integration testsnpm run test:e2e- Run end-to-end tests
If you have Task installed:
task package:login- Authenticate to GitHub packagestask build- Build the projecttask test- Run all tests
Run task --list to see all available tasks.
This API uses:
- AWS Lambda - Serverless compute for quote generation
- API Gateway - HTTP API endpoint
- DynamoDB - Caching for routes and pool data (optional but recommended)
- S3 - Token list and pool cache storage (optional)
The quote generation flow:
- API Gateway receives POST request
- Lambda handler validates request and extracts parameters
- Smart Order Router queries V3 subgraph for pool data
- Router calculates optimal V3-only route
- Response includes calldata ready for on-chain execution
The Lambda function emits logs to CloudWatch. Key metrics to monitor:
- Invocation count
- Error rate
- Duration (p50, p99)
- Throttles
"Cannot authenticate to @Labrys-Group packages"
- Run
task package:loginor manually configure npm authentication - Ensure your GitHub token has
read:packagesscope
"Chain ID not supported"
- This API only supports chain ID 13473 (Immutable zkEVM Testnet)
- Ensure both
tokenInChainIdandtokenOutChainIdare set to 13473
"Insufficient liquidity"
- Check that V3 pools exist for your token pair on Immutable zkEVM testnet
- Verify your V3 subgraph is properly indexed and accessible
"Subgraph query failed"
- Verify
V3_SUBGRAPH_URLis correct and accessible - Check subgraph sync status
This is a fork maintained by Labrys Group for Immutable zkEVM. For contributions:
- Create a feature branch
- Make your changes with tests
- Submit a pull request
See LICENSE file for details.
This document outlines all the changes required to add support for a new chain in the routing-api.
Note: The routing-api depends on
@uniswap/smart-order-router. You must first add chain support there and publish a new version before the routing-api will work. See the smart-order-router'sNEW_CHAIN_SUPPORT.mdfor those steps.
Create a new constants file for your chain:
// lib/constants/my-chain.ts
export const MY_CHAIN_ID = 12345import { MY_CHAIN_ID } from '../constants/my-chain'
// Add to SUPPORTED_CHAINS array
export const SUPPORTED_CHAINS: ChainId[] = [
// ... existing chains
MY_CHAIN_ID,
]This is the most critical file. Add your chain to ALL config maps:
import { MY_CHAIN_ID } from '../constants/my-chain'
// 1. RETRY_OPTIONS
export const RETRY_OPTIONS: { [chainId: number]: AsyncRetry.Options | undefined } = {
// ... existing entries
[MY_CHAIN_ID]: {
retries: 2,
minTimeout: 100,
maxTimeout: 1000,
},
}
// 2. OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS (for each protocol you support)
export const OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS = {
[Protocol.V3]: {
// ... existing entries
[MY_CHAIN_ID]: {
multicallChunk: 80,
gasLimitPerCall: 1_200_000,
quoteMinSuccessRate: 0.1,
},
},
// Add to V2, V4, MIXED if supported
}
// 3. NON_OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS (same structure)
export const NON_OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS = {
[Protocol.V3]: {
// ... existing entries
[MY_CHAIN_ID]: {
multicallChunk: 80,
gasLimitPerCall: 1_200_000,
quoteMinSuccessRate: 0.1,
},
},
}
// 4. GAS_ERROR_FAILURE_OVERRIDES
export const GAS_ERROR_FAILURE_OVERRIDES: { [chainId: number]: FailureOverrides } = {
// ... existing entries
[MY_CHAIN_ID]: {
gasLimitOverride: 3_000_000,
multicallChunk: 45,
},
}
// 5. SUCCESS_RATE_FAILURE_OVERRIDES
export const SUCCESS_RATE_FAILURE_OVERRIDES: { [chainId: number]: FailureOverrides } = {
// ... existing entries
[MY_CHAIN_ID]: {
gasLimitOverride: 3_000_000,
multicallChunk: 45,
},
}
// 6. BLOCK_NUMBER_CONFIGS
export const BLOCK_NUMBER_CONFIGS: { [chainId: number]: BlockNumberConfig } = {
// ... existing entries
[MY_CHAIN_ID]: {
baseBlockOffset: 0,
rollback: {
enabled: false,
},
},
}import { MY_CHAIN_ID } from '../constants/my-chain'
// Subgraph URL override (if using custom subgraph)
export const v3SubgraphUrlOverride = (chainId: number) => {
switch (chainId) {
case MY_CHAIN_ID:
return process.env.V3_SUBGRAPH_URL || ''
default:
return undefined
}
}
// Protocol configuration
export const chainProtocols: ChainProtocol[] = [
// ... existing entries
{
protocol: Protocol.V3,
chainId: MY_CHAIN_ID,
timeout: 90000,
provider: new V3SubgraphProvider(
MY_CHAIN_ID,
3, // retries
90000, // timeout
true, // useRawResults
v3TrackedEthThreshold,
v3UntrackedUsdThreshold,
v3SubgraphUrlOverride(MY_CHAIN_ID)
),
},
][
{
"chainId": 12345,
"useMultiProviderProb": 1,
"latencyEvaluationSampleProb": 0,
"healthCheckSampleProb": 0,
"providerInitialWeights": [1],
"providerUrls": ["MY_CHAIN_RPC"],
"providerNames": ["MY_CHAIN_RPC"]
}
]// In the switch statement for buildProviderUrl()
case 'MY_CHAIN_RPC': {
return value // Returns the env var value directly
}Set these in your deployment (AWS Lambda, etc.):
# RPC endpoint
WEB3_RPC_12345=https://rpc.mychain.com
# Or if using named provider in rpcProviderProdConfig.json
MY_CHAIN_RPC=https://rpc.mychain.com
# Subgraph URL (if applicable)
V3_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/...If your chain needs special handling (e.g., V3-only support), add custom logic:
// Custom quote provider for specific chain
if (chainId === (MY_CHAIN_ID as ChainId)) {
quoteProvider = new OnChainQuoteProvider(
chainId,
provider,
multicall2Provider,
RETRY_OPTIONS[chainId] || { retries: 2, minTimeout: 100, maxTimeout: 1000 },
(optimisticCachedRoutes, _protocol) => {
const protocol = Protocol.V3
return optimisticCachedRoutes
? OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS[protocol][chainId] || {
multicallChunk: 80,
gasLimitPerCall: 1_200_000,
quoteMinSuccessRate: 0.1,
}
: NON_OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS[protocol][chainId] || {
multicallChunk: 80,
gasLimitPerCall: 1_200_000,
quoteMinSuccessRate: 0.1,
}
},
(_protocol) => GAS_ERROR_FAILURE_OVERRIDES[chainId] || { gasLimitOverride: 3_000_000, multicallChunk: 45 },
(_protocol) => SUCCESS_RATE_FAILURE_OVERRIDES[chainId] || { gasLimitOverride: 3_000_000, multicallChunk: 45 },
(_protocol) => BLOCK_NUMBER_CONFIGS[chainId] || { baseBlockOffset: 0, rollback: { enabled: false } },
(_useMixedRouteQuoter, _mixedRouteContainsV4Pool, _protocol) => QUOTER_V2_ADDRESSES[chainId]
)
}For a new chain in the routing-api:
- Create chain constant in
lib/constants/ - Add to SUPPORTED_CHAINS in
injector-sor.ts - Configure quote provider params in
onChainQuoteProviderConfigs.ts:- RETRY_OPTIONS
- OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS
- NON_OPTIMISTIC_CACHED_ROUTES_BATCH_PARAMS
- GAS_ERROR_FAILURE_OVERRIDES
- SUCCESS_RATE_FAILURE_OVERRIDES
- BLOCK_NUMBER_CONFIGS
- Configure subgraph/protocol in
cache-config.ts - Add RPC config in
rpcProviderProdConfig.json - Add RPC URL resolver in
rpc/utils.ts - Set environment variables for RPC and subgraph URLs
Cause: Chain not in quote provider config maps.
Fix: Add chain to ALL maps in onChainQuoteProviderConfigs.ts.
Cause: SwapRouter02 address not configured in smart-order-router.
Fix: Add SWAP_ROUTER_02_ADDRESSES entry in smart-order-router's addresses.ts.
Correct properties:
multicallChunk(notbatchSize)gasLimitPerCallquoteMinSuccessRate(notdropUnexecutedFetches)
smart-order-router (npm package)
↓
routing-api (imports @uniswap/smart-order-router)
↓
AWS Lambda deployment
Always update and publish smart-order-router first, then update routing-api's package.json to use the new version.
The routing API uses a token list to provide token metadata (name, symbol, decimals, logo) for routing. The token list is stored in an S3 bucket and fetched by the Lambda function at runtime.
- Local file:
tokenlist.json(in repo root) - S3 bucket: Configured via
TOKEN_LIST_CACHE_BUCKETenvironment variable - S3 key: URL-encoded version of the token list URI (e.g.,
https%3A%2F%2Fgateway.ipfs.io%2Fipns%2Ftokens.uniswap.org)
The token list follows the Uniswap Token Lists standard:
{
"name": "IMX zkEVM Testnet Token List",
"timestamp": "2025-01-15T00:00:00.000Z",
"version": {
"major": 1,
"minor": 0,
"patch": 0
},
"tokens": [
{
"chainId": 13473,
"address": "0x60d7778daA2487B8bdD54A7B6Eabd1f3fb2Bc4Ca",
"name": "Test Stablecoin",
"symbol": "STBL",
"decimals": 18,
"logoURI": "https://example.com/token-icon.png"
}
]
}| Field | Type | Description |
|---|---|---|
chainId |
number | Chain ID (must be 13473 for IMX zkEVM Testnet) |
address |
string | Token contract address (checksummed) |
name |
string | Full token name |
symbol |
string | Token symbol (e.g., "USDC") |
decimals |
number | Number of decimals (usually 18 or 6) |
logoURI |
string | URL to token logo image (optional) |
-
Edit the token list file
-
Add the new token entry:
{ "chainId": 13473, "address": "0xYourTokenAddress", "name": "Your Token Name", "symbol": "TKN", "decimals": 18, "logoURI": "https://example.com/your-token-logo.png" } -
Update the version (bump patch/minor/major as appropriate):
"version": { "major": 1, "minor": 0, "patch": 1 }
-
Update the timestamp:
"timestamp": "2025-01-16T00:00:00.000Z"
After modifying the token list, you must upload it to S3 for the Lambda function to use the updated list.
# Set your bucket name
export TOKEN_LIST_BUCKET="your-token-list-bucket-name"
# The S3 key must match what the Lambda expects
# Default key is the URL-encoded version of the default token list URI
export TOKEN_LIST_KEY="https%3A%2F%2Fgateway.ipfs.io%2Fipns%2Ftokens.uniswap.org"
# Upload the token list
aws s3 cp tokenlist.json "s3://${TOKEN_LIST_BUCKET}/${TOKEN_LIST_KEY}"# S3 bucket for token list storage
TOKEN_LIST_CACHE_BUCKET=your-token-list-bucket-name- Verify the token is in the token list with the correct chain ID (13473)
- Check that the token address is checksummed correctly
- Ensure the token list has been uploaded to S3
- Check that you uploaded to the correct S3 bucket
- Verify the S3 key matches the expected format
- Wait up to 10 minutes for cache to expire, or redeploy Lambda
# List all objects in the token list bucket
aws s3 ls "s3://${TOKEN_LIST_CACHE_BUCKET}/"
# View a specific token list
aws s3 cp "s3://${TOKEN_LIST_CACHE_BUCKET}/https%3A%2F%2Fgateway.ipfs.io%2Fipns%2Ftokens.uniswap.org" - | jq .