mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat: XMR/WOW wallet handlers (LWS proxy, stateless-forward)
Second wallet-handler slice. Authenticated, non-custodial, stateless-forward proxies over the LwsClient: the wallet sends its private view key per request, the backend forwards it (never stores it; the LWS holds it for scanning), and the wallet constructs + signs spends locally. Endpoints (POST, under /api/v1/wallet/lws, asset = xmr|wow in body): - register -> add/import account (view key) for LWS scanning (+start_height) - balance -> total_received / unlocked / locked + scan state - history -> txs incl. mempool pending + candidate spent_outputs - unspent_outs -> outputs + fee params for client-side spend construction - random_outs -> ring decoys (count clamped + fan-out capped in the client) - submit_tx -> broadcast a finalized signed tx - height -> daemon chain height - confirmations-> tx confirmation count - JWT-gated; the view key is skipped from every tracing span (never logged) and never stored. Feature-gated dispatch; unknown/disabled asset -> 400. - Boundary validation: view_key (64 hex), address (bounded), txid (64 hex), tx_hex (even hex, <=2 MiB) — all before any LWS call. - Shared validators in wallet/mod.rs (validate_hex/view_key/cn_address). - Colliding DTO names disambiguated (Lws-prefixed) so the openapi schema registry stays correct; 8 paths + schemas + xmr_wow tag registered; spec regenerated, drift-clean. 6 L1 integration tests (auth gate on balance/register; unknown-asset, bad-view-key, malformed-tx-hex, malformed-txid 400s) + validator unit tests.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//! L1 integration: Monero/Wownero wallet endpoints through the real router.
|
||||
//!
|
||||
//! Deterministic, no-network paths only: the JWT gate, asset validation, and
|
||||
//! view-key / tx-hex / txid validation — all resolved before any LWS call. The
|
||||
//! funded/networked happy paths live in the separate gitignored L3 harness.
|
||||
|
||||
mod common;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn balance_requires_a_token() {
|
||||
let app = require_app!();
|
||||
let (status, _) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/balance",
|
||||
None,
|
||||
Some(json!({ "asset": "xmr", "address": "4Addr", "view_key": "00" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn balance_rejects_unknown_asset() {
|
||||
let app = require_app!();
|
||||
let (_uid, access, _r) = app.mint_session().await;
|
||||
let (status, body) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/balance",
|
||||
Some(&access),
|
||||
Some(json!({ "asset": "doge", "address": "x", "view_key": "00" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["code"], "VALIDATION_ERROR");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn balance_rejects_malformed_view_key() {
|
||||
let app = require_app!();
|
||||
let (_uid, access, _r) = app.mint_session().await;
|
||||
// Authed + valid asset + address, but the view key is not 64 hex -> 400
|
||||
// before any LWS call.
|
||||
let (status, body) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/balance",
|
||||
Some(&access),
|
||||
Some(json!({ "asset": "xmr", "address": "4SomeAddress", "view_key": "tooshort" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["code"], "VALIDATION_ERROR");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_requires_a_token() {
|
||||
let app = require_app!();
|
||||
let (status, _) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/register",
|
||||
None,
|
||||
Some(json!({ "asset": "xmr", "address": "4Addr", "view_key": "00" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn submit_rejects_malformed_tx_hex() {
|
||||
let app = require_app!();
|
||||
let (_uid, access, _r) = app.mint_session().await;
|
||||
let (status, body) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/submit_tx",
|
||||
Some(&access),
|
||||
Some(json!({ "asset": "xmr", "tx_hex": "nothex!!" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["code"], "VALIDATION_ERROR");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn confirmations_rejects_malformed_txid() {
|
||||
let app = require_app!();
|
||||
let (_uid, access, _r) = app.mint_session().await;
|
||||
let (status, body) = app
|
||||
.request(
|
||||
"POST",
|
||||
"/api/v1/wallet/lws/confirmations",
|
||||
Some(&access),
|
||||
Some(json!({ "asset": "xmr", "txid": "not-a-txid" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::BAD_REQUEST);
|
||||
assert_eq!(body["code"], "VALIDATION_ERROR");
|
||||
}
|
||||
Reference in New Issue
Block a user