feat(mcp,chain,dao): support Koios paid-tier bearer via ALDABRA_KOIOS_BEARER env

Adds optional Authorization: Bearer <token> on every Koios request,
sourced from ALDABRA_KOIOS_BEARER env var only — never from the
on-disk config.toml, never from CLI args, never hardcoded. Bearers
are credentials and the on-disk config dir gets routinely backed up;
keeping them env-only guarantees rotations don't leak into snapshots.

Wired through three Koios clients:
- aldabra-chain::KoiosClient — new with_timeout_and_bearer ctor;
  legacy new() / with_timeout() route through it with bearer=None.
- aldabra-dao::KoiosDaoReader — new with_bearer ctor; ditto.
- aldabra-dao::KoiosDiscoveryClient — new with_bearer ctor; ditto.

Bearer is set as a default header on the reqwest client builder so
every request inherits it without per-call boilerplate.
HeaderValue::set_sensitive(true) prevents the value from showing
in reqwest's debug-format output.

Config wiring (aldabra-mcp::config::Config):
- New koios_bearer: Option<String> field. Loaded ONLY from
  ALDABRA_KOIOS_BEARER env var; absent or empty-string means None.
- Startup tracing logs koios_bearer_set: bool — never the value.

WalletInner caches the bearer alongside the koios_base so the
on-demand KoiosDiscoveryClient (constructed inside
dao_discover_scripts) inherits paid-tier auth too.

Motivation: 2026-05-08 preprod_test2 bringup tripped Koios free-tier
daily quota (5240 req/day, 'Exceeded Tier Limit') mid-deploy. Sulkta
provided a paid-tier JWT (Aldabra project, exp 2026-06-26). Wiring
via env var lets the operator (systemd EnvironmentFile, docker run
-e, or k8s Secret) inject it without touching code or config files.
This commit is contained in:
Sulkta 2026-05-08 10:19:06 -07:00
parent 9547bd95e4
commit 48679883ec
6 changed files with 106 additions and 17 deletions

View file

@ -108,17 +108,43 @@ pub struct KoiosClient {
}
impl KoiosClient {
/// Construct a client with the default 10-second timeout.
/// Construct a client with the default 10-second timeout and no
/// bearer (public-tier; subject to free-tier daily quotas).
pub fn new(base_url: impl Into<String>) -> Self {
Self::with_timeout(base_url, DEFAULT_TIMEOUT)
Self::with_timeout_and_bearer(base_url, DEFAULT_TIMEOUT, None)
}
/// Construct a client with a custom request timeout.
/// Construct a client with a custom request timeout, no bearer.
pub fn with_timeout(base_url: impl Into<String>, timeout: Duration) -> Self {
Self::with_timeout_and_bearer(base_url, timeout, None)
}
/// Construct a client with optional `Authorization: Bearer <token>`
/// applied to every request. Used for paid-tier Koios access — the
/// JWT comes from the operator-supplied `ALDABRA_KOIOS_BEARER` env
/// var (NEVER from the on-disk config, NEVER hardcoded). Pass
/// `None` for the free public tier.
pub fn with_timeout_and_bearer(
base_url: impl Into<String>,
timeout: Duration,
bearer: Option<&str>,
) -> Self {
let mut builder = Client::builder().timeout(timeout);
if let Some(token) = bearer {
// Default header is applied to every request the client
// emits — request-level overrides still possible but no
// builder code path needs to remember to set it.
let mut hdrs = reqwest::header::HeaderMap::new();
let value = format!("Bearer {token}");
let mut hv = reqwest::header::HeaderValue::from_str(&value)
.expect("ALDABRA_KOIOS_BEARER contains invalid header bytes");
hv.set_sensitive(true);
hdrs.insert(reqwest::header::AUTHORIZATION, hv);
builder = builder.default_headers(hdrs);
}
Self {
base_url: base_url.into(),
http: Client::builder()
.timeout(timeout)
http: builder
.build()
.expect("reqwest client builds with rustls + json features"),
}