Skip to content

SmartDropLabs/smartdrop-backend

Repository files navigation

SmartDrop backend

CI

HTTP APIs, webhooks, and indexing for SmartDrop. This repository contains Node.js services that talk to Horizon, Soroban RPC, and external APIs.

Related repositories

Repository Role
smart-frontend Next.js static app
smartdrop-contracts Soroban Rust contracts
SmartDrop Original monorepo (reference)

Features

Price Oracle Service

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 (>20% changes)
  • Fallback chain: DEX → CoinGecko → CoinMarketCap → cached

Webhook Delivery System

Registers subscriber endpoints for SmartDrop lifecycle events and delivers signed JSON payloads with retry tracking.

Events:

  • airdrop.created
  • airdrop.executing
  • airdrop.completed
  • airdrop.failed
  • recipient.claimed

Features:

  • Webhook endpoint CRUD with secrets kept out of list responses
  • Timestamped HMAC-SHA256 request signatures
  • At-least-once delivery attempts with exponential backoff
  • Delivery logs with response code, error, duration, and attempt count
  • Dead-letter storage after retry exhaustion

🚀 Quick Start (Docker Development)

You can spin up the entire local development stack—including the API, PostgreSQL database, and Redis instance—using a single command.

Prerequisites

Spin Up the Stack

  1. Clone and Navigate to the project root directory.
  2. Set up Environment Variables:
    cp .env.example .env
    

3. **Launch the Infrastructure**:
```bash
docker compose up --build

The API will stand up on http://localhost:4000.

  • Hot Reloading: Any changes made to files within the ./src directory will instantly trigger an application restart inside the container.
  • Database & Cache: Health checks prevent the API from booting until Postgres and Redis are fully operational.
  • Teardown: To stop the containers and maintain volume data, run docker compose down. To wipe database volumes completely during stop, use docker compose down -v.

Configuration

The application reads configurations from the .env file at the root.

Environment Variables:

Variable Description Default Required
PORT Server port 4000 No
REDIS_HOST Redis server host redis No
REDIS_PORT Redis server port 6379 No
REDIS_PASSWORD Redis password undefined No
REDIS_URL Redis connection string redis://redis:6379 No
DATABASE_URL PostgreSQL connection string postgres://smartdrop:smartdrop@postgres:5432/smartdrop 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
ADMIN_API_KEY Bootstrap admin bearer token for API key management undefined Yes, for protected endpoints
LOG_LEVEL Logging level info No
CORS_ALLOWED_ORIGINS Allowed origins split by commas http://localhost:4000,http://localhost:3001 No
---------- ------------- --------- ----------
NODE_ENV Runtime environment: development, test, or production development No
PORT Server port 3000 No
REDIS_URL Redis connection URL redis://localhost:6379 in development/test Yes in production
DATABASE_URL Database connection URL reserved for persistence-backed features postgres://localhost/smartdrop in development, postgres://localhost/smartdrop_test in test Yes in production
STELLAR_HORIZON_URL Horizon API URL https://horizon.stellar.org No
USDC_ISSUER USDC issuer address GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335AX2OBFLDTQLNUEHRGPTM6RIA No
COINGECKO_API_KEY CoinGecko API key empty No
COINMARKETCAP_API_KEY CoinMarketCap API key empty No
PRICE_CACHE_TTL_SECONDS Cache TTL in seconds 60 No
PRICE_REFRESH_INTERVAL_SECONDS Refresh interval in seconds 30 No
PRICE_STALE_THRESHOLD_MINUTES Stale threshold in minutes 5 No
PRICE_ANOMALY_THRESHOLD_PCT Anomaly detection threshold % 20 No
ADMIN_API_KEY Bootstrap admin bearer token for API key management empty Yes, for protected endpoints
LOG_LEVEL Logging level: debug, info, warn, or error info No

API Endpoints

Get Asset Price

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"]
}

Force Price Refresh

GET /api/v1/prices/:asset_code/refresh?issuer=<issuer_address>

Requires Authorization: Bearer <api_key>.

API Keys

Protected endpoints use Authorization: Bearer <api_key>. Set ADMIN_API_KEY to a 32-byte hex token for bootstrap access, then create scoped API keys with the key-management endpoints.

GET /api/v1/keys
POST /api/v1/keys
DELETE /api/v1/keys/:id

POST /api/v1/keys returns the raw api_key only once. Stored keys are hashed with SHA-256 and listed with metadata only (label, created_at, last_used_at, scopes, and key_prefix).

Webhook Endpoints

POST   /api/v1/webhooks
GET    /api/v1/webhooks
DELETE /api/v1/webhooks/:id
POST   /api/v1/webhooks/:id/test
GET    /api/v1/webhooks/:id/deliveries

Health Check

GET /health

Response:

{
  "status": "ok",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Usage Examples

Fetch XLM Price

curl http://localhost:4000/api/v1/prices/XLM

Fetch Custom Asset Price

curl "http://localhost:4000/api/v1/prices/USDC?issuer=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335AX2OBFLDTQLNUEHRGPTM6RIA"

Force Price Refresh

curl http://localhost:4000/api/v1/prices/XLM/refresh \
  -H "Authorization: Bearer $API_KEY"

Create API Key

curl -X POST http://localhost:4000/api/v1/keys \
  -H "Authorization: Bearer $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"alerts worker","scopes":["alerts"]}'

Check Service Health

curl http://localhost:4000/health

Error Handling

The API returns appropriate HTTP status codes:

  • 200 - Success
  • 400 - Invalid request parameters
  • 404 - Price not available
  • 500 - Internal server error

Error Response Format:

{
  "error": "Error type",
  "message": "Detailed error message"
}

Development

Project Structure

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

Adding New Price Sources

To add a new price source:

  1. Create a new file in src/services/sources/
  2. Implement a fetchPrice(assetCode, issuer) function that returns a price or null
  3. Add the source to the SOURCES array in src/services/priceOracle.js

Example:

// src/services/sources/customSource.js
const axios = require('axios');
const logger = require('../../logger');

async function fetchPrice(assetCode, issuer) {
  try {
    const response = await axios.get('[https://api.example.com/price](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 };

Troubleshooting

Redis Connection Issues

If you see "Redis connection error" in logs:

  • Verify containers are running: docker compose ps
  • Check Redis logs: docker compose logs redis
  • Ensure environmental parameters (REDIS_HOST=redis) reference the compose network alias rather than localhost.
  • Verify Redis is running: redis-cli ping
  • Check REDIS_URL in .env
  • If Redis requires a password, include it in the connection URL

Price Not Available

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

Rate Limiting

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

Monitoring

The service logs important events:

  • Price fetches from each source
  • Price anomalies (>10% changes)
  • Stale price warnings
  • Cache refresh cycles
  • API errors
  • Price fetches from each source
  • Price anomalies (>20% 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)

License

MIT

About

SmartDrop backend: API, webhooks, and indexing (planned)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors