Skip to main content

Best Practices

Optimize your integration with these production-tested patterns.

๐Ÿš€ Performance Optimizationโ€‹

Batch Requests When Possibleโ€‹

Many endpoints support batch operations. Use them to reduce round trips:

// โŒ Slow: Multiple requests
const prices = await Promise.all([
fetch('https://api.web3identity.com/api/price/eth'),
fetch('https://api.web3identity.com/api/price/btc'),
fetch('https://api.web3identity.com/api/price/usdc')
]);

// โœ… Fast: Single batch request
const prices = await fetch(
'https://api.web3identity.com/api/price/batch?symbols=eth,btc,usdc'
);

Use Appropriate Endpointsโ€‹

Choose the right endpoint for your data needs:

NeedUseNot
Just ENS address/api/ens/resolve/:name/api/ens/profile/:name
Full profile/api/ens/profile/:nameMultiple single-field calls
Multiple prices/api/price/batchIndividual /api/price/:token
Wallet overview/api/wallet/:addressSeparate balance + NFT + tx calls

๐Ÿ’พ Caching Strategiesโ€‹

Cache Duration by Data Typeโ€‹

Different data has different freshness requirements:

Data TypeRecommended TTLExample
ENS resolution5-15 minutesAddress lookups
ENS profiles1-6 hoursAvatar, bio, socials
Token prices30-60 secondsReal-time pricing
TVL/DeFi stats5-15 minutesProtocol analytics
NFT metadata24 hoursCollection info
Gas prices15-30 secondsTransaction estimation
Historical data24+ hoursPast transactions

Implementation Exampleโ€‹

const cache = new Map();

async function cachedFetch(url, ttlSeconds) {
const cached = cache.get(url);
if (cached && Date.now() - cached.time < ttlSeconds * 1000) {
return cached.data;
}

const response = await fetch(url);
const data = await response.json();

cache.set(url, { data, time: Date.now() });
return data;
}

// Usage with appropriate TTLs
const ensProfile = await cachedFetch(
'https://api.web3identity.com/api/ens/profile/vitalik.eth',
3600 // 1 hour
);

const ethPrice = await cachedFetch(
'https://api.web3identity.com/api/price/eth',
30 // 30 seconds
);

Stale-While-Revalidate Patternโ€‹

For better UX, serve stale data while fetching fresh:

async function swr(url, ttl, staleTTL) {
const cached = cache.get(url);
const now = Date.now();

// Fresh cache - return immediately
if (cached && now - cached.time < ttl * 1000) {
return cached.data;
}

// Stale but usable - return stale, refresh in background
if (cached && now - cached.time < staleTTL * 1000) {
fetch(url).then(r => r.json()).then(data => {
cache.set(url, { data, time: Date.now() });
});
return cached.data;
}

// Too stale - must fetch fresh
const data = await fetch(url).then(r => r.json());
cache.set(url, { data, time: now });
return data;
}

๐Ÿ” Authentication Best Practicesโ€‹

SIWE Token Managementโ€‹

// Store token securely
const siweToken = await authenticate();
sessionStorage.setItem('siwe_token', siweToken); // โœ… Session only
// localStorage.setItem('siwe_token', siweToken); // โŒ Persists too long

// Include in requests
const headers = {
'Authorization': `Bearer ${sessionStorage.getItem('siwe_token')}`
};

API Key Securityโ€‹

// โœ… Server-side only
// .env file (never commit!)
API_KEY=your_api_key_here

// โŒ Never in client-side code
const API_KEY = 'sk_live_...'; // Exposed to users!

Rotate Credentials Regularlyโ€‹

  • API Keys: Rotate every 90 days
  • SIWE Tokens: Re-authenticate after 24 hours
  • Monitor usage: Check for unexpected spikes

โšก Rate Limit Managementโ€‹

Implement Exponential Backoffโ€‹

async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url);

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}

return response;
}
throw new Error('Max retries exceeded');
}

Spread Requests Over Timeโ€‹

// โŒ Burst: All at once
const results = await Promise.all(
addresses.map(addr => fetch(`/api/wallet/${addr}`))
);

// โœ… Throttled: Spread over time
async function throttledFetch(urls, requestsPerSecond = 10) {
const results = [];
const delay = 1000 / requestsPerSecond;

for (const url of urls) {
results.push(await fetch(url));
await new Promise(r => setTimeout(r, delay));
}

return results;
}

Track Your Usageโ€‹

let requestCount = 0;
const resetTime = Date.now() + 86400000; // 24 hours

function trackRequest() {
requestCount++;
if (requestCount > 80) { // 80% of 100 free tier
console.warn(`Approaching rate limit: ${requestCount}/100`);
}
}

๐Ÿ›ก๏ธ Error Handlingโ€‹

Handle All Response Typesโ€‹

async function apiCall(endpoint) {
const response = await fetch(`https://api.web3identity.com${endpoint}`);

switch (response.status) {
case 200:
return { success: true, data: await response.json() };

case 400:
const error = await response.json();
return { success: false, error: error.message };

case 402:
// Payment required - handle x402
const paymentInfo = await response.json();
return { success: false, paymentRequired: true, ...paymentInfo };

case 404:
return { success: false, error: 'Resource not found' };

case 429:
return {
success: false,
rateLimited: true,
retryAfter: response.headers.get('Retry-After')
};

case 500:
return { success: false, error: 'Server error - try again later' };

default:
return { success: false, error: `Unexpected status: ${response.status}` };
}
}

Graceful Degradationโ€‹

async function getENSProfile(name) {
try {
const response = await fetch(
`https://api.web3identity.com/api/ens/profile/${name}`
);

if (!response.ok) throw new Error('API unavailable');
return await response.json();

} catch (error) {
// Fallback: Try basic resolution only
try {
const basic = await fetch(
`https://api.web3identity.com/api/ens/resolve/${name}`
);
return { address: await basic.json().address, partial: true };
} catch {
// Final fallback: Return null, let UI handle
return null;
}
}
}

๐Ÿ“Š Monitoring & Observabilityโ€‹

Log API Interactionsโ€‹

async function instrumentedFetch(url, options = {}) {
const start = Date.now();
const requestId = crypto.randomUUID();

console.log(`[${requestId}] โ†’ ${url}`);

try {
const response = await fetch(url, options);
const duration = Date.now() - start;

console.log(`[${requestId}] โ† ${response.status} (${duration}ms)`);

// Track metrics
metrics.record({
endpoint: new URL(url).pathname,
status: response.status,
duration,
timestamp: new Date().toISOString()
});

return response;
} catch (error) {
console.error(`[${requestId}] โœ— ${error.message}`);
throw error;
}
}

Set Up Alertsโ€‹

Monitor for:

  • Error rate > 5% of requests
  • Latency > 2 seconds average
  • 402 responses (budget/quota issues)
  • 429 responses (rate limiting)

๐Ÿ”„ Data Consistencyโ€‹

Handle Blockchain Reorgsโ€‹

Recent blockchain data can change. For critical operations:

// Wait for finality before considering data permanent
async function getConfirmedTransaction(txHash) {
const tx = await fetch(
`https://api.web3identity.com/api/tx/${txHash}`
).then(r => r.json());

// Ethereum: ~15 blocks for finality
if (tx.confirmations < 15) {
return { ...tx, finalized: false, warning: 'May reorg' };
}

return { ...tx, finalized: true };
}

Validate Addresses Client-Sideโ€‹

function isValidAddress(address) {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}

function isValidENS(name) {
return name.endsWith('.eth') && name.length > 4;
}

// Validate before API call
if (!isValidAddress(userInput) && !isValidENS(userInput)) {
throw new Error('Invalid address or ENS name');
}

๐Ÿ“ฑ Mobile Optimizationโ€‹

Reduce Payload Sizeโ€‹

// Request only needed fields when available
const profile = await fetch(
'https://api.web3identity.com/api/ens/profile/vitalik.eth?fields=address,avatar'
);

Handle Offline Gracefullyโ€‹

async function fetchWithOfflineSupport(url) {
if (!navigator.onLine) {
const cached = await caches.match(url);
if (cached) return cached;
throw new Error('Offline and no cache available');
}

const response = await fetch(url);
const cache = await caches.open('api-cache');
cache.put(url, response.clone());
return response;
}

Next Stepsโ€‹