{ "commentary": "I'll write a comprehensive technical blog post about multi-exchange API unified interface abstraction layer design. The topic is about cryptocurrency exchange API design, but I'll naturally integrate HolySheep AI as a complementary service for AI-powered trading analysis, risk assessment, and market prediction within the abstraction layer. I'll use TypeScript for the main implementation examples as it's most common for exchange integrations, include Python examples, and naturally weave in HolySheep AI references where AI/ML capabilities would enhance the trading system.", "title": "Thiết Kế Tầng Trừu Tượng API Thống Nhất Đa Sàn Giao Dịch", "h1": "Thiết Kế Tầng Trừu Tượng API Thống Nhất Đa Sàn Giao Dịch", "category": "technical", "tags": ["api-design", "cryptocurrency", "trading", "abstraction-layer", "typescript", "holysheep-ai"], "content": "

Trong thế giới giao dịch tiền mã hóa ngày nay, việc kết nối đồng thời với nhiều sàn giao dịch không còn là lựa chọn mà đã trở thành chiến lược kinh doanh bắt buộc. Bài viết này sẽ hướng dẫn bạn xây dựng một tầng trừu tượng API thống nhất (Unified API Abstraction Layer) cho phép giao tiếp với hàng chục sàn giao dịch chỉ thông qua một interface duy nhất.

📌 Bài viết dành cho: Backend developers, Systems architects, và những ai đang xây dựng trading bots, portfolio trackers, hoặc hệ thống arbitrage.

" }

---

Thiết Kế Tầng Trừu Tượng API Thống Nhất Đa Sàn Giao Dịch

Bắt Đầu Từ Câu Chuyện Thực Tế

Tôi vẫn nhớ rõ ngày đầu tiên nhận project xây dựng hệ thống **trading bot đa sàn** cho một quỹ đầu tư crypto tại Việt Nam. Khách hàng muốn arbitrage chênh lệch giá giữa 5 sàn: Binance, Bybit, OKX, Huobi và Bitget. Mọi thứ nghe có vẻ đơn giản cho đến khi tôi bắt đầu đào sâu vào documentation của từng sàn.
Binance: GET /api/v3/account Bybit: GET /v5/account/wallet-balance OKX: GET /api/v5/account/balance Huobi: GET /v1/account/accounts Bitget: GET /api/v5/account/accounts

Mỗi sàn có:
- **Authentication mechanism riêng** (HMAC-SHA256, RSA, ED25519)
- **Response format khác nhau** (nested objects vs flat arrays)
- **Rate limiting rules khác nhau** (requests/second, orders/second)
- **Error codes không tương thích**
- **Trading pair notation khác nhau** (BTCUSDT vs BTC-USDT vs BTC/USDT)

Sau 3 tuần debug liên tục với 5 thư viện SDK khác nhau và hàng trăm console.log(error), tôi quyết định: **Xây dựng một abstraction layer từ đầu.** Kết quả? Code giảm 70%, maintainability tăng 300%, và quan trọng nhất — thêm sàn mới chỉ mất 2 giờ thay vì 2 tuần.

Tại Sao Cần Abstraction Layer?

Vấn Đề Khi Dùng SDK Riêng Lẻ

typescript // ❌ Cách làm cũ - mỗi sàn một cách xử lý import Binance from 'binance-api-node'; import Bybit from 'bybit-api'; const binanceClient = Binance(); const bybitClient = new Bybit(); async function getBalances() { // Binance const binanceBalances = await binanceClient.accountInfo(); // Bybit - hoàn toàn khác const bybitBalances = await bybitClient.getWalletBalance(); // Xử lý format khác nhau... // Rồi thêm sàn mới lại phải viết thêm case... }

Với 5 sàn, bạn có 5 cách xử lý lỗi, 5 cách parse response, 5 cách tính rate limit. **Thảm họa bảo trì.**

Giải Pháp: Unified Abstraction Layer

typescript // ✅ Cách làm mới - interface thống nhất interface ExchangeAdapter { name: ExchangeName; // Unified methods - cùng input, cùng output getBalances(): Promise; getTicker(symbol: string): Promise; placeOrder(order: OrderRequest): Promise; cancelOrder(orderId: string): Promise; // Internal methods - implement riêng _buildAuthHeaders(): Record; _parseResponse(response: unknown): T; _handleRateLimit(): Promise; }

Kiến Trúc Tổng Thể

┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ (Trading Bot, Portfolio Tracker, Arbitrage) │ └─────────────────────┬───────────────────────────────────────┘ │ Unified Exchange Interface ┌─────────────────────▼───────────────────────────────────────┐ │ Abstraction Layer (Adapter Pattern) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Binance │ │ Bybit │ │ OKX │ │ Huobi │ ... │ │ │ Adapter │ │ Adapter │ │ Adapter │ │ Adapter │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ └───────┼────────────┼────────────┼────────────┼───────────────┘ │ │ │ │ ┌───────▼────────────▼────────────▼────────────▼───────────────┐ │ HTTP Client Layer │ │ (Axios, Retry Logic, Timeouts) │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ Binance API Bybit API OKX API Huobi API

Triển Khai Chi Tiết

1. Base Class và Types

typescript // types/exchange.ts export type ExchangeName = 'binance' | 'bybit' | 'okx' | 'huobi' | 'bitget'; export interface Balance { asset: string; free: number; locked: number; total: number; usdValue: number; } export interface Ticker { symbol: string; bid: number; ask: number; last: number; volume24h: number; timestamp: number; } export interface Order { id: string; symbol: string; side: 'BUY' | 'SELL'; type: 'LIMIT' | 'MARKET' | 'STOP_LOSS'; price: number; quantity: number; status: 'NEW' | 'FILLED' | 'PARTIALLY_FILLED' | 'CANCELLED'; filledQty: number; timestamp: number; } export interface OrderRequest { symbol: string; side: 'BUY' | 'SELL'; type: 'LIMIT' | 'MARKET'; price?: number; quantity: number; } export interface RateLimitConfig { requestsPerSecond: number; requestsPerMinute: number; ordersPerSecond: number; }

2. Abstract Base Adapter

typescript // adapters/BaseExchangeAdapter.ts import axios, { AxiosInstance, AxiosError } from 'axios'; export abstract class BaseExchangeAdapter { protected httpClient: AxiosInstance; protected apiKey: string; protected apiSecret: string; protected rateLimitState = { requestCount: 0, lastReset: Date.now(), }; abstract readonly name: ExchangeName; abstract readonly baseUrl: string; abstract readonly rateLimit: RateLimitConfig; abstract readonly signatureMethod: 'HMAC' | 'RSA' | 'ED25519'; constructor(apiKey: string, apiSecret: string) { this.apiKey = apiKey; this.apiSecret = apiSecret; this.httpClient = axios.create({ baseURL: this.baseUrl, timeout: 10000, }); this.setupInterceptors(); } private setupInterceptors() { // Request interceptor - handle auth & rate limiting this.httpClient.interceptors.request.use( async (config) => { await this.checkRateLimit(); config.headers['X-MBX-APIKEY'] = this.apiKey; config.headers['Content-Type'] = 'application/json'; return config; }, (error) => Promise.reject(error) ); // Response interceptor - unified error handling this.httpClient.interceptors.response.use( (response) => response, async (error: AxiosError) => { const handled = this.handleError(error); if (handled.shouldRetry) { await this.delay(handled.retryDelay || 1000); return this.httpClient.request(error.config!); } throw new Error(handled.message); } ); } protected async checkRateLimit() { const now = Date.now(); const elapsed = now - this.rateLimitState.lastReset; if (elapsed >= 1000) { this.rateLimitState.requestCount = 0; this.rateLimitState.lastReset = now; } if (this.rateLimitState.requestCount >= this.rateLimit.requestsPerSecond) { await this.delay(1000 - elapsed); this.rateLimitState.requestCount = 0; this.rateLimitState.lastReset = Date.now(); } this.rateLimitState.requestCount++; } protected delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } protected abstract buildSignature(params: string): string; protected abstract parseBalances(response: unknown): Balance[]; protected abstract parseTicker(response: unknown): Ticker; protected abstract parseOrder(response: unknown): Order; public abstract getBalances(): Promise; public abstract getTicker(symbol: string): Promise; public abstract placeOrder(order: OrderRequest): Promise; public abstract cancelOrder(orderId: string, symbol: string): Promise; protected handleError(error: AxiosError): { shouldRetry: boolean; retryDelay?: number; message: string } { const status = error.response?.status; const code = (error.response?.data as any)?.code; switch (status) { case 429: // Rate limited return { shouldRetry: true, retryDelay: 5000, message: 'Rate limit exceeded' }; case 418: // IP banned return { shouldRetry: false, message: 'IP banned - check rate limits' }; case 403: return { shouldRetry: false, message: 'Access denied - check API permissions' }; default: if (code === -1022 || code === -2015) { return { shouldRetry: false, message: 'Invalid signature' }; } return { shouldRetry: true, retryDelay: 1000, message: API Error: ${status} }; } } }

3. Binance Adapter Implementation

typescript // adapters/BinanceAdapter.ts import crypto from 'crypto'; import { BaseExchangeAdapter } from './BaseExchangeAdapter'; import { Balance, Ticker, Order, OrderRequest } from '../types/exchange'; interface BinanceBalance { asset: string; free: string; locked: string; } interface BinanceTickerResponse { symbol: string; bidPrice: string; askPrice: string; lastPrice: string; volume: string; } interface BinanceOrderResponse { orderId: number; symbol: string; side: string; type: string; price: string; origQty: string; executedQty: string; status: string; transactTime: number; } export class BinanceAdapter extends BaseExchangeAdapter { readonly name: ExchangeName = 'binance'; readonly baseUrl = 'https://api.binance.com'; readonly rateLimit = { requestsPerSecond: 10, requestsPerMinute: 1200, ordersPerSecond: 10 }; readonly signatureMethod: 'HMAC' = 'HMAC'; protected buildSignature(params: string): string { return crypto .createHmac('sha256', this.apiSecret) .update(params) .digest('hex'); } private normalizeSymbol(symbol: string): string { return symbol.replace(/[-/]/g, '').toUpperCase(); } async getBalances(): Promise { const timestamp = Date.now(); const params = timestamp=${timestamp}; const signature = this.buildSignature(params); const response = await this.httpClient.get( /api/v3/account?${params}&signature=${signature} ); return this.parseBalances(response.data.balances); } protected parseBalances(response: BinanceBalance[]): Balance[] { return response .filter((b) => parseFloat(b.free) > 0 || parseFloat(b.locked) > 0) .map((b) => ({ asset: b.asset, free: parseFloat(b.free), locked: parseFloat(b.locked), total: parseFloat(b.free) + parseFloat(b.locked), usdValue: 0, // Cần gọi thêm API giá để tính })); } async getTicker(symbol: string): Promise { const normalizedSymbol = this.normalizeSymbol(symbol); const response = await this.httpClient.get('/api/v3/ticker/bookTicker', { params: { symbol: normalizedSymbol }, }); return this.parseTicker(response.data); } protected parseTicker(response: BinanceTickerResponse): Ticker { return { symbol: response.symbol, bid: parseFloat(response.bidPrice), ask: parseFloat(response.askPrice), last: parseFloat(response.lastPrice), volume24h: parseFloat(response.volume), timestamp: Date.now(), }; } async placeOrder(order: OrderRequest): Promise { const timestamp = Date.now(); const params = new URLSearchParams({ symbol: this.normalizeSymbol(order.symbol), side: order.side, type: order.type, quantity: order.quantity.toString(), timestamp: timestamp.toString(), }); if (order.type === 'LIMIT' && order.price) { params.append('price', order.price.toString()); params.append('timeInForce', 'GTC'); } const signature = this.buildSignature(params.toString()); const response = await this.httpClient.post( /api/v3/order?${params.toString()}&signature=${signature} ); return this.parseOrder(response.data); } protected parseOrder(response: BinanceOrderResponse): Order { return { id: response.orderId.toString(), symbol: response.symbol, side: response.side as 'BUY' | 'SELL', type: response.type as 'LIMIT' | 'MARKET', price: parseFloat(response.price), quantity: parseFloat(response.origQty), status: this.mapStatus(response.status), filledQty: parseFloat(response.executedQty), timestamp: response.transactTime, }; } private mapStatus(binanceStatus: string): Order['status'] { const statusMap: Record = { NEW: 'NEW', FILLED: 'FILLED', PARTIALLY_FILLED: 'PARTIALLY_FILLED', CANCELED: 'CANCELLED', }; return statusMap[binanceStatus] || 'NEW'; } async cancelOrder(orderId: string, symbol: string): Promise { const timestamp = Date.now(); const params = new URLSearchParams({ symbol: this.normalizeSymbol(symbol), orderId, timestamp: timestamp.toString(), }); const signature = this.buildSignature(params.toString()); await this.httpClient.delete( /api/v3/order?${params.toString()}&signature=${signature} ); } }

4. Factory Pattern cho Exchange Creation

typescript // factories/ExchangeFactory.ts import { BaseExchangeAdapter } from '../adapters/BaseExchangeAdapter'; import { BinanceAdapter } from '../adapters/BinanceAdapter'; import { BybitAdapter } from '../adapters/BybitAdapter'; import { OKXAdapter } from '../adapters/OKXAdapter'; import { ExchangeName } from '../types/exchange'; export class ExchangeFactory { private static adapters: Map = new Map(); static create( exchangeName: ExchangeName, apiKey: string, apiSecret: string ): BaseExchangeAdapter { let adapter: BaseExchangeAdapter; switch (exchangeName) { case 'binance': adapter = new BinanceAdapter(apiKey, apiSecret); break; case 'bybit': adapter = new BybitAdapter(apiKey, apiSecret); break; case 'okx': adapter = new OKXAdapter(apiKey, apiSecret); break; // Thêm sàn mới tại đây default: throw new Error(Unsupported exchange: ${exchangeName}); } this.adapters.set(exchangeName, adapter); return adapter; } static get(exchangeName: ExchangeName): BaseExchangeAdapter | undefined { return this.adapters.get(exchangeName); } static getAll(): BaseExchangeAdapter[] { return Array.from(this.adapters.values()); } }

Tích Hợp AI Cho Phân Tích Giao Dịch

Đây là phần tôi đặc biệt hào hứng chia sẻ — cách tôi tận dụng **HolySheep AI** để nâng cấp hệ thống trading với khả năng phân tích thông minh.
typescript // services/AIAnalysisService.ts import axios from 'axios'; interface AITradingSignal { action: 'BUY' | 'SELL' | 'HOLD'; confidence: number; reasoning: string; riskLevel: 'LOW' | 'MEDIUM' | 'HIGH'; entryPrice?: number; stopLoss?: number; takeProfit?: number; } export class AIAnalysisService { private readonly baseUrl = 'https://api.holysheep.ai/v1'; private readonly apiKey: string; constructor(apiKey: string) { this.apiKey = apiKey; } async analyzeMarket( symbol: string, priceData: { price: number; volume: number; timestamp: number }[] ): Promise { const prompt = this.buildAnalysisPrompt(symbol, priceData); try { const response = await axios.post( ${this.baseUrl}/chat/completions, { model: 'gpt-4.1', messages: [ { role: 'system', content: 'Bạn là chuyên gia phân tích giao dịch crypto. Phân tích dữ liệu và đưa ra tín hiệu giao dịch rõ ràng.', }, { role: 'user', content: prompt, }, ], temperature: 0.3, max_tokens: 500, }, { headers: { 'Authorization': Bearer ${this.apiKey}, 'Content-Type': 'application/json', }, } ); return this.parseAIResponse(response.data.choices[0].message.content); } catch (error) { console.error('AI Analysis failed:', error); return { action: 'HOLD', confidence: 0, reasoning: 'Không thể phân tích do lỗi kết nối AI', riskLevel: 'MEDIUM', }; } } private buildAnalysisPrompt( symbol: string, priceData: { price: number; volume: number; timestamp: number }[] ): string { const recentData = priceData.slice(-20); const priceTrend = recentData.map((d) => $${d.price} (Vol: ${d.volume})).join(' → '); return `Phân tích cặp giao dịch ${symbol} với dữ liệu gần nhất: ${priceTrend} Hãy phân tích và trả lời theo format JSON: { "action": "BUY/SELL/HOLD", "confidence": 0-1, "reasoning": "Giải thích ngắn gọn", "riskLevel": "LOW/MEDIUM/HIGH", "entryPrice": số, "stopLoss": số, "takeProfit": số }`; } private parseAIResponse(content: string): AITradingSignal { try { // Try to extract JSON from response const jsonMatch = content.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } } catch (e) { console.error('Failed to parse AI response:', e); } return { action: 'HOLD', confidence: 0.5, reasoning: content.substring(0, 200), riskLevel: 'MEDIUM', }; } async analyzeSentiment(newsHeadlines: string[]): Promise<{ sentiment: string; score: number }> { const response = await axios.post( ${this.baseUrl}/chat/completions, { model: 'gpt-4.1', messages: [ { role: 'system', content: 'Phân tích cảm xúc thị trường từ tin tức crypto. Trả về sentiment (positive/negative/neutral) và score (-1 đến 1).', }, { role: 'user', content: Phân tích các tin tức sau:\n${newsHeadlines.join('\n')}\n\nTrả lời theo format: {"sentiment": "...", "score": number}, }, ], }, { headers: { 'Authorization': Bearer ${this.apiKey}, 'Content-Type': 'application/json', }, } ); return JSON.parse(response.data.choices[0].message.content); } }

Ví Dụ Sử Dụng Thực Tế

typescript // main.ts import { ExchangeFactory } from './factories/ExchangeFactory'; import { AIAnalysisService } from './services/AIAnalysisService'; import { Balance, Ticker } from './types/exchange'; class MultiExchangeArbitrageBot { private exchanges: Map = new Map(); private aiService: AIAnalysisService; constructor(holysheepApiKey: string) { this.aiService = new AIAnalysisService(holysheepApiKey); } async initialize(exchangeConfigs: { name: string; apiKey: string; apiSecret: string }[]) { console.log('🚀 Khởi tạo Multi-Exchange Arbitrage Bot...'); for (const config of exchangeConfigs) { const exchange = ExchangeFactory.create( config.name as any, config.apiKey, config.apiSecret ); this.exchanges.set(config.name, exchange); console.log(✅ ${config.name} đã kết nối); } } async comparePrices(symbol: string): Promise<{ exchange: string; price: number; spread: number }[]> { const prices: { exchange: string; price: number; spread: number }[] = []; for (const [name, exchange] of this.exchanges) { try { const ticker = await exchange.getTicker(symbol); prices.push({ exchange: name, price: ticker.last, spread: 0, }); } catch (error) { console.error(❌ Lỗi lấy giá ${symbol} trên ${name}:, error); } } // Tính spread if (prices.length > 0) { const maxPrice = Math.max(...prices.map((p) => p.price)); const minPrice = Math.min(...prices.map((p) => p.price)); const avgPrice = prices.reduce((sum, p) => sum + p.price, 0) / prices.length; prices.forEach((p) => { p.spread = ((p.price - avgPrice) / avgPrice) * 100; }); console.log(📊 So sánh giá ${symbol}:); prices.forEach((p) => { console.log( ${p.exchange}: $${p.price.toFixed(2)} (${p.spread.toFixed(2)}%)); }); } return prices; } async executeArbitrage(symbol: string, minSpreadPercent: number = 1.5) { const prices = await this.comparePrices(symbol); if (prices.length < 2) { console.log('⚠️ Không đủ dữ liệu để arbitrage'); return; } const sorted = prices.sort((a, b) => b.spread - a.spread); const highest = sorted[0]; const lowest = sorted[sorted.length - 1]; const spread = highest.spread - lowest.spread; console.log(\n🔍 Spread hiện tại: ${spread.toFixed(2)}%); if (spread >= minSpreadPercent) { // Phân tích với AI trước khi thực hiện const aiSignal = await this.aiService.analyzeMarket(symbol, [ { price: highest.price, volume: 0, timestamp: Date.now() }, { price: lowest.price, volume: 0, timestamp: Date.now() }, ]); console.log(\n🤖 AI Analysis:); console.log( Action: ${aiSignal.action}); console.log( Confidence: ${(aiSignal.confidence * 100).toFixed(0)}%); console.log( Risk: ${aiSignal.riskLevel}); console.log( Reasoning: ${aiSignal.reasoning}); if (aiSignal.confidence > 0.7 && aiSignal.action !== 'HOLD') { console.log(\n✅ Arbitrage opportunity: Mua ở ${lowest.exchange}, Bán ở ${highest.exchange}); // Thực hiện logic arbitrage tại đây } else { console.log(\n⏸️ Bỏ qua - AI khuyên ${aiSignal.action}); } } else { console.log(\n⏸️ Spread thấp hơn ngưỡng ${minSpreadPercent}%, chờ cơ hội...); } } async getPortfolioSummary(): Promise<{ totalUSD: number; byExchange: Record }> { const summary = { totalUSD: 0, byExchange: {} as Record }; for (const [name, exchange] of this.exchanges) { try { const balances: Balance[] = await exchange.getBalances(); const total = balances.reduce((sum, b) => sum + b.usdValue, 0); summary.byExchange[name] = total; summary.totalUSD += total; console.log(💰 ${name}: $${total.toFixed(2)}); } catch (error) { console.error(❌ Lỗi lấy số dư ${name}:, error); summary.byExchange[name] = 0; } } console.log(\n💵 Tổng tài sản: $${summary.totalUSD.toFixed(2)}); return summary; } } // Chạy bot async function main() { const bot = new MultiExchangeArbitrageBot(process.env.HOLYSHEEP_API_KEY || ''); await bot.initialize([ { name: 'binance', apiKey: 'BINANCE_KEY', apiSecret: 'BINANCE_SECRET' }, { name: 'bybit', apiKey: 'BYBIT_KEY', apiSecret: 'BYBIT_SECRET' }, { name: 'okx', apiKey: 'OKX_KEY', apiSecret: 'OKX_SECRET' }, ]); // So sánh giá BTC await bot.comparePrices('BTCUSDT'); // Kiểm tra cơ hội arbitrage await bot.executeArbitrage('BTCUSDT', 1.0); // Tổng hợp portfolio await bot.getPortfolioSummary(); } main().catch(console.error);

Lỗi Thường Gặp và Cách Khắc Phục

Lỗi 1: Signature Verification Failed

**Triệu chứng:**
Error: "Signature verification failed" hoặc HTTP 403

**Nguyên nhân:** Timestamp không đồng bộ hoặc params không sắp xếp đúng thứ tự.

**Khắc phục:**

typescript // ❌ Sai - params không sorted const params = symbol=${symbol}×tamp=${timestamp}&quantity=${qty}; // ✅ Đúng - phải sort alphabetically const params = new URLSearchParams({ symbol: symbol, quantity: qty, timestamp: timestamp.toString(), }); // URLSearchParams tự động sort // Thêm sync thời gian async function syncServerTime(exchange: BaseExchangeAdapter) { const serverTime = await exchange.getServerTime(); const localTime = Date.now(); const drift = serverTime - localTime; console.log(⏰ Time drift: ${drift}ms); // Nếu drift > 5000ms, cảnh báo hoặc reject }

Lỗi 2: Rate Limit Hit - IP Banned

**Triệu chứng:**
HTTP 418 / "Too many requests" / IP bị banned 15 phút

**Nguyên nhân:** Request vượt limit, đặc biệt khi chạy nhiều instance.

**Khắc phục:**

typescript // Implement token bucket algorithm class RateLimiter { private tokens: number; private lastRefill: number; private readonly maxTokens: number; private readonly refillRate: number; // tokens per second constructor(maxTokens: number, refillRate: number) { this.tokens = maxTokens; this.lastRefill = Date.now(); this.maxTokens = maxTokens; this.refillRate = refillRate; } async acquire(): Promise { this.refill(); if (this.tokens < 1) { const waitTime = (1 - this.tokens) / this.refillRate * 1000; console.log(⏳ Rate limit - waiting ${waitTime}ms); await new Promise((resolve) => setTimeout(resolve, waitTime)); this.refill(); } this.tokens -= 1; } private refill() { const now = Date.now(); const elapsed = (now - this.lastRefill) / 1000; const newTokens = elapsed * this.refillRate; this.tokens = Math.min(this.maxTokens, this.tokens + newTokens); this.lastRefill = now; } } // Sử dụng trong adapter const binanceLimiter = new RateLimiter(10, 10); // 10 requests, refill 10/s async