Ran the real lookup against both live APIs rather than the stubs. Three
findings worth being written down instead of rediscovered:
- Kraken returns ~721 daily candles, so ~2 years for any pair it quotes.
EUR and GBP both resolve from it directly; the fallback never fires for
them.
- CoinGecko's free tier answers within 365 days and returns 401
Unauthorized beyond it — a plan limit wearing an auth error's clothes,
not a transient failure. The practical ceiling is therefore ~2 years for
major pairs and 1 year for anything else, which matters given the ask was
"historical data for up to even a year".
- The two sources disagree by ~2.5% on the same date (2026-05-15: Kraken
68,047.50 EUR/BTC, CoinGecko 69,743.26) — one exchange's daily close
versus a cross-exchange average. Neither is wrong, which is the reason
the ledger records rate_source beside rate rather than presenting a bare
figure as canonical.
Refusal behaviour confirmed live: an unknown pair and a 2019 date both come
back None rather than falling through to some other number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
Standalone piece for pricing a back-dated payout at the day it was due.
Nothing calls it yet; the pricing modes land next.
LNbits cannot answer this question. Its eight exchange providers are spot
tickers with no date parameter, and the rate history behind the admin chart
lives in TransientSettings — RAM only, wiped on restart, capped at
lnbits_exchange_history_size points, and collected for a single currency.
Measured on bohm: after 5.4h of uptime the buffer held 5h of USD at a 60
point cap, having already evicted its oldest points, with 85 fetch failures
leaving visible gaps. Fine for a monitoring graph, not for pricing a salary.
Kraken is primary and CoinGecko the fallback, decided on request count: one
OHLC?interval=1440 call returns ~720 daily candles, so a twelve-period
backfill costs a single request and every date is served from the parsed
series. CoinGecko is addressed by date, so the same backfill would be
twelve requests into free-tier rate limits — but it covers currencies
Kraken lists no pair for, and reaches back further than Kraken's ~2 years.
Kraken is also already one of the providers LNbits itself trusts.
The series is read from whichever result key is not "last": Kraken's key is
not predictable ("XXBTZEUR", "XBTUSDT", ...) and reconstructing it is a
guess. An error payload deliberately does not populate the cache, so an
unknown pair retries rather than serving an empty series for six hours.
Neither provider is allowed to raise. A rate that cannot be established
returns None, and the caller must treat that as "do not pay" — the one
outcome this module must never produce is a plausible-looking wrong number.
sats_for rounds rather than truncates, because always rounding down would
quietly shortchange the employee over the life of a contract.
Tests stub the HTTP layer rather than the function under test, so the real
cache path runs — an earlier version stubbed _kraken_series and
re-implemented the caching in the test, which would have passed even with
the cache deleted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj