Overview
When CoinGecko or CoinMarketCap are down or rate-limiting us, every price request waits for a full timeout before falling back. A circuit breaker will open after repeated failures and skip the failing source for a cool-down period, making responses faster and reducing load on recovering upstreams.
Behaviour
| State |
Description |
| Closed |
Normal operation — requests pass through |
| Open |
Source is failing — skip immediately, return null |
| Half-open |
After cool-down, allow one test request; if it succeeds, close; if it fails, reopen |
Configuration (per source)
CIRCUIT_BREAKER_FAILURE_THRESHOLD=3 # failures before opening
CIRCUIT_BREAKER_SUCCESS_THRESHOLD=1 # successes to close from half-open
CIRCUIT_BREAKER_TIMEOUT_MS=30000 # cool-down before half-open
Implementation
Create src/utils/circuitBreaker.js:
class CircuitBreaker {
constructor(name, options) { ... }
async call(fn) { ... } // wraps the source fetch
isOpen() { ... }
recordSuccess() { ... }
recordFailure() { ... }
}
Wrap each source in priceOracle.js with its own breaker instance.
Expose circuit state via GET /health:
{
"circuits": {
"coingecko": "closed",
"coinmarketcap": "open",
"stellar_dex": "closed"
}
}
Acceptance Criteria
Overview
When CoinGecko or CoinMarketCap are down or rate-limiting us, every price request waits for a full timeout before falling back. A circuit breaker will open after repeated failures and skip the failing source for a cool-down period, making responses faster and reducing load on recovering upstreams.
Behaviour
nullConfiguration (per source)
Implementation
Create
src/utils/circuitBreaker.js:Wrap each source in
priceOracle.jswith its own breaker instance.Expose circuit state via
GET /health:{ "circuits": { "coingecko": "closed", "coinmarketcap": "open", "stellar_dex": "closed" } }Acceptance Criteria
CircuitBreakerclass implemented with closed/open/half-open statesnullimmediately (no network call)/healthresponse