From a69d199fab9a230fd5cd1bd546ab6471ef69eb2e Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 9 Mar 2026 10:27:47 -0400 Subject: [PATCH] Switch rate provider from CoinGecko to Kraken Kraken public API is free and doesn't require an API key. Supports XMR/USD and XMR/EUR pairs. --- .env.example | 2 +- api/.env.example | 2 +- api/app/config.py | 2 +- api/app/rates.py | 44 +++++++++++++++++++++++--------------------- docker-compose.yml | 2 +- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index f8f0120..8564495 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ BACKUP_RETENTION_DAYS=7 # API authentication API_KEYS=dev-key-1 API_KEY_ENCRYPTION_KEY=replace-with-fernet-key -COINGECKO_API_KEY= +RATE_PROVIDER=kraken INVOICE_RECONCILE_INTERVAL_SECONDS=30 # Founder donation wallet (view-only) diff --git a/api/.env.example b/api/.env.example index 13f58b5..e82e3af 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,6 +1,6 @@ DATABASE_URL=postgresql://xmrcheckout:xmrcheckout@localhost:5432/xmrcheckout API_KEYS=change-me-1,change-me-2 -COINGECKO_API_KEY= +RATE_PROVIDER=kraken INVOICE_RECONCILE_INTERVAL_SECONDS=30 FOUNDER_PAYMENT_ADDRESS= FOUNDER_VIEW_KEY= diff --git a/api/app/config.py b/api/app/config.py index d2be15e..b64812f 100644 --- a/api/app/config.py +++ b/api/app/config.py @@ -29,7 +29,7 @@ INVOICE_DEFAULT_EXPIRY_HOURS = int(os.getenv("INVOICE_DEFAULT_EXPIRY_HOURS", "1" LATE_PAYMENT_LOOKBACK_HOURS = int(os.getenv("LATE_PAYMENT_LOOKBACK_HOURS", "48")) DONATION_EXPIRY_MINUTES = int(os.getenv("DONATION_EXPIRY_MINUTES", "30")) DONATION_ACTIVE_INVOICE_LIMIT = int(os.getenv("DONATION_ACTIVE_INVOICE_LIMIT", "25")) -COINGECKO_API_KEY = os.getenv("COINGECKO_API_KEY", "") +RATE_PROVIDER = os.getenv("RATE_PROVIDER", "kraken") DONATIONS_ENABLED = _get_bool_env("DONATIONS_ENABLED", False) MONERO_WALLET_RPC_URLS = [ diff --git a/api/app/rates.py b/api/app/rates.py index ae969e0..ce14e59 100644 --- a/api/app/rates.py +++ b/api/app/rates.py @@ -9,12 +9,16 @@ from typing import Any import requests -from .config import COINGECKO_API_KEY - -COINGECKO_URL = "https://api.coingecko.com/api/v3/simple/price" -COINGECKO_SOURCE = "coingecko" +KRAKEN_TICKER_URL = "https://api.kraken.com/0/public/Ticker" +KRAKEN_SOURCE = "kraken" RATE_TTL_SECONDS = 60 +# Kraken pair names for XMR against common fiat currencies +_KRAKEN_PAIRS: dict[str, str] = { + "usd": "XXMRZUSD", + "eur": "XXMRZEUR", +} + @dataclass(frozen=True) class QuoteResult: @@ -32,34 +36,32 @@ _cached_at: float | None = None def get_xmr_rate(currency: str) -> QuoteResult: global _cached_quote, _cached_at normalized_currency = currency.strip().lower() - if not COINGECKO_API_KEY: - raise RuntimeError("CoinGecko API key is not configured") + + pair = _KRAKEN_PAIRS.get(normalized_currency) + if pair is None: + raise ValueError(f"Unsupported fiat currency: {currency} (supported: {', '.join(_KRAKEN_PAIRS)})") with _cache_lock: if _cached_quote and _cached_at: if time.monotonic() - _cached_at < RATE_TTL_SECONDS: return _cached_quote - params = { - "vs_currencies": normalized_currency, - "ids": "monero", - "x_cg_demo_api_key": COINGECKO_API_KEY, - } - response = requests.get(COINGECKO_URL, params=params, timeout=5) + response = requests.get(KRAKEN_TICKER_URL, params={"pair": pair}, timeout=5) response.raise_for_status() data: dict[str, Any] = response.json() - rate_value = ( - data.get("monero", {}).get(normalized_currency) - if isinstance(data, dict) - else None - ) - if rate_value is None: - raise ValueError("Unsupported fiat currency at this time") - rate = Decimal(str(rate_value)) + if data.get("error"): + raise RuntimeError(f"Kraken API error: {data['error']}") + result = data.get("result", {}) + ticker = result.get(pair, {}) + # 'c' is the last trade closed: [price, lot-volume] + last_price = ticker.get("c", [None])[0] + if last_price is None: + raise ValueError("Could not get XMR rate from Kraken") + rate = Decimal(str(last_price)) quote = QuoteResult( rate=rate, currency=normalized_currency.upper(), - source=COINGECKO_SOURCE, + source=KRAKEN_SOURCE, quoted_at=datetime.now(timezone.utc), ) with _cache_lock: diff --git a/docker-compose.yml b/docker-compose.yml index d5b0aa1..e9fd0b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,7 @@ services: DATABASE_URL: ${DATABASE_URL} API_KEYS: ${API_KEYS} API_KEY_ENCRYPTION_KEY: ${API_KEY_ENCRYPTION_KEY} - COINGECKO_API_KEY: ${COINGECKO_API_KEY} + RATE_PROVIDER: ${RATE_PROVIDER:-kraken} FOUNDER_PAYMENT_ADDRESS: ${FOUNDER_PAYMENT_ADDRESS} FOUNDER_VIEW_KEY: ${FOUNDER_VIEW_KEY} DONATIONS_ENABLED: ${DONATIONS_ENABLED:-false}