mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat(wallet): unified GET /wallet/heights (tip per chain)
The wallet's ChainProvider seam fetches all chain tips in one call
(getBlockchainHeights) and selects per asset; the backend exposed per-chain
height endpoints but no aggregate, so the client's call 404'd. Add a
best-effort /wallet/heights returning {btc,ltc,xmr,wow,grin} concurrently — a
disabled or unreachable chain is null, never failing the others. JWT-gated,
OpenAPI-annotated (drift clean).
This commit is contained in:
@@ -1040,6 +1040,35 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/wallet/heights": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"system"
|
||||
],
|
||||
"summary": "Current tip height for every enabled chain (one round-trip).",
|
||||
"operationId": "heights",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Tip height per chain (null if disabled/unreachable)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HeightsResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/wallet/lws/balance": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -2366,6 +2395,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"HeightsResponse": {
|
||||
"type": "object",
|
||||
"description": "Current tip height per chain. `null` = the chain is disabled on this instance\nor its source was unreachable for this request.",
|
||||
"properties": {
|
||||
"btc": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64",
|
||||
"example": 955860
|
||||
},
|
||||
"grin": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64"
|
||||
},
|
||||
"ltc": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64"
|
||||
},
|
||||
"wow": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64"
|
||||
},
|
||||
"xmr": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HistoryEntry": {
|
||||
"type": "object",
|
||||
"description": "A transaction-history entry.",
|
||||
|
||||
@@ -58,6 +58,7 @@ use utoipa::OpenApi;
|
||||
crate::api::wallet::grin_relay::respond,
|
||||
crate::api::wallet::grin_relay::finalize,
|
||||
crate::api::wallet::grin_relay::cancel,
|
||||
crate::api::wallet::heights::heights,
|
||||
crate::api::nip05::well_known_nostr,
|
||||
),
|
||||
components(schemas(
|
||||
@@ -132,6 +133,7 @@ use utoipa::OpenApi;
|
||||
crate::api::wallet::grin_relay::SlateIdRequest,
|
||||
crate::api::wallet::grin_relay::RelayEntry,
|
||||
crate::api::wallet::grin_relay::PendingRelaysResponse,
|
||||
crate::api::wallet::heights::HeightsResponse,
|
||||
crate::api::nip05::WellKnownResponse,
|
||||
)),
|
||||
tags(
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
//! Unified blockchain-tip heights across all enabled chains.
|
||||
//!
|
||||
//! The wallet's ChainProvider seam fetches every chain's tip in a single call
|
||||
//! and selects per asset, so one `GET /wallet/heights` returns them together.
|
||||
//! Best-effort: a disabled chain (or a momentarily-unreachable source) is
|
||||
//! reported `null` rather than failing the whole response.
|
||||
//!
|
||||
//! JWT-gated like the rest of the wallet surface (abuse-gating only; tip height
|
||||
//! is public chain data and is not user-specific).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::State, http::HeaderMap, routing::get, Json, Router};
|
||||
use serde::Serialize;
|
||||
use tracing::instrument;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::api::middleware::extract_user_id_from_token;
|
||||
use crate::error::AppError;
|
||||
use crate::AppState;
|
||||
|
||||
/// Current tip height per chain. `null` = the chain is disabled on this instance
|
||||
/// or its source was unreachable for this request.
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct HeightsResponse {
|
||||
#[schema(example = 955860)]
|
||||
pub btc: Option<i64>,
|
||||
pub ltc: Option<i64>,
|
||||
pub xmr: Option<i64>,
|
||||
pub wow: Option<i64>,
|
||||
pub grin: Option<i64>,
|
||||
}
|
||||
|
||||
/// Current tip height for every enabled chain (one round-trip).
|
||||
#[utoipa::path(
|
||||
security(("bearer_auth" = [])),
|
||||
get,
|
||||
path = "/wallet/heights",
|
||||
responses(
|
||||
(status = 200, description = "Tip height per chain (null if disabled/unreachable)", body = HeightsResponse),
|
||||
(status = 401, description = "Missing or invalid token")
|
||||
),
|
||||
tag = "system"
|
||||
)]
|
||||
#[instrument(skip(state, headers))]
|
||||
pub async fn heights(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<HeightsResponse>, AppError> {
|
||||
extract_user_id_from_token(&state, &headers).await?;
|
||||
let c = &state.chains;
|
||||
// Concurrent best-effort: each chain's failure collapses to `None`, never
|
||||
// failing the others. Disabled chains short-circuit without a network call.
|
||||
let (btc, ltc, xmr, wow, grin) = tokio::join!(
|
||||
async {
|
||||
match &c.btc {
|
||||
Some(e) => e.get_tip_height().await.ok(),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async {
|
||||
match &c.ltc {
|
||||
Some(e) => e.get_tip_height().await.ok(),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async {
|
||||
match &c.xmr {
|
||||
Some(l) => l.get_blockchain_height().await.ok().map(|h| h as i64),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async {
|
||||
match &c.wow {
|
||||
Some(l) => l.get_blockchain_height().await.ok().map(|h| h as i64),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async {
|
||||
match &c.grin {
|
||||
Some(g) => g.get_height().await.ok().map(|h| h as i64),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
);
|
||||
Ok(Json(HeightsResponse {
|
||||
btc,
|
||||
ltc,
|
||||
xmr,
|
||||
wow,
|
||||
grin,
|
||||
}))
|
||||
}
|
||||
|
||||
/// `GET /wallet/heights`, RELATIVE to the `/api/v1` mount point.
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new().route("/wallet/heights", get(heights))
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use crate::AppState;
|
||||
pub mod btc_ltc;
|
||||
pub mod grin;
|
||||
pub mod grin_relay;
|
||||
pub mod heights;
|
||||
pub mod xmr_wow;
|
||||
|
||||
/// All wallet routes, RELATIVE to the `/api/v1` mount point.
|
||||
@@ -22,6 +23,7 @@ pub fn routes() -> Router<Arc<AppState>> {
|
||||
.merge(xmr_wow::routes())
|
||||
.merge(grin::routes())
|
||||
.merge(grin_relay::routes())
|
||||
.merge(heights::routes())
|
||||
}
|
||||
|
||||
// ── shared input validators ───────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user