2095 Commits

Author SHA1 Message Date
archipelago
7e8d3314d0 docs: backlog — optimise companion QR scan (quicker start/decode, low-light)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 09:12:01 -04:00
Dorian
07772b563f fix(companion): wallet scanner reads dense invoice QRs — 0.5.22
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
Root cause (diagnosed live via CDP on a Pixel 9a): camera permission
granted and the native camera streamed (2m35s active) but ZXing returned
zero decodes for Lightning-invoice QRs — window.__archyQrResult received
0 calls. Dense BOLT11 codes need more pixels/module AND sharp focus; the
9a's main lens rests at a far focus and 1280x720 wasn't enough, so dense
invoices never resolved while sparse address QRs did. Fix: analysis at
1920x1080 + a repeating centre autofocus tick (a static hand-held QR
never retriggers continuous-AF on its own).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:51:47 +01:00
archipelago
d500214766 docs: backlog — ship lightning false-failure fix; companion app version display
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 05:33:02 -04:00
archipelago
9cd507269c fix(lightning): never report a slow in-flight payment as failed
Slow multi-hop payments (>15s routing) surfaced as "Payment failed"
while LND settled them in the background: the shared LND REST client's
15s total timeout aborted the synchronous /v1/channels/transactions
wait, and every UI path treated that abort as a definitive failure. The
payment then succeeded anyway and only appeared in history on the next
background poll.

Backend: lnd.payinvoice now decodes the invoice up front for its payment
hash, pays on a dedicated 120s client, and answers status:"pending" with
the hash (never an error) when the wait elapses after the payment was
handed to LND — only a pre-connect failure is still a hard error. New
lnd.paymentstatus RPC reports succeeded/failed/in_flight (with humanized
failure reasons) from /v1/payments.

Frontend: new rpcClient.payLightningInvoice() pays then polls
lnd.paymentstatus to a real terminal state (3s interval, up to 2 min);
all five call sites (send modal, scan modal, web5 unified send, peer-file
purchase, app-launcher payments) migrated. Failure is only shown when LND
itself declares FAILED; a still-settling payment shows an in-flight state
and success fires the transaction refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 05:33:02 -04:00
Dorian
aa272bfcf4 chore(android): update companion APK download [skip ci] 2026-07-27 10:19:28 +01:00
Dorian
3b6a32b2f8 fix(companion): app webview content clears the status bar; HTTPS toggle on add/edit — 0.5.21
- In-app browser pages (node apps) now start below the status bar again:
  the WebView stays edge-to-edge (page colour fills the bar) and a
  body{padding-top:<statusbar>} injection pushes content down — the
  pre-edge-to-edge look without the black bar.
- Add/Edit server in the menu now has a Use HTTPS toggle (was scheme-locked;
  edit preserved the old scheme, add forced http).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:19:11 +01:00
Dorian
c66ef048f4 chore(companion): publish 0.5.20 to download QR (transport handoff + FIPS mesh settings)
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m57s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 09:43:28 +01:00
Dorian
32962db6a9 chore(android): update companion APK download [skip ci] 2026-07-27 09:40:08 +01:00
Dorian
4edc50ae47 feat(companion): seamless transport handoff + FIPS mesh settings section — 0.5.20
- Transport handoff (Wi-Fi ⇄ 5G, BLE-ready): ArchyVpnService registers a
  ConnectivityManager callback that re-pins the tunnel to the new default
  network (setUnderlyingNetworks) and re-homes the mesh (fresh warmer pass
  → discovery/sessions rebuild on the new path in seconds). Previously the
  tunnel stayed pinned to the network it launched on and roaming stranded
  the mesh until an app restart.
- FIPS Mesh section in the NESMenu settings modal: status, mesh address
  (fd… ULA), identity npub (both copyable), configured peer/anchor count,
  and a one-tap Reconnect (manual re-home). Gives users oversight of what
  the embedded mesh node is doing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 09:39:47 +01:00
Dorian
245fb1a815 chore: release v1.7.116-alpha (signed OTA manifest)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v1.7.116-alpha
2026-07-27 09:05:18 +01:00
Dorian
6dffd8e2e8 chore: bump version to 1.7.116-alpha + changelog/What's New
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
Bundles all post-115 fixes into an OTA release: boot READY-before-recovery
+ Restart=always (no more 'server starting up'), and the app-install
daemon-kill fix (v6 relay only bridges live app ports; port-cleanup
excludes our own PID).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 07:59:48 +01:00
Dorian
365f3d7d18 fix(install): mesh app-port relay no longer kills the daemon on install
The v6 app-port relay preemptively bound [::]:<port> for ALL catalog
ports, even apps not installed. Installing such an app (grafana:3000,
photoprism, uptime-kuma, jellyfin — framework-pt 2026-07-27) then hit
'address already in use' from the relay, which triggered
cleanup_stale_pasta_port ->  -> killed archipelago
itself (it held the port) mid-install. The daemon crash-looped and the
half-created apps were rolled back and vanished.

Two fixes:
- relay only bridges a port that a running app already answers on over
  IPv4 (probe 127.0.0.1:port first) — an uninstalled app's port is never
  held, so its install sees a free port and never triggers the cleanup.
- cleanup_stale_pasta_port excludes our own PID from both the ss-based
  kill and the fuser kill, so freeing a port can never terminate the
  daemon even when the relay legitimately holds it (reinstall case).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 07:29:54 +01:00
Dorian
70bcbc4acc fix(boot): signal systemd READY before heavy boot recovery + Restart=always
Root cause of 'server starting up' forever / crash-on-install
(framework-pt, v1.7.114->115, 2026-07-26): on a node with many stacks,
the synchronous boot recovery (recover + start_stopped_containers) runs
BEFORE sd_notify(Ready), so the unit sits in 'activating' for minutes.
Anything touching the service in that window — a superseding
start/restart, an install-time reconcile churn — killed a half-started
instance; it exits 0 on SIGTERM and Restart=on-failure then never
restarts it. Node dead behind 'server starting up'.

Fixes:
- signal READY (+ start the watchdog keepalive) BEFORE boot recovery, so
  the unit reaches 'active' in seconds; recovery/reconcile/listener
  continue after. No more minutes-long activating window.
- Restart=always (was on-failure): a clean-exit SIGTERM must still bring
  the daemon back. Manual  is still honored.
- OTA restart via a PID1-owned transient timer (systemd-run --on-active=2)
  instead of a tokio-sleep child of the process being stopped, whose
  start-half was being lost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 00:31:54 +01:00
Dorian
56e4c30261 fix(companion): edge-to-edge app webview, whole-overlay touch shield, no raw IP titles — 0.5.19
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m0s
- app webview draws behind the status bar again (the inset rework
  painted an opaque black bar there); page background owns the top
- the in-app overlay eats EVERY touch its children don't handle —
  a near-miss on Close was falling through to the kiosk AIUI tab
- loading screen never titles itself with a raw mesh IPv6/IP host
- re-land vc37 connect fixes vc38 shipped without: pairing restarts
  the mesh immediately when consent exists; session warmer probes all
  targets concurrently (5s) instead of 20s each in sequence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 23:31:48 +01:00
Dorian
f7208e1769 chore: release v1.7.115-alpha
Signed OTA manifest (release root verified). Also: create-release-manifest
tarball perms check made SIGPIPE-proof so releases cut on macOS too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v1.7.115-alpha
2026-07-26 23:24:24 +01:00
Dorian
77ee39a3df fix(server+quadlet): app UIs work over the mesh — v6 relay + v4-pinned publishes
Rootless podman's wildcard publish claims [::] but BLACK-HOLES inbound
v6 (accepts, forwards nothing — vaultwarden 'empty response' from the
phone, 2026-07-26), while most apps got no v6 listener at all. Two
halves: quadlets now publish unbound ports on 0.0.0.0 explicitly
(frees the v6 side; the one-time drift/recreate at upgrade is the
deploy vehicle), and the daemon runs a self-selecting V6ONLY relay on
every catalog launch port forwarding raw TCP to the v4 loopback
listener. Rescans every 60s so new installs bridge without a restart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:48:16 +01:00
Dorian
02917cc22c fix(fips): app launch ports allowed through the mesh firewall
The companion opens catalog apps by direct port over the mesh; the
fips0 default-deny baseline blocked every one of them (apps 'stuck'
from the phone, 2026-07-26). Core now writes a second fips.d drop-in
from the generated catalog launch-port list on every install/upgrade.
Service/RPC ports (bitcoind 8332, LND 10009, Tor) stay closed.
generate-app-catalog.py emits the Rust port list alongside the TS one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:23:46 +01:00
Dorian
325b9ea9c9 chore: bump version to 1.7.115-alpha (release prep)
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m6s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:05:09 +01:00
Dorian
9739bbb3db docs+fix: ship fips0 web-UI firewall allowance from core; changelog + What's New for v1.7.115-alpha
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
fips::config::install() now writes /etc/fips/fips.d/80-web-ui.nft
(tcp 80/8443 accept) and reloads the baseline on every install/upgrade —
the hardening firewall default-denies inbound on fips0 and the UI was
unreachable over the mesh without it (root-caused live 2026-07-26).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:03:25 +01:00
Dorian
4db1b85fc5 fix(server): web UI listens on IPv6 — the mesh could never load it
The mesh is IPv6-only; the main web listener bound 0.0.0.0 only, so a
phone reaching a node over its fips0 ULA got RST on :80
(ERR_CONNECTION_ABORTED) — UI over mesh was structurally impossible.
Confirmed 2026-07-26 on framework-pt: v4:80 = 200, v6:80 = refused.
Mirror an IPv4-any main listener with a V6ONLY [::] socket on the same
port; V6ONLY so it coexists with the v4 listener regardless of
net.ipv6.bindv6only.

Also: fips.yaml generator carries the fast-reconnect profile (validated
live on framework-pt since 2026-07-24; snapshot tests updated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 21:58:11 +01:00
archipelago
a78aa02890 chore: release v1.7.114-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m1s
v1.7.114-alpha
2026-07-26 13:40:03 -04:00
archipelago
0365cc0f9d docs: changelog + What's New for v1.7.114-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m52s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:24:36 -04:00
archipelago
0880824fb3 fix(ui): seed QRs use the SeedQR standard so hardware wallets can scan them
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m52s
Plain-text seed QRs didn't scan into Passport Prime — wallets that
import seeds by QR (Passport, SeedSigner, Keystone, Nunchuk, Sparrow)
expect the SeedQR standard: each BIP39 word as its zero-padded 4-digit
wordlist index, concatenated into a numeric QR.

- new utils/seedqr.ts encodes BIP39 words per the SeedSigner spec
  (@scure/bip39 wordlist; vector-checked abandon=0000, zoo=2047)
- new shared SeedRevealPanel (Words/QR tabs, tap-to-reveal blur) now
  backs the LND reveal AND the Settings→Backup recovery-phrase reveal,
  so every current and future seed reveal behaves the same
- onboarding seed + Settings reveal: QR defaults to SeedQR with a
  plain-text toggle
- LND seed stays plain-text-only with an explicit note: aezeed is not
  BIP39 and only restores into LND-based wallets (Zeus/Blixt/another
  node) — SeedQR-encoding it would just mislead

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:09:43 -04:00
archipelago
c0d34bd836 fix(mesh): serialize serial-port opens between listener and RPC probe
Observed live on .116 after the dedup/backoff fixes: the kiosk browser's
hot-swap auto-probe (mesh.probe-device) still interleaved its handshakes
with the listener's open sequence on the same tty every backoff window —
Linux double-opens ttys silently, both handshakes corrupted each other,
and every collision's open() DTR/RTS-reset the board again. The retry
heuristic from 5f01ec31 narrowed but couldn't close the race.

A static PORT_OPEN_LOCK now covers the listener's whole device-open
sequence and each probe attempt, so exactly one prober touches the port
at a time. With this + the settle/backoff/dedup fixes, the .116 radio
completed its first MeshCore handshake in days (node-fa161601, 8
contacts) and the session has been stable since.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 08:05:42 -04:00
archipelago
c8d0dda656 feat(ui): Words / QR code tabs on LND seed reveal + onboarding seed
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m54s
Both seed screens get the wallet-settings segmented tab style: words
stay the default first view; the QR tab renders the space-joined seed
words for wallets that support seed import by scan. The LND reveal QR
sits behind the same tap-to-reveal blur as the words, and both panes
warn that the code is equivalent to the words.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:34:19 -04:00
archipelago
57c6a4d512 style(ui): grey status dots for closing/force-closing channel cards
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m55s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:19:59 -04:00
archipelago
a32e40da31 fix(mesh): stop probing one physical radio as two devices
/dev/mesh-radio is a udev symlink to a ttyUSB*/ttyACM* node that is also
in SERIAL_CANDIDATES, so one board was detected, probed and DTR/RTS-reset
twice per reconnect cycle (and shown twice in the UI):

- detect_serial_devices() dedupes candidates by canonical path, keeping
  the stable /dev/mesh-radio name
- auto-detect fallback skips the preferred path it just probed this cycle
  instead of immediately resetting the same board again
- probe_device's active-session guard compares canonical paths, so a
  probe via the alias can no longer open the tty the live session holds

Together with the 2s boot-settle and stable-session backoff gate, this
takes a plugged-in CP2102/ESP32 board from up to 9 reset events per
cycle at a permanent 5s retry floor down to one probe sequence per
backoff window — enough for the radio to actually finish booting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:19:54 -04:00
archipelago
449ed7c1f7 fix(mesh): don't reset reconnect backoff for sessions that die young
Extracted from beff5dd5 on archy-hwconfig (the rest of that commit is
flash-feature code staying on its branch): a device that connects then
drops within seconds — e.g. mid-boot-loop — kept resetting backoff to
5s forever, and every retry's open() toggles DTR/RTS which itself
resets ESP32-family boards. Backoff now only resets after a session
survives STABLE_SESSION_THRESHOLD (20s), so an unstable device gets
progressively longer quiet gaps to actually finish booting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:13:27 -04:00
729cee573a fix(mesh): orphaned listener task could race a fresh one on the same port
Traced why a device that's alive and USB-enumerating correctly could still
never complete a single protocol handshake, indefinitely: journal logs
showed genuinely concurrent connection attempts on the same port
(duplicate "Opened serial port"/"Starting X handshake" lines within
microseconds of each other, from what should be sequential probe steps) —
two independent listener sessions were racing on the same tty, each
corrupting the other's reads/writes.

Root cause was in MeshService::stop() combined with mesh::flash's earlier
STOP_LISTENER_TIMEOUT fix: stop() calls `self.listener_handle.take()`
(clearing the field to None immediately) and then awaits the handle with
no bound of its own. When a caller wraps the whole stop() call in a
timeout (as the flash job does, to avoid hanging the RPC response), a slow
listener — mid multi-candidate probe when the shutdown signal arrives —
would cause that outer timeout to cancel the await. But by then
`listener_handle` was already None, so MeshService believed the listener
was stopped, while the actual task kept running, orphaned (dropping a
JoinHandle does not abort the task). A later start() then spawned a
genuinely new listener, racing the orphaned one forever on the same port.

Fixed at the source: stop() now awaits its own handle through a mutable
reference (not by value) with its own bounded timeout, so on timeout it
still owns the handle and can call .abort() on it directly — guaranteeing
the task is actually gone before stop() returns, regardless of how any
caller wraps it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b14af20d1acfc053957ec22234773b5c59582a66)
2026-07-26 07:09:26 -04:00
5b7ebd85b6 fix(mesh): DTR/RTS reset settle time was far shorter than real boot time
Every one of Reticulum/Meshcore/Meshtastic's open() deasserts DTR/RTS on
every connection attempt (needed to clear stale line state, but the
transition itself resets ESP32-S3 native-USB boards and CP2102/CH340-
bridged boards wired for Arduino-style auto-reset — acknowledged in the
existing code comments). Each only waited 300ms before expecting a
handshake response — nowhere near real firmware boot time (LoRa radio
init alone routinely takes longer).

A single auto-detect cycle tries multiple protocols in sequence
(Reticulum, then Meshcore, then Meshtastic), each with its own open() and
thus its own reset. With only 300ms of settle per attempt, a board could
plausibly never finish booting from one attempt's reset before the next
attempt's open() reset it again — a self-sustaining "never finishes
booting" loop that would look identical to firmware/hardware flakiness
from the logs, regardless of which firmware family was actually flashed.
Confirmed live 2026-07-23 on both a Heltec V3 (Meshtastic) and V4
(Meshcore): continuous device-side FROM_RADIO_REBOOTED / boot-loop
symptoms with zero config-write-triggered reboots (manage_radio was false
for part of the test), pointing at the connection layer itself rather
than firmware config provisioning.

Bumped the settle delay from 300ms to 2s in Meshcore's and Meshtastic's
open() — full protocol handshakes that need real boot time. Left
reticulum.rs's probe_rnode settle at 300ms deliberately: it's a
cheap/fast KISS-detect gate designed to fail quickly for non-RNode
firmware (documented elsewhere as "~1s"), not a full handshake, and each
subsequent protocol's own open()+settle is what actually needs to cover
real boot time regardless of what probe_rnode did moments before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 5799c3711121f4a04635d2b1c720ce9089ff3f12)
2026-07-26 07:09:24 -04:00
archipelago
c6f11f8ddb feat(wallet): on-chain send fee control + BTC/sats amount entry
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m55s
- sats/BTC unit toggle on the on-chain amount field with live conversion
  hint; canonical value stays sats end-to-end
- Fast / Standard / Slow fee presets (1 / 6 / 144 block targets) plus a
  custom pane taking target blocks or an explicit sat/vB rate
- confirm pane shows LND's fee estimate for the chosen speed via the new
  lnd.estimatefee RPC (GET /v1/transactions/fee)
- lnd.sendcoins now accepts target_conf / sat_per_vbyte with the same
  mutual-exclusion + range validation as channel opens

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:05:10 -04:00
archipelago
a26090e561 feat(ui): lightning channels All/Active/Pending/Closed tabs + closed-channel history
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m6s
Consumes the lnd.closedchannels RPC and closing/force_closing statuses
that shipped backend-side in 00b7e179 but never reached the panel:
- tab bar with per-state counts; pending covers pending_open/closing/force_closing
- closed-channel cards with close type, settled balance, block height and
  closing-tx explorer link
- closing/force_closing status dots + closing-tx link on in-flight closes
- Close button hidden for channels already mid-close

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 06:51:48 -04:00
Claude
2609f60e6c feat(release): gated ISO build pipeline
- scripts/build-iso-release.sh: single gated command from signed release
  to tested ISO (preflight version/signature parity, release gate harness,
  strict catalog drift, full Rust suite, artifact version checks, build,
  mount smoke, best-effort QEMU boot)
- scripts/iso-smoke-test.sh: standalone mount-level ISO verification incl.
  stale-binary version assertion inside the payload
- .gitea/workflows/build-iso.yml: resurrected ISO CI as dispatch-only job
  calling the gated orchestrator (old one was stranded in _archived/)
- tests/release/run.sh: catalog drift now runs --strict (was silently
  always-pass)
- test-iso-qemu.sh: headless mode used -append without -kernel, which
  QEMU rejects; use -display none so -serial file: capture works

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:34:54 -04:00
archipelago
c4c558954b chore: sign v1.7.113-alpha release manifest
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:14:07 -04:00
archipelago
d4ea4ed636 chore: release v1.7.113-alpha v1.7.113-alpha 2026-07-25 14:41:59 -04:00
archipelago
0fadbb1d0f docs: sync What's New modal for v1.7.113-alpha
Some checks failed
Demo images / Build & push demo images (push) Failing after 48s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:29:48 -04:00
archipelago
0e5cd24e18 style: rustfmt on lnd channels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:25:10 -04:00
archipelago
1d8e57d564 docs: changelog for v1.7.113-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:12:41 -04:00
archipelago
00b7e1798f feat(lnd): closed-channels RPC, closing channels in list, streaming close with txid
- lnd.closedchannels: closed-channel history via /v1/channels/closed
- channel list now includes waiting-close and force-closing pending
  channels with their closing_txid
- closechannel reads the close stream's first update with a dedicated
  client instead of hanging until the closing tx confirms; returns the
  closing txid in display byte order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:12:19 -04:00
archipelago
bca1698682 Merge branch 'main' of http://146.59.87.168:3000/lfg2025/archy
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m2s
2026-07-25 10:47:41 -04:00
archipelago
0641ee85ef feat(wallet): total-bitcoin row at top of wallet card; on-chain gets a chain icon
The wallet card (Home + Web5 views) now leads with a Total Bitcoin row
summing all rails (on-chain + lightning + cashu + fedimint + ark), using
the orange ₿ mark. The on-chain row swaps ₿ for a link/chain icon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 10:44:08 -04:00
901cf5713a Merge pull request #123 from fips-companion-5g-hardening
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m5s
fix(fips): harden companion mesh joins
2026-07-24 17:52:56 +00:00
Dorian
875da6c630 fix(fips): harden companion mesh joins 2026-07-24 18:47:57 +01:00
Dorian
4589e88442 chore(android): update companion apk download 2026-07-24 18:30:01 +01:00
Dorian
5862689949 perf(companion): fips fork with discovery re-fire on topology change — 0.5.15
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m56s
Pins fips to Zazawowow/fips-native fast-join-pinned (upstream pinned rev
46494a74 + one patch: pending/queued discovery lookups re-fire the
moment the tree parent lands instead of riding out timeouts started
before the mesh could answer). Measured: session lands 225ms after the
route appears; fresh-join route wait no longer stacks doomed lookups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:56:17 +01:00
Dorian
3995ab5cf5 perf(companion): 5s discovery timeout — fresh-join route lookups fail fast — 0.5.14
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m54s
First lookups fire before the phone's tree position settles and are
doomed; at 10s completion timeout each one cost 10s before the 1s
retry could fire (observed 19s to a route on a fresh 5G join, session
3ms after the route — discovery convergence was the whole cost).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:35:50 +01:00
Dorian
9d83ee3770 perf(companion): fast-connect mesh profile — 5G cold connect 40s+ → 5.4s — 0.5.13
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
Stock fips pacing is tuned for always-on routers: a failed discovery
backs off 30s, session resends gap out to 8-16s, dead links redial at
5s→300s. On a phone that stacked into a 40s+ first connect over
cellular. Config-only tuning in build_config (no fork changes):
discovery backoff 1s (cap 30s), session resends 400ms x1.5 (10 max),
link redial 1s (cap 30s). Measured on-device: anchor +1.2s, node
session +5.4s from cold start over 5G.

Ops note: vps2 anchor daemon had degraded again (multi-second link
RTTs after 15h uptime) — restarted, recycle tightened 1d → 6h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:23:57 +01:00
Dorian
7e3d01f633 fix(companion): first connect no longer stalls on unresolvable .fips dial hints — 0.5.12
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
A pairing QR minted while the node UI was browsed over the mesh carried
npub….fips as fhost. Android's system DNS can't resolve .fips, so every
direct handshake dial failed and first connect crawled through public-
anchor discovery (2m35s observed) instead of one LAN hop.

- node UI: resolveServerUrl treats .fips names and IPv6 ULA literals as
  phone-unreachable and substitutes the node's LAN IP (same guard as
  tailnet/localhost)
- app: QR parser falls back to the url param's host when fhost is .fips
- app: peer store never keeps .fips dial addresses
- app: peer aliases slugged for the fips host map (spaces dropped
  "Framework PT"/"Mesh anchor" from name resolution entirely)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:40:44 +01:00
385c950f00 Merge pull request 'fix(ecash): change proofs no longer ride along inside sent tokens — double-credit bug' (#122) from fix/cashu-double-credit into main 2026-07-24 12:34:56 +00:00
Dorian
98e15b3af0 fix(ecash): change proofs no longer ride along inside sent tokens — double-credit bug
send_token_at split the mint swap's results with
partition(send_denoms.contains(amount)) — membership, not count. When a
change denomination equaled a send denomination (guaranteed when
spending from a proof worth exactly 2x the amount: send [n], change
[n]), the change proofs were serialized INTO the token, and the
receiver redeemed double. Both the wallet send flow and peer-files
purchases mint through this path.

The split now consumes exactly one proof per required send denomination
(everything else returns to the wallet as change) and hard-fails if the
mint returns an incomplete denomination set, so a token can never again
be silently over- or under-packed.

Note: tokens minted BEFORE this fix already contain the extra proofs
and will still redeem at their inflated value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:34:48 +01:00