-
Notifications
You must be signed in to change notification settings - Fork 49
Open
Description
Context
The ShapeShift affiliate revenue API aggregates fee data from 6+ external swap provider APIs. Currently, every API request fetches fresh data from all external providers, which is slow (2-10 seconds) and puts unnecessary load on external APIs.
Repository: shapeshift/unchained
Location: node/proxy/api/src/affiliateRevenue/
Current Problem:
- Response time: 2-10 seconds per request
- Risk of rate limiting from external APIs
- If one external API is slow, entire response is delayed
- Same data fetched repeatedly even though it rarely changes
Solution
Implement in-memory caching that:
- Pre-loads the last 90 days of data on server startup
- Updates incrementally every 5 minutes
- Serves requests instantly from cache
Caching Pattern Precedent
The unchained codebase already has caching patterns to follow:
Map-based with interval updates (node/coinstacks/arbitrum/api/src/rfox/eventCache.ts):
private cache: Map<Address, Events> = new Map()
setInterval(() => this.reindex(), 1000 * 60 * 15)Acceptance Criteria
- Create cache class following existing
eventCache.tspattern - Initialize cache on server startup in
app.ts - Load last 90 days of data on initialization
- Update incrementally every 5 minutes
- Use 2-minute overlap on incremental fetches to handle eventual consistency
- Deduplicate entries by
${service}:${txHash}:${timestamp}key - Modify endpoint to read from cache instead of fetching live
- Fallback to live fetch if cache not yet initialized
- Add logging for cache initialization and update status
Files to Create/Modify
node/proxy/api/src/affiliateRevenue/cache.ts(new file)node/proxy/api/src/affiliateRevenue/index.ts(use cache)node/proxy/api/src/app.ts(initialize cache on startup)
Configuration
- Initial load: 90 days of historical data
- Update interval: 5 minutes
- Overlap for incremental updates: 2 minutes
- Cache key format:
${service}:${txHash}:${timestamp}
Implementation Guidance
class AffiliateRevenueCache {
private cache: Map<string, Fees> = new Map()
private lastUpdated: number = 0
private initialized: boolean = false
async initialize() {
const ninetyDaysAgo = Math.floor(Date.now() / 1000) - (90 * 24 * 60 * 60)
await this.fetchAndStore(ninetyDaysAgo, Math.floor(Date.now() / 1000))
this.initialized = true
setInterval(() => this.incrementalUpdate(), 5 * 60 * 1000)
}
private async incrementalUpdate() {
const since = this.lastUpdated - 120 // 2 min overlap
await this.fetchAndStore(since, Math.floor(Date.now() / 1000))
this.lastUpdated = Math.floor(Date.now() / 1000)
}
getRevenue(start: number, end: number): AffiliateRevenueResponse {
if (!this.initialized) throw new Error('Cache not initialized')
// Filter cache by time range and aggregate
}
}
export const affiliateRevenueCache = new AffiliateRevenueCache()coderabbitai
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
Backlog