Securityadvancedarticle25 min read

DeFi API Documentation: Complete Reference for Developers | VCC

Building a DeFi application requires reliable access to blockchain data. Whether you're querying token balances, executing swaps, or monitoring lending positions, the API layer sits between your code and the blockchain. This reference covers the major DeFi API providers, authentication patterns, rat

Vietcardano teamTeam

Đội ngũ biên tập VietCardanoCommunity — các chuyên gia blockchain và Cardano, cung cấp nội dung giáo dục và nghiên cứu chuyên sâu cho cộng đồng.

Published September 14, 2026

Building a DeFi application requires reliable access to blockchain data. Whether you're querying token balances, executing swaps, or monitoring lending positions, the API layer sits between your code and the blockchain. This reference covers the major DeFi API providers, authentication patterns, rate limiting strategies, and integration best practices.

Understanding the DeFi API Landscape

DeFi APIs fall into three categories, each serving different needs:

RPC Providers (Alchemy, Infura, QuickNode) offer direct blockchain access via JSON-RPC. They're the foundation — every other API type ultimately depends on RPC nodes. These providers run validated, archival nodes across multiple chains so you don't have to.

Enhanced APIs (Alchemy Enhanced APIs, Moralis, Covalent) abstract common patterns: token balances, NFT metadata, transaction history. Instead of writing eth_call for every ERC-20 balance, you call one endpoint.

Indexing & Query APIs (The Graph, Goldsky, SubQuery) let you define custom GraphQL schemas for complex queries. They're essential for analytics dashboards and historical analysis where raw RPC would be too slow.

Diagram - DeFi API Architecture Overview

Diagram - DeFi API Architecture Overview

Major Provider Comparison

ProviderTypeChainsFree TierWebSocketBest For
AlchemyRPC + Enhanced15+330 CUPS✓Ethereum/L2 depth, real-time
InfuraRPC + Enhanced15+100K req/day✓Enterprise reliability, IPFS
QuickNodeRPC + Streams100+10M req/mo✓Multi-chain breadth, low latency
The GraphIndexing40+Free (public)✗Complex historical queries
CovalentEnhanced REST100+1M req/mo✗Unified multi-chain data
MoralisEnhanced + Streams30+25K req/mo✓Real-time DeFi, Web3 auth
Comparison Table - Major DeFi API Providers Comparison

Choosing Your Provider Stack

Most production applications use multiple providers for different purposes:

  • Primary RPC: Alchemy or Infura for Ethereum mainnet/L2s (best archive support)
  • Multi-chain fallback: QuickNode for chains your primary doesn't support well
  • Analytics/history: The Graph for complex queries, Covalent for simple multi-chain fetches
  • Real-time events: Moralis Streams or Alchemy Notify for webhook-based eventing

This redundancy protects against provider outages and rate limit exhaustion.

Authentication Patterns

DeFi APIs use three primary authentication methods. Understanding each helps you implement secure, maintainable integrations.

API Key Authentication (Universal)

The simplest and most common pattern. Include the key in a header:

`javascript // Alchemy, Infura, QuickNode, Covalent, Moralis const headers = { 'Authorization': Bearer ${API_KEY}, 'Content-Type': 'application/json' };

// Or as query parameter (less secure, avoid in production) // https://eth-mainnet.alchemyapi.io/v2/${API_KEY} `

Security best practices:

  • Never commit API keys to version control
  • Use environment variables with rotation schedules
  • Restrict keys by domain/IP where providers allow
  • Monitor usage dashboards for anomalies

JWT Authentication (Infura)

Infura supports JWT tokens for enhanced security:

`javascript // 1. Generate JWT with project ID and secret const jwt = generateJWT({ projectId: INFURA_PROJECT_ID, projectSecret: INFURA_PROJECT_SECRET, expiresIn: '1h' });

// 2. Use in Authorization header const headers = { 'Authorization': Bearer ${jwt}, 'Content-Type': 'application/json' }; `

JWTs expire, limiting exposure if leaked. Implement automatic refresh before expiry.

Request Signing (QuickNode HMAC)

QuickNode offers HMAC-SHA256 signing for requests:

`javascript const crypto = require('crypto');

function signRequest(method, path, body, timestamp, secret) { const payload = ${timestamp}${method}${path}${body || ''}; return crypto.createHmac('sha256', secret).update(payload).digest('hex'); }

const timestamp = Date.now().toString(); const signature = signRequest('POST', '/', JSON.stringify(rpcRequest), timestamp, QUICKNODE_SECRET);

const headers = { 'Content-Type': 'application/json', 'X-QN-Signature': signature, 'X-QN-Timestamp': timestamp }; `

This proves request integrity without sending secrets over the wire.

Sequence Diagram - API Authentication Flow

Sequence Diagram - API Authentication Flow

Rate Limiting Strategies

Rate limits are the primary constraint in DeFi development. Each provider models limits differently.

Understanding Limit Models

Compute Units (Alchemy): Each RPC method has a CU cost. eth_blockNumber = 10 CUs, eth_call = 26 CUs + execution cost. Free tier: 330 CUPS (Compute Units Per Second).

`javascript // Estimate CU cost before sending const CU_COSTS = { 'eth_blockNumber': 10, 'eth_getBalance': 19, 'eth_call': 26, // + execution gas / 100000 'eth_getLogs': 75, // + topics/data size };

function estimateCU(method, params = []) { return CU_COSTS[method] || 100; // Default conservative estimate } `

Request Count (Infura, QuickNode, Covalent, Moralis): Simple request counting. Easier to predict but doesn't reflect actual node load.

`javascript // Track your own rate limit state class RateLimiter { constructor(limitPerWindow, windowMs) { this.limit = limitPerWindow; this.window = windowMs; this.requests = []; }

async acquire() { const now = Date.now(); this.requests = this.requests.filter(t => now - t < this.window);

if (this.requests.length >= this.limit) { const oldest = this.requests[0]; const waitMs = this.window - (now - oldest); await new Promise(r => setTimeout(r, waitMs)); return this.acquire(); }

this.requests.push(now); } }

// Usage: 100 requests per second const limiter = new RateLimiter(100, 1000); await limiter.acquire(); await fetch(rpcUrl, { method: 'POST', body: JSON.stringify(payload) }); `

Pagination Patterns

Large result sets require pagination. Two dominant patterns exist:

Cursor-based (Recommended): Opaque token pointing to next page. Stable under concurrent writes.

`javascript // Alchemy, Moralis, The Graph async function fetchAllPages(initialParams) { const allResults = []; let cursor = null;

do { const params = { ...initialParams, pageKey: cursor }; const response = await apiCall(params); allResults.push(...response.data); cursor = response.pageKey; // null when done } while (cursor);

return allResults; } `

Offset-based: Simple page + limit or offset + limit. Can skip/duplicate items under concurrent writes.

`javascript // Covalent, some REST APIs async function fetchAllPages(offset = 0, limit = 100) { const allResults = [];

while (true) { const response = await apiCall({ offset, limit }); if (response.data.length === 0) break; allResults.push(...response.data); offset += limit; }

return allResults; } `

Flowchart - Request/Response Lifecycle with Rate Limiting

Flowchart - Request/Response Lifecycle with Rate Limiting

Concept Diagram - Rate Limiting & Pagination Patterns

Concept Diagram - Rate Limiting & Pagination Patterns

Error Handling & Retry Logic

DeFi APIs return standard HTTP codes plus provider-specific error formats.

Common Error Codes

HTTP CodeMeaningAction
400Bad request (invalid params)Fix request, don't retry
401Invalid/missing authCheck credentials, refresh JWT
403Forbidden (domain/IP restriction)Check provider dashboard settings
429Rate limitedBack off, respect Retry-After header
500Provider internal errorRetry with exponential backoff
502/503/504Gateway/upstream unavailableRetry, consider fallback provider

Provider-Specific Error Formats

`javascript // Alchemy error response { "jsonrpc": "2.0", "id": 1, "error": { "code": -32005, "message": "request rate limit exceeded", "data": { "retryAfter": 1000 } } }

// Infura error response { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Daily request limit exceeded", "retryAfter": 86400 } }

// QuickNode error response { "error": "Rate limit exceeded", "code": "RATE_LIMIT", "retryAfterMs": 500 } `

Robust Retry Implementation

`javascript class DeFiAPIClient { constructor(baseUrl, apiKey, options = {}) { this.baseUrl = baseUrl; this.apiKey = apiKey; this.maxRetries = options.maxRetries || 3; this.baseDelay = options.baseDelay || 1000; this.maxDelay = options.maxDelay || 30000; this.fallbackProviders = options.fallbackProviders || []; }

async request(method, params, attempt = 0) { try { const response = await fetch(this.baseUrl, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ jsonrpc: '2.0', method, params, id: Date.now() }) });

if (response.status === 429) { const retryAfter = this.parseRetryAfter(response); await this.sleep(retryAfter); return this.request(method, params, attempt); // Don't count against maxRetries }

if (response.status >= 500 && attempt < this.maxRetries) { const delay = Math.min( this.baseDelay Math.pow(2, attempt) + Math.random() 1000, this.maxDelay ); await this.sleep(delay); return this.request(method, params, attempt + 1); }

if (!response.ok) { throw new Error(HTTP ${response.status}: ${await response.text()}); }

const data = await response.json(); if (data.error) throw new RPCError(data.error); return data.result;

} catch (error) { // Try fallback providers on network errors or 5xx if (this.isRetriable(error) && this.fallbackProviders.length > 0) { const fallback = this.fallbackProviders.shift(); return fallback.request(method, params); } throw error; } }

parseRetryAfter(response) { const header = response.headers.get('Retry-After'); if (header) return parseInt(header) * 1000; const data = await response.json().catch(() => ({})); return data.retryAfter || data.retryAfterMs || 1000; }

isRetriable(error) { return error instanceof TypeError || // Network error (error.response && error.response.status >= 500); }

sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } `

Flowchart - Request/Response Lifecycle with Error Handling

Flowchart - Request/Response Lifecycle with Error Handling

Real-Time Data: WebSockets & Webhooks

Polling RPC endpoints for block updates is inefficient. Use push-based patterns instead.

WebSocket Subscriptions (Alchemy, Infura, QuickNode, Moralis)

`javascript const WebSocket = require('ws');

class BlockSubscriber { constructor(rpcWsUrl) { this.ws = new WebSocket(rpcWsUrl); this.subscriptions = new Map(); this.ws.on('message', this.handleMessage.bind(this)); }

subscribe(event, params, callback) { const id = sub_${Date.now()}_${Math.random()}; this.subscriptions.set(id, callback);

this.ws.send(JSON.stringify({ jsonrpc: '2.0', id, method: 'eth_subscribe', params: [event, ...params] }));

return id; // Use to unsubscribe later }

handleMessage(data) { const msg = JSON.parse(data);

// Subscription confirmation if (msg.id && msg.result && this.subscriptions.has(msg.id)) { const subId = msg.result; // Actual subscription ID this.subscriptions.set(subId, this.subscriptions.get(msg.id)); this.subscriptions.delete(msg.id); return; }

// Event notification if (msg.method === 'eth_subscription' && msg.params) { const { subscription, result } = msg.params; const callback = this.subscriptions.get(subscription); if (callback) callback(result); } }

unsubscribe(id) { this.ws.send(JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method: 'eth_unsubscribe', params: [id] })); this.subscriptions.delete(id); } }

// Usage: Track new blocks const subscriber = new BlockSubscriber('wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'); subscriber.subscribe('newHeads', [], (block) => { console.log('New block:', block.number); });

// Track pending transactions to a specific address subscriber.subscribe('alchemy_pendingTransactions', [{ toAddress: '0xUniswapRouterAddress' }], (tx) => { console.log('Pending swap:', tx.hash); }); `

Webhook/Event Streaming (Alchemy Notify, Moralis Streams, QuickNode Streams)

Webhooks push events to your HTTP endpoint — ideal for serverless and multi-instance deployments.

`javascript // Alchemy Notify webhook registration async function registerAlchemyWebhook(webhookUrl, addresses, events) { const response = await fetch('https://dashboard.alchemy.com/api/create-webhook', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': Bearer ${ALCHEMY_API_KEY} }, body: JSON.stringify({ webhook_url: webhookUrl, addresses, event_types: events // ['ADDRESS_ACTIVITY', 'MINED_TRANSACTION', 'DROPPED_TRANSACTION'] }) }); return response.json(); }

// Moralis Streams webhook registration async function registerMoralisStream(webhookUrl, chain, abi, events) { const response = await fetch('https://deep-index.moralis.io/api/v2/streams/evm', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': MORALIS_API_KEY }, body: JSON.stringify({ name: 'DeFi Swap Monitor', chain, description: 'Track Uniswap V3 swaps', webhookUrl, advancedOptions: { includeNativeTxs: false }, abi, includeContractLogs: true, allAddresses: false, topic0: events.map(e => ethers.id(e)) // Event signatures }) }); return response.json(); }

// Webhook handler (Express.js example) app.post('/webhook/alchemy', express.raw({ type: 'application/json' }), (req, res) => { // Verify signature (Alchemy signs payloads) const signature = req.headers['x-alchemy-signature']; const expected = crypto .createHmac('sha256', ALCHEMY_WEBHOOK_SECRET) .update(req.body) .digest('hex');

if (signature !== expected) { return res.status(401).send('Invalid signature'); }

const events = JSON.parse(req.body); events.forEach(event => processEvent(event)); res.status(200).send('OK'); }); `

Sequence Diagram - Webhook/Event Subscription Flow

Sequence Diagram - Webhook/Event Subscription Flow

DeFi-Specific API Patterns

Token Data APIs

`javascript // Alchemy Token API - Get all ERC-20 balances for an address async function getTokenBalances(address) { const response = await fetch(https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}/getTokenBalances, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ owner: address, contractAddresses: [] }) // [] = all tokens }); return response.json(); }

// Moralis Token API - Get token price, metadata, holders async function getTokenData(chain, address) { const [metadata, price, holders] = await Promise.all([ fetch(https://deep-index.moralis.io/api/v2/erc20/metadata?chain=${chain}&addresses=${address}, { headers: { 'X-API-Key': MORALIS_KEY } }).then(r => r.json()), fetch(https://deep-index.moralis.io/api/v2/erc20/${address}/price?chain=${chain}, { headers: { 'X-API-Key': MORALIS_KEY } }).then(r => r.json()), fetch(https://deep-index.moralis.io/api/v2/erc20/${address}/holders?chain=${chain}&limit=10, { headers: { 'X-API-Key': MORALIS_KEY } }).then(r => r.json()) ]); return { metadata, price, holders }; }

// Covalent - Unified token balances across chains async function getCrossChainBalances(address) { const response = await fetch( https://api.covalenthq.com/v1/${address}/balances_v2/?key=${COVALENT_KEY}&nft=false ); return response.json(); // Returns balances for ALL supported chains } `

DEX & Swap APIs

`javascript // 1inch Aggregation API - Best swap routes across DEXs async function getSwapQuote(fromToken, toToken, amount, chain = 1) { const response = await fetch( https://api.1inch.dev/swap/v5.2/${chain}/quote?src=${fromToken}&dst=${toToken}&amount=${amount}, { headers: { 'Authorization': Bearer ${ONEINCH_KEY} } } ); return response.json(); // { dstAmount, protocols, gas, ... } }

// 0x Swap API - Professional market making async function getZeroXQuote(sellToken, buyToken, sellAmount, takerAddress) { const response = await fetch( https://api.0x.org/swap/v1/quote?sellToken=${sellToken}&buyToken=${buyToken}&sellAmount=${sellAmount}&takerAddress=${takerAddress}, { headers: { '0x-api-key': ZEROX_KEY } } ); return response.json(); }

// Uniswap V3 Subgraph (The Graph) - Pool data, ticks, positions const UNISWAP_V3_QUERY = query Pools($feeTier: Int!, $token0: String!, $token1: String!) { pools(where: { feeTier: $feeTier, token0: $token0, token1: $token1 }) { id token0 { symbol decimals } token1 { symbol decimals } feeTier liquidity sqrtPrice tick volumeUSD txCount } } ; `

Lending Protocol APIs

`javascript // Aave V3 GraphQL (The Graph) - User positions, reserves, rates const AAVE_QUERY = query UserReserves($user: String!) { user(id: $user) { reserves { reserve { symbol decimals liquidityRate variableBorrowRate stableBorrowRate utilizationRate } currentATokenBalance currentVariableDebt currentStableDebt } } } ;

// Compound V3 REST (via Alchemy Enhanced API or direct RPC) // Requires reading Comet contract storage — use Alchemy's getAssetTransfers // with category: ['erc20'] to track cToken balances

// Morpho Blue (new lending primitive) - Subgraph const MORPHO_QUERY = query Market($marketId: String!) { market(id: $marketId) { totalSupplyAssets totalBorrowAssets supplyApy borrowApy utilization lltv } } ; `

Oracle & Price Feed APIs

`javascript // Chainlink Price Feeds - Direct RPC reads (no API key needed for view calls) const PRICE_FEED_ABI = [ 'function latestRoundData() view returns (uint80, int256, uint256, uint256, uint80)', 'function decimals() view returns (uint8)' ];

async function getChainlinkPrice(feedAddress, provider) { const contract = new ethers.Contract(feedAddress, PRICE_FEED_ABI, provider); const [, answer, , updatedAt] = await contract.latestRoundData(); const decimals = await contract.decimals(); return { price: Number(answer) / 10**decimals, timestamp: Number(updatedAt) }; }

// Pyth Network - Pull-based price feeds (Solana/EVM) // Requires Wormhole bridge for cross-chain — use Pyth SDK

// CoinGecko/CoinMarketCap - REST APIs for reference prices async function getReferencePrice(tokenId, vsCurrency = 'usd') { const response = await fetch( https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=${vsCurrency} ); return response.json(); } `

Integration Best Practices

1. Provider Abstraction Layer

Never couple business logic to a single provider's SDK. Build an abstraction:

`typescript // types.ts interface RPCProvider { request(method: string, params: unknown[]): Promise; subscribe(event: string, params: unknown[], callback: (data: unknown) => void): string; unsubscribe(id: string): void; }

interface EnhancedAPI { getTokenBalances(address: string): Promise; getNFTs(address: string): Promise; getTransactionHistory(address: string): Promise; }

// providers/alchemy.ts class AlchemyProvider implements RPCProvider, EnhancedAPI { // ... implementation }

// providers/quicknode.ts class QuickNodeProvider implements RPCProvider, EnhancedAPI { // ... implementation }

// factory.ts function createProvider(config: ProviderConfig): RPCProvider & EnhancedAPI { switch (config.primary) { case 'alchemy': return new AlchemyProvider(config); case 'quicknode': return new QuickNodeProvider(config); case 'infura': return new InfuraProvider(config); default: throw new Error(Unknown provider: ${config.primary}); } }

// Usage — swap providers by changing config const provider = createProvider({ primary: 'alchemy', apiKey: process.env.ALCHEMY_KEY }); const balances = await provider.getTokenBalances('0x...'); `

2. Health Checks & Failover

`javascript class ProviderPool { constructor(providers) { this.providers = providers.map(p => ({ ...p, healthy: true, lastCheck: 0 })); this.currentIndex = 0; }

async getHealthyProvider() { // Check health periodically for (let i = 0; i < this.providers.length; i++) { const idx = (this.currentIndex + i) % this.providers.length; const provider = this.providers[idx];

if (Date.now() - provider.lastCheck > 30000) { // Check every 30s provider.healthy = await this.checkHealth(provider); provider.lastCheck = Date.now(); }

if (provider.healthy) { this.currentIndex = idx; return provider.instance; } } throw new Error('No healthy providers available'); }

async checkHealth(provider) { try { await provider.instance.request('eth_blockNumber', []); return true; } catch { return false; } } } `

3. Caching Strategy

Cache aggressively but invalidate correctly:

`javascript class DeFiCache { constructor(redisClient) { this.redis = redisClient; this.ttl = { tokenMetadata: 86400, // 24h — rarely changes tokenPrice: 60, // 1min — volatile userBalances: 30, // 30s — changes per block poolReserves: 12, // 12s — ~1 block historicalData: 3600 // 1h — immutable }; }

async get(key, fetcher, type = 'tokenPrice') { const cached = await this.redis.get(key); if (cached) return JSON.parse(cached);

const fresh = await fetcher(); await this.redis.setex(key, this.ttl[type], JSON.stringify(fresh)); return fresh; }

// Invalidate on relevant events async invalidateOnBlock(blockNumber) { // Clear balance/pool caches — they change every block const keys = await this.redis.keys('balance:*'); keys.forEach(k => this.redis.del(k)); } } `

4. Vietnamese Developer Considerations

Vietnamese DeFi developers face specific constraints:

Latency Optimization:

  • QuickNode Singapore node: ~40ms from Ho Chi Minh City
  • Alchemy Singapore: ~45ms
  • Infura Singapore: ~50ms
  • Run health checks from VN networks (Viettel, VNPT, FPT) to measure actual latency

Cost Management:

  • Free tiers often shared across team members
  • Implement request deduplication at application level
  • Use The Graph public endpoints for non-critical historical queries (free)

Regulatory Compliance:

  • Decree 53/2022/ND-CP: Data localization requirements for financial data
  • Consider hosting caching layer in Vietnam (AWS Vietnam, Viettel IDC)
  • VND stablecoins (VNDC, VNM) require local KYC/AML integration

Community Resources:

  • Vietnam Blockchain Developers Discord: ~5,000 members
  • Web3 Vietnam Telegram: Active technical discussions
  • Local hackathons (Vietnam Blockchain Week, ETH Vietnam) for networking

FAQ

Q: Should I use Alchemy or Infura for Ethereum mainnet? A: Alchemy has better Enhanced APIs and Notify webhooks. Infura has better enterprise SLAs and IPFS integration. For most DeFi apps, Alchemy's developer experience wins.

Q: How do I handle rate limits during high-traffic events (IDOs, airdrops)? A: Implement request queuing with priority lanes (user-facing > background). Pre-warm caches. Negotiate enterprise limits in advance. Have fallback providers ready.

Q: Can I use The Graph for real-time trading data? A: No. The Graph has indexing latency (seconds to minutes). Use Alchemy/Infura/QuickNode WebSocket for real-time, The Graph for historical analysis.

Q: What's the difference between Alchemy's CUPS and request-based limits? A: CUPS charges by computational cost. A complex eth_call costs more CUs than eth_blockNumber. Request-based limits treat all calls equally. CUPS is fairer but harder to predict.

Q: How do I verify webhook signatures securely? A: Always verify signatures using constant-time comparison. Store webhook secrets in secret managers (AWS Secrets Manager, HashiCorp Vault), not environment variables.

Q: Which provider has the best Vietnamese language support? A: Moralis has Vietnamese documentation and SE Asia community support. QuickNode has Singapore infrastructure. Most others are English-only.

Summary Checklist

  • [ ] Choose primary + fallback providers for each chain
  • [ ] Implement authentication with key rotation
  • [ ] Build rate limiter with provider-specific models
  • [ ] Add exponential backoff retry with fallback
  • [ ] Use WebSocket for real-time, webhooks for serverless
  • [ ] Abstract providers behind interfaces
  • [ ] Implement health checks and automatic failover
  • [ ] Cache with appropriate TTLs per data type
  • [ ] Monitor usage, errors, and latency per provider
  • [ ] Test failover scenarios regularly

This reference is maintained as a living document. API providers update frequently — check official documentation for latest endpoints, rate limits, and features.

Tags:

defi api documentationwhat is defi api documentationdefi api documentation guide