HTTP APIs, webhooks, and indexing for SmartDrop. This repository contains Node.js services that talk to Horizon, Soroban RPC, and external APIs.
| Repository | Role |
|---|---|
| smart-frontend | Next.js static app |
| smartdrop-contracts | Soroban Rust contracts |
| SmartDrop | Original monorepo (reference) |
Multi-source price oracle that fetches and caches USD prices for Stellar assets.
Data Sources:
- Stellar DEX (orderbook prices)
- CoinGecko API
- CoinMarketCap API
Features:
- Median price aggregation from multiple sources
- Redis caching with configurable TTL (default: 60s)
- Background job refreshes prices every 30 seconds
- Stale price detection (>5 minutes)
- Price anomaly logging (>10% changes)
- Fallback chain: DEX → CoinGecko → CoinMarketCap → cached
- Node.js >= 20.9.0
- Redis server (local or remote)
npm installmacOS (Homebrew):
brew install redis
brew services start redisLinux (Ubuntu/Debian):
sudo apt-get install redis-server
sudo systemctl start redis
sudo systemctl enable redisDocker:
docker run -d -p 6379:6379 redis:alpineVerify Redis is running:
redis-cli ping
# Should return: PONGCopy .env.example to .env and configure:
cp .env.example .envEnvironment Variables:
| Variable | Description | Default | Required |
|---|---|---|---|
PORT |
Server port | 3000 | No |
REDIS_HOST |
Redis server host | localhost | No |
REDIS_PORT |
Redis server port | 6379 | No |
REDIS_PASSWORD |
Redis password | undefined | No |
STELLAR_HORIZON_URL |
Horizon API URL | https://horizon.stellar.org | No |
USDC_ISSUER |
USDC issuer address | GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335AX2OBFLDTQLNUEHRGPTM6RIA | No |
COINGECKO_API_KEY |
CoinGecko API key | undefined | No |
COINMARKETCAP_API_KEY |
CoinMarketCap API key | undefined | No |
PRICE_CACHE_TTL |
Cache TTL in seconds | 60 | No |
PRICE_REFRESH_INTERVAL |
Refresh interval in seconds | 30 | No |
PRICE_STALE_THRESHOLD |
Stale threshold in minutes | 5 | No |
PRICE_ANOMALY_THRESHOLD |
Anomaly detection threshold % | 10 | No |
LOG_LEVEL |
Logging level | info | No |
# Development (with auto-reload)
npm run dev
# Production
npm startThe server will start on the configured port (default: 3000) and automatically begin the background price refresh job.
GET /api/v1/prices/:asset_code?issuer=<issuer_address>
Response:
{
"asset_code": "XLM",
"issuer": null,
"price_usd": 0.1234,
"source": "stellar_dex",
"fetched_at": "2024-01-15T10:30:00.000Z",
"is_stale": false,
"stale_warning": null,
"sources_attempted": ["stellar_dex", "coingecko"]
}GET /api/v1/prices/:asset_code/refresh?issuer=<issuer_address>
GET /health
Response:
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00.000Z"
}curl http://localhost:3000/api/v1/prices/XLMcurl "http://localhost:3000/api/v1/prices/USDC?issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335AX2OBFLDTQLNUEHRGPTM6RIA"curl http://localhost:3000/api/v1/prices/XLM/refreshcurl http://localhost:3000/healthThe API returns appropriate HTTP status codes:
200- Success400- Invalid request parameters404- Price not available500- Internal server error
Error Response Format:
{
"error": "Error type",
"message": "Detailed error message"
}src/
├── index.js # Express server entry point
├── config.js # Configuration management
├── logger.js # Winston logger setup
├── routes/
│ └── prices.js # Price API endpoints
├── services/
│ ├── cache.js # Redis cache wrapper
│ ├── priceOracle.js # Core oracle aggregation logic
│ └── sources/
│ ├── stellarDex.js # Stellar DEX price source
│ ├── coingecko.js # CoinGecko API source
│ └── coinmarketcap.js # CoinMarketCap API source
└── jobs/
└── priceRefresh.js # Background price refresh job
To add a new price source:
- Create a new file in
src/services/sources/ - Implement a
fetchPrice(assetCode, issuer)function that returns a price ornull - Add the source to the
SOURCESarray insrc/services/priceOracle.js
Example:
// src/services/sources/customSource.js
const axios = require('axios');
const logger = require('../../logger');
async function fetchPrice(assetCode, issuer) {
try {
// Fetch price from your source
const response = await axios.get('https://api.example.com/price', {
params: { asset: assetCode }
});
return response.data.price;
} catch (err) {
logger.warn('Custom source fetch failed', { assetCode, error: err.message });
return null;
}
}
module.exports = { fetchPrice };If you see "Redis connection error" in logs:
- Verify Redis is running:
redis-cli ping - Check Redis host and port in
.env - If using a password, ensure
REDIS_PASSWORDis set correctly
If prices return null:
- Check that at least one price source is configured
- Verify API keys for CoinGecko/CoinMarketCap if using those sources
- Check logs for specific source errors
- Stellar DEX may have no liquidity for the asset
External APIs may rate limit requests:
- CoinGecko: Free tier has rate limits
- CoinMarketCap: Requires API key for production use
- The service handles rate limits gracefully and falls back to other sources
The service logs important events:
- Price fetches from each source
- Price anomalies (>10% changes)
- Stale price warnings
- Cache refresh cycles
- API errors
Monitor logs for:
- Frequent source failures
- Price anomalies (may indicate market volatility or data issues)
- Stale prices (may indicate cache or source issues)
MIT