Gateway Connector Architecture¶
Overview¶
Gateway connectors follow a standardized architecture that ensures consistency, maintainability, and compatibility with the Hummingbot client. This page describes the architectural patterns and design principles used in Gateway connectors.
Core Components¶
1. Connector Class¶
The main connector class serves as the entry point and coordinator for all DEX operations:
export class ConnectorBase {
protected chain: string;
protected network: string;
protected config: ConnectorConfig;
protected sdk: ProtocolSDK;
constructor(chain: string, network: string) {
this.chain = chain;
this.network = network;
this.config = this.loadConfig();
this.sdk = this.initializeSDK();
}
}
Responsibilities: - SDK initialization and management - Configuration loading - Method coordination - Error handling and recovery
2. Route Handlers¶
Route handlers translate HTTP requests into connector method calls:
interface RouteHandler {
validateRequest(req: Request): void;
processRequest(req: Request): Promise<any>;
formatResponse(data: any): Response;
handleError(error: Error): ErrorResponse;
}
Key Features: - Request validation against schemas - Async operation handling - Standardized response formatting - Comprehensive error handling
3. Trading Types¶
Each trading type defines specific interfaces and operations:
Router Interface¶
interface RouterConnector {
quote(params: QuoteParams): Promise<Quote>;
trade(params: TradeParams): Promise<Transaction>;
estimateGas(params: TradeParams): Promise<BigNumber>;
getRoute(params: RouteParams): Promise<Route[]>;
}
AMM Interface¶
interface AMMConnector {
poolInfo(pair: TradingPair): Promise<PoolInfo>;
poolPrice(pair: TradingPair): Promise<Price>;
addLiquidity(params: LiquidityParams): Promise<Transaction>;
removeLiquidity(params: RemoveLiquidityParams): Promise<Transaction>;
positionInfo(params: PositionParams): Promise<Position>;
}
CLMM Interface¶
interface CLMMConnector {
openPosition(params: OpenPositionParams): Promise<Position>;
closePosition(positionId: string): Promise<Transaction>;
addLiquidity(params: AddLiquidityParams): Promise<Transaction>;
removeLiquidity(params: RemoveLiquidityParams): Promise<Transaction>;
collectFees(positionId: string): Promise<Transaction>;
getPositions(owner: string): Promise<Position[]>;
}
Design Patterns¶
Singleton Pattern¶
Connectors use the singleton pattern to ensure efficient resource usage:
class MyDex {
private static instances: Map<string, MyDex> = new Map();
public static getInstance(chain: string, network: string): MyDex {
const key = `${chain}:${network}`;
if (!this.instances.has(key)) {
this.instances.set(key, new MyDex(chain, network));
}
return this.instances.get(key)!;
}
}
Benefits: - Reuses SDK connections - Maintains state consistency - Reduces memory footprint - Improves performance
Factory Pattern¶
Token and pool creation uses factory patterns:
class TokenFactory {
static createToken(
address: string,
chain: string,
network: string
): Token {
const chainInstance = ChainFactory.getChain(chain, network);
return new Token(address, chainInstance);
}
}
Strategy Pattern¶
Different swap strategies based on protocol type:
interface SwapStrategy {
execute(params: SwapParams): Promise<Transaction>;
}
class UniswapV2Strategy implements SwapStrategy {
async execute(params: SwapParams): Promise<Transaction> {
// V2 specific logic
}
}
class UniswapV3Strategy implements SwapStrategy {
async execute(params: SwapParams): Promise<Transaction> {
// V3 specific logic
}
}
Data Flow¶
Request Lifecycle¶
graph TD
A[HTTP Request] --> B[Route Handler]
B --> C[Schema Validation]
C --> D[Connector Method]
D --> E[SDK Call]
E --> F[Blockchain RPC]
F --> G[Response Processing]
G --> H[HTTP Response]
- Request Reception: Gateway receives HTTP request
- Routing: Request routed to appropriate handler
- Validation: Request validated against schema
- Processing: Connector method processes request
- Execution: SDK executes blockchain operations
- Response: Formatted response returned to client
State Management¶
Gateway connectors maintain minimal state:
class ConnectorState {
// Cached data with TTL
private poolCache: Map<string, CachedPool>;
private tokenCache: Map<string, CachedToken>;
// Active operations
private pendingTransactions: Map<string, Transaction>;
// Performance metrics
private metrics: PerformanceMetrics;
}
Cached Items: - Token metadata - Pool information - Gas estimates - Route calculations
TTL Strategy: - Token info: 1 hour - Pool data: 30 seconds - Gas prices: 10 seconds - Routes: Quote TTL
Error Handling¶
Error Hierarchy¶
class GatewayError extends Error {
code: number;
details: any;
}
class ValidationError extends GatewayError {
code = 400;
}
class InsufficientLiquidityError extends GatewayError {
code = 422;
}
class BlockchainError extends GatewayError {
code = 503;
}
Error Recovery¶
Connectors implement retry logic for transient failures:
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (!isRetryable(error)) throw error;
await sleep(exponentialBackoff(i));
}
}
throw lastError;
}
Performance Optimization¶
Batching¶
Batch multiple operations when possible:
class BatchProcessor {
private queue: Operation[] = [];
private timer: NodeJS.Timeout;
add(operation: Operation): void {
this.queue.push(operation);
this.scheduleFlush();
}
private async flush(): Promise<void> {
const batch = this.queue.splice(0);
await this.processBatch(batch);
}
}
Caching¶
Implement intelligent caching strategies:
class Cache<T> {
private data: Map<string, CacheEntry<T>> = new Map();
get(key: string): T | undefined {
const entry = this.data.get(key);
if (!entry || this.isExpired(entry)) {
return undefined;
}
return entry.value;
}
set(key: string, value: T, ttl: number): void {
this.data.set(key, {
value,
expiry: Date.now() + ttl
});
}
}
Connection Pooling¶
Maintain connection pools for blockchain RPCs:
class RPCPool {
private connections: RPCConnection[] = [];
private currentIndex = 0;
getConnection(): RPCConnection {
const connection = this.connections[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.connections.length;
return connection;
}
}
Security Considerations¶
Input Validation¶
All inputs must be validated:
function validateAddress(address: string, chain: string): void {
if (!isValidAddress(address, chain)) {
throw new ValidationError('Invalid address format');
}
}
function validateAmount(amount: string): BigNumber {
const bn = BigNumber.from(amount);
if (bn.lte(0)) {
throw new ValidationError('Amount must be positive');
}
return bn;
}
Private Key Handling¶
Never log or expose private keys:
class WalletManager {
private wallets: Map<string, Wallet> = new Map();
addWallet(address: string, encryptedKey: string): void {
const wallet = this.decryptWallet(encryptedKey);
this.wallets.set(address, wallet);
// Never log wallet or private key
}
}
Rate Limiting¶
Implement rate limiting for external calls:
class RateLimiter {
private calls: number[] = [];
private limit: number;
private window: number;
async throttle(): Promise<void> {
const now = Date.now();
this.calls = this.calls.filter(t => t > now - this.window);
if (this.calls.length >= this.limit) {
const waitTime = this.calls[0] + this.window - now;
await sleep(waitTime);
}
this.calls.push(now);
}
}
Testing Architecture¶
Mock Framework¶
Create comprehensive mocks for testing:
class MockSDK {
async swap(params: SwapParams): Promise<MockTransaction> {
return {
hash: '0xmock...',
gasUsed: BigNumber.from('100000'),
status: 'success'
};
}
}
Test Fixtures¶
Maintain test fixtures for common scenarios:
export const fixtures = {
tokens: {
USDC: { address: '0x...', decimals: 6, symbol: 'USDC' },
WETH: { address: '0x...', decimals: 18, symbol: 'WETH' }
},
pools: {
USDC_WETH: { address: '0x...', fee: 3000 }
}
};
Best Practices¶
1. Separation of Concerns¶
- Keep business logic in connector class
- Route handlers only handle HTTP concerns
- Utils for pure functions
2. Dependency Injection¶
3. Immutability¶
4. Async/Await Consistency¶
// Good
async function process(): Promise<Result> {
const data = await fetchData();
return transform(data);
}
// Avoid mixing patterns
function process(): Promise<Result> {
return fetchData().then(transform);
}
5. Error Context¶
try {
await operation();
} catch (error) {
throw new ConnectorError(
'Operation failed',
{
originalError: error,
context: { operation: 'swap', params }
}
);
}