| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 | 
							- use std::collections::HashMap;
 
- use std::str::FromStr;
 
- use axum::body::Body;
 
- use axum::extract::{Query, State};
 
- use axum::http::StatusCode;
 
- use axum::response::{Html, Response};
 
- use axum::Form;
 
- use ldk_node::bitcoin::Address;
 
- use maud::html;
 
- use serde::{Deserialize, Serialize};
 
- use crate::web::handlers::utils::deserialize_optional_u64;
 
- use crate::web::handlers::AppState;
 
- use crate::web::templates::{
 
-     error_message, form_card, format_sats_as_btc, info_card, layout, success_message,
 
- };
 
- #[derive(Deserialize, Serialize)]
 
- pub struct SendOnchainActionForm {
 
-     address: String,
 
-     #[serde(deserialize_with = "deserialize_optional_u64")]
 
-     amount_sat: Option<u64>,
 
-     send_action: String,
 
- }
 
- #[derive(Deserialize)]
 
- pub struct ConfirmOnchainForm {
 
-     address: String,
 
-     amount_sat: Option<u64>,
 
-     send_action: String,
 
-     confirmed: Option<String>,
 
- }
 
- pub async fn get_new_address(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
 
-     let address_result = state.node.inner.onchain_payment().new_address();
 
-     let content = match address_result {
 
-         Ok(address) => {
 
-             html! {
 
-                 (success_message(&format!("New address generated: {address}")))
 
-                 div class="card" {
 
-                     h2 { "Bitcoin Address" }
 
-                     div class="info-item" {
 
-                         span class="info-label" { "Address:" }
 
-                         span class="info-value" style="font-family: monospace; font-size: 0.9rem;" { (address.to_string()) }
 
-                     }
 
-                 }
 
-                 div class="card" {
 
-                     a href="/onchain" { button { "← Back to On-chain" } }
 
-                     " "
 
-                     a href="/onchain/new-address" { button { "Generate Another Address" } }
 
-                 }
 
-             }
 
-         }
 
-         Err(e) => {
 
-             html! {
 
-                 (error_message(&format!("Failed to generate address: {e}")))
 
-                 div class="card" {
 
-                     a href="/onchain" { button { "← Back to On-chain" } }
 
-                 }
 
-             }
 
-         }
 
-     };
 
-     Ok(Html(layout("New Address", content).into_string()))
 
- }
 
- pub async fn onchain_page(
 
-     State(state): State<AppState>,
 
-     query: Query<HashMap<String, String>>,
 
- ) -> Result<Html<String>, StatusCode> {
 
-     let balances = state.node.inner.list_balances();
 
-     let action = query
 
-         .get("action")
 
-         .map(|s| s.as_str())
 
-         .unwrap_or("overview");
 
-     let mut content = html! {
 
-         h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
 
-         // Quick Actions section - individual cards
 
-         div class="card" style="margin-bottom: 2rem;" {
 
-             h2 { "Quick Actions" }
 
-             div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
 
-                 // Receive Bitcoin Card
 
-                 div class="quick-action-card" {
 
-                     h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
 
-                     p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
 
-                     a href="/onchain?action=receive" style="text-decoration: none;" {
 
-                         button class="button-outline" { "Receive Bitcoin" }
 
-                     }
 
-                 }
 
-                 // Send Bitcoin Card
 
-                 div class="quick-action-card" {
 
-                     h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
 
-                     p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
 
-                     a href="/onchain?action=send" style="text-decoration: none;" {
 
-                         button class="button-outline" { "Send Bitcoin" }
 
-                     }
 
-                 }
 
-             }
 
-         }
 
-         // On-chain Balance as metric cards
 
-         div class="card" {
 
-             h2 { "On-chain Balance" }
 
-             div class="metrics-container" {
 
-                 div class="metric-card" {
 
-                     div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
 
-                     div class="metric-label" { "Total Balance" }
 
-                 }
 
-                 div class="metric-card" {
 
-                     div class="metric-value" { (format_sats_as_btc(balances.spendable_onchain_balance_sats)) }
 
-                     div class="metric-label" { "Spendable Balance" }
 
-                 }
 
-             }
 
-         }
 
-     };
 
-     match action {
 
-         "send" => {
 
-             content = html! {
 
-                 h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
 
-                 // Quick Actions section - individual cards
 
-                 div class="card" style="margin-bottom: 2rem;" {
 
-                     h2 { "Quick Actions" }
 
-                     div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
 
-                         // Receive Bitcoin Card
 
-                         div class="quick-action-card" {
 
-                             h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
 
-                             p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
 
-                             a href="/onchain?action=receive" style="text-decoration: none;" {
 
-                                 button class="button-outline" { "Receive Bitcoin" }
 
-                             }
 
-                         }
 
-                         // Send Bitcoin Card
 
-                         div class="quick-action-card" {
 
-                             h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
 
-                             p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
 
-                             a href="/onchain?action=send" style="text-decoration: none;" {
 
-                                 button class="button-outline" { "Send Bitcoin" }
 
-                             }
 
-                         }
 
-                     }
 
-                 }
 
-                 // Send form above balance
 
-                 (form_card(
 
-                     "Send On-chain Payment",
 
-                     html! {
 
-                         form method="post" action="/onchain/send" {
 
-                             div class="form-group" {
 
-                                 label for="address" { "Recipient Address" }
 
-                                 input type="text" id="address" name="address" required placeholder="bc1..." {}
 
-                             }
 
-                             div class="form-group" {
 
-                                 label for="amount_sat" { "Amount (sats)" }
 
-                                 input type="number" id="amount_sat" name="amount_sat" placeholder="0" {}
 
-                             }
 
-                             input type="hidden" id="send_action" name="send_action" value="send" {}
 
-                             div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
 
-                                 a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
 
-                                 div style="display: flex; gap: 0.5rem;" {
 
-                                     button type="submit" onclick="document.getElementById('send_action').value='send'" { "Send Payment" }
 
-                                     button type="submit" onclick="document.getElementById('send_action').value='send_all'; document.getElementById('amount_sat').value=''" { "Send All" }
 
-                                 }
 
-                             }
 
-                         }
 
-                     }
 
-                 ))
 
-                 // On-chain Balance as metric cards
 
-                 div class="card" {
 
-                     h2 { "On-chain Balance" }
 
-                     div class="metrics-container" {
 
-                         div class="metric-card" {
 
-                             div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
 
-                             div class="metric-label" { "Total Balance" }
 
-                         }
 
-                         div class="metric-card" {
 
-                             div class="metric-value" { (format_sats_as_btc(balances.spendable_onchain_balance_sats)) }
 
-                             div class="metric-label" { "Spendable Balance" }
 
-                         }
 
-                     }
 
-                 }
 
-             };
 
-         }
 
-         "receive" => {
 
-             content = html! {
 
-                 h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
 
-                 // Quick Actions section - individual cards
 
-                 div class="card" style="margin-bottom: 2rem;" {
 
-                     h2 { "Quick Actions" }
 
-                     div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
 
-                         // Receive Bitcoin Card
 
-                         div class="quick-action-card" {
 
-                             h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
 
-                             p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
 
-                             a href="/onchain?action=receive" style="text-decoration: none;" {
 
-                                 button class="button-outline" { "Receive Bitcoin" }
 
-                             }
 
-                         }
 
-                         // Send Bitcoin Card
 
-                         div class="quick-action-card" {
 
-                             h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
 
-                             p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
 
-                             a href="/onchain?action=send" style="text-decoration: none;" {
 
-                                 button class="button-outline" { "Send Bitcoin" }
 
-                             }
 
-                         }
 
-                     }
 
-                 }
 
-                 // Generate address form above balance
 
-                 (form_card(
 
-                     "Generate New Address",
 
-                     html! {
 
-                         form method="post" action="/onchain/new-address" {
 
-                             p style="margin-bottom: 2rem;" { "Click the button below to generate a new Bitcoin address for receiving on-chain payments." }
 
-                             div style="display: flex; justify-content: space-between; gap: 1rem;" {
 
-                                 a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
 
-                                 button class="button-primary" type="submit" { "Generate New Address" }
 
-                             }
 
-                         }
 
-                     }
 
-                 ))
 
-                 // On-chain Balance as metric cards
 
-                 div class="card" {
 
-                     h2 { "On-chain Balance" }
 
-                     div class="metrics-container" {
 
-                         div class="metric-card" {
 
-                             div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
 
-                             div class="metric-label" { "Total Balance" }
 
-                         }
 
-                         div class="metric-card" {
 
-                             div class="metric-value" { (format_sats_as_btc(balances.spendable_onchain_balance_sats)) }
 
-                             div class="metric-label" { "Spendable Balance" }
 
-                         }
 
-                     }
 
-                 }
 
-             };
 
-         }
 
-         _ => {
 
-             // Show overview with just the balance and quick actions at the top
 
-         }
 
-     }
 
-     Ok(Html(layout("On-chain", content).into_string()))
 
- }
 
- pub async fn post_send_onchain(
 
-     State(_state): State<AppState>,
 
-     Form(form): Form<SendOnchainActionForm>,
 
- ) -> Result<Response, StatusCode> {
 
-     let encoded_form =
 
-         serde_urlencoded::to_string(&form).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
 
-     Ok(Response::builder()
 
-         .status(StatusCode::FOUND)
 
-         .header("Location", format!("/onchain/confirm?{}", encoded_form))
 
-         .body(Body::empty())
 
-         .unwrap())
 
- }
 
- pub async fn onchain_confirm_page(
 
-     State(state): State<AppState>,
 
-     query: Query<ConfirmOnchainForm>,
 
- ) -> Result<Response, StatusCode> {
 
-     let form = query.0;
 
-     // If user confirmed, execute the transaction
 
-     if form.confirmed.as_deref() == Some("true") {
 
-         return execute_onchain_transaction(State(state), form).await;
 
-     }
 
-     // Validate address
 
-     let _address = match Address::from_str(&form.address) {
 
-         Ok(addr) => addr,
 
-         Err(e) => {
 
-             let content = html! {
 
-                 (error_message(&format!("Invalid address: {e}")))
 
-                 div class="card" {
 
-                     a href="/onchain?action=send" { button { "← Back" } }
 
-                 }
 
-             };
 
-             return Ok(Response::builder()
 
-                 .status(StatusCode::BAD_REQUEST)
 
-                 .header("content-type", "text/html")
 
-                 .body(Body::from(
 
-                     layout("Send On-chain Error", content).into_string(),
 
-                 ))
 
-                 .unwrap());
 
-         }
 
-     };
 
-     let balances = state.node.inner.list_balances();
 
-     let spendable_balance = balances.spendable_onchain_balance_sats;
 
-     // Calculate transaction details
 
-     let (amount_to_send, is_send_all) = if form.send_action == "send_all" {
 
-         (spendable_balance, true)
 
-     } else {
 
-         let amount = form.amount_sat.unwrap_or(0);
 
-         if amount > spendable_balance {
 
-             let content = html! {
 
-                 (error_message(&format!("Insufficient funds. Requested: {}, Available: {}",
 
-                     format_sats_as_btc(amount), format_sats_as_btc(spendable_balance))))
 
-                 div class="card" {
 
-                     a href="/onchain?action=send" { button { "← Back" } }
 
-                 }
 
-             };
 
-             return Ok(Response::builder()
 
-                 .status(StatusCode::BAD_REQUEST)
 
-                 .header("content-type", "text/html")
 
-                 .body(Body::from(
 
-                     layout("Send On-chain Error", content).into_string(),
 
-                 ))
 
-                 .unwrap());
 
-         }
 
-         (amount, false)
 
-     };
 
-     let confirmation_url = if form.send_action == "send_all" {
 
-         format!(
 
-             "/onchain/confirm?address={}&send_action={}&confirmed=true",
 
-             urlencoding::encode(&form.address),
 
-             form.send_action
 
-         )
 
-     } else {
 
-         format!(
 
-             "/onchain/confirm?address={}&amount_sat={}&send_action={}&confirmed=true",
 
-             urlencoding::encode(&form.address),
 
-             form.amount_sat.unwrap_or(0),
 
-             form.send_action
 
-         )
 
-     };
 
-     let content = html! {
 
-         h2 style="text-align: center; margin-bottom: 3rem;" { "Confirm On-chain Transaction" }
 
-         div class="card" style="border: 2px solid hsl(var(--primary)); background-color: hsl(var(--primary) / 0.05);" {
 
-             h2 { "⚠️ Transaction Confirmation" }
 
-             p style="color: hsl(var(--muted-foreground)); margin-bottom: 1.5rem;" {
 
-                 "Please review the transaction details carefully before proceeding. This action cannot be undone."
 
-             }
 
-         }
 
-         (info_card(
 
-             "Transaction Details",
 
-             vec![
 
-                 ("Recipient Address", form.address.clone()),
 
-                 ("Amount to Send", if is_send_all {
 
-                     format!("{} (All available funds)", format_sats_as_btc(amount_to_send))
 
-                 } else {
 
-                     format_sats_as_btc(amount_to_send)
 
-                 }),
 
-                 ("Current Spendable Balance", format_sats_as_btc(spendable_balance)),
 
-             ]
 
-         ))
 
-         @if is_send_all {
 
-             div class="card" style="border: 1px solid hsl(32.6 75.4% 55.1%); background-color: hsl(32.6 75.4% 55.1% / 0.1);" {
 
-                 h3 style="color: hsl(32.6 75.4% 55.1%);" { "Send All Notice" }
 
-                 p style="color: hsl(32.6 75.4% 55.1%);" {
 
-                     "This transaction will send all available funds to the recipient address. "
 
-                     "Network fees will be deducted from the total amount automatically."
 
-                 }
 
-             }
 
-         }
 
-         div class="card" {
 
-             div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
 
-                 a href="/onchain?action=send" {
 
-                     button type="button" class="button-secondary" { "Cancel" }
 
-                 }
 
-                 div style="display: flex; gap: 0.5rem;" {
 
-                     a href=(confirmation_url) {
 
-                         button class="button-primary" {
 
-                             "✓ Confirm & Send Transaction"
 
-                         }
 
-                     }
 
-                 }
 
-             }
 
-         }
 
-     };
 
-     Ok(Response::builder()
 
-         .header("content-type", "text/html")
 
-         .body(Body::from(
 
-             layout("Confirm Transaction", content).into_string(),
 
-         ))
 
-         .unwrap())
 
- }
 
- async fn execute_onchain_transaction(
 
-     State(state): State<AppState>,
 
-     form: ConfirmOnchainForm,
 
- ) -> Result<Response, StatusCode> {
 
-     tracing::info!(
 
-         "Web interface: Executing on-chain transaction to address={}, send_action={}, amount_sat={:?}",
 
-         form.address,
 
-         form.send_action,
 
-         form.amount_sat
 
-     );
 
-     let address = match Address::from_str(&form.address) {
 
-         Ok(addr) => addr,
 
-         Err(e) => {
 
-             tracing::warn!(
 
-                 "Web interface: Invalid address for on-chain transaction: {}",
 
-                 e
 
-             );
 
-             let content = html! {
 
-                 (error_message(&format!("Invalid address: {e}")))
 
-                 div class="card" {
 
-                     a href="/onchain" { button { "← Back" } }
 
-                 }
 
-             };
 
-             return Ok(Response::builder()
 
-                 .status(StatusCode::BAD_REQUEST)
 
-                 .header("content-type", "text/html")
 
-                 .body(Body::from(
 
-                     layout("Send On-chain Error", content).into_string(),
 
-                 ))
 
-                 .unwrap());
 
-         }
 
-     };
 
-     // Handle send all action
 
-     let txid_result = if form.send_action == "send_all" {
 
-         tracing::info!(
 
-             "Web interface: Sending all available funds to {}",
 
-             form.address
 
-         );
 
-         state.node.inner.onchain_payment().send_all_to_address(
 
-             address.assume_checked_ref(),
 
-             false,
 
-             None,
 
-         )
 
-     } else {
 
-         let amount_sats = form.amount_sat.ok_or(StatusCode::BAD_REQUEST)?;
 
-         tracing::info!(
 
-             "Web interface: Sending {} sats to {}",
 
-             amount_sats,
 
-             form.address
 
-         );
 
-         state.node.inner.onchain_payment().send_to_address(
 
-             address.assume_checked_ref(),
 
-             amount_sats,
 
-             None,
 
-         )
 
-     };
 
-     let content = match txid_result {
 
-         Ok(txid) => {
 
-             if form.send_action == "send_all" {
 
-                 tracing::info!(
 
-                     "Web interface: Successfully sent all available funds, txid={}",
 
-                     txid
 
-                 );
 
-             } else {
 
-                 tracing::info!(
 
-                     "Web interface: Successfully sent {} sats, txid={}",
 
-                     form.amount_sat.unwrap_or(0),
 
-                     txid
 
-                 );
 
-             }
 
-             let amount = form.amount_sat;
 
-             html! {
 
-                         (success_message("Transaction sent successfully!"))
 
-                         (info_card(
 
-                             "Transaction Details",
 
-                             vec![
 
-                                 ("Transaction ID", txid.to_string()),
 
-                                 ("Amount", if form.send_action == "send_all" {
 
-                                     format!("{} (All available funds)", format_sats_as_btc(amount.unwrap_or(0)))
 
-                                 } else {
 
-                                     format_sats_as_btc(form.amount_sat.unwrap_or(0))
 
-                                 }),
 
-                                 ("Recipient", form.address),
 
-                             ]
 
-                         ))
 
-                         div class="card" {
 
-                             a href="/onchain" { button { "← Back to On-chain" } }
 
-                         }
 
-             }
 
-         }
 
-         Err(e) => {
 
-             tracing::error!("Web interface: Failed to send on-chain transaction: {}", e);
 
-             html! {
 
-                 (error_message(&format!("Failed to send payment: {e}")))
 
-                 div class="card" {
 
-                     a href="/onchain" { button { "← Try Again" } }
 
-                 }
 
-             }
 
-         }
 
-     };
 
-     Ok(Response::builder()
 
-         .header("content-type", "text/html")
 
-         .body(Body::from(
 
-             layout("Send On-chain Result", content).into_string(),
 
-         ))
 
-         .unwrap())
 
- }
 
 
  |