feat: bitcoin-ui CSS fix, HTTPS proxy support, deploy script improvements
Bitcoin UI: - Replace cdn.tailwindcss.com with locally bundled tailwind.css (CSP blocks external scripts) - Make all asset paths relative for nginx proxy compatibility - Add bitcoin-ui build/deploy to deploy-to-target.sh (was missing entirely) - Use --network host (bitcoin-ui proxies Bitcoin RPC at 127.0.0.1:8332) HTTPS mixed content fix: - Add HTTPS_PROXY_PATHS in AppSession.vue — when parent page is HTTPS, iframe loads through nginx proxy instead of direct HTTP port - Prevents browser blocking HTTP iframes inside HTTPS pages - All Tailscale servers use HTTPS, this was breaking all app iframes Deploy & first-boot improvements: - first-boot-containers.sh auto-detects disk size for pruning vs txindex - first-boot-containers.sh checks fallback source path for UI containers - Added mempool-electrs to APP_PORTS mapping - ElectrumX container creation in first-boot - Podman doctor/fix/uptime skills added Also includes: session persistence, identity management, LND transactions, ElectrumX status UI, nostr-provider improvements, Web5 enhancements Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4e54b8bd4d
commit
367b483a72
@@ -674,6 +674,129 @@ impl RpcHandler {
|
||||
"broadcast": true,
|
||||
}))
|
||||
}
|
||||
|
||||
/// List on-chain transactions from LND.
|
||||
/// Returns all transactions, with incoming (amount > 0) flagged.
|
||||
pub(super) async fn handle_lnd_gettransactions(&self) -> Result<serde_json::Value> {
|
||||
let (client, macaroon_hex) = self.lnd_client().await?;
|
||||
|
||||
let resp = client
|
||||
.get("https://127.0.0.1:8080/v1/transactions")
|
||||
.header("Grpc-Metadata-macaroon", &macaroon_hex)
|
||||
.send()
|
||||
.await
|
||||
.context("LND REST connection failed")?;
|
||||
|
||||
let status = resp.status();
|
||||
let body: serde_json::Value = resp
|
||||
.json()
|
||||
.await
|
||||
.context("Failed to parse transactions response")?;
|
||||
|
||||
if !status.is_success() {
|
||||
let msg = body
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Unknown error");
|
||||
return Err(anyhow::anyhow!("Failed to list transactions: {}", msg));
|
||||
}
|
||||
|
||||
let empty_vec = vec![];
|
||||
let raw_txs = body
|
||||
.get("transactions")
|
||||
.and_then(|v| v.as_array())
|
||||
.unwrap_or(&empty_vec);
|
||||
|
||||
let mut transactions: Vec<serde_json::Value> = Vec::new();
|
||||
for tx in raw_txs {
|
||||
let amount: i64 = tx
|
||||
.get("amount")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse().ok())
|
||||
.or_else(|| tx.get("amount").and_then(|v| v.as_i64()))
|
||||
.unwrap_or(0);
|
||||
|
||||
let num_confirmations: i64 = tx
|
||||
.get("num_confirmations")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0);
|
||||
|
||||
let tx_hash = tx
|
||||
.get("tx_hash")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let time_stamp: i64 = tx
|
||||
.get("time_stamp")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse().ok())
|
||||
.or_else(|| tx.get("time_stamp").and_then(|v| v.as_i64()))
|
||||
.unwrap_or(0);
|
||||
|
||||
let total_fees: i64 = tx
|
||||
.get("total_fees")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| s.parse().ok())
|
||||
.or_else(|| tx.get("total_fees").and_then(|v| v.as_i64()))
|
||||
.unwrap_or(0);
|
||||
|
||||
let dest_addresses: Vec<String> = tx
|
||||
.get("dest_addresses")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|a| a.as_str().map(|s| s.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let label = tx
|
||||
.get("label")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let block_height: i64 = tx
|
||||
.get("block_height")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0);
|
||||
|
||||
let direction = if amount > 0 { "incoming" } else { "outgoing" };
|
||||
|
||||
transactions.push(serde_json::json!({
|
||||
"tx_hash": tx_hash,
|
||||
"amount_sats": amount.abs(),
|
||||
"direction": direction,
|
||||
"num_confirmations": num_confirmations,
|
||||
"time_stamp": time_stamp,
|
||||
"total_fees": total_fees,
|
||||
"dest_addresses": dest_addresses,
|
||||
"label": label,
|
||||
"block_height": block_height,
|
||||
}));
|
||||
}
|
||||
|
||||
// Sort by timestamp descending (most recent first)
|
||||
transactions.sort_by(|a, b| {
|
||||
let ta = a.get("time_stamp").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
let tb = b.get("time_stamp").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
tb.cmp(&ta)
|
||||
});
|
||||
|
||||
let incoming_pending: usize = transactions
|
||||
.iter()
|
||||
.filter(|t| {
|
||||
t.get("direction").and_then(|v| v.as_str()) == Some("incoming")
|
||||
&& t.get("num_confirmations").and_then(|v| v.as_i64()) == Some(0)
|
||||
})
|
||||
.count();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"transactions": transactions,
|
||||
"incoming_pending_count": incoming_pending,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Channel types
|
||||
|
||||
Reference in New Issue
Block a user