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