Back to list
Jul 14 2026

Development Update — July 14

Today began with an audit of the transport bandwidth pipeline and found the rolled-up totals inflating throughput by roughly two — every network and per-visor aggregate had been reading a legacy combined field that summed both edges of a transport, each of which independently reports the full throughput. Fixing that read-side math corrected the dashboards immediately. A second transport-discovery bug explained why a freshly-booted visor’s routes 404’d for the first half-minute: the shared route-finder index was only refreshed on a 30-second ticker, so a new visor’s feed simply wasn’t subscribed yet — now it subscribes the moment the connection lands. Alongside the correctness work, transports gained a direction — every transport was originated by one side, and that in-versus-out distinction now shows in both the CLI and the hypervisor node list — the BTC-over-mesh gateway got its final blockers cleared and a health surface, and the wasm-visor’s auto-update became reliable enough to actually move connected tabs onto new builds.

Skywire: Transport-Discovery Correctness

3477 fix(tpd): stop double-counting network/visor bandwidth aggregates corrects a roughly 2× inflation in every rolled-up bandwidth total. Each edge of a transport reports the full transport throughput independently, so a per-transport redis hash keeps correct per-reporter fields but also a legacy combined bandwidth field that increments both reporters’ deltas — doubling any transport whose two edges both report. The per-transport metrics view was already correct because it reads the per-edge fields; the network dashboard total, the per-visor bandwidth sum and the single-transport daily total all read the doubled combined field. The fix routes all three through a new helper that takes a transport’s true daily throughput as the max across reporters of (sent+recv) — exact for a single-reporter transport, correct enough when both report — falling back to the combined field only for legacy rows. It also replaces a fabricated 50/50 sent/recv split (wrong for any asymmetric workload like a skysocks exit) with real stored sent and recv fields, and makes LogEntry.Reset() atomic. The read-side fixes correct existing redis data immediately. 3478 fix(tpd): subscribe to a visor feed on connect, not on the 30s poll fixes a related symptom: route find returning 404 “transport not found” for a just-booted visor even though its transport is registered, until it “warms up.” The route-finder shares the TPD’s redis, so this was never replication lag — the source visor’s edge-set was simply empty because the cxoaggregator’s reconcile loop, which subscribes to a connection’s feed and walks its published transport snapshot into redis, only ran on a 30-second ticker. Wiring the CXO node’s OnConnect callback to nudge an immediate reconcile means a freshly-connected visor’s feed is subscribed right away; the nudge coalesces bursts through a buffered channel and stays idempotent, with the periodic ticker kept as a safety net. 3476 fix(cli): route find over visor DmsgHTTP RPC, not CLI dmsg dial fixes the route find command itself, which built its own plain http.Client and died with “unsupported protocol scheme dmsg” because the route-finder is addressed by a dmsg:// URL. It now sends the /routes POST through the running visor’s existing DmsgHTTP RPC — the visor already holds a dmsg-capable client — and falls back to the old direct HTTP path only when no visor is reachable and the RF address is clearnet, while also guarding against an empty path set instead of blindly indexing [0].

Skywire: Incoming vs Outgoing Transports

3479 feat(cli): show incoming vs outgoing transports (tp -s / mode column) surfaces a distinction that was never recorded: a transport is bidirectional, but one side originated it — this visor either dialed out or accepted an inbound dial. ManagedTransport gains an isInitiator flag, set true where we dial and false where we accept, exposed through TransportSummary as a new initiator JSON field. In the CLI, tp -s now breaks each type into total, in and out with colored columns, and the existing mode column doubles as the direction indicator across all four table variants, so no new column and no format churn. Direction is correct by construction from the dial-versus-accept call sites, and against an old visor the absent field simply decodes as false without crashing. 3480 feat(hv-ui): show incoming vs outgoing transports in the node list carries the same field into the hypervisor UI’s Transports column, mapping initiator in both the node-list and single-node paths and rendering per-type counts with a green ↓ for incoming and a blue ↑ for outgoing; legacy placeholder-only nodes that predate transport summaries show just the total rather than a misleading 0/0 split. 3491 fix(wasm-visor): populate transport Initiator so HV-UI in/out direction is correct fixes a wasm-side gap where every browser-visor transport showed as incoming — backwards, since a browser visor can only dial out, the sole exception being an accepted WebRTC. The wasm visor builds its own mirror TransportSummary struct, which had never carried the initiator field; adding it and setting it from IsInitiator() in both the self-enumeration and the add-transport controller makes the node list render, for example, SWTR 0↓ 2↑ and WEBRTC 1↓ 0↑. 3490 chore(wasm): re-embed blob so the wasm visor reports transport direction rebuilt the committed blob for the same reason — it had drifted behind develop and predated the direction work — though the deeper fix was the mirror-struct field in #3491.

Skywire: Finishing Bitcoin Over the Mesh

3474 chore(btc): vendor electrum self-signed-cert fix + rebuild wasm cleared the last certificate blocker: the gateway failed every self-signed ssl:// electrum server with x509: certificate is not valid for any names, so vendoring the skycoin fix that skips CA verification — electrum servers routinely use self-signed certs — let the TLS handshake complete once the skysocks route and TCP connect already worked. 3475 chore(btc): vendor 90s electrum mesh timeout + rebuild wasm cleared the piece after that: with the cert fix in place the TLS handshake succeeded, but the electrum protocol handshake over a multihop route plus skysocks plus in-tab TLS blew the 30-second deadline, so a 90-second client timeout for mesh-tunnel dialers was vendored, completing the traced route → TCP → TLS → electrum-handshake chain. 3473 fix(wasm): fail fast (18s) on skysocks route setup instead of 45s caps a skysocks-lite route setup to a dead exit at 18 seconds instead of 45 — a reachable exit establishes in a few seconds, and since most random SD exits aren’t routable from a browser visor, failing fast lets the wallet’s BTC egress or the iframe browser move on to another. 3492 fix(wallet): route skycoin-web’s /api/v1/btc/* to the BTC gateway, not the node fixes BTC balance, history and send being broken in the browser wallet while only the health strip worked: skycoin-web’s ApiService prefixes /api/v1/, so it fetches btc/balance as /api/v1/btc/balance, but both shims classified BTC with an anchored regex that only matched /v1/btc/, sending those requests to the skycoin node — which has no such endpoint. Reclassifying BTC as “path contains /v1/btc/”, checked before the node branch, covers both forms; the genesis address’s 57.225 BTC now returns end-to-end.

Skywire: Wallet Health and Node Convergence

3481 feat(wallet): /v1/btc/health + connection-log visibility; vendor skycoin health/UI is the skywire half of a coordinated connection-health effort. It adds a GET /v1/btc/health endpoint returning the electrum tip height (502 when the server is unreachable) for both native and wasm, makes both wallet shims log every node and BTC request and its outcome — status and timing — to the shared log with a [wallet] prefix so the config page’s live log pane shows what is crossing the mesh even at the seed-entry screen, and re-embeds a skycoin-web build carrying an always-visible node/electrum status bar and create-wallet coin-availability gating. 3483 feat(wallet): shim honors skycoin-web’s Nodes-GUI node selection converges node selection onto skycoin-web’s own Settings → Nodes page as the authoritative source, instead of the shim overriding every /api call with a parallel skywire key that silently made the Nodes page a no-op. Both shims now take the coin node from the URL skycoin-web addressed the request to — its Nodes page makes the fetch URL absolute — routing dmsg for a .dmsg host and skysocks for clearnet, and falling back to the legacy config key only for a relative same-origin path so nothing breaks. 3484 chore(wallet): vendor skycoin@16104d93 + re-embed (node-config convergence) then re-embedded the wallet so the status bar follows that effective node, completing the convergence where Settings → Nodes is authoritative and the skywire config panel is a fallback.

Skywire: Reliable Wasm Auto-Update

3482 fix(wasm-hv): reliable auto-update (background + SharedWorker) + align transport counts fixes two reasons a served wasm-visor could stay on an old build. The updater polled /wasm-version with a setInterval that browsers throttle in background tabs and freeze on discard, so an unfocused tab could miss a new build for a long time — it now also re-checks on visibilitychange and pageshow and once shortly after boot. More subtly, the runtime lives in a SharedWorker that survives location.reload(), so a reloaded page reconnected to the stale worker; adding a shutdown path that the updater posts before reloading makes the post-reload page boot a fresh worker that fetches the new wasm. It also lays the node-list per-type in/out counts out as a fixed grid so the ↓/↑ columns line up instead of shifting with each type name’s width. 3485 fix(hv-serve): /wasm-version fingerprints the whole build, not just the wasm blob fixes the complementary detection gap: /wasm-version hashed only the wasm blob, so a UI-only, serve-only or plain version-bump deploy — where the blob is byte-identical — never moved the fingerprint, and connected tabs stayed on the old build even though the server was already serving the new one. The fingerprint is now the hash of the wasm blob plus the Angular index plus the build version, so any deploy moves it.

Skywire: Documentation

3487 docs(forwarding): run any app over skynet/dmsg — with/without Caddy + browser access expands the forwarding doc into the generic “run any app over skynet/dmsg” recipe — the far-end forward and near-end resolving proxy, hosting HTTP apps with the built-in port-80 proxy versus Caddy or nginx with --preserve-host for many vhosted sites (node.skycoin.com as the worked example), and a new section on reaching a served app via both cli got and the browser’s resolving SOCKS5 proxy. 3488 docs(forwarding): point to the existing websites guides instead of duplicating them then trimmed the overlap, since the Caddy, vhost and base32 material already lives in the website-auth and skynet-tls guides, keeping a clean split: port forwarding here, websites over skywire in those two.