diff --git a/core/archipelago/src/api/handler/content.rs b/core/archipelago/src/api/handler/content.rs index 2074a68e..e4a424c0 100644 --- a/core/archipelago/src/api/handler/content.rs +++ b/core/archipelago/src/api/handler/content.rs @@ -222,8 +222,12 @@ impl ApiHandler { hyper::Body::from(r#"{"error":"Invoice missing payment hash"}"#), )), Err(e) => { + // Surface the FULL error chain ({:#}) — the generic top-level + // message hid the real cause (e.g. the LND REST connection + // failing), which made this 503 undiagnosable. + tracing::warn!("content invoice creation failed: {e:#}"); let body = serde_json::json!({ - "error": format!("Could not create invoice: {e}") + "error": format!("Could not create invoice: {e:#}") }); Ok(build_response( StatusCode::SERVICE_UNAVAILABLE, diff --git a/core/archipelago/src/api/rpc/lnd/wallet.rs b/core/archipelago/src/api/rpc/lnd/wallet.rs index b479467c..1981048c 100644 --- a/core/archipelago/src/api/rpc/lnd/wallet.rs +++ b/core/archipelago/src/api/rpc/lnd/wallet.rs @@ -173,13 +173,42 @@ impl RpcHandler { "value": amount_sats.to_string(), "memo": memo, }); - let resp = client - .post(format!("{LND_REST_BASE_URL}/v1/invoices")) - .header("Grpc-Metadata-macaroon", &macaroon_hex) - .json(&invoice_body) - .send() - .await - .context("Failed to create invoice")?; + // LND's REST endpoint can briefly drop/reset connections under load + // (swap pressure, just-restarted, TLS handshake races), which used to + // hard-fail the buy-file invoice with an opaque 503. Retry the send a + // few times with short backoff so a transient blip doesn't surface as + // a payment failure. The surrounding error now carries the real cause. + let mut last_err: Option = None; + let mut resp = None; + for attempt in 0..3u32 { + match client + .post(format!("{LND_REST_BASE_URL}/v1/invoices")) + .header("Grpc-Metadata-macaroon", &macaroon_hex) + .json(&invoice_body) + .send() + .await + { + Ok(r) => { + resp = Some(r); + break; + } + Err(e) => { + last_err = Some(anyhow::anyhow!( + "LND REST connect failed (attempt {}): {e}", + attempt + 1 + )); + tokio::time::sleep(std::time::Duration::from_millis(400)).await; + } + } + } + let resp = match resp { + Some(r) => r, + None => { + return Err(last_err.unwrap_or_else(|| { + anyhow::anyhow!("Failed to reach LND REST to create invoice") + })) + } + }; let status = resp.status(); let body: serde_json::Value = resp diff --git a/core/archipelago/src/wallet/ecash.rs b/core/archipelago/src/wallet/ecash.rs index 81ce5a43..03d9b9da 100644 --- a/core/archipelago/src/wallet/ecash.rs +++ b/core/archipelago/src/wallet/ecash.rs @@ -1175,8 +1175,13 @@ pub async fn get_balance(data_dir: &Path) -> Result { } /// Default mint URL (local Fedimint). +/// Default Cashu mint. Minibits is a well-known public Cashu mint — note this +/// is a CASHU mint, distinct from the local Fedimint guardian (:8175), which is +/// a separate ecash protocol managed under the Fedimint Federations tab. The +/// old default pointed at :8175, which incorrectly surfaced the Fedimint URL in +/// the Cashu mints list. fn default_mint_url() -> String { - "http://127.0.0.1:8175".to_string() + "https://mint.minibits.cash/Bitcoin".to_string() } #[cfg(test)] @@ -1359,11 +1364,11 @@ mod tests { async fn test_save_and_load_wallet_roundtrip() { let tmp = TempDir::new().unwrap(); let mut wallet = WalletState { - mint_url: "http://127.0.0.1:8175".into(), + mint_url: "https://mint.minibits.cash/Bitcoin".into(), ..Default::default() }; wallet.add_proofs( - "http://127.0.0.1:8175", + "https://mint.minibits.cash/Bitcoin", vec![Proof { amount: 42, id: "ks1".into(), @@ -1375,7 +1380,7 @@ mod tests { TransactionType::Mint, 42, "Test mint", - "http://127.0.0.1:8175", + "https://mint.minibits.cash/Bitcoin", "", ); @@ -1498,7 +1503,7 @@ mod tests { #[test] fn test_default_mint_url() { - assert_eq!(default_mint_url(), "http://127.0.0.1:8175"); + assert_eq!(default_mint_url(), "https://mint.minibits.cash/Bitcoin"); } #[test] @@ -1517,9 +1522,11 @@ mod tests { .await .unwrap()); // Trailing slash on the home URL still matches. - assert!(is_mint_trusted(tmp.path(), "http://127.0.0.1:8175/") - .await - .unwrap()); + assert!( + is_mint_trusted(tmp.path(), "https://mint.minibits.cash/Bitcoin/") + .await + .unwrap() + ); } #[tokio::test] @@ -1553,7 +1560,7 @@ mod tests { let err = swap_between_mints( tmp.path(), &default_mint_url(), - "http://127.0.0.1:8175/", + "https://mint.minibits.cash/Bitcoin/", 100, 10, ) diff --git a/core/archipelago/src/wallet/fedimint_client.rs b/core/archipelago/src/wallet/fedimint_client.rs index b644f24c..8e0b77e4 100644 --- a/core/archipelago/src/wallet/fedimint_client.rs +++ b/core/archipelago/src/wallet/fedimint_client.rs @@ -95,7 +95,7 @@ pub async fn ensure_default_federation(data_dir: &Path) -> Result<()> { { reg.federations.push(JoinedFederation { federation_id, - name: None, + name: Some("Archipelago Federation".to_string()), }); save_registry(data_dir, ®).await?; } diff --git a/neode-ui/src/views/Mesh.vue b/neode-ui/src/views/Mesh.vue index ad9e8378..26f32d3d 100644 --- a/neode-ui/src/views/Mesh.vue +++ b/neode-ui/src/views/Mesh.vue @@ -884,6 +884,17 @@ function scrollChatToBottom() { } } +// Wheel over the chat must scroll ONLY the chat — never leak to the contacts +// list or the page. CSS overscroll-behavior wasn't enough (the leak happens +// even when the chat doesn't overflow), so consume the wheel and apply it to +// the chat container directly. Used with `@wheel.prevent` so the default +// (page/contacts) scroll never fires. +function onChatWheel(e: WheelEvent) { + const el = chatScrollEl.value + if (!el) return + el.scrollTop += e.deltaY +} + async function handleBroadcast() { broadcasting.value = true try { await mesh.broadcastIdentity() } finally { broadcasting.value = false } @@ -1593,7 +1604,7 @@ function isImageMime(mime?: string): boolean { {{ timeAgo(activeChatPeer.last_heard) }} -
+
No messages yet. Say hello!