feat: GET /api/v1/capabilities (client feature discovery)

Public, unauthenticated description of what this deployment offers, so the
wallet can adapt to the instance it's pointed at — grey out disabled chains and
features, and pick the right network. This is the client-facing half of the
open-core/feature-flag design: every capability is a config switch, surfaced
here. No secrets — only on/off flags and public network names.

Response: { version, chains: { btc/ltc/xmr/wow/grin: { enabled, network } },
features: { grin_relay, prices, nostr_identity, tips } }. UTXO chains carry
their configured network; CryptoNote/Grin report null. On the normal rate-limit
tier. Registered in openapi (system tag); 1 L1 test (public, well-shaped).
This commit is contained in:
jwinterm
2026-06-25 16:27:13 -04:00
parent c558e9d7bd
commit 1e2bdf3f16
6 changed files with 254 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
//! Capability discovery.
//!
//! A public, unauthenticated description of what *this* deployment offers, so the
//! wallet can adapt to the instance it's pointed at (grey out disabled chains and
//! features, pick the right network). This is the client-facing half of the
//! open-core/feature-flag design: every capability is a config switch, surfaced
//! here. No secrets — only on/off flags and the public network names.
use std::sync::Arc;
use axum::{extract::State, routing::get, Json, Router};
use serde::Serialize;
use tracing::instrument;
use crate::AppState;
/// Per-chain availability. `network` is the configured network for UTXO chains
/// (so the wallet derives addresses for the right one); `null` for chains whose
/// network isn't a backend setting.
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct ChainCapability {
pub enabled: bool,
pub network: Option<String>,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct ChainCapabilities {
pub btc: ChainCapability,
pub ltc: ChainCapability,
pub xmr: ChainCapability,
pub wow: ChainCapability,
pub grin: ChainCapability,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct FeatureCapabilities {
/// Grin async slatepack relay mailbox.
pub grin_relay: bool,
/// Fiat price feed.
pub prices: bool,
/// Nostr-native identity (NIP-98 login/link, NIP-05 directory).
pub nostr_identity: bool,
/// Tipping (parked).
pub tips: bool,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct CapabilitiesResponse {
/// Backend version (Cargo package version).
pub version: String,
pub chains: ChainCapabilities,
pub features: FeatureCapabilities,
}
/// Describe this instance's enabled chains and features.
#[utoipa::path(
get,
path = "/capabilities",
responses((status = 200, description = "Enabled chains and features", body = CapabilitiesResponse)),
tag = "system"
)]
#[instrument(skip(state))]
pub async fn capabilities(State(state): State<Arc<AppState>>) -> Json<CapabilitiesResponse> {
let cfg = &state.config;
let chains = &cfg.features.chains;
Json(CapabilitiesResponse {
version: env!("CARGO_PKG_VERSION").to_string(),
chains: ChainCapabilities {
btc: ChainCapability {
enabled: chains.btc,
network: Some(cfg.chains.btc.network.clone()),
},
ltc: ChainCapability {
enabled: chains.ltc,
network: Some(cfg.chains.ltc.network.clone()),
},
xmr: ChainCapability {
enabled: chains.xmr,
network: None,
},
wow: ChainCapability {
enabled: chains.wow,
network: None,
},
grin: ChainCapability {
enabled: chains.grin,
network: None,
},
},
features: FeatureCapabilities {
grin_relay: cfg.features.grin_relay,
prices: cfg.features.prices,
nostr_identity: cfg.features.nostr_identity,
tips: cfg.features.tips,
},
})
}
/// Capability route, RELATIVE to the `/api/v1` mount point. Public (no auth).
pub fn routes() -> Router<Arc<AppState>> {
Router::new().route("/capabilities", get(capabilities))
}
+1
View File
@@ -1,6 +1,7 @@
//! HTTP API layer: handlers and the OpenAPI assembly.
pub mod auth;
pub mod capabilities;
pub mod health;
pub mod middleware;
pub mod nip05;
+5
View File
@@ -16,6 +16,7 @@ use utoipa::OpenApi;
),
paths(
crate::api::health::health,
crate::api::capabilities::capabilities,
crate::api::auth::pow_challenge,
crate::api::auth::extension_register,
crate::api::auth::check_restore,
@@ -59,6 +60,10 @@ use utoipa::OpenApi;
),
components(schemas(
crate::api::health::HealthResponse,
crate::api::capabilities::CapabilitiesResponse,
crate::api::capabilities::ChainCapabilities,
crate::api::capabilities::ChainCapability,
crate::api::capabilities::FeatureCapabilities,
crate::api::auth::AuthResponse,
crate::api::auth::UserInfo,
crate::api::auth::AssetPublicKey,