Back to list
Development Update — August 10
Yesterday’s dmsg-bootstrap-floor RFC named the “real prize”: generalize the dmsg server’s peer-forwarding to any visor, carried over its own transports, so a visor need not stay attached to public dmsg servers once it has transports of its own. Today that primitive lands and gets wired into the two paths that make it carry real traffic — .skynet fetches and .dmsg reach — with a relay-discovery layer robust to a stale or empty transport graph. Alongside it, the real-origin mesh browser reaches its hosted form: mesh sites now open as genuine isolated web origins served entirely through the in-tab visor, behind a single wildcard TLS certificate. Skycoin had its second big day of the window: the front-end toolchain converged onto one shared ESLint base and aligned tsconfig strictness, all three Angular apps adopted OnPush change detection, the code moved to the @ngx-translate v18 API, and CI now builds, lints and tests every front-end. That shared-ESLint base is the standard skywire’s own manager UI was subsequently brought up to match.
Skywire: The Visor Becomes a Relay
3814 feat(transport): visor-as-relay over skynet is the primitive — component 2 of the bootstrap-floor RFC. It generalizes the dmsg server’s forwardViaPeer/bridgeStream to any visor, carried over its skynet transports: a visor forwards a stream to a third party it has a direct non-dmsg transport to, PK-addressed with no route. Five gaps are closed in vstream.go plus a new vstream_relay.go — a forward branch that byte-splices DATA/FIN both ways keyed by (transport, streamID); a new Relay SYN that appends [dstPK][originID][sig] after the base header (non-relay frames unchanged on the wire, backward compatible); peer discovery via WalkTransports; a Relayed flag plus “forward only to a directly-connected dst” that bounds a relayed stream to a single hop; and a stream-id remap so ids from different inbound transports can’t collide. Security follows the “sign the SYN” decision: the SYN is signed by the originator over originID || senderPK || dstPK, so the relay verifies before forwarding (no spoofable open amplifier), the destination attributes the stream to the true origin rather than the relay, and originID is preserved end-to-end so the signature still verifies after the id remap. Concurrent relayed streams are capped at 4096; the 1-hop guard prevents chains. vstream_relay_test.go covers forward, the three reject cases, local termination, and the originator’s signed SYN.
3815 feat(visor): .skynet relay tier is the first consumer — when there’s no direct transport to a .skynet destination, DialSkynet reaches it through a 1-hop relay (a peer sharing a direct transport with us that also reaches the destination) via DialThroughRelay before falling back to a multihop route, so the reachability ladder becomes direct → relay → route. 3818 feat(visor): dmsg over skynet extends the same idea to the .dmsg path, which previously always dialed dmsg servers directly: dmsgOverSkynet now reaches a peer’s :80 over dialDirectOrRelay (direct VStream → 1-hop relay, route ID 0, no route-finder) before any dmsg-server dial — a visor mirrors :80 over dmsg and its skynet forwarding server, so the relayed skynet path serves the same content, with dmsg-servers as the bootstrap-floor fallback. Crucially this is never a route: dmsg is a relay layer. dialDirectOrRelay is extracted as the shared direct→relay primitive so both paths use it. 3819 and 3820 make relay discovery robust: the graph query that intersects our direct peers with the destination’s transport-discovery edges returns nothing when TPD is backlogged or the destination just restarted, so the dial fell straight through to servers even with a working relay one hop away. Discovery now prefers graph-confirmed relays, then appends our other direct peers as blind fallbacks (deduped, bounded by maxRelayFanout=8), and dials the whole set in parallel with first-success-wins — the relay validates it can actually reach the destination before bridging, so a wrong guess fails its own handshake harmlessly. Per-candidate failure logging was added because the parallel rewrite had made live relay failures invisible; live validation reached a .dmsg peer’s :80 over a skynet transport through a 1-hop relay with no route-finder and no dmsg-server (reached destination via 1-hop relay), and surfaced the mid-rollout reality that confirmed relays still on a pre-relay binary receive the SYN but can’t forward.
Skywire: The Real-Origin Mesh Browser, Hosted
3803 feat(visor): serve the standalone wasm-visor from the visor process lets hv serve build the standalone wasm-visor PWA (plus the optional /ctl/* harness) from the running binary’s embedded wasm and UI, so setting hypervisor.wasm_serve.addr makes the visor process itself host that surface — one binary, one process, re-embedding the latest wasm on every rebuild-restart. It extracts the serve-handler builder into a shared visor.ServeWasm (removing ~450 lines that would otherwise drift between the two entry points). 3807 feat: real-origin mesh browser opens any .dmsg/.skynet site in the in-visor iframe browser as a normal isolated secure web page — a genuine per-site origin, so native subresource loading, cookies, localStorage, service workers, redirects, WASM and streaming all work, while skywire proxies only the transport over the visor’s own routes. On the native HV UI a loopback reverse-proxy origin serves each site as <vhost>.<base32pk><suffix> (or a per-site 127.0.0.1:<port>); on the wasm surface a Service Worker on a separate origin relays every fetch through the in-tab visor, with browse-responder.js as the trust boundary so untrusted mesh content never reaches the visor’s key. 3808 feat(wasm-visor): configurable browse-origin suffix + two-port hosted mode makes that wasm surface deployable on a real host — browse_suffix, browse_origin_addr (a second listener serving only the bootstrap), and browse_v_origin let one process serve the PWA on one port and the browse-origin bootstrap on another, so a hosted deploy runs V at theskywirenetwork.net and each mesh site at <base32pk>.<suffix> (cross-site, its own cookies/storage), with the host serving only the bootstrap and never content.
3809 feat(wasm-visor): clearnet real-origin + content-addressed browse origins completes it, and is the interesting turn. Clearnet sites over skysocks-lite now get real isolated origins too, the same treatment .dmsg/.skynet already had. The first hosted approach encoded the target into the hostname, but that collides head-on with TLS wildcards: a wildcard cert matches exactly one leftmost label, so *.*.<domain> is invalid and a dmsg name-vhost like magnetosphere.net.<base32pk>.dmsg — where the 53-char base32 PK alone nearly fills the 63-char label budget — has no cert. The fix is to stop encoding the target in the name at all: origin B is now base32(sha256(target))[:20], the visor keeps the shortid → target map, and the bootstrap sends its shortid at handshake (an untrusted B can’t redirect the fetch). Now one wildcard *.<domain> covers every site at any PK length, clearnet subdomain depth, or dmsg name-vhost, and the per-net Caddy blocks go away; the address bar still shows the true URL while the hash stays an internal id. Validated live on theskywirenetwork.net through the real browser UI: a hex-PK .dmsg site, an 89-character magnetosphere.net name-vhost, and https://skycoin.com with 31 subresource images all relayed through the SW, every one isSecureContext on the single cert.
Skywire: Smaller Operator Surfaces
3804 fix(dmsgweb): move exit-IP check from visor landing page to home.dmsg corrects the placement of the “Check your exit IP” links added earlier: in the wasm-visor’s iframe browser home.dmsg is fetched through skysocks-client-lite, so its echoed IP is the proxy exit’s — what a user wants to check — whereas the visor landing page is served directly and reported a misleading IP. 3816 feat(tools): browser-inspection dev tooling adds cmd/cdpeval (evaluate JS against a specific CDP target, so OOPIF iframes and workers hvinspect can’t disambiguate are inspectable directly), cmd/wfdrive (a Waterfox/Firefox driver over WebDriver BiDi), and an HVINSPECT_RELOAD=hard mode that clears the HTTP cache plus SW/CacheStorage to pick up a rebuilt wasm blob — all using only the already-vendored coder/websocket, so nothing new enters the dependency graph. 3821 feat(cli): add tp public [true|false] gives CLI parity for the SetIsPublic/GetIsPublic RPCs that were previously only reachable from the HV UI’s public toggle, and 3817 feat(wasm-visor): distinct browser-tab favicon serves a violet-tinted mesh-cloud icon from ServeWasm so a wasm-visor tab is distinguishable at a glance from a host-native hypervisor tab.
Skycoin: One Shared Front-End Toolchain
The July 30 upgrade left the three Angular apps on the same framework but with three drifted rule sets — the same code could be clean in one project and rejected in another. 3003 chore(frontend): share one ESLint base and align tsconfig strictness fixes that by adding an eslint.base.config.js at the repository root carrying the rules that should apply everywhere. It’s a factory rather than a plain config, because each project installs its own copies of @eslint/js, typescript-eslint and angular-eslint and passes them in, so the shared file needs no dependencies and no repository-root node_modules; each project supplies only what’s genuinely local, with the rules its code can’t satisfy yet named in a legacyRules block labelled as debt meant to shrink rather than policy. Convergence is purely additive — no rule was removed or weakened, the explorer gains curly/eqeqeq/prefer-const, the two wallets gain valid-typeof and consistent-type-definitions, and all three still pass npm run lint with no source changes. strictTemplates and strictInjectionParameters were also lifted into each project’s root angularCompilerOptions, verified inherited rather than assumed by probing a deliberate template type error. This shared ESLint base is the standard skywire’s manager UI was later brought up to match — the front-end lint policy now originates here and is shared across repos.
Skycoin: OnPush Change Detection
With the lint baseline shared, all three front-ends adopted Angular’s OnPush change-detection strategy — declared explicitly rather than left on the default, so a component re-renders on input/observable changes instead of on every tick. 3007 does the desktop wallet (gui/static), 3008 the web wallet, and 3004 the explorer (declaring the strategy explicitly first, then adopting OnPush). 3006 test(explorer): guard the OnPush change detection contract locks it in with tests so a future component can’t silently regress to Eager, and a lint companion stops requiring OnPush on the handful of components deliberately pinned to Eager, keeping the rule honest.
Skycoin: ngx-translate v18 and CI That Builds Everything
3000 sweep open Dependabot updates + repair the ngx-translate v18 build both replicates the open Dependabot npm/Go PRs on one branch (so the Angular lock files resolve together on 22.1.1 without --legacy-peer-deps) and migrates the code to the @ngx-translate v18 API to fix a build break already on develop; it also has Dependabot stop proposing TypeScript majors, since Angular 22 pins TypeScript to >=6.0 <6.1 and TS7 (the Go rewrite) doesn’t yet expose the programmatic API Angular’s template type-checker needs — so an unmergeable weekly PR is retired at the config. 3009 then sweeps the next round of open Dependabot updates. Underneath, CI grew to build, lint and test every Angular front-end and to run the explorer e2e suite against a pinned blockchain database (3005), the Angular test bootstraps were repaired so what runs is what’s actually gated, and the Angular builder was allowed to clean its own output directory — together making the shared toolchain enforceable on every push rather than aspirational.