mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat: wire chain clients into AppState (feature-gated)
Add ChainClients (infra/chains.rs): per-chain data-source handles built once at startup from config and shared via AppState. Each client is present only when its chain feature flag is on, and a malformed chain endpoint now fails startup (fail-closed) rather than the first request. Cloneable (grin's session state is shared via Arc) so it lives in the cloneable AppState. Foundation for the wallet-handlers phase — handlers will read these clients from State. No endpoints yet (openapi unchanged).
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
//! Per-chain client handles, constructed once at startup and shared via
|
||||
//! [`crate::AppState`]. Each chain's client is config-pointed at an external node
|
||||
//! service (Fulcrum/Electrum, monero/wownero-lws, grin-wallet + grin node) and is
|
||||
//! present only when that chain's feature flag is enabled. A malformed endpoint
|
||||
//! fails startup (fail-closed), not the first request.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::AppError;
|
||||
use crate::infra::electrum::ElectrumClient;
|
||||
use crate::infra::grin::GrinClient;
|
||||
use crate::infra::lws::LwsClient;
|
||||
|
||||
/// The enabled chains' data-source clients. Cloneable (the heavyweight grin
|
||||
/// session state is shared via `Arc`) so it can live in the cloneable `AppState`.
|
||||
#[derive(Clone)]
|
||||
pub struct ChainClients {
|
||||
pub btc: Option<ElectrumClient>,
|
||||
pub ltc: Option<ElectrumClient>,
|
||||
pub xmr: Option<LwsClient>,
|
||||
pub wow: Option<LwsClient>,
|
||||
pub grin: Option<Arc<GrinClient>>,
|
||||
}
|
||||
|
||||
impl ChainClients {
|
||||
/// Build clients for the enabled chains from config.
|
||||
pub fn from_config(cfg: &Config) -> Result<Self, AppError> {
|
||||
let flags = &cfg.features.chains;
|
||||
Ok(Self {
|
||||
btc: flags
|
||||
.btc
|
||||
.then(|| ElectrumClient::bitcoin(&cfg.chains.btc))
|
||||
.transpose()?,
|
||||
ltc: flags
|
||||
.ltc
|
||||
.then(|| ElectrumClient::litecoin(&cfg.chains.ltc))
|
||||
.transpose()?,
|
||||
xmr: flags
|
||||
.xmr
|
||||
.then(|| LwsClient::monero(&cfg.chains.xmr))
|
||||
.transpose()?,
|
||||
wow: flags
|
||||
.wow
|
||||
.then(|| LwsClient::wownero(&cfg.chains.wow))
|
||||
.transpose()?,
|
||||
grin: flags
|
||||
.grin
|
||||
.then(|| GrinClient::new(&cfg.chains.grin).map(Arc::new))
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Infrastructure layer: adapters for external services (database, chain nodes,
|
||||
//! light-wallet servers). Chain clients are wired in as they land.
|
||||
|
||||
pub mod chains;
|
||||
pub mod db;
|
||||
pub mod electrum;
|
||||
pub mod grin;
|
||||
|
||||
@@ -23,6 +23,7 @@ use tokio::sync::RwLock;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::core::session::{SessionManager, WebChallenge};
|
||||
use crate::infra::chains::ChainClients;
|
||||
use crate::infra::db::Database;
|
||||
|
||||
/// Shared application state injected into handlers via `State<Arc<AppState>>`.
|
||||
@@ -31,6 +32,8 @@ pub struct AppState {
|
||||
pub config: Config,
|
||||
pub db: Database,
|
||||
pub sessions: SessionManager,
|
||||
/// Per-chain data-source clients (present only for enabled chains).
|
||||
pub chains: ChainClients,
|
||||
/// In-memory website-auth challenges, keyed by nonce. Single-node store;
|
||||
/// a shared/stateless variant is the load-balanced-fleet path.
|
||||
pub web_challenges: Arc<RwLock<HashMap<String, WebChallenge>>>,
|
||||
|
||||
+4
-1
@@ -9,7 +9,8 @@ use std::sync::Arc;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
|
||||
use smirk_backend_core::{
|
||||
build_router, config::Config, core::session::SessionManager, infra::db::Database, AppState,
|
||||
build_router, config::Config, core::session::SessionManager, infra::chains::ChainClients,
|
||||
infra::db::Database, AppState,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
@@ -33,12 +34,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
config.secrets.ip_salt.clone(),
|
||||
);
|
||||
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
||||
let chains = ChainClients::from_config(&config)?;
|
||||
|
||||
let addr = format!("{}:{}", config.server_host, config.server_port);
|
||||
let state = Arc::new(AppState {
|
||||
config,
|
||||
db,
|
||||
sessions,
|
||||
chains,
|
||||
web_challenges: Arc::default(),
|
||||
});
|
||||
|
||||
|
||||
+4
-1
@@ -21,7 +21,8 @@ use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
use smirk_backend_core::{
|
||||
build_router, config::Config, core::session::SessionManager, infra::db::Database, AppState,
|
||||
build_router, config::Config, core::session::SessionManager, infra::chains::ChainClients,
|
||||
infra::db::Database, AppState,
|
||||
};
|
||||
|
||||
static INIT_ENV: Once = Once::new();
|
||||
@@ -68,10 +69,12 @@ pub async fn try_app() -> Option<TestApp> {
|
||||
config.secrets.ip_salt.clone(),
|
||||
);
|
||||
let sessions = SessionManager::new(&config.auth.jwt_secret, config.auth.jwt_expiry_hours);
|
||||
let chains = ChainClients::from_config(&config).expect("build chain clients");
|
||||
let state = Arc::new(AppState {
|
||||
config,
|
||||
db,
|
||||
sessions,
|
||||
chains,
|
||||
web_challenges: Arc::default(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user