theseus.x site lives under WebsiteDev; whitepaper source saved beside the page

The website sources are grouped under WebsiteDev/. site-theseus-x moves there
for real (the other site folders are still junctions from WebsiteDev to the
root); a junction at the old root path keeps existing scripts working and is
ignored so the files are tracked once. Docs, the icon script and the mirror
example follow the new path.

whitepaper/whitepaper.md is the Markdown the published page was built from,
so the text can be edited and rebuilt without the original document.
This commit is contained in:
Hermes Dev 2026-11-01 17:40:06 +02:00
commit 78da8fbbde
20 changed files with 3543 additions and 0 deletions

15
assets/favicon.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- theseus.x — Navigator compass mark. Rounded dark square (matches the
sirius.x sibling family). N (top) half red like every real compass
needle, S (bottom) half in Silent Mode acid green. E-W arm softened
so the N-S bearing reads clearly at 16x16 tab-icon size. -->
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<!-- N half — red bearing indicator -->
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<!-- S half — acid -->
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<!-- E-W arm — softened acid so the vertical bearing dominates -->
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity="0.55"/>
<!-- Center hole -->
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B

16
assets/logo.svg Normal file
View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 96">
<!-- theseus.x wordmark for OpenGraph previews. Compass mark on the left,
wordmark on the right; kept single-color palette (dark + acid + red
bearing) so it survives dark and light preview cards. -->
<g>
<rect x="8" y="16" width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M40 22 L46 48 L34 48 Z" fill="#e04e4e"/>
<path d="M46 48 L40 74 L34 48 Z" fill="#d6ff3d"/>
<path d="M14 48 L40 43 L66 48 L40 53 Z" fill="#d6ff3d" opacity="0.55"/>
<circle cx="40" cy="48" r="3" fill="#0b0e14"/>
</g>
<text x="88" y="60" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
font-size="30" font-weight="600" fill="#e7eaf1" letter-spacing=".5">
theseus<tspan fill="#d6ff3d">.x</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 826 B

134
assets/thread.svg Normal file
View file

@ -0,0 +1,134 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 108" role="img" aria-label="Ariadne's thread">
<defs>
<radialGradient id="core" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFAE0" stop-opacity="0.95"/>
<stop offset="18%" stop-color="#F5D983" stop-opacity="0.55"/>
<stop offset="55%" stop-color="#F5D983" stop-opacity="0.14"/>
<stop offset="100%" stop-color="#F5D983" stop-opacity="0"/>
</radialGradient>
<radialGradient id="ambient" cx="50%" cy="50%" r="55%">
<stop offset="0%" stop-color="#F5D983" stop-opacity="0.28"/>
<stop offset="65%" stop-color="#F5D983" stop-opacity="0.06"/>
<stop offset="100%" stop-color="#F5D983" stop-opacity="0"/>
</radialGradient>
<radialGradient id="hot" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#FFFAE0" stop-opacity="1"/>
<stop offset="55%" stop-color="#FAE7AA" stop-opacity="0.55"/>
<stop offset="100%" stop-color="#FAE7AA" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- ambient outer glow -->
<rect width="108" height="108" fill="url(#ambient)"/>
<!-- soft core glow behind the strands -->
<circle cx="54" cy="54" r="42" fill="url(#core)"/>
<ellipse cx="54" cy="54" rx="38.0" ry="3.1"
transform="rotate(63.5 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.44"
opacity="0.42"/>
<ellipse cx="54" cy="54" rx="37.5" ry="3.4"
transform="rotate(92.3 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.79"
opacity="0.55"/>
<ellipse cx="54" cy="54" rx="39.1" ry="3.5"
transform="rotate(-4.9 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.83"
opacity="0.66"/>
<ellipse cx="54" cy="54" rx="41.3" ry="3.5"
transform="rotate(151.8 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.56"
opacity="0.56"/>
<ellipse cx="54" cy="54" rx="39.4" ry="4.5"
transform="rotate(133.6 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.46"
opacity="0.60"/>
<ellipse cx="54" cy="54" rx="39.2" ry="4.5"
transform="rotate(13.7 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.67"
opacity="0.44"/>
<ellipse cx="54" cy="54" rx="38.0" ry="4.6"
transform="rotate(23.9 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.45"
opacity="0.31"/>
<ellipse cx="54" cy="54" rx="38.6" ry="4.9"
transform="rotate(122.3 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.76"
opacity="0.67"/>
<ellipse cx="54" cy="54" rx="40.9" ry="5.3"
transform="rotate(66.5 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.50"
opacity="0.31"/>
<ellipse cx="54" cy="54" rx="37.0" ry="5.8"
transform="rotate(107.1 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.84"
opacity="0.68"/>
<ellipse cx="54" cy="54" rx="37.9" ry="6.5"
transform="rotate(120.6 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.70"
opacity="0.47"/>
<ellipse cx="54" cy="54" rx="37.6" ry="7.0"
transform="rotate(19.6 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.54"
opacity="0.53"/>
<ellipse cx="54" cy="54" rx="38.8" ry="7.6"
transform="rotate(46.1 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.52"
opacity="0.67"/>
<ellipse cx="54" cy="54" rx="40.7" ry="8.5"
transform="rotate(77.2 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.44"
opacity="0.30"/>
<ellipse cx="54" cy="54" rx="37.7" ry="8.5"
transform="rotate(3.3 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.39"
opacity="0.67"/>
<ellipse cx="54" cy="54" rx="41.2" ry="9.5"
transform="rotate(57.8 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.48"
opacity="0.68"/>
<ellipse cx="54" cy="54" rx="40.5" ry="9.7"
transform="rotate(51.2 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.48"
opacity="0.68"/>
<ellipse cx="54" cy="54" rx="39.6" ry="10.0"
transform="rotate(133.2 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.58"
opacity="0.56"/>
<ellipse cx="54" cy="54" rx="41.8" ry="10.7"
transform="rotate(81.6 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.60"
opacity="0.72"/>
<ellipse cx="54" cy="54" rx="37.9" ry="12.0"
transform="rotate(150.8 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.50"
opacity="0.46"/>
<ellipse cx="54" cy="54" rx="40.5" ry="12.1"
transform="rotate(89.0 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.81"
opacity="0.45"/>
<ellipse cx="54" cy="54" rx="39.4" ry="12.2"
transform="rotate(167.7 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.64"
opacity="0.36"/>
<ellipse cx="54" cy="54" rx="40.0" ry="12.9"
transform="rotate(176.4 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.77"
opacity="0.53"/>
<ellipse cx="54" cy="54" rx="38.3" ry="13.4"
transform="rotate(36.8 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.49"
opacity="0.42"/>
<ellipse cx="54" cy="54" rx="40.2" ry="13.5"
transform="rotate(156.9 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.66"
opacity="0.50"/>
<ellipse cx="54" cy="54" rx="37.0" ry="13.9"
transform="rotate(102.6 54 54)"
fill="none" stroke="#F5D983" stroke-width="0.54"
opacity="0.50"/>
<!-- bright inner core, catches the eye -->
<circle cx="54" cy="54" r="14" fill="url(#hot)"/>
<circle cx="54" cy="54" r="3" fill="#FFFAE0"/>
</svg>

After

Width:  |  Height:  |  Size: 6 KiB

15
brand/avatar.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<!-- theseus.x avatar — 512x512 rounded square. Same compass mark as
favicon, scaled up. Social platforms apply a circular mask; the
corner radius (96px) is generous enough that no part of the
compass touches the edge, so the mark stays intact when clipped. -->
<rect width="512" height="512" rx="96" fill="#0b0e14"/>
<!-- N half — red bearing indicator -->
<path d="M256 56 L304 256 L208 256 Z" fill="#e04e4e"/>
<!-- S half — acid -->
<path d="M304 256 L256 456 L208 256 Z" fill="#d6ff3d"/>
<!-- E-W arm — softened acid so the vertical bearing dominates -->
<path d="M56 256 L256 216 L456 256 L256 296 Z" fill="#d6ff3d" opacity="0.55"/>
<!-- Center hole -->
<circle cx="256" cy="256" r="18" fill="#0b0e14"/>
</svg>

After

Width:  |  Height:  |  Size: 828 B

46
brand/banner.svg Normal file
View file

@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
<!-- theseus.x social banner — 1200x630 OpenGraph / Twitter Card canonical.
Composition is horizontal: compass mark on the left third, wordmark
centred, tagline underneath. Centre-safe for Facebook + Discord crops. -->
<defs>
<radialGradient id="glow" cx="30%" cy="40%" r="60%">
<stop offset="0%" stop-color="#16202e" stop-opacity="1"/>
<stop offset="100%" stop-color="#0b0e14" stop-opacity="1"/>
</radialGradient>
<filter id="mark-shadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="12" in="SourceGraphic"/>
</filter>
</defs>
<rect width="1200" height="630" fill="url(#glow)"/>
<!-- Compass mark, large, left of centre -->
<g transform="translate(280 175) scale(4.4)">
<g opacity="0.28" filter="url(#mark-shadow)">
<rect width="64" height="64" rx="12" fill="#d6ff3d"/>
</g>
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity="0.55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</g>
<!-- Wordmark, right of the compass -->
<text x="580" y="330" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
font-size="90" font-weight="700" fill="#e7eaf1" letter-spacing="-1.5">
theseus<tspan fill="#d6ff3d">.x</tspan>
</text>
<!-- Tagline -->
<text x="580" y="385" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
font-size="26" font-weight="500" fill="#8b98a9" letter-spacing=".3">
The browser that speaks Bitcoin Cash names
</text>
<!-- Small metadata footer, centre-safe -->
<text x="600" y="560" text-anchor="middle" font-family="ui-monospace, monospace"
font-size="18" fill="#5e6678" letter-spacing="2">
NATIVE BCNR RESOLUTION · WINDOWS + ANDROID
</text>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

11
brand/favicon.svg Normal file
View file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- theseus.x — Navigator compass mark. Rounded dark square (matches the
sirius.x sibling family). N (top) half red like every real compass
needle, S (bottom) half in Silent Mode acid green. E-W arm softened
so the N-S bearing reads clearly at 16x16 tab-icon size. -->
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity="0.55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

353
brand/index.html Normal file
View file

@ -0,0 +1,353 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Brand — Theseus.X</title>
<meta name="description" content="Theseus.X brand kit — compass mark, wordmark, avatar, banner, favicon, colour palette, and usage guidance. All assets served as SVG so they stay crisp at any size.">
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="./favicon.svg" color="#d6ff3d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Theseus.X">
<meta property="og:title" content="Theseus.X brand kit">
<meta property="og:description" content="Compass mark, wordmark, avatar, banner, favicon, colour palette. All SVG, downloadable. The marks are reserved: use them to refer to Theseus, not as your own.">
<meta property="og:url" content="https://theseus.x/brand/">
<meta property="og:image" content="https://theseus.x/brand/banner.svg">
<meta name="twitter:card" content="summary_large_image">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--bearing:#e04e4e;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:1040px;margin:0 auto;padding:0 1.2rem}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;
display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a.get{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.get:hover{background:rgba(214,255,61,.18)}
.topnav a .g{color:var(--acid)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
/* Mobile hamburger — matches the home-page pattern. */
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:220px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
}
/* Sidebar overlay — same shape as index.html */
:root{--sn-w:200px;--tn-h:52px}
body{padding-top:var(--tn-h)}
.sidenav{position:fixed;top:var(--tn-h);left:0;bottom:0;width:var(--sn-w);z-index:15;
background:transparent;padding:32px 12px 22px 18px;
overflow-y:auto;pointer-events:none;transition:width .15s ease}
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
padding:9px 12px;border-radius:10px;
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
display:none;justify-content:center;align-items:center}
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
.sidenav a.active{opacity:.85;color:var(--acid)}
.sidenav a.active:hover{opacity:1}
section{scroll-margin-top:70px}
@media (max-width:1180px){
:root{--sn-w:64px}
.sidenav{padding:22px 6px}
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
@media (max-width:720px){
:root{--sn-w:0}
body{padding-bottom:64px}
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
padding:4px;flex-direction:row;background:rgba(11,14,20,.94);backdrop-filter:blur(12px);
display:flex;justify-content:space-around;overflow-x:auto;
scrollbar-width:none;-ms-overflow-style:none}
.sidenav::-webkit-scrollbar{display:none;width:0;height:0}
.sidenav a{flex:1;min-width:44px;padding:8px 4px;justify-content:center;gap:0;opacity:.5;font-size:15px}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
.mark{font-size:44px;line-height:1}
h1{font-size:clamp(1.7rem,4.6vw,2.3rem);margin:.3rem 0 .3rem}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
section{padding:2.2rem 0;border-top:1px solid var(--line);margin-top:2rem}
section:first-of-type{border-top:none;margin-top:1.2rem}
h2{font-size:1.35rem;margin:0 0 .35rem}
h3{font-size:1rem;margin:0 0 .3rem}
.lede{color:var(--mut);margin:0 0 1.4rem;max-width:720px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
.asset{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px 16px;display:flex;flex-direction:column}
.asset .preview{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:20px;
margin-bottom:12px;min-height:130px;display:flex;align-items:center;justify-content:center}
.asset .preview.wide{padding:0}
.asset .preview.wide img{width:100%;height:auto;display:block;border-radius:9px}
.asset .preview.avatar{padding:0}
.asset .preview.avatar img{width:104px;height:104px;display:block;border-radius:20px}
.asset .preview.favicon img{width:64px;height:64px;display:block}
.asset .preview.mono{background:#eef1f7;color:#0b0e14}
.asset h3{display:flex;align-items:center;justify-content:space-between;gap:10px}
.asset .meta{color:var(--dim);font-size:12.5px;margin:2px 0 10px;font-family:ui-monospace,monospace}
.asset .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto}
.btn{display:inline-block;text-decoration:none;padding:6px 12px;border-radius:8px;background:transparent;
border:1px solid var(--line);color:var(--ink);font-size:12.5px;font-family:inherit;cursor:pointer}
.btn:hover{background:var(--panel2)}
.btn.acid{background:var(--acid);color:#0b0e14;border-color:var(--acid);font-weight:600}
.btn.acid:hover{filter:brightness(1.1)}
.swatches{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:1rem}
.sw{border:1px solid var(--line);border-radius:10px;overflow:hidden}
.sw .chip{height:70px}
.sw .info{padding:10px 12px;font-size:12.5px;font-family:ui-monospace,monospace;color:var(--mut)}
.sw .info b{color:var(--ink);display:block;font-family:inherit}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:12px 15px;color:var(--mut);font-size:13.5px;margin-top:1.2rem}
.note b{color:var(--ink)}
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="../#download">Get Theseus</a>
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</summary>
<div class="menu-panel">
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/">Sirius.X</a>
</div>
</details>
</nav>
<aside class="sidenav" aria-label="On this page">
<a href="#logo" title="Compass mark &amp; wordmark"><span class="ic" aria-hidden="true">🧭</span><span class="lb">Logo</span></a>
<a href="#avatar" title="Avatar &amp; favicon" ><span class="ic" aria-hidden="true">🎭</span><span class="lb">Avatar</span></a>
<a href="#banner" title="Social banner" ><span class="ic" aria-hidden="true">🖼</span><span class="lb">Banner</span></a>
<a href="#palette" title="Colour palette" ><span class="ic" aria-hidden="true">🎨</span><span class="lb">Palette</span></a>
<a href="#typography" title="Typography" ><span class="ic" aria-hidden="true">🅰️</span><span class="lb">Typography</span></a>
<a href="#usage" title="Usage &amp; trademark" ><span class="ic" aria-hidden="true">📜</span><span class="lb">Usage</span></a>
</aside>
<header class="hero">
<div class="mark">🎨</div>
<h1>Theseus.X <span class="g">brand kit</span></h1>
<p class="tag">The Navigator compass mark, wordmark, avatar, banner, favicon, and colour palette.
Every asset ships as SVG so it stays crisp at any size and reads correctly in light and dark
contexts. Free to reuse with attribution.</p>
</header>
<div class="wrap">
<section id="logo">
<h2>Compass mark &amp; wordmark</h2>
<p class="lede">A rounded dark square with a compass rose — north half in bearing red like every
real compass needle, south half in Silent Mode acid green, softened east-west arm so the N-S
bearing dominates. Plus <b>theseus.x</b> with the <code>.x</code> TLD picked out in acid. The
mark alone works when you have no room for the wordmark; the full lockup is the default choice
everywhere else.</p>
<div class="grid">
<div class="asset">
<div class="preview"><img src="./logo.svg" alt="theseus.x logo" style="max-width:100%;height:auto"></div>
<h3>Wordmark <span class="meta">SVG · 320×64</span></h3>
<p class="lede" style="margin-bottom:.6rem">Full colour, dark backgrounds. Use this by default.</p>
<div class="row">
<a class="btn acid" href="./logo.svg" download>Download SVG</a>
<a class="btn" href="./logo.svg" target="_blank">Open</a>
</div>
</div>
<div class="asset">
<div class="preview mono" style="color:#0b0e14"><img src="./logo-mono.svg" alt="theseus.x mono" style="max-width:100%;height:auto;color:inherit"></div>
<h3>Wordmark, monochrome <span class="meta">SVG · currentColor</span></h3>
<p class="lede" style="margin-bottom:.6rem">Uses <code>currentColor</code>, inherits from the
surrounding text. Print, embossing, single-colour merch.</p>
<div class="row">
<a class="btn acid" href="./logo-mono.svg" download>Download SVG</a>
<a class="btn" href="./logo-mono.svg" target="_blank">Open</a>
</div>
</div>
</div>
</section>
<section id="avatar">
<h2>Avatar &amp; favicon</h2>
<p class="lede">Square icons for social profiles, Nostr, GitHub, and browser tabs.</p>
<div class="grid">
<div class="asset">
<div class="preview avatar"><img src="./avatar.svg" alt="theseus.x avatar"></div>
<h3>Avatar <span class="meta">SVG · 512×512</span></h3>
<p class="lede" style="margin-bottom:.6rem">Square profile picture. Social platforms apply
their own round mask; the corner radius keeps the compass intact when clipped.</p>
<div class="row">
<a class="btn acid" href="./avatar.svg" download>Download SVG</a>
<a class="btn" href="./avatar.svg" target="_blank">Open</a>
</div>
</div>
<div class="asset">
<div class="preview favicon"><img src="./favicon.svg" alt="theseus.x favicon"></div>
<h3>Favicon <span class="meta">SVG · 64×64, 12px radius</span></h3>
<p class="lede" style="margin-bottom:.6rem">Rounded browser-tab icon. Scales down to 16×16
cleanly; used on every Theseus.X page.</p>
<div class="row">
<a class="btn acid" href="./favicon.svg" download>Download SVG</a>
<a class="btn" href="./favicon.svg" target="_blank">Open</a>
</div>
</div>
</div>
</section>
<section id="banner">
<h2>Social banner</h2>
<p class="lede">1200×630 (OpenGraph / Twitter Card canonical). Composition survives aggressive
centre-cropping — the compass mark and tagline both stay visible even after Facebook and Discord
chop the sides.</p>
<div class="asset">
<div class="preview wide"><img src="./banner.svg" alt="theseus.x banner"></div>
<div class="row">
<a class="btn acid" href="./banner.svg" download>Download SVG</a>
<a class="btn" href="./banner.svg" target="_blank">Open</a>
</div>
</div>
</section>
<section id="palette">
<h2>Colour palette</h2>
<p class="lede">Silent Mode family colours. Dark chromes are the default; acid green is the
accent that signals "this is Theseus.X", and bearing red is reserved for the compass mark's
north triangle — never used for links, buttons, or generic UI accents.</p>
<div class="swatches">
<div class="sw"><div class="chip" style="background:#d6ff3d"></div><div class="info"><b>Acid</b>#d6ff3d — accent, links, active nav</div></div>
<div class="sw"><div class="chip" style="background:#e04e4e"></div><div class="info"><b>Bearing</b>#e04e4e — compass north, mark only</div></div>
<div class="sw"><div class="chip" style="background:#0b0e14"></div><div class="info"><b>Background</b>#0b0e14 — page canvas</div></div>
<div class="sw"><div class="chip" style="background:#141a24"></div><div class="info"><b>Panel</b>#141a24 — cards, elevated surfaces</div></div>
<div class="sw"><div class="chip" style="background:#18202c"></div><div class="info"><b>Panel 2</b>#18202c — nested / step items</div></div>
<div class="sw"><div class="chip" style="background:#e7eaf1"></div><div class="info"><b>Ink</b>#e7eaf1 — body text on dark</div></div>
<div class="sw"><div class="chip" style="background:#8b98a9"></div><div class="info"><b>Muted</b>#8b98a9 — secondary text</div></div>
<div class="sw"><div class="chip" style="background:#5e6678"></div><div class="info"><b>Dim</b>#5e6678 — tertiary text, dividers</div></div>
<div class="sw"><div class="chip" style="background:#4fd1a5"></div><div class="info"><b>OK</b>#4fd1a5 — success / verified chain</div></div>
<div class="sw"><div class="chip" style="background:#ffc75f"></div><div class="info"><b>Warn</b>#ffc75f — caution, holds</div></div>
</div>
</section>
<section id="typography">
<h2>Typography</h2>
<p class="lede">System font stack, so nothing needs to load and every OS renders the wordmark in
the same family the reader is used to.</p>
<div class="asset" style="max-width:640px">
<p style="margin:0;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
font-size:36px;font-weight:600;letter-spacing:-.5px">The quick brown fox</p>
<p style="margin:.4rem 0 0;color:var(--mut);font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
font-size:16px">jumps over the lazy dog · 0123456789 · theseus.x</p>
<div class="meta" style="margin-top:12px">system-ui, -apple-system, Segoe UI, Roboto, sans-serif</div>
</div>
<div class="note">The wordmark's <code>.x</code> is a <code>&lt;tspan fill="#d6ff3d"&gt;</code> —
don't render the whole word in acid. The compass north triangle is the only place
<b>bearing red</b> appears; don't add red anywhere else in the chrome.</div>
</section>
<section id="usage">
<h2>Usage &amp; trademark</h2>
<p class="lede">These files are the marks of Theseus and Silent Mode, not open-licensed artwork:
the code is MPL-2.0, the name and the compass are reserved, and the
<a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TRADEMARKS.md">trademark note</a> says what that allows. Use them to refer to Theseus in
third-party tools that integrate with Theseus.X (BCNR name explorers, wallet bridges, blog
posts, review sites), never as your own mark. Keep the compass proportions intact and don't
recolour the acid to a similar-but-different green — palette drift is what makes brands look
untrustworthy over time.</p>
<ul style="color:var(--mut);font-size:14px;padding-left:1.2rem">
<li><b style="color:var(--ink)">Do</b> use the mark to indicate "opens in Theseus" or "supports BCNR".</li>
<li><b style="color:var(--ink)">Do</b> shrink to the favicon at 16×16 — the SVG scales cleanly.</li>
<li><b style="color:var(--ink)">Do</b> use the monochrome logo where the acid green would clash.</li>
<li><b style="color:var(--ink)">Don't</b> imply endorsement of a specific product without asking.</li>
<li><b style="color:var(--ink)">Don't</b> ship a fork, a product or a name under this name or mark — a fork is welcome under its own.</li>
<li><b style="color:var(--ink)">Don't</b> stretch, skew, or re-tint the compass.</li>
<li><b style="color:var(--ink)">Don't</b> introduce a second accent colour — acid green is the only one.</li>
</ul>
</section>
</div>
<footer>
Sibling brand kits: <a href="//sirius.x/brand/">sirius.x/brand</a> ·
<a href="//ariadne.x/brand/">ariadne.x/brand</a>
</footer>
<script>
// Sidenav scroll-spy: highlight the section currently in view.
(() => {
const links = document.querySelectorAll(".sidenav a[href^='#']");
if (!links.length) return;
const entries = [];
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
if (!entries.length) return;
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
const update = () => {
const doc = document.documentElement;
const scrollBottom = window.scrollY + window.innerHeight;
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
const line = window.scrollY + window.innerHeight * 0.35;
let active = entries[0].link;
for (const e of entries) {
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
else break;
}
setActive(active);
};
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
update();
})();
</script>
</body>
</html>

17
brand/logo-mono.svg Normal file
View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 64" fill="currentColor">
<!-- theseus.x monochrome wordmark. Uses currentColor so it inherits from
the surrounding text. Compass mark is drawn as one silhouette — the
N and S triangles fused into a diamond, with the E-W arm behind at
50% opacity so the bearing still reads. Print, embossing, single-
colour merch. -->
<g>
<rect x="0" y="0" width="64" height="64" rx="12" fill="none" stroke="currentColor" stroke-width="2"/>
<path d="M32 6 L38 32 L32 58 L26 32 Z" fill="currentColor"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="currentColor" opacity="0.5"/>
<circle cx="32" cy="32" r="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
</g>
<text x="80" y="43" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
font-size="30" font-weight="600" fill="currentColor" letter-spacing=".5">
theseus.x
</text>
</svg>

After

Width:  |  Height:  |  Size: 963 B

16
brand/logo.svg Normal file
View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 64">
<!-- theseus.x wordmark. Compass mark on the left, "theseus" in ink, ".x"
in acid green. Kept minimal — no drop-shadow, no gradients — so the
SVG scales cleanly on both dark and light chromes. -->
<g transform="translate(0 0)">
<rect x="0" y="0" width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity="0.55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</g>
<text x="80" y="43" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
font-size="30" font-weight="600" fill="#e7eaf1" letter-spacing=".5">
theseus<tspan fill="#d6ff3d">.x</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 851 B

View file

@ -0,0 +1,208 @@
# Claude connector — design and plan
An opt-in MCP server built into Theseus, so a Claude client on the same
computer (Claude Code, Claude Desktop) can work inside the user's real,
signed-in profile: open a tab, read it, click, type, paste rich text into an
editor, upload a file.
## Why
On 2026-10-22 a Claude Code session had to paste a prepared story into a
Medium draft as the user's author account. The Claude desktop app's built-in
browser could not do it: its synthetic paste arrived empty and its typed text
never reached Medium's editor. The two other routes are worse:
- `--remote-debugging-port` on the main profile opens a browser-wide DevTools
port. Any local process can attach and read every tab, cookie and session.
- Theseus is a desktop app, and these sessions have no computer-use access to
drive it from outside.
So the browser itself has to offer a narrow, user-controlled door.
## Shape
A built-in add-on, `bundled-addons/claude-connector/`, following the Pithos
pattern: `activate(api)` starts a small HTTP server on `127.0.0.1` from the
main process and registers a sidebar panel. What it needs from the browser
that no add-on API offered yet comes through one new host capability,
`tab-control`, which only built-in add-ons can hold.
```
Claude Code ──HTTP (Bearer token)──► 127.0.0.1:<port>/mcp ─┐
Claude Desktop ─stdio─► stdio-shim.cjs ──HTTP──────────────┤
▼
claude-connector add-on (main process)
policy: allowlist · password guard · upload folders · log
│ api.tabControl (tab-control capability)
▼
main.js: tabs, open/close/navigate, indicator
│ webContents.debugger (CDP, per tab)
▼
one tab's page
```
### Off by default
- The add-on ships with `"defaultEnabled": false`. main.js puts any built-in
add-on with that flag into `settings.disabledAddons` the first time it sees
it (remembered in `settings.addonsDefaultOffSeen`, so the user's later
choice is never overridden). The user turns it on in **Settings ›
Extensions › Claude connector**.
- While it is off, nothing listens. When it is on, the panel has a second
switch, **Accept connections**, on by default once the extension is
turned on, so the stop button can close the port without uninstalling
anything.
### Transport and authentication
- MCP **Streamable HTTP** at `http://127.0.0.1:<port>/mcp`. POST carries
JSON-RPC; GET opens an SSE stream (kept alive with comments, used as the
"connected" signal); DELETE ends a session.
- Binds `127.0.0.1` only. The port is picked once per install (first free in
47800–47899, else any) and kept, so the config the user pasted stays valid.
- A random 256-bit token per install, base64url. Every request needs
`Authorization: Bearer <token>`, compared in constant time.
- DNS-rebinding and browser guard: the `Host` header must be
`127.0.0.1:<port>` or `localhost:<port>`, and any request carrying an
`Origin` header is refused. A web page cannot reach the server even if it
guessed the token.
- **Revoke** in the panel issues a new token, which drops every client at
once. **Stop** (toolbar chip or panel) closes the port.
- Claude Desktop speaks stdio, so the add-on ships `stdio-shim.cjs`, run by
the Theseus executable itself in Node mode (`ELECTRON_RUN_AS_NODE=1`, the
same mode the extension sandbox already uses). It reads the port from
`extensions-data/claude-connector/endpoint.json` and the token from
`THESEUS_MCP_TOKEN`, and relays line-delimited JSON-RPC to the HTTP endpoint.
### Per-tab control, no browser-wide port
- Each controlled tab gets `webContents.debugger` (CDP 1.3) attached on its
own webContents. No `--remote-debugging-port`, no DevTools target list.
- main.js already attaches that debugger to freeze background tabs
(`setTabFrozen`). The connector shares the attachment: it attaches only if
nothing is attached, and detaches only what it attached itself.
- Only ordinary web tabs (`http:`, `https:`) are controllable. Settings,
add-on pages, the home page, `file:` and internal schemes are refused:
a Settings tab would let a client switch the connector's own safety
settings.
- A dormant restored tab is loaded and a frozen one thawed before it is used.
While a tab is controlled it gets focus emulation (`Emulation.
setFocusEmulationEnabled`), so editors that check `document.hasFocus()`
accept input while the Theseus window is in the background.
### Host capability `tab-control` (addons-host.js + main.js)
`api.tabControl`, built-in add-ons only, manifest must declare `tab-control`:
| call | does |
| --- | --- |
| `list()` | every tab: id, title, url, kind (`web`/`settings`/`addon`/`home`), active, pending |
| `webContents(id)` | the tab's webContents, after loading a dormant tab and thawing a frozen one; throws for non-web tabs |
| `open(url, {background})` | new tab through main's own `createTab` (so BCNR names resolve as typed) |
| `navigate(id, url)` | main's `navigateTab` |
| `close(id)`, `activate(id)` | as the tab strip does |
| `setStatus({connected, clients, busy, tabIds})` | drives the toolbar indicator and the per-tab marker |
| `onStop(cb)` | the indicator's stop button |
### Indicator
- A chip in the toolbar, next to Downloads: **Claude** with a dot that pulses
while a tool call runs. Visible whenever a client is connected (an SSE
stream is open or a request arrived in the last 2 minutes). Clicking it
opens the connector panel; its ✕ stops the connector at once (closes the
port, ends sessions, detaches every debugger).
- Each tab the client is controlling gets an outline marker in the tab strip.
### Tools
| tool | how |
| --- | --- |
| `list_tabs` | `tabControl.list()`; tabs on sites not yet approved show only their host |
| `open_tab(url, background?)` | approval for the target origin first |
| `navigate(tab_id, url \| action)` | url, or `back`/`forward`/`reload` |
| `close_tab(tab_id)`, `activate_tab(tab_id)` | |
| `screenshot(tab_id, full_page?)` | `Page.captureScreenshot`, scaled to CSS pixels so coordinates match `click(x, y)`; a hidden tab is brought to the front first (hidden views render no frames) |
| `read_page_text(tab_id, max_chars?)` | `innerText` read in an isolated world |
| `snapshot(tab_id, interactive_only?)` | `Accessibility.getFullAXTree`, compacted to `role "name" [ref=eN]` lines; refs map to `backendDOMNodeId` |
| `click(tab_id, ref \| x,y)` | `DOM.scrollIntoViewIfNeeded` + `DOM.getContentQuads`, then `Input.dispatchMouseEvent` press/release |
| `type(tab_id, text, ref?)` | `DOM.focus`, then `Input.insertText` per line and a real Enter (`Input.dispatchKeyEvent`) between lines; `mode: "keys"` sends every character as keyDown/keyUp |
| `press_key(tab_id, key, modifiers?)` | Enter, Tab, Backspace, arrows, ctrl+a … |
| `paste_html(tab_id, html, ref?)`, `paste_text(tab_id, text, ref?)` | write the real clipboard (`clipboard.write({html, text})`), `webContents.paste()`, then put the user's clipboard back |
| `upload_file(tab_id, ref, path)` | `DOM.setFileInputFiles` on an `<input type=file>` |
| `wait_for(tab_id, selector \| text, timeout_ms?)` | polls in an isolated world |
| `evaluate(tab_id, expression)` | `Runtime.evaluate` in the page; listed only while **Allow evaluate** is on (off by default) |
Page reads run in an isolated world (`Page.createIsolatedWorld`), so page
scripts can neither see them nor tamper with the helpers.
### Safety
- **Per-site allowlist.** The first time a client acts on an origin (opens it,
navigates to it, reads or drives a tab on it), Theseus asks: *Allow Claude
to control medium.com?* — **Allow** (remembered), **This session only**, or
**Don't allow**. A refusal is remembered for 10 minutes, so a client cannot
spam the prompt. The panel lists approved sites with a remove button.
- **Never types into password fields.** Before `type`, `press_key`,
`paste_*` and `evaluate`-free input, the focused element (followed into
shadow roots and same-origin frames) is checked in an isolated world:
`input[type=password]`, `autocomplete=current-password|new-password|
one-time-code`, or a cross-origin frame that cannot be inspected → refused.
`click` on a ref that is a password field is refused too, so a client
cannot focus one by ref and then type.
- **Upload folders.** `upload_file` resolves the real path (symlinks and
junctions followed) and refuses anything outside the folders the user added
in the panel (none by default), anything that is not a regular file, and
files over 100 MB. The ref must be an `<input type=file>`.
- **evaluate** is a separate switch, off by default, with a warning in the
panel: it runs arbitrary script as the page.
- **Action log.** Every call is logged with an ISO timestamp, the tool, the
tab's origin, a short summary of the arguments (typed and pasted text is
logged by length and first 60 characters) and the outcome. The panel shows
the newest entries live; the log is kept in
`extensions-data/claude-connector/actions.log` (JSON lines, rotated at 1 MB).
### What it does not defend against
- A program already running as the user. The token sits in the add-on store
(`extensions-data/claude-connector.json`) next to the profile's cookies,
which such a program can read anyway. The connector keeps the door shut to
web pages and to other machines, and keeps the user in charge of which sites
a connected client may touch.
- A site that changes a field's type after the focus check. The check runs
right before each input; what the page does in between is up to the page.
## Files
- `bundled-addons/claude-connector/` — `addon.json`, `index.js` (activate,
settings, panel messages), `lib/mcp-server.cjs` (HTTP + JSON-RPC +
sessions), `lib/tab-driver.cjs` (CDP per tab), `lib/tools.cjs` (tool
definitions and handlers), `lib/policy.cjs` (allowlist, upload paths,
password guard script), `lib/action-log.cjs`, `panel.html`,
`stdio-shim.cjs`, `README.md`.
- `addons-host.js` — `tab-control` capability and `api.tabControl`.
- `main.js` — the `tabControl` hook, indicator state, `defaultEnabled:false`
handling, `claude-connector` kept as a reserved first-party id.
- `preload.js`, `chrome.html` — the indicator chip and the tab marker.
- `tests/claude-connector.test.mjs` — launches Theseus on a scratch profile
(`THESEUS_USER_DATA`, `THESEUS_NO_UPDATE_CHECK`), with the connector
pre-enabled in that profile, and drives a local test page over MCP: type
into a rich editor, paste HTML with headings and links, upload a PNG,
refuse a password field and an upload outside the folder.
- `tests/claude-connector-unit.test.mjs` — protocol, auth and policy checks
that run under plain Node.
- README section with the `claude mcp add` command.
## Steps
1. Host: `tab-control` capability in addons-host.js; hook, indicator state,
default-off handling in main.js; chip in chrome.html/preload.js.
2. Add-on: server, driver, tools, policy, log, panel, shim.
3. Tests: unit (plain Node), then the Electron end-to-end test.
4. Docs: README section, add-on README.
## Not in this round
- Cross-origin iframes (OOPIFs) are not driven; typing into one is refused
because its focused element cannot be checked.
- No per-tool approval prompts beyond the per-site one.
- No remote (non-loopback) clients, by design.

363
dev/index.html Normal file
View file

@ -0,0 +1,363 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Developers — Theseus.X</title>
<meta name="description" content="Theseus Navigator developer guide: how the embedded resolver works, how to write a plug-in or an extension, how to add Ariadne's Thread to your own app so your users can resolve BNS names.">
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="assets/favicon.svg" color="#d6ff3d">
<meta property="og:type" content="article">
<meta property="og:site_name" content="theseus.x">
<meta property="og:title" content="Developers — Theseus.X">
<meta property="og:description" content="Resolver spec, plug-in and extension APIs, and how to embed Ariadne's Thread in your own app.">
<meta property="og:url" content="https://theseus.x/dev/">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#4fd1a5; --warn:#ffc75f;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;padding-top:52px}
a{color:var(--acid)}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;
letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;
display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
.topnav a .g{color:var(--acid)}
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:220px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
}
header.hero{text-align:center;padding:4rem 1.2rem 1.5rem}
.lede-mark{display:inline-block}
.lede-mark svg{width:72px;height:72px}
h1{font-size:clamp(1.9rem,5vw,2.6rem);margin:.6rem 0 .4rem;letter-spacing:-.4px}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.02rem;max-width:720px;margin:0 auto}
.tag code{background:#0e131b;border:1px solid var(--line);border-radius:5px;padding:1px 5px;
font-family:ui-monospace,monospace;font-size:.92em;color:var(--acid)}
section{padding:2.2rem 0;border-top:1px solid var(--line);margin-top:2rem;scroll-margin-top:72px}
section:first-of-type{border-top:none;margin-top:1rem}
h2{font-size:1.4rem;margin:0 0 .5rem}
h2 .g{color:var(--acid)}
h3{font-size:1.05rem;margin:1.3rem 0 .4rem;color:var(--ink)}
p{color:var(--mut);margin:0 0 .9rem}
p b{color:var(--ink);font-weight:600}
ul,ol{color:var(--mut);line-height:1.7;padding-left:1.3rem}
ul li+li,ol li+li{margin-top:.25rem}
code{background:#0e131b;border:1px solid var(--line);border-radius:5px;padding:1px 6px;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--acid);word-break:break-all}
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:14px 16px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;line-height:1.55;color:#dde3ec;margin:.8rem 0}
pre code{background:transparent;border:0;padding:0;color:inherit}
pre .k{color:var(--acid)}
pre .s{color:#9fd59f}
pre .c{color:#5e6678;font-style:italic}
pre .f{color:#8fc5ff}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin:1rem 0}
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px}
.card h3{margin:0 0 .4rem;font-size:1rem}
.card p{margin:0;font-size:13.5px}
.card a.cta{display:inline-block;margin-top:.6rem;color:var(--acid);text-decoration:none;font-size:13px}
.card a.cta:hover{text-decoration:underline}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.2rem 0}
.note b{color:var(--ink)}
footer.deep{background:linear-gradient(180deg,transparent,rgba(255,255,255,.02));
border-top:1px solid var(--line);padding:3rem 0 2rem;margin-top:3rem;color:var(--mut);font-size:13.5px}
footer.deep .fwrap{max-width:980px;margin:0 auto;padding:0 1.2rem;display:grid;
grid-template-columns:1.5fr repeat(3,1fr);gap:2rem}
@media (max-width:820px){footer.deep .fwrap{grid-template-columns:1fr 1fr;gap:1.6rem}}
@media (max-width:460px){footer.deep .fwrap{grid-template-columns:1fr;gap:1.4rem}}
footer.deep .brand-col{max-width:280px}
footer.deep .brand-col .brand{display:inline-flex;align-items:center;gap:8px;color:var(--ink);
text-decoration:none;font-weight:600;font-size:16px;margin-bottom:.5rem}
footer.deep .brand-col .brand .g{color:var(--acid)}
footer.deep h5{color:var(--ink);font-size:12px;text-transform:uppercase;letter-spacing:.14em;
margin:0 0 .8rem;font-weight:600}
footer.deep ul{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}
footer.deep ul a{color:var(--mut);text-decoration:none;font-size:13.5px}
footer.deep ul a:hover{color:var(--acid)}
footer.deep ul a .g{color:var(--acid)}
footer.deep ul a .ext{color:var(--dim);font-size:11px;margin-left:2px}
footer.deep .bar{max-width:980px;margin:2rem auto 0;padding:1rem 1.2rem 0;
border-top:1px solid var(--line);display:flex;justify-content:space-between;gap:1rem;
flex-wrap:wrap;color:var(--dim);font-size:12px}
footer.deep .bar .badge{background:rgba(214,255,61,.12);color:var(--acid);
padding:2px 8px;border-radius:5px;font:600 10px/1.4 system-ui;
letter-spacing:.14em;text-transform:uppercase;margin-right:8px}
footer.deep .bar .mono{font-family:ui-monospace,monospace;font-size:11.5px}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a class="here" href="./">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="../#download" style="margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500">Get Theseus</a>
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</summary>
<div class="menu-panel">
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="./">Developers</a>
<a href="//sirius.x/">Sirius.X</a>
</div>
</details>
</nav>
<header class="hero">
<div class="lede-mark">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
</div>
<h1>Theseus for <span class="g">developers</span></h1>
<p class="tag">
Everything you need to extend Theseus Navigator, or borrow its superpower — on-device
Bitcoin Cash Domain Name resolution — for whatever you are building. Theseus ships
<a href="../plug-ins/">Ariadne's Thread</a> as a bundled plug-in; the same resolver can live
inside your app too.
</p>
</header>
<div class="wrap">
<section id="resolver">
<h2>Resolve BNS names <span class="g">from your own code</span></h2>
<p>The authoritative developer page is on Ariadne.X — the resolver is the Ariadne project. It
covers the BNS1 protocol in one page, the record types, a 60-line Node.js resolver you can
run as-is, and the pointers to every production implementation.</p>
<div class="cards">
<div class="card">
<h3>→ Resolve a BNS name from your own code</h3>
<p>Protocol, record types, minimum resolver, reference implementations, public endpoints.</p>
<a class="cta" href="//ariadne.x/dev/">ariadne.x/dev</a>
</div>
<div class="card">
<h3>→ Ariadne's Thread</h3>
<p>What it is, how it works, system-wide mode, trust model.</p>
<a class="cta" href="//ariadne.x/thread/">ariadne.x/thread</a>
</div>
<div class="card">
<h3>→ Protocol spec</h3>
<p>The authoritative BNS1 specification on the forge.</p>
<a class="cta" href="https://code.silentmode.st/silentmode/ariadne/src/branch/master/Decentralized.DNS/PROTOCOL.md">PROTOCOL.md ↗</a>
</div>
</div>
</section>
<section id="embed">
<h2>Add BNS to <span class="g">your own app</span></h2>
<p>If you ship any kind of app with a browser surface — Electron, native, game launcher, mail
client — your users can resolve <code>.bch</code>, <code>.x</code>, <code>.p2p</code> as soon
as Ariadne's Thread is on the machine. Three paths:</p>
<h3>Easiest — chain-install the Windows service</h3>
<p>Bundle the Thread installer and run <code>AriadneResolver-Setup.exe /S</code> after your own
install finishes. Theseus does this today. The resolver registers an NRPT rule that only
affects BNS TLDs, so everything else on the machine is untouched.</p>
<h3>JavaScript / Electron — import the resolver</h3>
<pre><code><span class="k">import</span> { resolveName } <span class="k">from</span>
<span class="s">"https://code.silentmode.st/silentmode/ariadne/raw/branch/master/Argus/src/lib/resolver-web.js"</span>;
<span class="k">const</span> { records } = <span class="k">await</span> <span class="f">resolveName</span>(<span class="s">"sirius.x"</span>);
<span class="c">// records.s3 → "bns/sirius.x/"</span></code></pre>
<h3>Any language — HTTP API against a local Thread</h3>
<p>If the Thread is installed, it exposes a local HTTP API on <code>127.0.0.1:4626</code>:</p>
<pre><code>GET /api/name/&lt;name&gt;
GET /api/dns/&lt;name&gt;
GET /health</code></pre>
<p>Full embedding guide, platform coverage table, and the NSIS/Inno chain-install snippets
live on <a href="//ariadne.x/dev/#embed">ariadne.x/dev/#embed</a>.</p>
</section>
<section id="extensions">
<h2>Build an <span class="g">extension</span></h2>
<p>Extensions are everyone's add-ons: published by any BCNR name holder, signed by that
name's wallet, verified on your users' devices before a byte runs. They are not plug-ins —
plug-ins are first-party and ship inside Theseus (see <a href="../plug-ins/">Plug-ins</a>).</p>
<h3>Publish an extension</h3>
<ol>
<li>Register a <code>.x</code> name on <a href="//sirius.x/">sirius.x</a>.</li>
<li>Build your extension tarball — same manifest as Chrome extensions plus a Silent Mode
<code>addon.json</code>.</li>
<li>Upload it with <code>PUT /api/ext</code>, signed with the wallet that owns the name.</li>
<li>Theseus verifies the signature against the on-chain owner before installing. Updates work
the same way; your users' Theseus checks for a newer <code>updates.json</code> on your
own Sia bucket.</li>
</ol>
<p>Walk-through: <a href="../extensions/publish/">theseus.x/extensions/publish</a>.</p>
</section>
<section id="claude">
<h2>Connect <span class="g">Claude</span> to Theseus</h2>
<p>The built-in <b>Claude connector</b> is a local MCP server: Claude Code or Claude Desktop on the
same computer can open and read your tabs, click, type into rich editors, paste formatted text and
upload files, in the profile where you are already signed in. It is off until you turn it on, and
it reaches nothing beyond this computer.</p>
<h3>Set it up</h3>
<ol>
<li>Turn it on in <b>Settings › Extensions › Claude connector</b>. Its panel (the connector icon in
the toolbar) shows the address, the token, the sites you have approved, the upload folders and a
live log.</li>
<li>Copy the command from the panel and run it once. <code>--scope user</code> makes it available in
every project:</li>
</ol>
<pre><code>claude mcp add --scope user --transport http theseus http://127.0.0.1:47800/mcp --header <span class="s">"Authorization: Bearer &lt;token from the panel&gt;"</span></code></pre>
<p><code>claude mcp list</code> should then show <code>theseus</code> as connected. For <b>Claude Desktop</b>,
the panel's <i>Claude Desktop</i> tab has a ready <code>claude_desktop_config.json</code> entry. It runs
a small stdio relay through the Theseus executable itself, so there is nothing else to install.</p>
<h3>What Claude can do</h3>
<p><code>list_tabs</code>, <code>open_tab</code>, <code>navigate</code>, <code>activate_tab</code>,
<code>close_tab</code>, <code>screenshot</code>, <code>read_page_text</code>, <code>snapshot</code>
(the accessibility tree, with a ref per element), <code>click</code>, <code>type</code>,
<code>press_key</code>, <code>paste_html</code>, <code>paste_text</code>, <code>upload_file</code>,
<code>wait_for</code>, and <code>evaluate</code> while you allow it. Typing is real key input and
pastes go through the system clipboard and the browser's own paste, so editors such as Medium's
accept them; your clipboard is put back afterwards.</p>
<div class="note"><b>What keeps you in charge.</b> The connector listens on 127.0.0.1 only, behind a
random token; requests from web pages are refused. There is no browser-wide debugging port: each
tab is driven on its own. Theseus asks the first time Claude acts on a site. Password and one-time-code
fields are always refused, files can be uploaded only from folders you add, and running page scripts
(<code>evaluate</code>) is a separate switch, off by default. Every action is logged with its time.
The toolbar chip's ✕ stops the connector at once; <i>Revoke token</i> cuts off every client you set
up.</div>
<p>Design notes:
<a href="claude-connector/DESIGN-claude-connector.md">DESIGN-claude-connector.md</a>
(hosted here &middot;
<a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/DESIGN-claude-connector.md">forge mirror ↗</a>).
The connector shipped with Theseus 0.3.107.</p>
</section>
<section id="plug-ins">
<h2>Plug-ins <span class="g">(first-party)</span></h2>
<p>Plug-ins are the few add-ons Silent Mode builds, signs with the operator key, and ships as
part of Theseus — Aegis wallet, Ariadne's Thread, Shield blocker. Users enable or disable
them from <b>Settings › Plug-ins</b>.</p>
<p>If you have an idea for one, open an issue on
<a href="https://code.silentmode.st/silentmode/theseus">silentmode/theseus</a> first — the
plug-in surface is intentionally small and we triage proposals before accepting code.</p>
</section>
<section id="source">
<h2>Where everything lives</h2>
<ul>
<li><a href="https://code.silentmode.st/silentmode/theseus">silentmode/theseus</a> — the browser</li>
<li><a href="https://code.silentmode.st/silentmode/ariadne">silentmode/ariadne</a> — Ariadne.X site + resolver docs</li>
<li><a href="https://code.silentmode.st/silentmode/sirius">silentmode/sirius</a> — the registrar</li>
<li><a href="https://code.silentmode.st/silentmode/hephaestus">silentmode/hephaestus</a> — this forge</li>
</ul>
</section>
</div>
<footer class="deep">
<div class="fwrap">
<div class="brand-col">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true" width="24" height="24">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<p>A Chromium-based browser that resolves Bitcoin Cash Domain Names on the device itself.</p>
</div>
<div>
<h5>Developers</h5>
<ul>
<li><a href="./">Theseus dev guide</a></li>
<li><a href="//ariadne.x/dev/">Resolver guide <span class="ext">↗</span></a></li>
<li><a href="https://code.silentmode.st/silentmode/theseus">Source <span class="ext">↗</span></a></li>
</ul>
</div>
<div>
<h5>Product</h5>
<ul>
<li><a href="../">Home</a></li>
<li><a href="../plug-ins/">Plug-ins</a></li>
<li><a href="../extensions/">Extensions</a></li>
<li><a href="../brand/">Brand book</a></li>
<li><a href="../whitepaper/">Whitepaper</a></li>
</ul>
</div>
<div>
<h5>Pantheon</h5>
<ul>
<li><a href="//ariadne.x/">Ariadne<span class="g">.X</span> — mobile</a></li>
<li><a href="//sirius.x/">Sirius<span class="g">.X</span> — names</a></li>
<li><a href="//hephaestus.x/">Hephaestus<span class="g">.X</span> — forge</a></li>
</ul>
</div>
</div>
<div class="bar">
<span><span class="badge">Alpha</span>theseus.x — a BCNR name resolving to a Sia-hosted site</span>
<span class="mono">MPL-2.0 · docs CC BY 4.0</span>
</div>
</footer>
</body>
</html>

46
dl/index.html Normal file
View file

@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x/dl — downloads</title>
<meta name="robots" content="noindex">
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
<style>
:root{--bg:#0b0e14;--panel:#141a24;--line:rgba(255,255,255,.09);--ink:#e7eaf1;--mut:#8b98a9;--acid:#d6ff3d}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));color:var(--ink);
font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;display:grid;place-items:center;padding:1.2rem}
a{color:var(--acid)}
.card{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:1.6rem 1.8rem;max-width:560px;width:100%}
h1{margin:0 0 .3rem;font-size:1.5rem}
.mut{color:var(--mut);font-size:.95rem;margin:0 0 1.2rem}
.row{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.7rem 0;border-top:1px solid var(--line)}
.row b{display:block}.row small{color:var(--mut)}
.btn{display:inline-block;padding:.5rem .9rem;border-radius:10px;background:var(--acid);color:#0b0e14;font-weight:650;text-decoration:none;white-space:nowrap}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
.foot{margin-top:1.2rem;font-size:.9rem;color:var(--mut)}
</style>
</head>
<body>
<div class="card">
<h1>Downloads</h1>
<p class="mut">These links always point at the current build. Verify the SHA-256 on <a href="/#verify">theseus.x</a> before running.</p>
<div class="row"><div><b>Theseus Navigator for Windows</b><small>installer · v<span data-live="theseus-version">0.3.48</span></small></div><a class="btn" href="https://dl.silentmode.st/latest/TheseusNavigator-Setup.exe">Installer</a></div>
<div class="row"><div><b>Theseus Navigator, portable</b><small>single .exe, no install</small></div><a class="btn ghost" href="https://dl.silentmode.st/latest/TheseusNavigator-portable.exe">Portable</a></div>
<div class="row"><div><b>Ariadne for Android</b><small>APK · v<span data-live="android-version">0.18</span></small></div><a class="btn ghost" href="https://dl.silentmode.st/latest/ariadne.apk">APK</a></div>
<div class="row"><div><b>Ariadne's Thread for Windows</b><small>system-wide resolver, installed by Theseus's setup by default</small></div><a class="btn ghost" href="https://dl.silentmode.st/latest/AriadneResolver-Setup.exe">Installer</a></div>
<p class="foot">Machine-readable: <a href="https://dl.silentmode.st/releases-manifest.json">releases-manifest.json</a> · all releases: <a href="https://silentmode.st/releases/">silentmode.st/releases</a></p>
</div>
<script>
(async () => {
try {
const r = await fetch("https://dl.silentmode.st/releases-manifest.json", { cache: "no-store" }); if (!r.ok) return;
const m = await r.json(); const g = (id) => (m.releases || []).find((x) => x.id === id);
const set = (k, v) => { if (v) document.querySelectorAll('[data-live="' + k + '"]').forEach((el) => el.textContent = v); };
set("theseus-version", g("theseus-navigator")?.version); set("android-version", g("ariadne-android")?.version);
} catch {}
})();
</script>
</body>
</html>

512
extensions/index.html Normal file
View file

@ -0,0 +1,512 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x — Extensions</title>
<meta name="description" content="Signed over-the-air update endpoint for Theseus's bundled add-ons. Each entry is Ed25519-signed by the Silent Mode operator; verified on every install without waiting for a Theseus release.">
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="theseus.x">
<meta property="og:title" content="theseus.x — Extensions">
<meta property="og:description" content="Signed OTA update endpoint for Theseus's bundled add-ons. Ed25519, no cert authority, no app-store gatekeeper.">
<meta property="og:url" content="https://theseus.x/extensions/">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#4fd1a5;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;
display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a .g{color:var(--acid)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
/* Mobile hamburger — matches the home-page pattern. */
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:220px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
}
.topnav a.get{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.get:hover{background:rgba(214,255,61,.18)}
/* Adaptive edge nav: full-width panel at left edge on wide,
icon rail on medium, bottom bar with icons on mobile. */
:root{--sn-w:200px;--tn-h:52px}
body{padding-top:var(--tn-h)}
.sidenav{position:fixed;top:var(--tn-h);left:0;bottom:0;width:var(--sn-w);z-index:15;
background:transparent;padding:56px 12px 22px 18px;
overflow-y:auto;transition:width .15s ease;pointer-events:none}
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
padding:9px 12px;border-radius:10px;
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
display:none;justify-content:center;align-items:center}
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
.sidenav a.active{opacity:.85;color:var(--acid)}
.sidenav a.active:hover{opacity:1}
@media (max-width:1180px){
:root{--sn-w:64px}
.sidenav{padding:22px 6px}
.sidenav .lbl{display:none}
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
@media (max-width:720px){
:root{--sn-w:0px}
body{padding-left:0;padding-bottom:64px}
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
padding:4px 4px;flex-direction:row;background:rgba(11,14,20,.52);
display:flex;justify-content:space-around;overflow-x:auto}
.sidenav .lbl{display:none}
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
h1{font-size:clamp(1.8rem,4.5vw,2.4rem);margin:.6rem 0 .3rem;letter-spacing:-.3px}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:1.6rem;scroll-margin-top:72px}
section:first-of-type{border-top:none;margin-top:1rem}
h2{font-size:1.35rem;margin:0 0 .5rem}
.lede{color:var(--mut);margin:0 0 1.2rem;max-width:720px}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:.8rem 0}
.ext{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin:1rem 0}
.ext h3{margin:0 0 .3rem;font-size:1.15rem;display:flex;align-items:center;gap:10px}
.ext h3 .icon{font-size:1.4rem}
.ext .cur{color:var(--acid);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase}
.ext .desc{color:var(--mut);font-size:14px;margin:.3rem 0 .8rem}
.ext .verifybox{margin-top:.9rem;padding-top:.8rem;border-top:1px solid var(--line);font-size:11.5px;color:var(--dim)}
.ext .verifybox .lbl{color:var(--mut);font-size:12px;margin-bottom:6px}
.ext .h{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline;margin-top:4px}
.ext .h b{color:var(--mut);font-weight:600;min-width:70px}
.ext .h code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
.ext .meta{color:var(--dim);font-size:12.5px;margin-top:.4rem}
.ext .act .download{display:inline-block;padding:9px 14px;border-radius:9px;text-decoration:none;
font-size:13.5px;font-weight:600;background:transparent;color:var(--ink);border:1px solid var(--line)}
.ext .act .download:hover{background:var(--panel)}
.ext .act .hint{color:var(--dim);font-size:11.5px;margin-left:auto;flex-basis:100%}
.ext .act .hint b{color:var(--mut);font-weight:500}
.ext .act{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:.8rem}
.ext .install{display:inline-block;background:var(--acid);color:#0b0e14;font-weight:600;font-size:14px;
border:none;border-radius:9px;padding:9px 18px;cursor:pointer;text-decoration:none;font-family:inherit}
.ext .install:hover{filter:brightness(1.08)}
.ext .install[disabled]{opacity:.6;cursor:default}
.ext .install.alt{background:rgba(214,255,61,.12);color:var(--acid)}
.ext .act .st{font-size:13px;color:var(--mut)}
.ext .act .st.ok{color:var(--acid)} .ext .act .st.err{color:#f6768a}
.ext .act .how{flex-basis:100%;font-size:13px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:8px 12px}
.ext .how b{color:var(--ink)}
.search{max-width:560px;margin:1.4rem auto 0;position:relative}
.search input{width:100%;background:var(--panel);border:1px solid var(--line);border-radius:12px;color:var(--ink);
font:inherit;font-size:15px;padding:11px 14px 11px 40px;outline:none}
.search input:focus{border-color:rgba(214,255,61,.5)}
.search svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);width:17px;height:17px;color:var(--dim)}
.search .n{position:absolute;right:13px;top:50%;transform:translateY(-50%);font-size:12px;color:var(--dim)}
.ext.hide, .nomatch{display:none}
.nomatch.show{display:block}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
.note b{color:var(--ink)}
.pubkey{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;
font-family:ui-monospace,monospace;font-size:11px;color:#c9d4e3;word-break:break-all;margin:.6rem 0}
.flow{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 20px;margin-top:1rem;color:var(--mut);font-size:14px}
.flow ol{margin:0;padding-left:20px}
.flow li{margin:.4rem 0}
.flow b{color:var(--ink)}
footer{color:var(--dim);font-size:12.5px;margin:3rem 0 2rem;text-align:center}
footer a{color:var(--mut)}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a href="../plug-ins/">Plug-ins</a>
<a class="here" href="./">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="../#download">Get Theseus</a>
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</summary>
<div class="menu-panel">
<a href="../plug-ins/">Plug-ins</a>
<a href="./">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/">Sirius.X</a>
</div>
</details>
</nav>
<aside class="sidenav" aria-label="On this page">
<a href="#bundled" title="Bundled" ><span class="ic" aria-hidden="true">📦</span><span class="lb">Bundled</span></a>
<a href="#community" title="Community" ><span class="ic" aria-hidden="true">👥</span><span class="lb">Community</span></a>
<a href="#build" title="Build &amp; publish" ><span class="ic" aria-hidden="true">🔨</span><span class="lb">Build &amp; publish</span></a>
<a href="#updates" title="How updates work" ><span class="ic" aria-hidden="true">🔄</span><span class="lb">How updates work</span></a>
<a href="#operator-key" title="Operator key" ><span class="ic" aria-hidden="true">🔑</span><span class="lb">Operator key</span></a>
<a href="#roadmap" title="Roadmap" ><span class="ic" aria-hidden="true">🗺</span><span class="lb">Roadmap</span></a>
</aside>
<header class="hero">
<h1>Theseus <span class="g">Extensions</span></h1>
<p class="tag">Add-ons for Theseus Navigator — the ones that ship in the box and the ones anyone can
publish. Every package is signed (by the Silent Mode operator, or by the publisher's own BCDN name)
and verified on your device before install. No cert authority, no app-store gatekeeper.
First-party plug-ins (Aegis, Ariadne's Thread) have their <a href="../plug-ins/">own page</a>.</p>
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="extSearch" type="search" placeholder="Search extensions — name, what it does, publisher" autocomplete="off" aria-label="Search extensions">
<span class="n" id="extSearchN"></span>
</div>
</header>
<div class="wrap">
<section id="bundled">
<h2>Bundled extensions</h2>
<p class="lede">Ship with every Theseus build and update over the operator-signed channel. Versions and hashes
are read from each add-on's signed <code>updates.json</code> when this page loads.</p>
<div class="ext" id="screenshot" data-ext="screenshot">
<h3><span class="icon">📸</span> Screenshot <span class="cur" style="margin-left:auto">current <span data-live="version">0.6.5</span></span></h3>
<div class="desc">Capture the current tab — visible viewport, entire scrollable page, or a rectangle
you draw. The capture opens in a full-tab editor: crop, annotate, line tool, resizable text,
redact, save.</div>
<div class="meta">Settings › Extensions · <span data-live="count"></span></div>
<div class="act">
<a class="install" href="theseus://extensions/install/screenshot" data-install="screenshot">Install in Theseus</a>
<a class="download" data-live="download" href="https://navigate.st/bns/theseus.x/extensions/screenshot/screenshot-0.6.5.tar.gz" download>Download tarball</a>
<div class="hint">Updates: signed <a href="./screenshot/updates.json">updates.json</a> · SHA-256 verified before every install</div>
</div>
<div class="verifybox">
<div class="lbl">Signed update manifest — SHA-256 verified before install</div>
<div class="h"><b>updates.json</b><code><a href="./screenshot/updates.json">theseus.x/extensions/screenshot/updates.json</a></code></div>
<div class="h"><b>Tarball</b><code><a data-live="url" href="./screenshot/screenshot-0.6.5.tar.gz">screenshot-0.6.5.tar.gz</a></code></div>
<div class="h"><b>sha256</b><code data-live="sha256">87ae730d329ff9f552bbf513efba19bd14bb016bb90e24f7b62868434dadda60</code></div>
</div>
</div>
<div class="ext" id="notepad">
<h3><span class="icon">📝</span> Notepad <span class="cur" style="margin-left:auto">bundled 0.1.0</span></h3>
<div class="desc">Right-sidebar notes. Text lives on this machine only and autosaves as you type.</div>
<div class="meta">Settings › Extensions · no update channel yet — ships with each Theseus release.</div>
<div class="act">
<span class="hint" style="margin-left:0"><b>Ships with Theseus</b> — nothing to install; appears under Settings › Extensions on first launch.</span>
</div>
</div>
<div class="ext" id="translate">
<h3><span class="icon">🌐</span> Translate <span class="cur" style="margin-left:auto">bundled 0.1.0</span></h3>
<div class="desc">Select text on any page and right-click <i>Translate</i> — the sidebar panel shows the translation,
with LibreTranslate or Google as the backend (your choice in the panel). Nothing is sent until you ask.</div>
<div class="meta">Settings › Extensions · no update channel yet — ships with each Theseus release.</div>
<div class="act">
<span class="hint" style="margin-left:0"><b>Ships with Theseus</b> — nothing to install; appears under Settings › Extensions on first launch.</span>
</div>
</div>
<div class="ext" id="pdf-editor" data-ext="pdf-editor">
<h3><span class="icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="vertical-align:middle"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" fill="#dc2626"/><path d="M14 2v6h6z" fill="#991b1b"/><text x="12" y="17" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-size="5.5" font-weight="800" fill="#fff">PDF</text></svg></span> PDF Editor <span class="cur" style="margin-left:auto">current <span data-live="version">0.1.0</span></span></h3>
<div class="desc">Open a PDF in a full tab — read, highlight, mark up, reorder or rotate or drop pages,
fill form fields, save a new copy. Annotations are written as real PDF content, the original
is never touched. Opens from the toolbar's PDF Editor menu or the right-click <i>Open PDF in editor</i>
on any link.</div>
<div class="meta">Settings › Extensions · <span data-live="count">signed update channel</span></div>
<div class="act">
<a class="install" href="theseus://extensions/install/pdf-editor" data-install="pdf-editor">Install in Theseus</a>
<a class="download" data-live="download" href="https://navigate.st/bns/theseus.x/extensions/pdf-editor/pdf-editor-0.1.0.tar.gz" download>Download tarball</a>
<div class="hint">Updates: signed <a href="./pdf-editor/updates.json">updates.json</a> · SHA-256 verified before every install</div>
</div>
<div class="verifybox">
<div class="lbl">Signed update manifest — SHA-256 verified before install</div>
<div class="h"><b>updates.json</b><code><a href="./pdf-editor/updates.json">theseus.x/extensions/pdf-editor/updates.json</a></code></div>
<div class="h"><b>Tarball</b><code><a data-live="url" href="./pdf-editor/pdf-editor-0.1.0.tar.gz">pdf-editor-0.1.0.tar.gz</a></code></div>
<div class="h"><b>sha256</b><code data-live="sha256">—</code></div>
</div>
</div>
<div class="ext" id="claude-connector">
<h3><span class="icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" style="vertical-align:middle"><rect x="4" y="4" width="56" height="56" rx="14" fill="#d97757"/><path d="M32 13v38M13 32h38M18.6 18.6l26.8 26.8M45.4 18.6 18.6 45.4" stroke="#fff4ee" stroke-width="6" stroke-linecap="round"/></svg></span> Claude connector <span class="cur" style="margin-left:auto">bundled 0.1.0</span></h3>
<div class="desc">Lets Claude Code or Claude Desktop on your computer work in your signed-in tabs through a
local MCP connection: read pages, click, type into rich editors, paste formatted text, upload files.
Off until you turn it on. Theseus asks before Claude touches each new site, never lets it type into
a password field, uploads only from folders you choose, and logs every action. A <i>Claude</i> chip
in the toolbar shows while a client is connected; its ✕ stops everything.</div>
<div class="meta">Settings › Extensions · off by default · ships with the next Theseus release (after 0.3.106)</div>
<div class="act">
<span class="hint" style="margin-left:0"><b>Ships with Theseus</b> — nothing to install. Setup: <a href="../dev/#claude">theseus.x/dev/#claude</a>.</span>
</div>
</div>
<div class="nomatch note" id="nomatchBundled">No bundled extension matches that search.</div>
</section>
<section id="community">
<h2>Community extensions</h2>
<p class="lede">Published by BCDN name holders, signed with the wallet that holds the name. Open this page in Theseus
and click <b>Install in Theseus</b> — or use <b>Settings › Extensions › Community</b>. Either way Theseus fetches the
package itself and re-checks every signature against the name's current owner in its own chain index — the relay
cannot substitute code under a trusted name.</p>
<div id="communityList"><div class="ext" style="color:var(--dim)">Loading the catalog…</div></div>
<div class="nomatch note" id="nomatchCommunity">No community extension matches that search.</div>
<div class="note"><b>Trust, stated plainly.</b> The signature proves <i>who</i> published an extension, not that it
is safe. A community extension runs in its own sandboxed process: it can read only its own folder, cannot start
other programs, and cannot see your vault, your wallet or the browser's memory. It reaches Theseus only through
the capabilities its manifest declares, and it <i>can</i> use the network. Its panel and anything it injects
into pages still do what those capabilities allow, so read the capability list before installing. Older Theseus
builds ran extensions inside the browser itself — keep Theseus up to date.</div>
</section>
<section id="build">
<h2>Build and publish your own</h2>
<p class="lede">An extension is a folder with an <code>addon.json</code> and a main script. Package it, sign it with
the wallet that holds one of your BCDN names, and it is listed above within a minute. Nothing to apply for.</p>
<div class="flow">
<ol>
<li><b>Write it.</b> The manifest and a CommonJS entry that exports <code>activate(api)</code>:
<pre>my-ext/
addon.json {"id":"my-ext","name":"My Extension","version":"1.0.0",
"description":"…","author":"you.bch","icon":"✨",
"main":"index.js","capabilities":["sidebar-panel"]}
index.js module.exports = { activate(api) {
api.registerSidebarPanel({ id:"main", title:"My Extension", icon:"✨", page:"panel.html" });
} };
panel.html plain HTML, runs in the sidebar; window.silentmode.storage for local state</pre>
The bundled <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TheseusNavigator/bundled-addons/notepad">Notepad</a>
is the smallest complete example; <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TheseusNavigator/addons-host.js">addons-host.js</a>
is the full API. <code>id</code> is <code>[a-z0-9._-]</code>, 2–64 chars, and belongs to the first name that publishes it.</li>
<li><b>Mind the sandbox.</b> Your main script runs in its own process, not inside the browser:
<ul>
<li>It can read its own folder and write to <code>api.dataDir</code>, nothing else on disk. No child processes, workers or native modules.</li>
<li>Calls into Theseus return promises, <code>api.tabs.active()</code> included. <code>api.storage.get</code> stays synchronous.</li>
<li><code>api.require</code> and <code>api.import</code> are not available: bundle the modules you need in the package.</li>
<li>Request filters and site routes are for built-in add-ons. The vault is reachable only as <code>api.vault.derive</code> under your own id and <code>api.vault.requestUnlock</code>, where Theseus draws the prompt.</li>
<li><code>api.features.sandboxed</code> is <code>true</code> when this applies.</li>
</ul></li>
<li><b>Test it locally.</b> Settings › Extensions › <i>Open extensions folder</i>, drop the folder in, <i>Reload</i>.</li>
<li><b>Package it.</b> <code>tar -czf my-ext-1.0.0.tar.gz -C my-ext .</code> (max 8 MB; <code>addon.json</code> at the root or in a single top folder).</li>
<li><b>Publish it.</b> Open <a class="btn" href="./publish/" style="display:inline-block;padding:.35rem .8rem;border-radius:8px;background:var(--acid);color:#0b0e14;font-weight:650;text-decoration:none">Publish an extension →</a>
sign in with the recovery phrase of the wallet that holds your name, pick the package, and sign. The gateway
verifies the signature against the name's on-chain owner, stores the package on Sia, and updates the catalog.
New versions: same page, higher version number, same name.</li>
</ol>
</div>
<p class="lede" style="margin-top:1rem">Prefer the command line? The upload is one signed <code>PUT</code>:
<code>PUT https://silentmode.st/api/ext/&lt;your-name&gt;/&lt;id&gt;/&lt;version&gt;</code> with the tarball as body and headers
<code>x-bns-ts</code>, <code>x-bns-sig</code> (over <code>sha256("BNS-EXT1\n&lt;name&gt;\n&lt;id&gt;\n&lt;version&gt;\n&lt;sha256&gt;\n&lt;ts&gt;")</code>)
and <code>x-bns-entry-sig</code> (over <code>sha256("silentmode.extension-v1|&lt;id&gt;|&lt;version&gt;|&lt;sha256&gt;|&lt;name&gt;")</code>),
both 65-byte BCH message signatures by the name's owner key.</p>
</section>
<script>
// Bundled cards: fill from each add-on's signed manifest (relative URLs work on every origin the page is served from).
(async () => {
const cmp = (a, b) => { const x = String(a).split(".").map(Number), y = String(b).split(".").map(Number); for (let i = 0; i < Math.max(x.length, y.length); i++) { const d = (x[i] || 0) - (y[i] || 0); if (d) return d; } return 0; };
for (const card of document.querySelectorAll(".ext[data-ext]")) {
const id = card.dataset.ext;
try {
const r = await fetch("./" + id + "/updates.json", { cache: "no-store" });
if (!r.ok) continue;
const m = await r.json();
const list = (m.addons || []).slice().sort((x, y) => cmp(y.version, x.version));
const top = list[0]; if (!top) continue;
const set = (k, f) => card.querySelectorAll('[data-live="' + k + '"]').forEach(f);
set("version", (el) => { el.textContent = top.version; });
set("sha256", (el) => { el.textContent = top.sha256 || "—"; });
set("url", (el) => { el.href = top.url; el.textContent = String(top.url).split("/").pop(); });
set("download", (el) => { if (top.url) el.href = top.url; });
set("count", (el) => { el.textContent = list.length + " signed version" + (list.length === 1 ? "" : "s") + " on the channel"; });
} catch {}
}
// Community catalog
const box = document.getElementById("communityList");
const esc = (s) => String(s || "").replace(/[&<>"']/g, (c) => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" })[c]);
// Inside Theseus every page has window.bcnr; builds since 0.3.50 add
// installExtension(id). The page only passes the catalog id — Theseus
// fetches the package, confirms in a native dialog and verifies the signature.
const inTheseus = !!window.bcnr;
const canInstall = inTheseus && typeof window.bcnr.installExtension === "function";
const mode = canInstall ? "bridge" : inTheseus ? "old" : "none";
const installUi = (id) => mode === "none"
? `<a class="install" href="../#download">Get Theseus to install</a><span class="st">one click from this page inside Theseus</span>`
: `<button class="install" type="button" data-install="${esc(id)}" data-mode="${mode}">Install in Theseus</button><span class="st" data-st="${esc(id)}"></span>`;
try {
const r = await fetch("./community/catalog.json", { cache: "no-store" });
const cat = r.ok ? await r.json() : { extensions: [] };
const list = Array.isArray(cat.extensions) ? cat.extensions : [];
if (!list.length) { box.innerHTML = '<div class="ext" style="color:var(--dim)">Nothing published yet — yours could be the first. See <a href="#build">Build and publish your own</a>.</div>'; return; }
const iconHtml = (icon) => {
const s = String(icon || "🧩");
if (/^data:image\//i.test(s)) return `<img src="${esc(s)}" alt="" style="width:20px;height:20px;vertical-align:middle">`;
return esc(s);
};
box.innerHTML = list.map((e) => `
<div class="ext" id="ext-${esc(e.id)}">
<h3><span class="icon">${iconHtml(e.icon)}</span> ${esc(e.name)} <span class="cur" style="margin-left:auto">v${esc(e.latest)}</span></h3>
<div class="desc">${esc(e.description)}</div>
<div class="meta">Published by <b>${esc(e.publisher)}</b> · ${esc(String(e.versions || 1))} version${e.versions === 1 ? "" : "s"} · updated ${esc(String(e.updatedAt || "").slice(0, 10))}${e.capabilities && e.capabilities.length ? " · " + esc(e.capabilities.join(", ")) : ""}</div>
<div class="act">${installUi(e.id)}</div>
<div class="verifybox">
<div class="lbl">Publisher-signed channel — verified by Theseus against ${esc(e.publisher)}'s current owner</div>
<div class="h"><b>updates.json</b><code><a href="${esc(e.updatesUrl)}">${esc(String(e.updatesUrl).replace(/^https?:\/\/[^/]+\/bns\//, ""))}</a></code></div>
<div class="h"><b>Tarball</b><code><a href="${esc(e.tarballUrl)}">${esc(String(e.tarballUrl).split("/").pop())}</a></code></div>
<div class="h"><b>sha256</b><code>${esc(e.sha256)}</code></div>
<div class="h"><b>Link</b><code>theseus://extensions/install/${esc(e.id)}</code></div>
</div>
</div>`).join("");
box.querySelectorAll("button[data-install]").forEach((btn) => btn.addEventListener("click", async () => {
const id = btn.dataset.install, st = box.querySelector(`[data-st="${CSS.escape(id)}"]`);
if (btn.dataset.mode === "old") {
// This Theseus predates the page bridge: point at the path it does have.
const name = [...btn.closest(".ext").querySelector("h3").childNodes].find((x) => x.nodeType === 3 && x.textContent.trim())?.textContent.trim() || id;
let how = btn.parentElement.querySelector(".how");
if (!how) { how = document.createElement("div"); how.className = "how"; btn.parentElement.appendChild(how); }
how.innerHTML = `Your Theseus is older than this button. Open <b>Settings › Extensions</b>, scroll to <b>Community</b> and press
<b>Install</b> next to <b>${esc(name)}</b> — same signed package, same checks. Newer builds install from here in one click:
<a href="../#download">update Theseus</a>.`;
return;
}
btn.disabled = true; st.className = "st"; st.textContent = "Confirm in the Theseus dialog…";
let r; try { r = await window.bcnr.installExtension(id); } catch (err) { r = { ok: false, error: err?.message || String(err) }; }
if (r && r.ok) { st.className = "st ok"; st.textContent = `Installed v${r.version} — signed by ${r.publisher}`; btn.textContent = "Installed"; }
else { btn.disabled = false; st.className = r?.error === "cancelled" ? "st" : "st err"; st.textContent = r?.error === "cancelled" ? "" : (r?.error || "install failed"); }
}));
} catch (e) { box.innerHTML = '<div class="ext" style="color:var(--dim)">Catalog unavailable right now.</div>'; }
})();
// Search: filters the bundled and community cards by name, description, publisher, capabilities.
(() => {
const q = document.getElementById("extSearch"), n = document.getElementById("extSearchN");
const apply = () => {
const s = q.value.trim().toLowerCase();
let shown = 0;
for (const sec of ["bundled", "community"]) {
const cards = [...document.querySelectorAll(`#${sec} .ext`)].filter((c) => c.querySelector("h3"));
let hit = 0;
for (const c of cards) { const ok = !s || c.textContent.toLowerCase().includes(s); c.classList.toggle("hide", !ok); if (ok) hit++; }
document.getElementById(sec === "bundled" ? "nomatchBundled" : "nomatchCommunity").classList.toggle("show", !!s && cards.length > 0 && hit === 0);
shown += hit;
}
n.textContent = s ? `${shown} match${shown === 1 ? "" : "es"}` : "";
};
q.addEventListener("input", apply);
new MutationObserver(() => { if (q.value.trim()) apply(); }).observe(document.getElementById("communityList"), { childList: true });
if (location.hash === "#search") q.focus();
})();
</script>
<section id="updates">
<h2>How updates work</h2>
<p class="lede">Same mechanism a Firefox XPI update uses, cut down to what a name-native browser needs.</p>
<div class="flow">
<ol>
<li>Every 30 seconds after boot Theseus fetches <code>updates.json</code> for each installed add-on that declares an <code>updateURL</code> in its manifest.</li>
<li>Each entry carries an Ed25519 signature over <code>silentmode.addon-update-v1|&lt;id&gt;|&lt;version&gt;|&lt;sha256&gt;</code>. Theseus verifies against a small set of operator public keys baked into the build. Unsigned or badly-signed entries are dropped silently.</li>
<li>If a signed version beats what's installed, Theseus downloads the tarball, re-checks its sha256, and stages the new copy under <code>&lt;userData&gt;/addons-updates-staged/&lt;id&gt;-&lt;version&gt;/</code>.</li>
<li>On the next Theseus launch, <code>promoteStagedUpdates</code> moves the staged copy into <code>addons/&lt;id&gt;/</code>. Any prior copy is renamed to <code>addons-backups/&lt;id&gt;-&lt;oldver&gt;-&lt;timestamp&gt;/</code> so hand-edits survive.</li>
<li>Settings › Extensions has a <b>Check for updates</b> button and shows what is staged; first-party plug-ins (Aegis) can also stage and restart from their own panel.</li>
</ol>
</div>
</section>
<section id="operator-key">
<h2>Operator key</h2>
<p class="lede">The Ed25519 public key baked into Theseus 0.3.19+.
Rotation is by adding a new pubkey and shipping a Theseus release that carries both, then a follow-up release removing the old — during the overlap, entries can be signed by either.</p>
<div class="pubkey">732b1263a236b0030383a2376597cfa43c3624b3ca2912a46134f8f2a06e6012</div>
<p style="color:var(--dim);font-size:12.5px;margin-top:.6rem">Generated 2026-09-07. Held offline by the Silent Mode operator.</p>
</section>
<section id="roadmap">
<h2>Roadmap</h2>
<p class="lede">The endpoint is generic — any bundled add-on can advertise an <code>updateURL</code>.
Concrete next-steps:</p>
<ul style="color:var(--mut);font-size:14px;line-height:1.7">
<li>On-BCNR pubkey publication so rotation is auditable end-to-end.</li>
<li>Multi-sig (N-of-M operator keys) so a single compromised private key can't push code.</li>
<li>Extend the channel to <b>Notepad</b> (bundled, no channel yet).</li>
</ul>
</section>
<footer>
Silent Mode · <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TheseusNavigator/docs/ADDON-UPDATES.md">source &amp; operator brief</a>
</footer>
</div>
<script>
// Sidenav scroll-spy: highlight the section currently in view.
// Uses scroll-position tracking so short sections at the end of the page
// still activate their link when the reader scrolls to them.
(() => {
const links = document.querySelectorAll(".sidenav a[href^='#']");
if (!links.length) return;
const entries = [];
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
if (!entries.length) return;
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
const update = () => {
const doc = document.documentElement;
const scrollBottom = window.scrollY + window.innerHeight;
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
const line = window.scrollY + window.innerHeight * 0.35;
let active = entries[0].link;
for (const e of entries) {
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
else break;
}
setActive(active);
};
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
update();
})();
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

141
extensions/js/session.js Normal file
View file

@ -0,0 +1,141 @@
// Sirius.X persistent sign-in ("stay signed in on this device").
//
// Problem: every visit asked for the seed phrase, the password or the PIN
// again. A dashboard people come back to daily cannot work like that.
//
// Model: after one successful sign-in the wallet's recovery phrase is
// encrypted with a random AES-GCM key that lives ONLY inside IndexedDB as a
// non-extractable CryptoKey (the browser can use it, scripts cannot read
// its bytes). The ciphertext sits in localStorage. On the next visit the
// dashboard decrypts silently and the user is in — no prompt. Signing out,
// or turning "stay signed in" off in Settings, destroys both halves.
//
// Trust boundary, stated honestly: this is a device session, like a
// "remember me" cookie. Anyone who can run scripts in this browser profile
// can use the wallet while the session exists. The password-encrypted
// wallet blob (bns.wallet.v1) and the optional PIN escrow are unchanged and
// still the durable secrets. Users who want a prompt before every payment
// can keep that in Settings ("Ask for PIN before payments").
//
// window.siriusSession = { isEnabled, setEnabled, remember, restore, forget,
// requirePinForPayments, setRequirePinForPayments }
(() => {
const DB = "sirius-session";
const STORE = "keys";
const KEY_ID = "device";
const BLOB = "siriusSessionBlob";
const ENABLED = "siriusStaySignedIn"; // "0" = off; anything else = on (default on)
const REQUIRE_PIN = "siriusRequirePin"; // "1" = ask PIN before payments (default off)
const enc = new TextEncoder();
const dec = new TextDecoder();
const b64 = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes)));
const unb64 = (str) => Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
function openDb() {
return new Promise((resolve, reject) => {
if (!("indexedDB" in window)) return reject(new Error("IndexedDB unavailable"));
const req = indexedDB.open(DB, 1);
req.onupgradeneeded = () => { req.result.createObjectStore(STORE); };
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error || new Error("IndexedDB open failed"));
});
}
function idbGet(db, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE, "readonly");
const req = tx.objectStore(STORE).get(key);
req.onsuccess = () => resolve(req.result || null);
req.onerror = () => reject(req.error);
});
}
function idbPut(db, key, value) {
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE, "readwrite");
tx.objectStore(STORE).put(value, key);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
function idbDel(db, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE, "readwrite");
tx.objectStore(STORE).delete(key);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function deviceKey(create) {
const db = await openDb();
try {
let key = await idbGet(db, KEY_ID);
if (!key && create) {
key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
await idbPut(db, KEY_ID, key);
}
return key;
} finally { db.close(); }
}
function isEnabled() {
try { return localStorage.getItem(ENABLED) !== "0"; } catch { return true; }
}
function setEnabled(on) {
try { localStorage.setItem(ENABLED, on ? "1" : "0"); } catch {}
if (!on) forget();
}
function requirePinForPayments() {
try { return localStorage.getItem(REQUIRE_PIN) === "1"; } catch { return false; }
}
function setRequirePinForPayments(on) {
try { localStorage.setItem(REQUIRE_PIN, on ? "1" : "0"); } catch {}
}
function hasSession() {
try { return !!localStorage.getItem(BLOB); } catch { return false; }
}
// Encrypt and store the recovery phrase for silent restore. `meta` may
// carry { accountPath } for wallets derived on a non-default path.
async function remember(mnemonic, meta = {}) {
if (!isEnabled() || !mnemonic) return false;
try {
const key = await deviceKey(true);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, enc.encode(mnemonic));
localStorage.setItem(BLOB, JSON.stringify({ v: 1, iv: b64(iv), ct: b64(ct), at: Date.now(), path: meta.accountPath || null }));
return true;
} catch { return false; }
}
// Silent restore. Resolves { mnemonic, accountPath } or null. Never throws.
async function restore() {
if (!isEnabled()) return null;
let raw = null;
try { raw = localStorage.getItem(BLOB); } catch {}
if (!raw) return null;
try {
const blob = JSON.parse(raw);
const key = await deviceKey(false);
if (!key) { forget(); return null; }
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: unb64(blob.iv) }, key, unb64(blob.ct));
return { mnemonic: dec.decode(pt), accountPath: blob.path || null };
} catch {
// Key rotated / storage cleared / tampered — drop the stale blob.
forget();
return null;
}
}
async function forget() {
try { localStorage.removeItem(BLOB); } catch {}
try { const db = await openDb(); try { await idbDel(db, KEY_ID); } finally { db.close(); } } catch {}
}
window.siriusSession = {
isEnabled, setEnabled, hasSession,
remember, restore, forget,
requirePinForPayments, setRequirePinForPayments,
};
})();

View file

@ -0,0 +1,239 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x — Publish an extension</title>
<meta name="description" content="Publish a Theseus extension: sign the package with the wallet that holds your BCDN name; it is stored on Sia and listed in the catalog.">
<meta name="robots" content="noindex">
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
<style>
:root{--bg:#0b0e14;--panel:#141a24;--panel2:#18202c;--line:rgba(255,255,255,.09);--ink:#e7eaf1;--mut:#8b98a9;--dim:#5e6678;--acid:#d6ff3d;--ok:#4fd1a5;--bad:#f6768a}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:760px;margin:0 auto;padding:0 1.2rem 3rem}
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
h1{font-size:clamp(1.6rem,4vw,2.1rem);margin:2.2rem 0 .3rem}
h1 .g{color:var(--acid)}
.lede{color:var(--mut);margin:0 0 1.4rem}
.step{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px;margin:1rem 0}
.step h2{font-size:1.05rem;margin:0 0 .6rem;display:flex;align-items:center;gap:10px}
.step h2 .n{display:inline-grid;place-items:center;width:24px;height:24px;border-radius:50%;background:var(--acid);color:#0b0e14;font-size:13px;font-weight:700}
.step h2 .done{color:var(--ok);font-size:13px;font-weight:500;margin-left:auto}
label{display:block;color:var(--mut);font-size:13px;margin:.6rem 0 .25rem}
input[type=text],textarea{width:100%;background:#0e131b;border:1px solid var(--line);border-radius:9px;color:var(--ink);padding:9px 11px;font:14px system-ui,sans-serif}
textarea{font-family:ui-monospace,monospace;font-size:13px;min-height:74px}
input[type=file]{color:var(--mut);font-size:13px}
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:.6rem}
.btn{display:inline-block;padding:.5rem 1rem;border-radius:9px;background:var(--acid);color:#0b0e14;font-weight:650;border:0;cursor:pointer;font:600 14px system-ui,sans-serif}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
.btn:disabled{opacity:.45;cursor:default}
.st{font-size:13px;color:var(--mut)}
.st.ok{color:var(--ok)} .st.bad{color:var(--bad)}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
.kv{display:grid;grid-template-columns:110px 1fr;gap:4px 12px;font-size:13px;margin-top:.6rem}
.kv b{color:var(--mut);font-weight:600}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);border-radius:10px;padding:12px 16px;color:var(--mut);font-size:13.5px;margin:1.2rem 0}
.note b{color:var(--ink)}
.chk{display:flex;gap:8px;align-items:center;color:var(--mut);font-size:13px;margin-top:.6rem}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="/"><span>theseus<span class="g">.x</span></span></a>
<a href="/">Home</a>
<a href="/plug-ins/">Plug-ins</a>
<a href="../">Extensions</a>
<a class="here" href="./">Publish</a>
</nav>
<div class="wrap">
<h1>Publish an <span class="g">extension</span></h1>
<p class="lede">Three steps, all in this tab: unlock the wallet that holds your BCDN name, choose the packaged
extension, sign. The gateway checks the signature against the name's on-chain owner, stores the package on
Sia and updates the <a href="../#community">catalog</a>. Your recovery phrase never leaves this page.</p>
<div class="step" id="s1">
<h2><span class="n">1</span> Wallet <span class="done" id="s1done" hidden>✓ unlocked</span></h2>
<div id="walletForm">
<label for="phrase">Recovery phrase of the wallet that holds your name (12 or 24 words)</label>
<textarea id="phrase" spellcheck="false" autocomplete="off" placeholder="word word word …"></textarea>
<label class="chk"><input type="checkbox" id="stay" checked> Stay signed in on this device (encrypted with a key only this browser profile can use)</label>
<div class="row"><button class="btn" id="unlock">Unlock</button><span class="st" id="walletSt"></span></div>
</div>
<div id="walletInfo" hidden>
<div class="kv"><b>Address</b><span class="mono" id="addr"></span></div>
<div class="row"><button class="btn ghost" id="signout">Sign out</button></div>
</div>
</div>
<div class="step" id="s2">
<h2><span class="n">2</span> Publisher name <span class="done" id="s2done" hidden>✓ you own it</span></h2>
<label for="pname">A BCDN name you own (e.g. <code>you.bch</code>) — it becomes the publisher of record</label>
<div class="row"><input type="text" id="pname" placeholder="name.tld" style="max-width:320px" spellcheck="false"><button class="btn ghost" id="checkName">Check ownership</button></div>
<div class="st" id="nameSt"></div>
</div>
<div class="step" id="s3">
<h2><span class="n">3</span> Package <span class="done" id="s3done" hidden>✓ looks valid</span></h2>
<label>Gzipped tar of the extension folder (<code>tar -czf my-ext-1.0.0.tar.gz -C my-ext .</code>, max 8 MB)</label>
<input type="file" id="pkg" accept=".tar.gz,.tgz,application/gzip">
<div class="kv" id="pkgInfo" hidden>
<b>id</b><span id="pid"></span>
<b>name</b><span id="pnameOut"></span>
<b>version</b><span id="pver"></span>
<b>main</b><span id="pmain"></span>
<b>files</b><span id="pfiles"></span>
<b>sha256</b><span class="mono" id="psha"></span>
</div>
<div class="st" id="pkgSt"></div>
</div>
<div class="step" id="s4">
<h2><span class="n">4</span> Sign and publish</h2>
<p class="st">Two signatures are made with your wallet key: one authorises this upload, the other is stored in the
extension's channel so every Theseus can verify the package came from your name.</p>
<div class="row"><button class="btn" id="publish" disabled>Sign &amp; publish</button><span class="st" id="pubSt"></span></div>
<div id="result" hidden>
<div class="kv">
<b>Catalog</b><span><a href="../#community">theseus.x/extensions</a></span>
<b>Channel</b><span class="mono" id="rUpdates"></span>
<b>Package</b><span class="mono" id="rTarball"></span>
</div>
<p class="st" style="margin-top:.6rem">Install it from Theseus › Settings › Extensions › Community. New versions: come back with a higher version number.</p>
</div>
</div>
<div class="note"><b>What the gateway enforces.</b> The signature must recover to the name's current NFT owner ·
<code>addon.json</code> at the package root with matching <code>id</code>/<code>version</code> and a <code>main</code> script ·
an id belongs to the first name that publishes it · each new version must be higher than the last · 8 MB cap.</div>
</div>
<script src="../js/session.js"></script>
<script type="module">
import * as BNS from "../js/bns-register.js";
const API = "https://silentmode.st";
const NAME_API = "https://navigate.st";
const $ = (id) => document.getElementById(id);
const st = (id, text, cls = "") => { const el = $(id); el.textContent = text; el.className = "st " + cls; };
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
const b64 = (u8) => btoa(String.fromCharCode(...u8));
let wallet = null, pubName = "", pkgBytes = null, pkgSha = "", manifest = null;
function showWallet() {
$("walletForm").hidden = !!wallet; $("walletInfo").hidden = !wallet; $("s1done").hidden = !wallet;
if (wallet) $("addr").textContent = wallet.address;
gate();
}
function gate() { $("publish").disabled = !(wallet && pubName && pkgBytes && manifest); }
// --- 1. wallet ---
(async () => {
try {
const S = window.siriusSession;
const s = S?.restore ? await S.restore() : null;
if (s?.mnemonic) { wallet = await BNS.BuiltInWallet.fromMnemonic(s.mnemonic, BNS.CHIPNET_PREFIX, s.accountPath || undefined); showWallet(); }
} catch {}
})();
$("unlock").onclick = async () => {
const phrase = $("phrase").value.trim().toLowerCase().replace(/\s+/g, " ");
const n = phrase.split(" ").length;
if (n !== 12 && n !== 24) return st("walletSt", "A recovery phrase has 12 or 24 words.", "bad");
st("walletSt", "Deriving…");
try {
wallet = await BNS.BuiltInWallet.fromMnemonic(phrase, BNS.CHIPNET_PREFIX);
if ($("stay").checked && window.siriusSession?.remember) await window.siriusSession.remember(phrase);
$("phrase").value = "";
st("walletSt", ""); showWallet();
} catch (e) { st("walletSt", "Could not derive a wallet: " + (e?.message || e), "bad"); }
};
$("signout").onclick = async () => { wallet = null; try { await window.siriusSession?.forget(); } catch {} showWallet(); };
// --- 2. name ownership ---
$("checkName").onclick = async () => {
const name = $("pname").value.trim().toLowerCase();
pubName = ""; $("s2done").hidden = true; gate();
if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(name)) return st("nameSt", "Enter a registered name like you.bch.", "bad");
if (!wallet) return st("nameSt", "Unlock the wallet first.", "bad");
st("nameSt", "Looking up the owner…");
try {
const r = await fetch(`${NAME_API}/api/name/${encodeURIComponent(name)}`, { cache: "no-store" });
const j = await r.json();
if (!j.registered) return st("nameSt", `${name} is not registered.`, "bad");
if (!j.owner) return st("nameSt", "The index has no owner for this name yet — try again in a minute.", "bad");
if (j.owner !== wallet.address) return st("nameSt", `${name} is owned by ${j.owner}, not by this wallet.`, "bad");
pubName = name; $("s2done").hidden = false; st("nameSt", "This wallet holds " + name + ".", "ok"); gate();
} catch (e) { st("nameSt", "Lookup failed: " + (e?.message || e), "bad"); }
};
// --- 3. package: sha256 + read addon.json out of the gzipped tar ---
async function gunzip(bytes) {
const ds = new DecompressionStream("gzip");
const out = new Blob([bytes]).stream().pipeThrough(ds);
return new Uint8Array(await new Response(out).arrayBuffer());
}
function tarFiles(raw) {
const files = []; const td = new TextDecoder();
const cstr = (a, b) => td.decode(raw.subarray(a, b)).replace(/\0[\s\S]*$/, "");
for (let off = 0; off + 512 <= raw.length; ) {
const hdr = raw.subarray(off, off + 512);
if (hdr.every((x) => x === 0)) break;
let name = cstr(off, off + 100);
const size = parseInt(cstr(off + 124, off + 136).trim() || "0", 8) || 0;
const type = String.fromCharCode(hdr[156] || 48);
if (cstr(off + 257, off + 263) === "ustar") { const pre = cstr(off + 345, off + 500); if (pre) name = pre + "/" + name; }
name = name.replace(/^\.\//, "");
if (type === "0" || type === "\0") files.push({ name, data: raw.subarray(off + 512, off + 512 + size) });
off += 512 + Math.ceil(size / 512) * 512;
}
return files;
}
$("pkg").onchange = async () => {
pkgBytes = null; manifest = null; $("pkgInfo").hidden = true; $("s3done").hidden = true; gate();
const f = $("pkg").files[0]; if (!f) return;
if (f.size > 8 * 1024 * 1024) return st("pkgSt", "Package is over 8 MB.", "bad");
try {
const bytes = new Uint8Array(await f.arrayBuffer());
const files = tarFiles(await gunzip(bytes));
const mf = files.filter((x) => x.name === "addon.json" || /^[^/]+\/addon\.json$/.test(x.name)).sort((a, b) => a.name.length - b.name.length)[0];
if (!mf) return st("pkgSt", "No addon.json at the package root.", "bad");
const root = mf.name === "addon.json" ? "" : mf.name.slice(0, -"addon.json".length);
const m = JSON.parse(new TextDecoder().decode(mf.data));
if (!/^[a-z0-9][a-z0-9._-]{1,63}$/.test(m.id || "")) return st("pkgSt", "addon.json id must be 2–64 chars of [a-z0-9._-].", "bad");
if (!/^\d+(\.\d+){1,3}$/.test(m.version || "")) return st("pkgSt", "addon.json version must look like 1.2.3.", "bad");
if (!m.main || !files.some((x) => x.name === root + String(m.main).replace(/^\.\//, ""))) return st("pkgSt", `main script "${m.main}" is not in the package.`, "bad");
if (!m.name) return st("pkgSt", "addon.json needs a name.", "bad");
pkgBytes = bytes; manifest = m; pkgSha = hex(await crypto.subtle.digest("SHA-256", bytes));
$("pid").textContent = m.id; $("pnameOut").textContent = m.name; $("pver").textContent = m.version; $("pmain").textContent = m.main;
$("pfiles").textContent = files.length + " files, " + (bytes.length / 1024).toFixed(1) + " KB"; $("psha").textContent = pkgSha;
$("pkgInfo").hidden = false; $("s3done").hidden = false; st("pkgSt", ""); gate();
} catch (e) { st("pkgSt", "Could not read the package: " + (e?.message || e), "bad"); }
};
// --- 4. sign + PUT ---
$("publish").onclick = async () => {
if (!(wallet && pubName && pkgBytes && manifest)) return;
$("publish").disabled = true; st("pubSt", "Signing…");
try {
const ts = String(Date.now());
const digest = async (msg) => new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(msg)));
const sig = b64(wallet.signMessage(await digest(`BNS-EXT1\n${pubName}\n${manifest.id}\n${manifest.version}\n${pkgSha}\n${ts}`)));
const entrySig = b64(wallet.signMessage(await digest(`silentmode.extension-v1|${manifest.id}|${manifest.version}|${pkgSha}|${pubName}`)));
st("pubSt", "Uploading to the gateway…");
const r = await fetch(`${API}/api/ext/${encodeURIComponent(pubName)}/${manifest.id}/${manifest.version}`, {
method: "PUT", body: pkgBytes, headers: { "content-type": "application/gzip", "x-bns-ts": ts, "x-bns-sig": sig, "x-bns-entry-sig": entrySig },
});
const j = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(j.error || ("HTTP " + r.status));
st("pubSt", `Published ${j.id} v${j.version} as ${j.publisher}.`, "ok");
$("rUpdates").textContent = j.updates; $("rTarball").textContent = j.tarball; $("result").hidden = false;
} catch (e) { st("pubSt", "Publish failed: " + (e?.message || e), "bad"); $("publish").disabled = false; }
};
</script>
</body>
</html>

525
index.html Normal file
View file

@ -0,0 +1,525 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x — the browser that speaks Bitcoin Cash names</title>
<meta name="description" content="Theseus Navigator: a Chromium-based browser with native Bitcoin Cash name resolution built in. No OS-level daemon, no DNS shim, no root-store install. Windows + Android.">
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="assets/favicon.svg" color="#d6ff3d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="theseus.x">
<meta property="og:title" content="theseus.x — the browser that speaks Bitcoin Cash names">
<meta property="og:description" content="Native .bch / BCNR resolution in a Chromium browser. No daemon, no root-store install. Windows + Android.">
<meta property="og:url" content="https://theseus.x/">
<meta property="og:image" content="https://theseus.x/assets/logo.svg">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="theseus.x — the browser that speaks Bitcoin Cash names">
<meta name="twitter:description" content="Native BCNR resolution in a Chromium browser. No daemon, no root-store install.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--chain:#d6ff3d; --sia:#b39ddb; --srv:#4fd1a5; --ok:#4fd1a5; --warn:#ffc75f;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:980px;margin:0 auto;padding:0 1.2rem}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;
display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a.get{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.get:hover{background:rgba(214,255,61,.18)}
.topnav a .g{color:var(--acid)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
/* Mobile menu: a native <details>/<summary> hamburger that holds the
contextual nav. Pure HTML+CSS, no JavaScript, keyboard-accessible.
Hidden on desktop (where the inline links show instead). */
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
/* Hide desktop inline nav + the floating Get CTA on mobile — the hero
sits one scroll away with its own download buttons. */
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:200px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel a svg.tabicon{width:16px;height:16px}
}
/* Adaptive edge nav: full-width panel at left edge on wide,
icon rail on medium, bottom bar with icons on mobile.
Topnav is fixed at the top spanning the full width; sidebar starts
just below it and content flows in the right column. */
:root{--sn-w:200px;--tn-h:52px}
/* Content stays centered — the sidebar overlays on top of the page. */
body{padding-top:var(--tn-h)}
.sidenav{position:fixed;top:var(--tn-h);left:0;bottom:0;width:var(--sn-w);z-index:15;
background:transparent;padding:56px 12px 22px 18px;
overflow-y:auto;transition:width .15s ease;pointer-events:none}
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
padding:9px 12px;border-radius:10px;
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
display:none;justify-content:center;align-items:center}
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
.sidenav a.active{opacity:.85;color:var(--acid)}
.sidenav a.active:hover{opacity:1}
/* Medium: shrink to an icon rail — icons only */
@media (max-width:1180px){
:root{--sn-w:64px}
.sidenav{padding:22px 6px}
.sidenav .lbl{display:none}
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
/* Mobile: bottom icon bar */
@media (max-width:720px){
:root{--sn-w:0px}
body{padding-left:0;padding-bottom:64px}
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
padding:4px 4px;flex-direction:row;background:rgba(11,14,20,.52);
display:flex;justify-content:space-around;overflow-x:auto}
.sidenav .lbl{display:none}
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
header.hero{text-align:center;padding:5rem 1.2rem 1.5rem}
.mark{display:inline-block}
.mark svg{width:80px;height:80px;filter:drop-shadow(0 4px 24px rgba(214,255,61,.18))}
h1{font-size:clamp(2rem,5.5vw,2.9rem);margin:.9rem 0 .3rem;letter-spacing:-.5px}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.05rem;max-width:660px;margin:0 auto}
.cta{margin-top:1.8rem;display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.btn{display:inline-block;text-decoration:none;padding:11px 20px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;transition:filter .1s}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
.btn:hover{filter:brightness(1.12)}
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem;scroll-margin-top:72px}
section:first-of-type{border-top:none;margin-top:1.5rem}
h2{font-size:1.5rem;margin:0 0 .4rem}
h3{font-size:1.1rem;margin:0 0 .35rem}
.lede{color:var(--mut);margin:0 0 1.6rem;max-width:720px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
.card h3{margin:0 0 .5rem;display:flex;align-items:center;gap:8px}
.card p{margin:0;color:var(--mut);font-size:14px}
.dot{width:9px;height:9px;border-radius:50%;display:inline-block;flex:none}
.d-chain{background:var(--chain)} .d-sia{background:var(--sia)} .d-srv{background:var(--srv)}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
.note.warn{border-left-color:var(--warn)}
.note b{color:var(--ink)}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:.8rem 0}
/* Downloads: platform cards */
.plats{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:1rem}
@media (max-width:720px){.plats{grid-template-columns:1fr}}
.plat{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px}
.plat .lbl{color:var(--acid);font:600 11px/1 system-ui;letter-spacing:.22em;text-transform:uppercase}
.plat h3{margin:.4rem 0 .3rem;font-size:1.15rem}
.plat p{margin:0;color:var(--mut);font-size:13.5px}
.plat .meta{color:var(--dim);font-size:12px;margin-top:.6rem}
.plat .btns{margin-top:.9rem;display:flex;gap:8px;flex-wrap:wrap}
.plat .btns .btn{padding:9px 16px;font-size:13.5px}
.plat .hashes{margin-top:.9rem;padding-top:.7rem;border-top:1px solid var(--line);font-size:11.5px;color:var(--dim)}
.plat .hashes .h{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline;margin-top:4px}
.plat .hashes .h b{color:var(--mut);font-weight:600;min-width:70px}
.plat .hashes code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
/* Deep, multi-column footer — same shape as Hephaestus.X and Sirius.X.
Brand column on the left plus three link columns; meta bar below with
licence badge and origin tag. Kept inside the .wrap so the sidenav
overlay never covers it. */
footer.deep{background:linear-gradient(180deg,transparent,rgba(255,255,255,.02));
border-top:1px solid var(--line);padding:3rem 0 2rem;margin-top:3rem;color:var(--mut);
font-size:13.5px}
footer.deep .fwrap{max-width:980px;margin:0 auto;padding:0 1.2rem;display:grid;
grid-template-columns:1.5fr repeat(3,1fr);gap:2rem}
@media (max-width:820px){footer.deep .fwrap{grid-template-columns:1fr 1fr;gap:1.6rem}}
@media (max-width:460px){footer.deep .fwrap{grid-template-columns:1fr;gap:1.4rem}}
footer.deep .brand-col{max-width:280px}
footer.deep .brand-col .brand{display:inline-flex;align-items:center;gap:8px;color:var(--ink);
text-decoration:none;font-weight:600;font-size:16px;margin-bottom:.5rem}
footer.deep .brand-col .brand .g{color:var(--acid)}
footer.deep .brand-col .brand svg{width:22px;height:22px}
footer.deep .brand-col p{margin:.3rem 0 0;color:var(--mut);font-size:13px;line-height:1.55}
footer.deep h5{color:var(--ink);font-size:12px;text-transform:uppercase;letter-spacing:.14em;
margin:0 0 .8rem;font-weight:600}
footer.deep ul{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}
footer.deep ul a{color:var(--mut);text-decoration:none;font-size:13.5px;transition:color .1s}
footer.deep ul a:hover{color:var(--acid)}
footer.deep ul a .g{color:var(--acid)}
footer.deep ul a .ext{color:var(--dim);font-size:11px;margin-left:2px}
footer.deep .bar{max-width:980px;margin:2rem auto 0;padding:1rem 1.2rem 0;
border-top:1px solid var(--line);display:flex;justify-content:space-between;gap:1rem;
flex-wrap:wrap;color:var(--dim);font-size:12px}
footer.deep .bar .badge{background:rgba(214,255,61,.12);color:var(--acid);
padding:2px 8px;border-radius:5px;font:600 10px/1.4 system-ui;
letter-spacing:.14em;text-transform:uppercase;margin-right:8px}
footer.deep .bar a{color:var(--mut)}
footer.deep .bar a:hover{color:var(--acid)}
footer.deep .bar .mono{font-family:ui-monospace,monospace;font-size:11.5px}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="./">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a href="plug-ins/">Plug-ins</a>
<a href="extensions/">Extensions</a>
<a href="dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="#download">Get Theseus</a>
<!-- Mobile hamburger — shown only at ≤720px, holds the same contextual
nav as the inline links above. Brand book + Whitepaper intentionally
live in the footer only; Get Theseus is in the hero one scroll away. -->
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<path d="M4 7h16M4 12h16M4 17h16"/>
</svg>
</summary>
<div class="menu-panel">
<a href="plug-ins/">Plug-ins</a>
<a href="extensions/">Extensions</a>
<a href="dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
</div>
</details>
</nav>
<aside class="sidenav" aria-label="On this page">
<a href="#what" title="What it is"><span class="ic" aria-hidden="true">🌐</span><span class="lb">What it is</span></a>
<a href="#features" title="Features" ><span class="ic" aria-hidden="true">⚡</span><span class="lb">Features</span></a>
<a href="#download" title="Download" ><span class="ic" aria-hidden="true">⬇</span><span class="lb">Download</span></a>
<a href="#verify" title="Verify" ><span class="ic" aria-hidden="true">🔐</span><span class="lb">Verify</span></a>
</aside>
<header class="hero">
<div class="mark">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
</div>
<h1>Theseus <span class="g">Navigator</span></h1>
<p class="tag">A Chromium-based browser that resolves Bitcoin&nbsp;Cash names on the
device itself. No OS-level daemon, no DNS shim, no root-store install. Type
<code>silentmode.bch</code>, get the on-chain answer, see which registry &amp;
which storage served it.</p>
<div class="cta">
<a class="btn acid" href="#download">Download for Windows</a>
<a class="btn ghost" href="#download">Android APK</a>
<a class="btn ghost" href="#what">How it works</a>
</div>
</header>
<div class="wrap">
<section id="what">
<h2>What it is</h2>
<p class="lede">Theseus is a browser built around the assumption that names should
be resolved by the person using them, not by an intermediary. It ships an
embedded resolver (Ariadne) that watches the Bitcoin Cash chain directly and answers
name lookups locally — the app <i>is</i> both the browser and the resolver.
Every page carries a badge showing which registry produced the record
(BCNR, ICANN) and which backend served the bytes (on-chain, Sia, direct IP,
or a plain HTTPS fallback).</p>
<div class="grid">
<div class="card">
<h3><span class="dot d-chain"></span> On-chain first</h3>
<p>Every dotted host is asked of BCNR before anything else. If the chain
answers, that answer wins — no matter what any DNS operator says. If
BCNR has no record, Theseus falls through to the clearnet.</p>
</div>
<div class="card">
<h3><span class="dot d-sia"></span> Trust one thing</h3>
<p>The app is the trust boundary. No daemon, no root cert, no proxy in the
middle. Uninstall it and BCNR is gone from that machine — nothing lingers
in the OS.</p>
</div>
<div class="card">
<h3><span class="dot d-srv"></span> Warm from the first click</h3>
<p>A bundled beacon snapshot loads before the first navigation can fire; a
background delta refresh keeps the index current every 30&nbsp;s; a Sia
snapshot pull on boot catches long-idle installs up in one HTTP call.</p>
</div>
</div>
</section>
<section id="features">
<h2>What's inside</h2>
<p class="lede">Everything a mainstream browser has, plus what the chain enables.</p>
<div class="grid">
<div class="card">
<h3>Native BCNR</h3>
<p>Type a name, get the on-chain record. <code>h</code> serves from the chain,
<code>s3</code> from Sia storage, <code>ip</code> direct-connects, <code>u</code>
redirects. TLS fingerprint pinning against the on-chain <code>tls</code> record
for <code>ip</code> hops.</p>
</div>
<div class="card">
<h3>window.bcnr API</h3>
<p>Every page can call <code>window.bcnr.resolveName(…)</code>,
<code>isRegistered(…)</code>, <code>getBcnrTlds()</code>, and
<code>getRecordVersion(…)</code>. dApps don't need to ship their own resolver.</p>
</div>
<div class="card">
<h3>Password manager</h3>
<p>Local vault, deterministic derivation from a BIP-39 seed. Same seed on a
different device produces the same passwords. Autofill on demand — never
watches the page for you.</p>
</div>
<div class="card">
<h3>Tor toggle</h3>
<p>One click routes everything through a bundled Tor client — pluggable
transports included for hostile networks. Off by default; nothing is
proxied without your say-so.</p>
</div>
<div class="card">
<h3>Anti-fingerprinting</h3>
<p>Timezone / locale / language / geolocation can be shown, hidden, spoofed
with a plausible decoy, or set manually. Media-device labels blanked by
default so <code>enumerateDevices</code> can't leak an audiooutput ID.</p>
</div>
<div class="card">
<h3>Privacy by default</h3>
<p>Cookies, cache, storage, and history clear on quit unless you opt in.
Camera and microphone denied by default. WebRTC IP policy set to
public-only.</p>
</div>
<div class="card">
<h3>System-wide, optional</h3>
<p>Theseus's resolver is in-process by default — nothing else on the
machine is touched. The Windows installer offers <b>Ariadne's Thread</b>
as a recommended-on checkbox: one click also installs a system-wide
resolver so Chrome, Edge, and every other browser resolve BCNR too.
Uninstalling Theseus asks whether to remove it. Skip the checkbox and
Theseus is still fully self-contained.</p>
</div>
<div class="card">
<h3>Small, auditable</h3>
<p>Under 100 MB, Electron + a few kilobytes of BCNR glue. Source at
<a href="https://silentmode.st/">silentmode.st</a>. Reproducible builds
with <code>SOURCE_DATE_EPOCH</code>.</p>
</div>
</div>
</section>
<section id="download">
<h2>Download</h2>
<p class="lede">Latest is <b data-live="theseus-version">0.3.134</b>. Windows installer + portable, and an
Android build (Ariadne mobile — same brand, phone form factor).</p>
<div class="plats">
<div class="plat">
<div class="lbl">Desktop · Windows 64-bit</div>
<h3>Theseus for Windows</h3>
<p>Full-window desktop browser with Tor toggle and the multi-source BNS
warm-start.</p>
<div class="meta">v<span data-live="theseus-version">0.3.134</span> · installer ~137 MB · portable ~137 MB · unsigned</div>
<div class="btns">
<a class="btn acid" href="https://dl.silentmode.st/latest/TheseusNavigator-Setup.exe">Installer</a>
<a class="btn ghost" href="https://dl.silentmode.st/latest/TheseusNavigator-portable.exe">Portable .exe</a>
</div>
<div class="hashes" id="hashes-win">
<div class="h"><b>Installer</b><code data-live="theseus-setup-sha">c1a3310a58043f352fec41fb3acf18fc216386d5763d1d84d3187ace216f4c1e</code></div>
<div class="h"><b>Portable</b><code data-live="theseus-portable-sha">a852b40244cbd1ea0220be80325b7c64a6099780e3a14e3ba5036ca79980d93d</code></div>
</div>
</div>
<div class="plat">
<div class="lbl">Mobile · Android 5.0+ (API 21)</div>
<h3>Ariadne for Android</h3>
<p>Phone-sized browser with tap-to-verify provenance — the block and
transaction behind each name are one tap away.</p>
<div class="meta">v<span data-live="android-version">0.18</span> · ~146 KB · self-signed release key · in-app auto-update</div>
<div class="btns">
<a class="btn acid" href="https://dl.silentmode.st/latest/ariadne.apk">Download APK</a>
</div>
<div class="hashes" id="hashes-android">
<div class="h"><b>APK</b><code data-live="android-sha">920272deb1ff3cb163d609882f4fdaf422c163648b96944d16670ba0ba2ac5bf</code></div>
<div class="h"><b>Signing key</b><code>46ebbbe5dc73148d66410fd0337b1e6683472128c983e8e2578200b9e3ca548c</code></div>
</div>
</div>
</div>
<div class="note"><b>Nothing is signed by an authority your device trusts</b>
— by design. Verify the SHA-256 below before running. The download buttons point at
<code>dl.silentmode.st/latest/…</code>, which always redirects to the current
build, so a saved link never goes stale. The machine-readable
manifest is at
<a href="https://dl.silentmode.st/releases-manifest.json">releases-manifest.json</a>.</div>
</section>
<section id="verify">
<h2>Verify what you downloaded</h2>
<p class="lede">The hashes above are the same ones published at
<a href="https://silentmode.bch/releases/">silentmode.bch/releases</a> (Sia
mirror, name-anchored) and inside the on-chain
<code>releases.silentmode.bch</code> pointer. Three independent surfaces —
they should all agree.</p>
<pre>Windows PowerShell:
Get-FileHash <span data-live="theseus-setup-file">TheseusNavigator-Setup-0.3.134.exe</span> -Algorithm SHA256
macOS / Linux:
sha256sum <span data-live="theseus-setup-file">TheseusNavigator-Setup-0.3.134.exe</span>
Expected:
<span data-live="theseus-setup-sha">c1a3310a58043f352fec41fb3acf18fc216386d5763d1d84d3187ace216f4c1e</span></pre>
</section>
<footer class="deep">
<div class="fwrap">
<div class="brand-col">
<a class="brand" href="./">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<p>A Chromium-based browser that resolves Bitcoin Cash Domain Names on the device itself.
No custom DNS, no root-store install, no server between you and the chain.</p>
</div>
<div>
<h5>Product</h5>
<ul>
<li><a href="./">🏛 Home</a></li>
<li><a href="plug-ins/">🧩 Plug-ins</a></li>
<li><a href="extensions/">🧱 Extensions</a></li>
<li><a href="dev/">⚙️ Developers</a></li>
<li><a href="brand/">🎨 Brand book</a></li>
<li><a href="whitepaper/">📄 Whitepaper</a></li>
</ul>
</div>
<div>
<h5>Download</h5>
<ul>
<li><a href="#download">⬇️ Windows &amp; Android</a></li>
<li><a href="https://silentmode.st/releases" rel="noopener">📦 Release notes <span class="ext">↗</span></a></li>
<li><a href="https://dl.silentmode.st/" rel="noopener">🔗 dl.silentmode.st <span class="ext">↗</span></a></li>
<li><a href="https://code.silentmode.st/silentmode/theseus" rel="noopener">🛠 Source <span class="ext">↗</span></a></li>
</ul>
</div>
<div>
<h5>Pantheon</h5>
<ul>
<li><a href="//sirius.x/" rel="noopener">Sirius<span class="g">.X</span> — names</a></li>
<li><a href="//ariadne.x/" rel="noopener">Ariadne<span class="g">.X</span> — mobile</a></li>
<li><a href="//hephaestus.x/" rel="noopener">Hephaestus<span class="g">.X</span> — forge</a></li>
<li><a href="https://silentmode.st/" rel="noopener">silentmode.st <span class="ext">↗</span></a></li>
</ul>
</div>
</div>
<div class="bar">
<span><span class="badge">Alpha</span>theseus.x — a BCNR name resolving to a Sia-hosted site · one wallet, all of it</span>
<span class="mono">MIT · self-hostable</span>
</div>
</footer>
</div>
<script>
// Keep versions + hashes current from the releases manifest. The text baked
// into the page is the fallback when this fetch can't run (offline, or a
// scheme that blocks cross-origin reads).
(async () => {
try {
const r = await fetch("https://dl.silentmode.st/releases-manifest.json", { cache: "no-store" });
if (!r.ok) return;
const m = await r.json();
const th = (m.releases || []).find((x) => x.id === "theseus-navigator");
const an = (m.releases || []).find((x) => x.id === "ariadne-android");
const set = (key, val) => { if (val == null) return; document.querySelectorAll('[data-live="' + key + '"]').forEach((el) => { el.textContent = String(val); }); };
if (th) {
const files = Object.keys(th.files || {});
const setup = files.find((f) => /Setup-.*\.exe$/.test(f)), portable = files.find((f) => /-portable\.exe$/.test(f));
set("theseus-version", th.version);
set("theseus-setup-file", setup);
set("theseus-setup-sha", setup && th.files[setup]);
set("theseus-portable-sha", portable && th.files[portable]);
}
if (an) {
const apk = Object.keys(an.files || {})[0];
set("android-version", an.version);
set("android-sha", apk && an.files[apk]);
}
} catch {}
})();
// Sidenav scroll-spy: highlight the section currently in view.
// Uses scroll-position tracking so short sections at the end of the page
// still activate their link when the reader scrolls to them.
(() => {
const links = document.querySelectorAll(".sidenav a[href^='#']");
if (!links.length) return;
const entries = [];
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
if (!entries.length) return;
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
const update = () => {
const doc = document.documentElement;
const scrollBottom = window.scrollY + window.innerHeight;
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
const line = window.scrollY + window.innerHeight * 0.35;
let active = entries[0].link;
for (const e of entries) {
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
else break;
}
setActive(active);
};
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
update();
})();
</script>
</body>
</html>

287
plug-ins/index.html Normal file
View file

@ -0,0 +1,287 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x — Plug-ins</title>
<meta name="description" content="Silent Mode's first-party plug-ins for Theseus Navigator: the Aegis wallet and the Ariadne's Thread system-wide resolver. Current signed versions, hashes, and how they update.">
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="theseus.x">
<meta property="og:title" content="theseus.x — Plug-ins">
<meta property="og:description" content="First-party plug-ins for Theseus: Aegis wallet and Ariadne's Thread. Signed, verified on the device.">
<meta property="og:url" content="https://theseus.x/plug-ins/">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; --ok:#4fd1a5;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;
display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a .g{color:var(--acid)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
/* Mobile hamburger — matches the home-page pattern. */
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:220px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
}
.topnav a.get{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.get:hover{background:rgba(214,255,61,.18)}
/* Adaptive edge nav: full-width panel at left edge on wide,
icon rail on medium, bottom bar with icons on mobile. */
:root{--sn-w:200px;--tn-h:52px}
body{padding-top:var(--tn-h)}
.sidenav{position:fixed;top:var(--tn-h);left:0;bottom:0;width:var(--sn-w);z-index:15;
background:transparent;padding:56px 12px 22px 18px;
overflow-y:auto;transition:width .15s ease;pointer-events:none}
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
padding:9px 12px;border-radius:10px;
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
display:none;justify-content:center;align-items:center}
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
.sidenav a.active{opacity:.85;color:var(--acid)}
.sidenav a.active:hover{opacity:1}
@media (max-width:1180px){
:root{--sn-w:64px}
.sidenav{padding:22px 6px}
.sidenav .lbl{display:none}
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
@media (max-width:720px){
:root{--sn-w:0px}
body{padding-left:0;padding-bottom:64px}
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
padding:4px 4px;flex-direction:row;background:rgba(11,14,20,.52);
display:flex;justify-content:space-around;overflow-x:auto}
.sidenav .lbl{display:none}
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0}
.sidenav a .ic{display:inline-flex}
.sidenav a .lb{display:none}
}
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
h1{font-size:clamp(1.8rem,4.5vw,2.4rem);margin:.6rem 0 .3rem;letter-spacing:-.3px}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:1.6rem;scroll-margin-top:72px}
section:first-of-type{border-top:none;margin-top:1rem}
h2{font-size:1.35rem;margin:0 0 .5rem}
.lede{color:var(--mut);margin:0 0 1.2rem;max-width:720px}
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
.ext{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin:1rem 0}
.ext h3{margin:0 0 .3rem;font-size:1.15rem;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.ext h3 .icon{font-size:1.4rem;display:inline-flex;align-items:center;line-height:1}
.ext h3 .icon img{width:26px;height:26px;display:block}
.ext .cur{color:var(--acid);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase}
.ext .desc{color:var(--mut);font-size:14px;margin:.3rem 0 .8rem}
.ext .verifybox{margin-top:.9rem;padding-top:.8rem;border-top:1px solid var(--line);font-size:11.5px;color:var(--dim)}
.ext .verifybox .lbl{color:var(--mut);font-size:12px;margin-bottom:6px}
.ext .h{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline;margin-top:4px}
.ext .h b{color:var(--mut);font-weight:600;min-width:90px}
.ext .h code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
.ext .meta{color:var(--dim);font-size:12.5px;margin-top:.4rem}
.btns{display:flex;gap:8px;flex-wrap:wrap;margin-top:.8rem}
.btn{display:inline-block;padding:.45rem .9rem;border-radius:9px;background:var(--acid);color:#0b0e14;font-weight:650;text-decoration:none;font-size:14px}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
.note b{color:var(--ink)}
footer{color:var(--dim);font-size:12.5px;margin:3rem 0 2rem;text-align:center}
footer a{color:var(--mut)}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a class="here" href="./">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="../#download">Get Theseus</a>
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</summary>
<div class="menu-panel">
<a href="./">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/">Sirius.X</a>
</div>
</details>
</nav>
<aside class="sidenav" aria-label="On this page">
<a href="#aegis-section" title="Aegis wallet" ><span class="ic" aria-hidden="true"><svg viewBox="0 0 32 32" fill="none" width="20" height="20"><polygon points="16,2 28,9 28,23 16,30 4,23 4,9" fill="#0a0a0d" stroke="#D6FF3D" stroke-width="1.6" stroke-linejoin="round"/><circle cx="16" cy="16" r="4.5" fill="none" stroke="#D6FF3D" stroke-width="1.4"/><circle cx="16" cy="16" r="1.6" fill="#D6FF3D"/></svg></span><span class="lb">Aegis wallet</span></a>
<a href="#ariadne-section" title="Ariadne's Thread" ><span class="ic" aria-hidden="true">🧵</span><span class="lb">Ariadne's Thread</span></a>
<a href="#line" title="Plug-ins vs extensions"><span class="ic" aria-hidden="true">🧩</span><span class="lb">Plug-ins vs extensions</span></a>
</aside>
<header class="hero">
<h1>Theseus <span class="g">Plug-ins</span></h1>
<p class="tag">Silent Mode's own components that ship inside Theseus and appear under
<b>Settings › Plug-ins</b>: the wallet and the system-wide resolver. Community-made
add-ons live on the separate <a href="../extensions/">Extensions</a> page.</p>
</header>
<div class="wrap">
<section id="aegis-section">
<h2>Aegis — the wallet</h2>
<p class="lede">Multi-chain wallet derived from your Theseus vault (BCH, BTC, TRX, ETH, SOL, SC, DGB) with a
dApp bridge for the pages you visit. Bundled with every Theseus build and updated over a signed channel
between releases; the panel's own <i>Stage → Restart</i> flow applies an update without reinstalling Theseus.</p>
<div class="ext" id="aegis" data-channel="../extensions/aegis/updates.json">
<h3><span class="icon" style="font-size:0;line-height:0"><svg viewBox="0 0 32 32" fill="none" width="26" height="26" style="vertical-align:-6px"><polygon points="16,2 28,9 28,23 16,30 4,23 4,9" fill="#0a0a0d" stroke="#D6FF3D" stroke-width="1.6" stroke-linejoin="round"/><circle cx="16" cy="16" r="4.5" fill="none" stroke="#D6FF3D" stroke-width="1.4"/><circle cx="16" cy="16" r="1.6" fill="#D6FF3D"/></svg></span> Aegis <span class="cur" style="margin-left:auto">current <span data-live="version">0.6.31</span></span></h3>
<div class="desc">Vault-derived keys, per-chain accounts, imported keys, message signing, dApp permissions per site.</div>
<div class="meta">Plug-in · Settings › Plug-ins · <span data-live="count">signed versions on the channel</span></div>
<div class="verifybox">
<div class="lbl">Signed update manifest — Ed25519 by the Silent Mode operator, SHA-256 verified before install</div>
<div class="h"><b>updates.json</b><code><a href="../extensions/aegis/updates.json">theseus.x/extensions/aegis/updates.json</a></code></div>
<div class="h"><b>Tarball</b><code><a data-live="url" href="../extensions/aegis/">…</a></code></div>
<div class="h"><b>sha256</b><code data-live="sha256">—</code></div>
</div>
</div>
</section>
<section id="ariadne-section">
<h2>Ariadne's Thread — the system-wide resolver</h2>
<p class="lede">Makes BCDN names resolve for <em>every</em> program on the machine, not only Theseus: a local
DNS resolver plus gateway, installed by Theseus's setup by default (opt-out) and switchable from
<b>Settings › Plug-ins</b>. The same resolver ships standalone for Windows and as Ariadne for Android.</p>
<div class="ext" id="ariadne">
<h3><span class="icon"><img src="../assets/thread.svg" alt="" aria-hidden="true"></span> Ariadne's Thread <span class="cur" style="margin-left:auto">current <span data-live="resolver-version">0.1.3</span></span></h3>
<div class="desc">Windows service: answers A/AAAA for registered names, forwards everything else to your upstream
resolver, serves Sia-hosted content through a local gateway with a machine-local CA. Answers MX/TXT/NS from
owner-signed DNS records.</div>
<div class="meta">Plug-in · Settings › Plug-ins · Turn on / off · Install / Update / Uninstall from inside Theseus</div>
<div class="btns">
<a class="btn" href="https://dl.silentmode.st/latest/AriadneResolver-Setup.exe">Windows installer</a>
<a class="btn ghost" href="https://dl.silentmode.st/latest/ariadne.apk">Ariadne for Android (APK) · v<span data-live="android-version">0.19</span></a>
</div>
<div class="verifybox">
<div class="lbl">Hashes from the releases manifest — verify before running</div>
<div class="h"><b>Installer</b><code data-live="resolver-sha">—</code></div>
<div class="h"><b>APK</b><code data-live="android-sha">—</code></div>
<div class="h"><b>Manifest</b><code><a href="https://dl.silentmode.st/releases-manifest.json">dl.silentmode.st/releases-manifest.json</a></code></div>
</div>
</div>
</section>
<section id="line">
<h2>Where the line is</h2>
<p class="lede">Plug-ins are first-party: Silent Mode builds them, signs them with the operator key, and Theseus treats
them as part of itself (own Settings section, own update flow). Extensions are everyone else's: published by
any BCDN name holder, signed with that name's wallet, listed on <a href="../extensions/">theseus.x/extensions</a>
and installed from <b>Settings › Extensions</b>. Both are verified on your device before a byte runs.</p>
<div class="note"><b>Want to build one?</b> Start on the <a href="../extensions/#build">Extensions page</a> — the same add-on
format powers both, and publishing takes a name and a signature, nothing else.</div>
</section>
<footer>
Silent Mode · <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TheseusNavigator/docs/ADDON-UPDATES.md">how signed updates work</a>
</footer>
</div>
<script>
(async () => {
const set = (k, f) => document.querySelectorAll('[data-live="' + k + '"]').forEach(f);
const cmp = (a, b) => { const x = String(a).split(".").map(Number), y = String(b).split(".").map(Number); for (let i = 0; i < Math.max(x.length, y.length); i++) { const d = (x[i] || 0) - (y[i] || 0); if (d) return d; } return 0; };
try {
const r = await fetch("../extensions/aegis/updates.json", { cache: "no-store" });
if (r.ok) {
const m = await r.json(); const list = (m.addons || []).slice().sort((x, y) => cmp(y.version, x.version)); const top = list[0];
if (top) {
set("version", (el) => { el.textContent = top.version; });
set("sha256", (el) => { el.textContent = top.sha256 || "—"; });
set("url", (el) => { el.href = top.url; el.textContent = String(top.url).split("/").pop(); });
set("count", (el) => { el.textContent = list.length + " signed version" + (list.length === 1 ? "" : "s") + " on the channel"; });
}
}
} catch {}
try {
const r = await fetch("https://dl.silentmode.st/releases-manifest.json", { cache: "no-store" });
if (r.ok) {
const m = await r.json(); const g = (id) => (m.releases || []).find((x) => x.id === id);
const res = g("ariadne-resolver"), an = g("ariadne-android");
if (res) { set("resolver-version", (el) => { el.textContent = res.version; }); const f = Object.keys(res.files || {})[0]; set("resolver-sha", (el) => { el.textContent = f ? res.files[f] : "—"; }); }
if (an) { set("android-version", (el) => { el.textContent = an.version; }); const f = Object.keys(an.files || {})[0]; set("android-sha", (el) => { el.textContent = f ? an.files[f] : "—"; }); }
}
} catch {}
})();
// Sidenav scroll-spy: highlight the section currently in view.
// Uses scroll-position tracking so short sections at the end of the page
// still activate their link when the reader scrolls to them.
(() => {
const links = document.querySelectorAll(".sidenav a[href^='#']");
if (!links.length) return;
const entries = [];
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
if (!entries.length) return;
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
const update = () => {
const doc = document.documentElement;
const scrollBottom = window.scrollY + window.innerHeight;
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
const line = window.scrollY + window.innerHeight * 0.35;
let active = entries[0].link;
for (const e of entries) {
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
else break;
}
setActive(active);
};
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
update();
})();
</script>
</body>
</html>

280
whitepaper/index.html Normal file
View file

@ -0,0 +1,280 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Whitepaper — Theseus.X</title>
<meta name="description" content="How Theseus Navigator resolves Bitcoin Cash names without a registrar or a resolver in the middle, how content and records are published and verified, the protections it ships on by default, the wallet and extension model, and the security model as it stands.">
<link rel="icon" href="../brand/favicon.svg" type="image/svg+xml">
<meta property="og:type" content="article">
<meta property="og:site_name" content="Theseus.X">
<meta property="og:title" content="Theseus Navigator Whitepaper">
<meta property="og:description" content="A browser in which a site's name is a record on the Bitcoin Cash blockchain, not an entry in a registrar's database.">
<meta property="og:url" content="https://theseus.x/whitepaper/">
<style>
:root{ --bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; }
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));color:var(--ink);
font:16px/1.7 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;padding-top:52px}
a{color:var(--acid)}
.topnav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.88);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px;display:inline-flex;align-items:center;gap:6px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a.get{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.get:hover{background:rgba(214,255,61,.18)}
.topnav a .g{color:var(--acid)}
.topnav a svg.tabicon{width:15px;height:15px;flex:none}
/* Mobile hamburger — matches the home-page pattern. */
.topnav .menu{display:none}
@media (max-width:720px){
.topnav{flex-wrap:nowrap;padding:10px 1rem}
.topnav .brand{margin-right:0}
.topnav > a:not(.brand){display:none}
.topnav .menu{display:block;margin-left:auto;position:relative}
.topnav .menu > summary{list-style:none;cursor:pointer;padding:8px 10px;
border-radius:8px;color:var(--mut);display:inline-flex;align-items:center;gap:6px}
.topnav .menu > summary::-webkit-details-marker{display:none}
.topnav .menu > summary svg{width:22px;height:22px;display:block}
.topnav .menu > summary:hover,
.topnav .menu[open] > summary{color:var(--ink);background:var(--panel)}
.topnav .menu .menu-panel{position:absolute;top:calc(100% + 6px);right:0;
background:var(--bg);border:1px solid var(--line);border-radius:12px;
padding:6px;min-width:220px;display:flex;flex-direction:column;gap:2px;
box-shadow:0 10px 32px rgba(0,0,0,.55)}
.topnav .menu .menu-panel a{padding:10px 14px;border-radius:8px;font-size:14.5px;
color:var(--mut);text-decoration:none;display:flex;align-items:center;gap:8px}
.topnav .menu .menu-panel a:hover{color:var(--ink);background:var(--panel)}
}
.wrap{max-width:1040px;margin:0 auto;padding:0 1.2rem}
.layout{display:grid;grid-template-columns:220px minmax(0,760px);gap:48px;justify-content:center;align-items:start}
header.hero{padding:3rem 0 1rem}
h1{font-size:clamp(1.8rem,4.6vw,2.5rem);line-height:1.15;margin:0 0 .5rem}
h1 .g{color:var(--acid)}
.byline{color:var(--mut);font-size:.95rem;margin:0}
.toc{position:sticky;top:72px;font-size:13.5px;padding-top:3.4rem}
.toc a{display:block;color:var(--mut);text-decoration:none;padding:5px 10px;border-left:2px solid var(--line);line-height:1.35}
.toc a:hover{color:var(--ink)}
.toc a.active{color:var(--acid);border-left-color:var(--acid)}
article h2{font-size:1.45rem;margin:2.6rem 0 .8rem;padding-top:1.4rem;border-top:1px solid var(--line);scroll-margin-top:70px}
article h2:first-of-type{border-top:none;margin-top:1rem;padding-top:0}
article p{margin:0 0 1rem;max-width:72ch}
article b{color:var(--ink)}
article ul,article ol{padding-left:1.4rem;margin:0 0 1rem;max-width:72ch}
article li{margin:0 0 .45rem}
article table{border-collapse:collapse;width:100%;margin:.4rem 0 1.4rem;font-size:14px;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
article th,article td{padding:9px 12px;border-top:1px solid var(--line);text-align:left;vertical-align:top}
article thead th{border-top:none;background:var(--panel2);color:var(--mut);font-weight:600;font-size:12.5px;letter-spacing:.04em;text-transform:uppercase}
article td:first-child{white-space:nowrap;color:var(--ink)}
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-word}
figure.diagram{margin:1.2rem 0 1.6rem;background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:10px 12px 6px}
figure.diagram svg{width:100%;height:auto;display:block}
figure.diagram figcaption{color:var(--dim);font-size:12.5px;padding:6px 6px 4px}
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:3rem}
@media (max-width:900px){ .layout{grid-template-columns:minmax(0,1fr)} .toc{position:static;padding-top:0;display:flex;flex-wrap:wrap;gap:4px} .toc a{border-left:none;border:1px solid var(--line);border-radius:8px;padding:4px 9px} article td:first-child{white-space:normal} }
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>Theseus<span class="g">.X</span></span>
</a>
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/" title="Register a BCNR name"><svg class="tabicon" viewBox="0 0 64 64" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span>Sirius<span class="g">.X</span></span></a>
<a class="get" href="../#download">Get Theseus</a>
<details class="menu">
<summary aria-label="Open menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</summary>
<div class="menu-panel">
<a href="../plug-ins/">Plug-ins</a>
<a href="../extensions/">Extensions</a>
<a href="../dev/">Developers</a>
<a href="//sirius.x/">Sirius.X</a>
</div>
</details>
</nav>
<div class="wrap">
<div class="layout">
<aside class="toc" aria-label="Contents">
<a href="#abstract">Abstract</a>
<a href="#the-problem">The problem</a>
<a href="#names-on-bitcoin-cash">Names on Bitcoin Cash</a>
<a href="#resolution-from-a-name-to-content">Resolution: from a name to content</a>
<a href="#content-and-records">Content and records</a>
<a href="#privacy-and-protections">Privacy and protections</a>
<a href="#wallet-and-dapps">Wallet and dapps</a>
<a href="#extensions-and-plug-ins">Extensions and plug-ins</a>
<a href="#beyond-the-browser">Beyond the browser</a>
<a href="#security-model">Security model</a>
<a href="#distribution-and-updates">Distribution and updates</a>
<a href="#roadmap-and-open-questions">Roadmap and open questions</a>
<a href="#glossary-and-references">Glossary and references</a>
</aside>
<main>
<header class="hero">
<h1>Theseus Navigator <span class="g">Whitepaper</span></h1>
<p class="byline">Sep 28, 2026 · Silent Mode</p>
</header>
<article>
<h2 id="abstract">Abstract</h2>
<p>Theseus Navigator is a desktop web browser in which a site's name is a record on the Bitcoin Cash blockchain, not an entry in a registrar's database. The browser resolves those names itself, from several independent sources at once, and fetches the content from decentralized storage or from any server the owner points at. No registrar can seize a name, no resolver sits between the user and the ledger for a name lookup, and the browser ships its privacy protections on by default, request blocking, consent-banner handling, DNS over HTTPS and Global Privacy Control, with anti-fingerprinting controls and a bundled Tor route one click away.</p>
<p>This paper describes the name system Theseus reads (the Bitcoin Cash Name Registry), how the browser turns a name into content without trusting any single party, how content and DNS-style records are published and verified, the protections and the wallet built into the browser, the extension model, and the companion tools that bring the same names to every other browser and to Android. It closes with the security model as it stands at the time of writing, and with the open questions.</p>
<p>Theseus is built by Silent Mode on Electron, currently for Windows, and is free software distributed with published checksums from two mirrors, one on ordinary DNS and one served over the name system itself.</p>
<h2 id="the-problem">The problem</h2>
<p>A web address is only as free as the weakest party between the user and the page, and today that chain has four parties in it who can each break the link without the owner's consent.</p>
<ul><li><b>The registrar and the registry.</b> The record of who holds a domain sits in a company's database, behind a registrar, a registry and their jurisdictions. It can be suspended on a complaint, seized by court order, lost to a missed renewal, or hijacked by whoever talks their way past a support desk. Whether a name is bought outright or leased for a term is not the problem; who keeps the record, and how many parties stand between the holder and it, is.</li><li><b>The resolver.</b> Whichever DNS resolver a device uses sees every name it looks up and can answer with something else. Hostile networks do exactly that: one pattern seen in the field returns A-typed data for AAAA queries, which breaks the operating system's own lookups until the device switches to an encrypted resolver.</li><li><b>The certificate authority.</b> HTTPS trust rests on a few hundred authorities, any one of which can issue a certificate for any name. The user cannot tell an issued certificate from a mis-issued one.</li><li><b>The host and the CDN.</b> A page lives on a server someone can be ordered to switch off, behind a content network that can decline to serve it.</li></ul>
<p>On top of that, the browser itself has become the instrument of tracking: third-party requests on almost every page, consent banners engineered to be accepted, fingerprinting through timezone, language, fonts and hardware, and a real IP address that follows the user everywhere.</p>
<p>A browser that wants to fix this cannot bolt a plug-in onto the existing stack. The name layer has to be replaced by something no single party controls, the resolution has to happen inside the browser from sources the browser can check against each other, content has to be addressable without a host that can be switched off, and the privacy defaults have to be on from the first launch. Theseus is built around those four requirements, and the rest of this paper describes how each one is met and where the limits still are.</p>
<h2 id="names-on-bitcoin-cash">Names on Bitcoin Cash</h2>
<p>A name in this system is a digital certificate record on the Bitcoin Cash blockchain: whoever holds the certificate holds the name, and the chain is the only registry. The record is kept on the chain instead of at a company, which removes the registrar, the registry and the resolver from between the holder and the name. The protocol is called BNS1 after its payload prefix; the name system as a whole is referred to as the Bitcoin Cash Name Registry, BCNR.</p>
<p><b>Registration.</b> One transaction does everything. It mints an immutable digital certificate whose commitment is the name itself, writes a single OP_RETURN payload of at most 200 bytes, <code>BNS1</code> followed by JSON with the operation, the name and its records, and pays 600 satoshis of dust to a fixed beacon address. The beacon is what makes the registry discoverable: any client can ask an ordinary Electrum server for the beacon's history and obtain every registration ever made, without an indexer. The certificate holds 1,000 satoshis and the transaction's first output is change, placed there so the next registration has a fresh coin at output zero, which a new certificate category requires.</p>
<p><b>Four operations</b> exist: <code>REG</code> and <code>UPD</code> for names, <code>TREG</code> and <code>TUPD</code> for top-level domains. An update spends the certificate and re-issues it, so the same transaction proves ownership and carries the new records; the address that receives the certificate becomes the owner of record. A transfer is an update whose only change is the recipient.</p>
<p><b>Records</b> are short keys in the payload.</p>
<table><thead><tr><th>Key</th><th>Meaning</th></tr></thead><tbody><tr><td><code>s3</code></td><td>A folder on Sia holding the site</td></tr><tr><td><code>ip</code></td><td>IPv4 address of a server that answers for the name</td></tr><tr><td><code>tls</code></td><td>SHA-256 fingerprint of that server's certificate</td></tr><tr><td><code>p</code></td><td>An upstream URL the gateway reverse-proxies</td></tr><tr><td><code>u</code></td><td>A URL to redirect to</td></tr><tr><td><code>h</code></td><td>A page stored inline on the chain</td></tr><tr><td><code>a</code></td><td>A Bitcoin Cash address for payments</td></tr></tbody></table>
<p>Two names carry system records: <code>electrum.bch</code> lists Electrum servers (<code>el</code>), and <code>tlds.bch</code> carries the legacy top-level list that clients still read as the native set.</p>
<p><b>Validity is computed, not declared.</b> Every client sorts the beacon's history by height and transaction, orders it so no transaction is indexed before one it spends from, and applies two rules: the first valid registration of a name wins, and an update counts only if one of its outputs carries the name's own certificate category. Uniqueness is therefore indexer policy rather than chain consensus: clients that apply the same rules to the same history reach the same index. The protocol has no expiry of its own. Whether names under a top-level domain are bought outright or leased, per year, per two years or not at all, is a term for that domain's certificate holder to set; how such a term is enforced is an open question below, and no name expires today. Front-running of a pending registration is possible today; a commit-reveal scheme is specified but not implemented.</p>
<p><b>Top-level domains</b> are digital certificates of the same shape on a second beacon. A TLD's records set its policy: <code>open</code>, <code>cosign</code>, <code>covenant</code> or <code>frozen</code>; a minimum label length; reserved labels; a price the owner sets for names under it; and <code>hidden</code>, which takes the TLD off public listings and restricts registration to the owner. A registration under a <code>cosign</code> or <code>hidden</code> TLD is valid only if the transaction also spends and re-issues the TLD's certificate, so the TLD owner countersigns every name under it; the gateway keeps a seven-day queue of co-sign requests and signs nothing itself. Every policy change is a <code>TUPD</code> kept on a timeline, so a name is judged by the policy in force at its own height. Of these fields, every client's indexer enforces co-signing and hidden; the minimum length, reserved labels and the frozen and covenant policies are enforced by the registrar when a name is registered, not by every client.</p>
<p><b>Ownership can be sold in one transaction.</b> A seller signs the certificate input and a payout output with the SIGHASH_SINGLE | ANYONECANPAY flag; anyone can complete the sale by adding payment and receiving the certificate, and cancelling is spending the certificate once. USD-pegged listings move the certificate into a covenant that pays out <code>target / price</code>, clamped between a floor and a ceiling, against an oracle quote the gateway signs from the median of several public price sources.</p>
<h2 id="resolution-from-a-name-to-content">Resolution: from a name to content</h2>
<p>Theseus treats every dotted host as a Bitcoin Cash name first and falls back to ordinary DNS only when the registry has no record for it. The registry does not privilege ICANN: <code>example.com</code> is looked up on the chain before it is looked up in DNS, and only <code>localhost</code>, IP literals and single-label hosts skip the chain. What the browser resolves against is not a remote service but a local index of the whole registry, rebuilt from sources that can be checked against each other.</p>
<figure class="diagram"><svg viewBox="0 0 760 328" role="img" aria-label="Four sources build one local index and the record picks the source" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif" font-size="13"><defs><marker id="res-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#5e6678"/></marker></defs>
<text x="24" y="34" font-size="15" font-weight="600" fill="#e7eaf1">Four sources build one local index; the record picks the source</text>
<g fill="none" stroke="#5e6678" stroke-width="1.25"><path d="M220 88L290 184" marker-end="url(#res-arrow)"/><path d="M220 152L290 184" marker-end="url(#res-arrow)"/><path d="M220 216L290 184" marker-end="url(#res-arrow)"/><path d="M220 280L290 184" marker-end="url(#res-arrow)"/><path d="M470 184L540 88" marker-end="url(#res-arrow)"/><path d="M470 184L540 152" marker-end="url(#res-arrow)"/><path d="M470 184L540 216" marker-end="url(#res-arrow)"/><path d="M470 184L540 280" marker-end="url(#res-arrow)"/></g>
<g><rect x="24" y="64" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="36" y="84" font-weight="600" fill="#e7eaf1">Bundled snapshot</text><text x="36" y="101" font-size="11.5" fill="#8b98a9">ships in the build</text></g><g><rect x="24" y="128" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="36" y="148" font-weight="600" fill="#e7eaf1">Profile snapshot</text><text x="36" y="165" font-size="11.5" fill="#8b98a9">saved after every poll</text></g><g><rect x="24" y="192" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="36" y="212" font-weight="600" fill="#e7eaf1">Electrum delta poll</text><text x="36" y="229" font-size="11.5" fill="#8b98a9">beacon history, every 30 s</text></g><g><rect x="24" y="256" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="36" y="276" font-weight="600" fill="#e7eaf1">Sia name list</text><text x="36" y="293" font-size="11.5" fill="#8b98a9">a further floor, rarely needed</text></g>
<g><rect x="290" y="140" width="180" height="88" rx="8" fill="#d6ff3d" fill-opacity=".12" stroke="#d6ff3d" stroke-width="2"/><text x="380" y="170" text-anchor="middle" font-weight="600" fill="#e7eaf1">Local name index</text><text x="380" y="192" text-anchor="middle" font-size="11.5" fill="#8b98a9">first valid REG wins</text><text x="380" y="210" text-anchor="middle" font-size="11.5" fill="#8b98a9">UPD only from the holder</text></g>
<g><rect x="540" y="64" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="552" y="84" font-weight="600" fill="#e7eaf1">Sia site (s3)</text><text x="552" y="101" font-size="11.5" fill="#8b98a9">through the gateway mount</text></g><g><rect x="540" y="128" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="552" y="148" font-weight="600" fill="#e7eaf1">Owner's server (ip)</text><text x="552" y="165" font-size="11.5" fill="#8b98a9">TLS fingerprint pinned</text></g><g><rect x="540" y="192" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="552" y="212" font-weight="600" fill="#e7eaf1">Proxy and inline</text><text x="552" y="229" font-size="11.5" fill="#8b98a9">p and h records</text></g><g><rect x="540" y="256" width="196" height="48" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="552" y="276" font-weight="600" fill="#e7eaf1">Redirect (u)</text><text x="552" y="293" font-size="11.5" fill="#8b98a9">to any URL</text></g>
</svg><figcaption>Resolution: four sources, one index, four ways to serve.</figcaption></figure>
<p>Every source writes the same index, so the browser never depends on one of them; the name's own record then decides where the bytes come from.</p>
<p><b>Four sources feed the index.</b> They start in a fixed order at launch and none of them is trusted alone.</p>
<ol><li><b>Bundled snapshot.</b> Every build ships a snapshot of the registry's beacon history. The browser prefers a newer snapshot it has saved itself under the profile. Either way the snapshot is a floor: the index is marked stale the moment it loads, so the live sources overwrite it.</li><li><b>Delta poll.</b> Every 30 seconds the browser opens an Electrum connection, asks for the beacon's transaction history, fetches only the transactions it has not seen, and rebuilds the index. The result is written back as the profile's snapshot, so the next launch starts where this one ended.</li><li><b>Full walk.</b> If no snapshot can be read, the browser walks the beacon's entire history before answering.</li><li><b>Sia snapshot.</b> A published name list is fetched from decentralized storage as a further floor. Today the poll's own snapshot overwrites it as soon as an Electrum server answers, so it matters only when none does.</li></ol>
<p>The Electrum servers come from a seed list compiled into the resolver and from the chain itself: the <code>electrum.bch</code> name carries an <code>el</code> record listing servers, refreshed at most every 30 minutes. The browser connects by hostname first and, if that fails, by the server's pinned IP with the hostname as the TLS name, which defeats DNS interference against the resolver's own transport. With Tor on, these connections go through the SOCKS proxy.</p>
<p><b>Reducing history to an index</b> is deterministic, so clients that see the same history compute the same index: the first valid registration of a name wins, and an update is honoured only when the transaction carries a proof output of the name's own certificate category, that is, only when the current holder signed it. A client that reaches the same history therefore computes the same index as every other client, with no resolver in between.</p>
<p><b>Serving the name.</b> Once a host has an entry, the browser loads it through its own <code>bns://</code> scheme and chooses the source in this order:</p>
<ol><li>A subdomain rule set by the owner (block or redirect), fetched from the gateway for hosts served directly.</li><li>A subdomain with an <code>ip</code> record is served from that IP.</li><li>Inline HTML stored on the chain (<code>h</code>), for the root path only.</li><li>A Sia site (<code>s3</code>), fetched through the public gateway's <code>/bns/</code> mount, which holds the storage keys so the browser never does.</li><li>A server address (<code>ip</code>), fetched directly. If the name also carries a <code>tls</code> record, the connection is pinned: the server certificate's SHA-256 fingerprint must equal the record or the socket is dropped, with no fallback to plain HTTP.</li><li>A proxied upstream (<code>p</code>), reverse-proxied by the browser with any path prefix kept.</li><li>A redirect (<code>u</code>).</li></ol>
<p>On-chain records are authoritative for the name itself; a signed subdomain rule can block or redirect a subdomain the on-chain record would otherwise serve. Records for a name are fetched only after the name is known to be registered, so ordinary DNS hosts the user visits are never sent to the gateway.</p>
<p><b>Collisions.</b> A name that exists both on the chain and in ICANN's root is a collision. The default policy is chain first; the user can choose ICANN first, or ask each time, in which case an in-tab page offers both and can remember the choice per name or per top-level domain. Names under the registry's native top-level domains, listed on the chain by the <code>tlds.bch</code> name, are never treated as collisions.</p>
<p><b>Limits at the time of writing.</b> A lookup miss on an index older than eight seconds waits for one poll before answering, so an unknown host can pause a navigation by the length of one Electrum round trip. History is taken from one server at a time; the resolver library can union several servers but the browser does not yet use that path. The profile snapshot is validated by shape only, not by signature. And the transactions an Electrum server returns are taken in their decoded form, token data included, without a check against the raw transaction or a block header, so a lying server could present a registration that is not on the chain.</p>
<h2 id="content-and-records">Content and records</h2>
<p>A name points at content in one of five ways, and the two that matter most, a site on Sia and a pinned server, need no host that can be switched off and no certificate authority that can be fooled.</p>
<p><b>Sites on Sia.</b> The owner uploads a folder to the Sia network through an S3-compatible endpoint and puts its path in the name's <code>s3</code> record. The public gateway serves it at <code>navigate.st/bns/&lt;name&gt;/</code>, with the usual conveniences of a static host: <code>index.html</code> for folders, <code>/about</code> for <code>about.html</code>, and a single-page fallback to the root. Theseus reads the same mount through its <code>bns://</code> scheme, so the storage keys live on the gateway and never in the browser. Sirius Studio and Sirius Press publish straight into that folder through a gateway API where every upload is signed over the name, the path, the file's hash and a timestamp, and the signature must recover to the current holder of the certificate. No one else can write there through the gateway; the gateway itself holds the storage keys, which is the trust discussed under Security model.</p>
<p><b>Pinned servers.</b> An <code>ip</code> record names a server; a <code>tls</code> record beside it names the SHA-256 fingerprint of that server's certificate. The browser connects directly, ignores the certificate chain, and accepts the connection only if the fingerprint matches. The owner's own key is the trust anchor, published on the chain they control, so no authority can issue a certificate the browser would accept for that name. If the fingerprint does not match, the socket is dropped and there is no fallback to plain HTTP.</p>
<p><b>Inline, proxied and redirected.</b> A page small enough to fit the payload can live on the chain itself (<code>h</code>), served at the root path only. A <code>p</code> record makes the gateway or the browser reverse-proxy an upstream URL, keeping the path. A <code>u</code> record redirects. On the apex the order is inline, then Sia, then IP, then proxy, then redirect; for a subdomain an <code>ip</code> record wins first and a proxy is not inherited.</p>
<p><b>Signed records without a transaction.</b> Everything a DNS zone would carry lives in a file the owner signs and stores beside the site: <code>_records.json</code>. It carries a sequence number, a timestamp and A, AAAA, MX, TXT, CNAME, NS, CAA and SRV records, canonicalised with sorted keys and signed with the same key that holds the name. The gateway verifies the signature against the current certificate holder on every read, rejects a sequence number that goes backwards, and answers <code>GET /api/dns/&lt;name&gt;</code> with the verified records and a 30-second cache. Publishing an update is a signed POST, no chain transaction and no fee. Version 2 adds a <code>hosts</code> map: each subdomain can have its own folder in the bucket, a redirect, a block, or its own record set, with a wildcard entry and a depth of up to eight labels. A block beats a redirect beats a folder, and an exact entry beats the wildcard.</p>
<p>On-chain records are authoritative for content, and the signed records extend them with everything DNS-shaped. Signed A records are not yet used for serving: the manifest lives beside a Sia site, and a name with a Sia site is served from it. The on-chain <code>tls</code> pin applies to whatever is served from an address. Theseus fetches these records only for names it already knows are registered, so ordinary hosts the user visits are never revealed to the gateway, and a page is not held for the fetch; only a subdomain served from an IP or from inline HTML waits, up to three seconds, for its host rule.</p>
<p><b>Subdomain rules apply everywhere.</b> The browser asks the gateway for a host's verified rule before serving a subdomain from an IP or from inline HTML, the two paths it serves without the gateway. A subdomain the owner has blocked or redirected therefore behaves the same whether the name is served from Sia or from the owner's server, and a subdomain served from Sia gains no round trip.</p>
<p><b>Certificates for other browsers.</b> Theseus needs no certificate authority because it checks the pin itself. Other browsers do, which is what the system-wide resolver described later provides: a local root constrained to the registry's top-level domains that mints a leaf per name on the user's own machine.</p>
<h2 id="privacy-and-protections">Privacy and protections</h2>
<p>The protections below are on from the first launch unless the table says otherwise. The table gives the mechanism and the default; the paragraphs after it give the detail that matters for a threat model.</p>
<table><thead><tr><th>Protection</th><th>Mechanism</th><th>Default</th></tr></thead><tbody><tr><td>Shield</td><td>Network request blocking against EasyList and EasyPrivacy, refreshed daily</td><td>On, allow per site</td></tr><tr><td>Cookie Pop-ups</td><td>Answers consent banners with the DuckDuckGo autoconsent rules</td><td>On, reject all but essentials</td></tr><tr><td>DNS over HTTPS</td><td>Chromium's built-in resolver with an encrypted upstream</td><td>Automatic, Quad9</td></tr><tr><td>Global Privacy Control</td><td><code>Sec-GPC: 1</code> header and <code>navigator.globalPrivacyControl</code></td><td>On</td></tr><tr><td>Camera, microphone, location</td><td>Denied for every site unless the global setting allows them; there is no per-site prompt</td><td>Denied</td></tr><tr><td>Hardware device APIs (USB, HID, serial, Bluetooth, MIDI)</td><td>Always denied</td><td>Denied</td></tr><tr><td>Media device enumeration</td><td>One blank entry per kind</td><td>Hidden</td></tr><tr><td>WebRTC</td><td>Public interface only, no non-proxied UDP under Tor</td><td>Public only</td></tr><tr><td>Timezone and language</td><td>Chromium emulation overrides; show, hide or spoof</td><td>Show</td></tr><tr><td>Browser identity</td><td>User agent and client hints shaped like the Chromium release inside</td><td>Always</td></tr><tr><td>Cookies, cache, history, site storage</td><td>Cleared on quit</td><td>Cleared</td></tr><tr><td>Tor</td><td>Bundled Tor, SOCKS proxy for the whole session</td><td>Off, one click</td></tr></tbody></table>
<p><b>Shield</b> runs inside the browser's one request hook. The main process owns the single <code>onBeforeRequest</code> listener for the session and asks each registered filter for a verdict, so add-ons cannot install competing hooks. The engine is Ghostery's adblocker library with network filters only; cosmetic filtering is not yet applied. A blocked request is cancelled before it leaves the machine. Top-level navigations are never blocked, so a page always loads even when its trackers do not.</p>
<p><b>Cookie Pop-ups</b> runs in the isolated world of every page and answers the consent dialog the way a careful user would, rejecting everything but the essentials. It never sends anything anywhere; the rule set ships with the browser.</p>
<p><b>DNS over HTTPS</b> has three modes. Automatic uses the encrypted resolver and falls back to the system resolver when it is unreachable. Increased protection never falls back, so a network that blocks the resolver blocks browsing rather than downgrading it. Off uses the system resolver. Providers are Quad9, Cloudflare, Mullvad, AdGuard, or any HTTPS resolver URL. This covers ordinary DNS hosts; a chain name's lookup never touches DNS, though fetching its content through the gateway does. With Tor on, hostnames resolve at the exit, not locally.</p>
<p><b>Identity.</b> Theseus does not announce itself. The user agent drops the Electron and application tokens, the client-hint brands and their order are computed the way Chromium computes them, and both the JavaScript surface and the request headers agree. This is what keeps commercial bot filters from rejecting the browser. A <code>window.chrome</code> shim fills the properties sites test for.</p>
<p><b>Tor</b> is bundled, not downloaded. One click starts the bundled binary with its own data directory and geoip files and points the session's proxy at it; the Electrum connections, Sia fetches and pinned IP fetches follow the same proxy, and WebRTC is restricted to proxied paths so no UDP leaves directly. Whether Tor is on is not remembered across launches; Tor's own data directory is. The browser is explicit that this buys IP privacy, not anonymity: it does not change the fingerprinting surface the way Tor Browser does.</p>
<p><b>VPN</b> is an add-on rather than a core feature. It runs a pinned build of sing-box speaking VLESS over REALITY with a Chrome TLS fingerprint, verifies the binary's hash before every start, and can use Silent Mode exits, a pasted <code>vless://</code> URL or a subscription URL. Exits with keys are not yet issued at the time of writing.</p>
<p><b>What is not done.</b> There is no referrer-policy override, no Do Not Track header, and no third-party cookie blocking beyond what clearing on quit achieves. Several main-process fetches use the process's plain HTTP client and do not follow the Tor proxy: gateway record lookups, the Sia snapshot pull, proxied <code>p</code> upstreams, the update checks, add-on channel and package downloads, the Shield list refresh, the community catalogue and the home cards. Fingerprinting defences are opt-in emulation rather than a uniform fingerprint.</p>
<h2 id="wallet-and-dapps">Wallet and dapps</h2>
<p>Aegis is the wallet built into Theseus as a plug-in. It holds keys for seven chains from one recovery phrase, keeps key material in memory only and re-derives it from the vault on every launch, and lets pages transact through injected providers that can do nothing without an approval drawn by the browser itself.</p>
<table><thead><tr><th>Chain</th><th>Networks</th><th>Derivation</th></tr></thead><tbody><tr><td>Bitcoin Cash</td><td>mainnet, test network</td><td>BIP32 <code>m/44'/145'/0'</code></td></tr><tr><td>Bitcoin</td><td>mainnet, testnet, signet</td><td>BIP44, 49, 84, 86</td></tr><tr><td>DigiByte</td><td>mainnet</td><td>BIP84 native segwit, coin type 20</td></tr><tr><td>Ethereum</td><td>mainnet, Sepolia, custom EIP-3085 chains</td><td><code>m/44'/60'/0'/0/0</code></td></tr><tr><td>Solana</td><td>mainnet, devnet</td><td>SLIP-0010 <code>m/44'/501'/0'/0'</code></td></tr><tr><td>Tron</td><td>mainnet, Nile</td><td><code>m/44'/195'/0'/0/0</code></td></tr><tr><td>Siacoin</td><td>mainnet</td><td>walletd v2 keys, <code>blake2b(seed, index)</code></td></tr></tbody></table>
<p><b>Key material.</b> The recovery phrase becomes a 64-byte seed the standard way, and that seed is used only to derive per-purpose roots with HKDF, one for wallets and one for messaging. The vault, which is the same vault the password manager uses, stores only those roots, encrypted with 200,000 rounds of PBKDF2 and AES-GCM; the seed itself is never written to disk. Each wallet then derives its own 32-byte root from the purpose root with a fixed HKDF label, and each chain derives its keys from that. Changing any of these labels would orphan funds, so they are frozen. Imported wallets live in a second file under the same password with a different salt, so a leaked purpose root yields none of their keys. The host scopes derivation to the plug-in's own identifier and to legacy identifiers its manifest declares it absorbs; that absorb list is not yet gated, which the roadmap covers.</p>
<p><b>Unlocking.</b> The vault unlocks with the master password, entered in the wallet's panel and handed to the main process for the unlock. Two opt-in conveniences keep a wrapped copy of it on disk: stay-signed-in encrypts it under the operating system's own store, with a 15-minute idle lock and a lock on close by default, and a quick-access PIN wraps it under a PBKDF2-derived key.</p>
<p><b>The page bridge.</b> On .x sites Aegis injects <code>window.bitcoincash</code> with three calls: get an address, sign and send, sign a message. On every HTTPS page it injects an EIP-1193 <code>window.ethereum</code> provider (announced through EIP-6963 as <code>st.silentmode.aegis</code>), a <code>window.solana</code> provider, and the Tron provider surface, all speaking to the isolated world over tagged messages. Nothing injected can sign, read a balance or read an address on its own.</p>
<p><b>Approval.</b> Every request from a page lands in an approval sheet the browser draws in its own view, never in the page: it names the plug-in and the requesting origin, one request at a time per origin, with focus on Cancel so Enter never approves a spend by accident. Permissions are remembered per origin: address access can be granted once, and a Bitcoin Cash spending allowance of 0.001, 0.01 or 0.1 BCH lets small payments through without a prompt until it is used up.</p>
<p><b>Pairing.</b> For wallet-to-app sessions Aegis speaks WizardConnect, the LGPL protocol used by Bitcoin Cash applications, with an explicit pairing approval; a page is scanned for a pairing URI only when the user asks, and only matching URIs come back. Price lookups are off until the user turns them on, so a fresh install contacts no oracle.</p>
<p><b>Limits at the time of writing.</b> Siacoin needs a walletd endpoint the user provides, with a public explorer as read-only fallback. WalletConnect is not implemented. The wallet is ordinary JavaScript running with the browser's privileges, which is the trust model of every plug-in and is discussed under Security model.</p>
<h2 id="extensions-and-plug-ins">Extensions and plug-ins</h2>
<p>Theseus has its own add-on model rather than Chrome's, and it publishes community extensions the same way it publishes sites: signed by a name's owner and stored on Sia, with the browser checking the signature against its own copy of the registry.</p>
<p><b>The host.</b> An add-on is a folder with a manifest and a main script. The manifest declares an identifier, a version, a category (extension by default, or plug-in for wallets and other trusted components), and the capabilities it needs. Eleven capabilities exist, each unlocking one API:</p>
<table><thead><tr><th>Capability</th><th>Grants</th></tr></thead><tbody><tr><td><code>sidebar-panel</code></td><td>A panel in the browser's sidebar</td></tr><tr><td><code>page-inject</code></td><td>Runs a script in matching pages, in the isolated world</td></tr><tr><td><code>request-filter</code></td><td>A verdict on every subresource request; top-level navigations are never blocked</td></tr><tr><td><code>session-proxy</code></td><td>Swaps the session's outbound proxy, the primitive Tor and VPN use</td></tr><tr><td><code>vault-derive</code></td><td>Keys derived from the vault under the add-on's own identifier only</td></tr><tr><td><code>approval-modal</code></td><td>An approval sheet drawn by the browser</td></tr><tr><td><code>capture-tab</code></td><td>Screenshots of the active tab</td></tr><tr><td><code>scan-page</code></td><td>Matching URIs from the active tab, never page content</td></tr><tr><td><code>open-tab</code></td><td>The add-on's own pages as full tabs</td></tr><tr><td><code>toolbar-menu</code></td><td>A dropdown on the toolbar</td></tr><tr><td><code>context-menu-item</code></td><td>Right-click items with a condition</td></tr></tbody></table>
<p>Unknown capabilities grant nothing. Storage, host module imports and messaging need no capability. Page scripts run in the isolated world with a bridge that offers only the add-on's identifier, the page's origin, a message channel and an explicit evaluate-in-page call. Messages from a page are accepted only from origins the manifest lists, and messages from a panel only from the add-on's own files.</p>
<p><b>The trust model is stated plainly in the code:</b> the main script is loaded into the browser's main process with full privileges, the same model as a developer-mode browser extension. Capabilities scope what the host offers; they are not a sandbox. Installing a community extension shows a prompt naming the publisher and the site that asked, and says so in those words.</p>
<p><b>The add-ons that ship with the browser.</b> Aegis (wallet, plug-in), Shield, Cookie Pop-ups, a Word editor for <code>.docx</code>, a PDF editor, a screenshot tool with annotation, a translator using LibreTranslate or Google, a VPN client, and a notepad that serves as the reference add-on. Bundled add-ons cannot be removed, only turned off, and are re-seeded from the build only when the bundled version is newer than the installed one.</p>
<p><b>Updates are signed.</b> Each add-on has an update channel on Sia, reached through the gateway. A channel entry names a version, a tarball, its SHA-256 and a signature; the browser accepts it if the operator's Ed25519 key signs it, or if the publisher's signature verifies as described below. The tarball's hash must match, the extracted manifest's identifier and version must match the entry, size caps apply (16 MB for the package, 128 KB for the manifest), and only versions newer than the installed one are taken. Updates are staged and promoted at the next launch, or in place for extensions on request; a wallet mid-session is never hot-swapped. There is one operator key today, and it can sign an update for any identifier; a channel entry names its publisher but is not yet pinned to the publisher the add-on was installed from. Both are on the roadmap to narrow.</p>
<p><b>Community publishing.</b> A publisher who owns a name uploads a package to the gateway with two signatures from that name's key, one over the upload and one over the catalogue entry, both required to recover to the name's current certificate holder. An extension identifier belongs to the first name that publishes it, and versions must increase. When Theseus installs or updates a community extension, it recovers the signer of the entry and compares it with the owner it finds for that name in its own index, not the gateway's, so the gateway cannot substitute a publisher. The publish page on <code>theseus.x</code> does the signing in the page from a recovery phrase that never leaves it, and installs are one click from that site through a <code>theseus://extensions/install/</code> link or a page-side bridge.</p>
<p><b>Plug-ins</b> are the category for components that hold keys or route traffic: Aegis is one, and the system-wide resolver is managed from the same place. They are listed apart from extensions in Settings, their updates wait for a restart, and the toolbar chip that announces a ready update stays silent for them.</p>
<h2 id="beyond-the-browser">Beyond the browser</h2>
<p>Theseus needs nothing installed on the machine to resolve names, but everything else on the machine does. Three companions extend the same registry to other browsers, to Android, and to the people who register and publish names. All of them share the resolver code with Theseus, so the same rules reduce the same history everywhere.</p>
<p><b>Ariadne's Thread</b> is a Windows service that makes the registry visible to every application. Its installer adds one Name Resolution Policy Table rule per routable top-level domain, so only those names are sent to a local daemon and everything else keeps the system's own DNS; a full mode that repoints the adapters exists but is not the default. The daemon answers DNS on the loopback address, serves HTTP and HTTPS for names, and keeps its own index of the chain, taken from a published snapshot first and from an Electrum walk when that fails, refreshed every 30 seconds, with the gateway asked about a name it does not know. For HTTPS it holds a root certificate generated on the machine itself with a critical name-constraints extension limiting it to the registry's top-level domains and excluding every IP range, and it mints a two-year leaf per name on first request; the installer refuses to trust any root without those constraints, and the private key never leaves the machine. Names with an <code>ip</code> and <code>tls</code> record are reverse-proxied through the same fingerprint check Theseus applies. The list of top-level domains is synchronised every six hours from the registry's routable list minus every TLD delegated by IANA, so <code>.com</code> and its kind stay with ordinary DNS; when the list changes, the rules are adjusted and the root is rotated. A collision policy file is re-read every five seconds, so the browser's setting and the system's can agree. Theseus offers install, update, on and off from its Settings and checks the installer's hash before it runs.</p>
<p><b>Ariadne for Android</b> is a tabbed browser for phones with the same resolution stack: a bundled index, a snapshot the gateway refreshes every ten minutes, an on-device Electrum walk with a 60-second cache, and a gateway fast path that races the trustless path. It claims the registry's routable top-level domains at start and refreshes them from the gateway. An optional system-wide mode runs a DNS-only VPN service on the device so other apps resolve names too; it tunnels nothing else. The package is signed with Silent Mode's own key, its certificate hash is published in the release manifest, and it is distributed from the download host rather than a store.</p>
<p><b>Sirius</b> is the registrar and the publishing desk, reachable at <code>sirius.x</code> and mirrored on the ICANN site. Registration runs entirely in the page: a built-in wallet unlocked, created or imported there, or a WizardConnect pairing with a mobile wallet, builds the one transaction that mints the certificate and pays the fees. The dashboard manages every name and TLD the wallet holds: DNS records and subdomain rules through the signed manifest at no cost, content and hosting, redirects, transfers, marketplace listings in BCH or USD, TLD policy and the owner's share of each registration, and the co-sign queue for gated TLDs. Sirius Studio is a page builder on GrapesJS that publishes straight to the name's Sia folder through signed uploads and sets the <code>s3</code> record with one transaction when needed; its optional assistant runs a small language model in the tab over WebGPU or against a local endpoint, and never executes model output. Sirius Press is a WordPress fork whose only change to core is 75 lines: accounts are Bitcoin Cash addresses, sign-in is a signed challenge, and every page is mirrored as static HTML to the name. The registrar speaks seven languages.</p>
<p><b>Hephaestus</b> hosts the source. It is a Forgejo instance where the account is a Bitcoin Cash address and sign-in is a signed message through an OpenID Connect proxy, with repositories on the server and large files, attachments and packages on Sia. It answers on the ICANN name with a public certificate and on <code>hephaestus.x</code> with the project's own root.</p>
<figure class="diagram"><svg viewBox="0 0 760 368" role="img" aria-label="Every client reads the chain itself; the gateway only serves bytes" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif" font-size="13"><defs><marker id="arch-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#5e6678"/></marker></defs>
<text x="24" y="34" font-size="15" font-weight="600" fill="#e7eaf1">Every client reads the chain itself; the gateway only serves bytes</text>
<g fill="none" stroke="#5e6678" stroke-width="1.25"><path d="M109 120L109 176" marker-end="url(#arch-arrow)"/><path d="M283 120L283 176" marker-end="url(#arch-arrow)"/><path d="M457 120L320 176" marker-end="url(#arch-arrow)"/><path d="M631 120L320 176" marker-end="url(#arch-arrow)"/><path d="M109 120L440 176" marker-end="url(#arch-arrow)"/><path d="M283 120L440 176" marker-end="url(#arch-arrow)"/><path d="M457 120L457 176" marker-end="url(#arch-arrow)"/><path d="M631 120L631 176" marker-end="url(#arch-arrow)"/><path d="M416 204H344" marker-end="url(#arch-arrow)"/><path d="M576 232V288" marker-end="url(#arch-arrow)"/></g>
<text x="380" y="196" text-anchor="middle" font-size="11.5" fill="#8b98a9">same chain</text>
<g><rect x="24" y="64" width="170" height="56" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="36" y="84" font-weight="600" fill="#e7eaf1">Theseus Navigator</text><text x="36" y="101" font-size="11.5" fill="#8b98a9">desktop browser</text></g><g><rect x="198" y="64" width="170" height="56" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="210" y="84" font-weight="600" fill="#e7eaf1">Ariadne's Thread</text><text x="210" y="101" font-size="11.5" fill="#8b98a9">Windows resolver</text></g><g><rect x="372" y="64" width="170" height="56" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="384" y="84" font-weight="600" fill="#e7eaf1">Ariadne for Android</text><text x="384" y="101" font-size="11.5" fill="#8b98a9">phone browser</text></g><g><rect x="546" y="64" width="170" height="56" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="558" y="84" font-weight="600" fill="#e7eaf1">Sirius</text><text x="558" y="101" font-size="11.5" fill="#8b98a9">registrar and studio</text></g>
<g><rect x="24" y="176" width="320" height="56" rx="8" fill="#d6ff3d" fill-opacity=".12" stroke="#d6ff3d" stroke-width="2"/><text x="184" y="196" text-anchor="middle" font-weight="600" fill="#e7eaf1">Bitcoin Cash chain</text><text x="184" y="213" text-anchor="middle" font-size="11.5" fill="#8b98a9">read through Electrum servers, reduced locally</text></g>
<g><rect x="416" y="176" width="320" height="56" rx="8" fill="none" stroke="#5e6678" stroke-width="1.25"/><text x="576" y="196" text-anchor="middle" font-weight="600" fill="#e7eaf1">Gateway, navigate.st</text><text x="576" y="213" text-anchor="middle" font-size="11.5" fill="#8b98a9">serves Sia sites, verifies signed records</text></g>
<g><rect x="416" y="288" width="320" height="56" rx="8" fill="rgba(255,255,255,.04)" stroke="#5e6678" stroke-width="1.25"/><text x="576" y="310" text-anchor="middle" font-weight="600" fill="#e7eaf1">Sia storage</text><text x="576" y="328" text-anchor="middle" font-size="11.5" fill="#8b98a9">sites, signed records, extensions</text></g>
</svg><figcaption>Architecture: four clients, one chain, one gateway.</figcaption></figure>
<p>The three resolvers read the chain themselves; the gateway is where bytes come from and where signed uploads land, and it reads the same chain to know who may write.</p>
<h2 id="security-model">Security model</h2>
<p>The design goal is that no single party between the user and a page can change what the page is. The table says who is trusted for what today; the paragraphs after it say what an attacker in each position can and cannot do.</p>
<table><thead><tr><th>Question</th><th>Who decides</th><th>What Theseus checks</th></tr></thead><tbody><tr><td>Who owns a name</td><td>The Bitcoin Cash chain</td><td>Beacon history from Electrum, reduced locally by fixed rules</td></tr><tr><td>What a name's records say</td><td>The owner, by an on-chain update</td><td>Same reduction; an update must carry the name's certificate</td></tr><tr><td>What a name's DNS records say</td><td>The owner, by a signed file</td><td>Signature recovered to the current certificate holder, by the gateway</td></tr><tr><td>Whether a pinned server is genuine</td><td>The owner's <code>tls</code> record</td><td>Certificate fingerprint compared in the browser, no chain of trust</td></tr><tr><td>What a Sia site contains</td><td>Whoever holds the name's signing key</td><td>Gateway checks the upload signature; the browser trusts the gateway for bytes</td></tr><tr><td>Which extension is genuine</td><td>The publisher's name, or the operator key</td><td>Publisher signature checked against the browser's own registry index</td></tr><tr><td>Which browser update is genuine</td><td>The ICANN mirror over TLS</td><td>SHA-256 from the manifest, no publisher signature yet</td></tr></tbody></table>
<p><b>A hostile network</b> can block or delay but not redirect. It sees encrypted DNS to a chosen resolver, TLS to Electrum servers reached by pinned IP if hostnames fail, and, with Tor on, page traffic through Tor with the exceptions listed under Privacy. It cannot alter a name on the way through: the index is computed from history the browser fetches over TLS from servers it chose. It cannot forge a pinned site: the fingerprint is on the chain. It can still see which Electrum servers and which gateway the browser talks to when Tor is off.</p>
<p><b>A hostile Electrum server</b> can lie by omission, returning a truncated history, which would make a recently registered name resolve as absent and fall through to ordinary DNS. It can also invent one: the browser takes the server's decoded transactions, token data included, without checking them against raw transactions or block headers. The defence today is thin, since the browser takes the first server that answers and the on-chain server list is consulted first; verifying decoded data against raw transactions and headers, and taking the union of several servers, are both on the roadmap.</p>
<p><b>A hostile gateway</b> is the largest remaining trust. For Sia sites the browser takes the bytes from the gateway's mount and cannot verify them against the chain, because the site is not hashed on the chain. For signed DNS records the gateway does the verification and the browser trusts its answer. The gateway can therefore serve a substituted Sia site or a substituted A record to a browser, though not a substituted pinned site, and a substituted publisher for an extension still has to own a name in the browser's own index. Reducing this trust, by verifying manifests in the browser and by hashing site roots on the chain, is the main item on the roadmap.</p>
<p><b>A hostile certificate authority</b> gains nothing against a pinned name and nothing against a Sia site reached through the gateway's own certificate, which is itself an ordinary TLS endpoint. For names without a <code>tls</code> record that are served from an IP, the connection is plain HTTP and offers no protection at all; owners are expected to set the pin.</p>
<p><b>A hostile page</b> sees a browser shaped like the Chromium inside it, without the Electron tokens, with hardware and media APIs denied, and with wallet providers that can do nothing without an approval drawn outside the page. It can request fullscreen, notifications and the other benign permissions. It cannot reach the registry APIs beyond read-only lookups, and while it can see that a wallet provider is present, as with any wallet extension, it learns nothing about wallets or addresses until the user approves.</p>
<p><b>A hostile add-on</b> is the user's own decision. Add-on code runs in the main process with the browser's privileges, and the capabilities scope what the host offers rather than what the code can reach. Bundled add-ons come from the build; community extensions come from a named publisher whose ownership the browser checks; anything else is a folder the user placed there. There is no sandbox, and this paper does not claim one.</p>
<p><b>A hostile update</b> to the browser would have to come from the ICANN mirror over TLS, since the hash the browser checks is fetched from the same place as the file. A publisher signature on the manifest, verified against a key in the build, would close that; add-on updates already have it.</p>
<p><b>What the chain does not enforce.</b> Name uniqueness and the co-sign rule are indexer rules, the other top-level-domain policies are registrar rules, and none are consensus rules of Bitcoin Cash. The co-sign rule is applied on the live walk and not yet on the snapshot path, which the roadmap covers. A name under a top-level domain nobody has registered still indexes today because the existence gate is off by default. There is no recovery for a lost key and no authority that can return a name.</p>
<h2 id="distribution-and-updates">Distribution and updates</h2>
<p>Theseus ships as an unsigned Windows installer and a portable executable, with SHA-256 checksums published beside them, from two mirrors: one reached through ordinary DNS and one through the name system, which today run on the same server and differ in how they are reached rather than where they live.</p>
<ul><li><b>silentmode.st</b> on ordinary DNS, with stable links at <code>dl.silentmode.st/latest/</code> that redirect to the current versioned file. This is the mirror the updater reads.</li><li><b>silentmode.bch</b> on the name system, served from Sia through the gateway. A systemd timer mirrors the ICANN site into the Sia bucket every five minutes, so both surfaces carry the same release notes and hashes, and a user whose names resolve can fetch the browser from the name system's own surface.</li></ul>
<p>The release page, the manifest and the <code>theseus.x</code> site all print the same two hashes, so a downloaded file can be checked against three surfaces.</p>
<p><b>The updater</b> checks the release manifest at launch and every six hours, downloads the installer silently in the background, and refuses to install anything whose SHA-256 does not match the manifest: a mismatch or a missing hash deletes the file, and the toolbar chip falls back to offering a manual download. Only on success does the chip offer to install. Clicking it quits the browser and hands over to a detached helper script that waits for the process to exit, runs the installer silently, checks that the application archive is present afterwards and reruns the installer once if it is not, then deletes itself. That last step exists because an installer killed mid-update once left an install with no application archive.</p>
<p>The manifest travels over HTTPS from the ICANN mirror; it is not yet signed by a publisher key, so the trust in an update rests on TLS to that host. A signed manifest is on the roadmap.</p>
<p><b>Add-ons update separately.</b> Bundled add-ons are seeded into the profile at first launch and re-seeded only when the build carries a newer one. Updates for them and for community extensions arrive over their own channel on Sia, are verified against the operator's key or a publisher's name key, staged, and promoted at the next launch, or in place for extensions on request; a four-hourly check and a toolbar chip cover the case where the user never restarts.</p>
<p><b>Companion downloads.</b> The same manifest lists Ariadne's Thread for Windows and Ariadne for Android, and Theseus reads it to offer the system-wide resolver's install or update from Settings, with the same hash check before the installer runs.</p>
<h2 id="roadmap-and-open-questions">Roadmap and open questions</h2>
<p>The system works end to end today; the items below are what stands between that and a system a stranger can rely on, in the order they matter.</p>
<ol><li><b>Less trust in the gateway.</b> Verify signed record manifests inside the browser instead of taking the gateway's word, and put a content hash for a Sia site on the chain so the bytes can be checked. Until then the gateway can substitute a Sia site or a DNS answer.</li><li><b>Front-running.</b> The commit-reveal scheme for registrations is specified and not implemented.</li><li><b>Enforcement gates.</b> Turn on the top-level-domain existence check by default so names under unregistered TLDs stop indexing, and bring the co-sign rule to the snapshot path and to Android.</li><li><b>A signed release manifest</b> for browser updates, checked against a key in the build, so an update no longer rests on TLS to one host.</li><li><b>Per-identifier update keys</b> for add-ons, so the single operator key cannot sign an update for any extension, and each installed add-on pinned to the publisher it came from, with the vault's absorb list gated the same way.</li><li><b>Resolution robustness.</b> Verify the transactions Electrum servers return against raw transactions and block headers, use the union of several servers' history rather than one, and stop a lookup miss from waiting on a poll.</li><li><b>Tor coverage</b> for the remaining main-process fetches: gateway record lookups, the Sia snapshot pull, proxied upstreams and the update check.</li><li><b>Protections still missing:</b> an HTTPS-only mode, cosmetic filtering in Shield, a referrer policy, and third-party cookie controls.</li><li><b>Packaging.</b> macOS and Linux builds, and a code-signing arrangement that does not depend on a certificate authority's approval.</li></ol>
<p><b>Open questions</b> the design has not settled:</p>
<ul><li>Whether name uniqueness and TLD policy should stay indexer rules or become covenant-enforced on the chain, which is the difference between "every honest client agrees" and "no client can disagree".</li><li>How a top-level domain holder's rental terms, yearly, two-yearly or none, should be enforced on the chain rather than by the registrar, and what becomes of an abandoned name under a domain with no rent.</li><li>Whether a sandbox for add-ons is worth the loss of capability, given that the wallet itself is an add-on.</li></ul>
<h2 id="glossary-and-references">Glossary and references</h2>
<table><thead><tr><th>Term</th><th>Meaning</th></tr></thead><tbody><tr><td>BCNR</td><td>Bitcoin Cash Name Registry: the name system as a whole</td></tr><tr><td>BNS1</td><td>The payload protocol: <code>BNS1</code> plus JSON in one OP_RETURN output</td></tr><tr><td>Beacon</td><td>A fixed address every registration pays dust to, so its history is the registry</td></tr><tr><td>Certificate</td><td>The immutable digital certificate on the chain whose commitment is the name; holding it is holding the name</td></tr><tr><td>REG, UPD, TREG, TUPD</td><td>Register or update a name; register or update a top-level domain</td></tr><tr><td>Routable TLDs</td><td>The top-level domains a client should resolve and certify: the public listing plus the advertised set</td></tr><tr><td>Co-sign</td><td>A registration that must also spend and re-issue the TLD's certificate</td></tr><tr><td>Signed records</td><td><code>_records.json</code>, the owner-signed file on Sia that carries DNS-style records and subdomain rules</td></tr><tr><td>Gateway</td><td>The public HTTP service at navigate.st that serves Sia sites and verifies signed records; a convenience tier, not the trustless one</td></tr><tr><td>Pinned server</td><td>An <code>ip</code> record with a <code>tls</code> fingerprint; the browser accepts only that certificate</td></tr><tr><td>Collision</td><td>A name that exists both on the chain and in ICANN's root</td></tr><tr><td>Ariadne's Thread</td><td>The Windows system-wide resolver; Ariadne is also the Android browser</td></tr><tr><td>Sirius</td><td>The registrar, dashboard, Studio site builder and Press</td></tr><tr><td>Hephaestus</td><td>The wallet-authenticated code forge</td></tr></tbody></table>
<p><b>Public surfaces</b></p>
<ul><li>Theseus: <a href="https://navigate.st/bns/theseus.x/">theseus.x</a> through the gateway, <a href="https://silentmode.st/theseus">silentmode.st/theseus</a> on ICANN DNS</li><li>Downloads and release manifest: <a href="https://dl.silentmode.st/releases-manifest.json">dl.silentmode.st</a>, mirrored at <a href="https://navigate.st/bns/silentmode.bch/releases">silentmode.bch</a></li><li>Registrar: <a href="https://navigate.st/bns/sirius.x/">sirius.x</a></li><li>Source: <a href="https://code.silentmode.st/silentmode/theseus">code.silentmode.st/silentmode/theseus</a></li><li>Gateway API root: <a href="https://navigate.st/api/tlds">navigate.st/api/tlds</a>, <code>/api/registry</code>, <code>/api/dns/&lt;name&gt;</code>, <code>/api/records/&lt;name&gt;</code>, <code>/api/market</code>, <code>/api/cosign</code>, <code>/api/site</code>, <code>/api/ext</code></li></ul>
<p><b>Design documents in the repository</b> (paths under the Silent Mode monorepo): <code>Decentralized.DNS/PROTOCOL.md</code>, <code>Decentralized.DNS/DESIGN-tld-registry.md</code>, <code>Decentralized.DNS/DESIGN-signed-records-manifest.md</code>, <code>Decentralized.DNS/INTEGRATION-signed-records-clients.md</code>, <code>Argus/DESIGN-collision-modes.md</code>, <code>TheseusNavigator/docs/ADDON-UPDATES.md</code>, <code>Argus/src/lib/resolver-web.js</code> (the reduction rules), <code>Argus/src/lib/manifest.js</code> (manifest validity), <code>Argus/src/lib/register-tx.js</code> (transaction shapes).</p>
<p><b>Licensing.</b> Theseus Navigator, Ariadne's Thread and Ariadne for Android are published under the Mozilla Public License 2.0, the file-level copyleft Firefox and Brave use: changes to the project's own files stay open, and anything may be built on the browser. The resolver, gateway and protocol libraries are under the Apache License 2.0, so that other implementations of the registry can reuse them freely; the protocol documents and this paper are under Creative Commons Attribution 4.0. Sirius Press is GPL-2.0-or-later, as WordPress requires. Each build ships a third-party notices file: Chromium via Electron, Tor, DuckDuckGo autoconsent and the Ghostery adblocker (MPL-2.0), EasyList and EasyPrivacy, pdf.js, pdf-lib, mammoth, WizardConnect (LGPL-3.0-or-later), and the sing-box binary the VPN add-on fetches (GPL-3.0-or-later), whose complete source is published beside the binary. The project's names and marks are reserved and not covered by any of these licenses, so a fork must ship under its own name. The brand kits published at <code>theseus.x/brand</code> and <code>sirius.x/brand</code> carry the same note: their files may be used to refer to the projects, for example on a page that integrates with them, never as another product's mark.</p>
<p>All facts in this paper were read from the source tree and the live surfaces on 2026-10-01. The software moves faster than a paper; the repository and the release notes are the record of what has changed since.</p>
</article>
</main>
</div>
</div>
<footer>
<p>theseus.x is a BCNR name resolving to a Sia-hosted static site.
Source and issues: <a href="https://silentmode.st/">silentmode.st</a>.
Alpha software on a test network.</p>
<p>This paper is published under <a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>.
Theseus Navigator is free software under the <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/LICENSE">Mozilla Public License 2.0</a>;
its <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/THIRD-PARTY-NOTICES.md">third-party notices</a> and
<a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TRADEMARKS.md">trademark note</a> are in the source repository.</p>
</footer>
<script>
const links = [...document.querySelectorAll('.toc a')], heads = links.map(a => document.getElementById(a.getAttribute('href').slice(1)));
const mark = () => { let cur = 0; heads.forEach((h, i) => { if (h && h.getBoundingClientRect().top < 90) cur = i; }); links.forEach((a, i) => a.classList.toggle('active', i === cur)); };
addEventListener('scroll', mark, { passive: true }); mark();
</script>
</body>
</html>

312
whitepaper/whitepaper.md Normal file
View file

@ -0,0 +1,312 @@
# Theseus Navigator Whitepaper
Sep 28, 2026 · Silent Mode
## Abstract
Theseus Navigator is a desktop web browser in which a site's name is a record on the Bitcoin Cash blockchain, not an entry in a registrar's database. The browser resolves those names itself, from several independent sources at once, and fetches the content from decentralized storage or from any server the owner points at. No registrar can seize a name, no resolver sits between the user and the ledger for a name lookup, and the browser ships its privacy protections on by default, request blocking, consent-banner handling, DNS over HTTPS and Global Privacy Control, with anti-fingerprinting controls and a bundled Tor route one click away.
This paper describes the name system Theseus reads (the Bitcoin Cash Name Registry), how the browser turns a name into content without trusting any single party, how content and DNS-style records are published and verified, the protections and the wallet built into the browser, the extension model, and the companion tools that bring the same names to every other browser and to Android. It closes with the security model as it stands at the time of writing, and with the open questions.
Theseus is built by Silent Mode on Electron, currently for Windows, and is free software distributed with published checksums from two mirrors, one on ordinary DNS and one served over the name system itself.
## The problem
A web address is only as free as the weakest party between the user and the page, and today that chain has four parties in it who can each break the link without the owner's consent.
- **The registrar and the registry.** The record of who holds a domain sits in a company's database, behind a registrar, a registry and their jurisdictions. It can be suspended on a complaint, seized by court order, lost to a missed renewal, or hijacked by whoever talks their way past a support desk. Whether a name is bought outright or leased for a term is not the problem; who keeps the record, and how many parties stand between the holder and it, is.
- **The resolver.** Whichever DNS resolver a device uses sees every name it looks up and can answer with something else. Hostile networks do exactly that: one pattern seen in the field returns A-typed data for AAAA queries, which breaks the operating system's own lookups until the device switches to an encrypted resolver.
- **The certificate authority.** HTTPS trust rests on a few hundred authorities, any one of which can issue a certificate for any name. The user cannot tell an issued certificate from a mis-issued one.
- **The host and the CDN.** A page lives on a server someone can be ordered to switch off, behind a content network that can decline to serve it.
On top of that, the browser itself has become the instrument of tracking: third-party requests on almost every page, consent banners engineered to be accepted, fingerprinting through timezone, language, fonts and hardware, and a real IP address that follows the user everywhere.
A browser that wants to fix this cannot bolt a plug-in onto the existing stack. The name layer has to be replaced by something no single party controls, the resolution has to happen inside the browser from sources the browser can check against each other, content has to be addressable without a host that can be switched off, and the privacy defaults have to be on from the first launch. Theseus is built around those four requirements, and the rest of this paper describes how each one is met and where the limits still are.
## Names on Bitcoin Cash
A name in this system is a digital certificate record on the Bitcoin Cash blockchain: whoever holds the certificate holds the name, and the chain is the only registry. The record is kept on the chain instead of at a company, which removes the registrar, the registry and the resolver from between the holder and the name. The protocol is called BNS1 after its payload prefix; the name system as a whole is referred to as the Bitcoin Cash Name Registry, BCNR.
**Registration.** One transaction does everything. It mints an immutable digital certificate whose commitment is the name itself, writes a single OP\_RETURN payload of at most 200 bytes, `BNS1` followed by JSON with the operation, the name and its records, and pays 600 satoshis of dust to a fixed beacon address. The beacon is what makes the registry discoverable: any client can ask an ordinary Electrum server for the beacon's history and obtain every registration ever made, without an indexer. The certificate holds 1,000 satoshis and the transaction's first output is change, placed there so the next registration has a fresh coin at output zero, which a new certificate category requires.
**Four operations** exist: `REG` and `UPD` for names, `TREG` and `TUPD` for top-level domains. An update spends the certificate and re-issues it, so the same transaction proves ownership and carries the new records; the address that receives the certificate becomes the owner of record. A transfer is an update whose only change is the recipient.
**Records** are short keys in the payload.
| Key | Meaning |
| --- | --- |
| `s3` | A folder on Sia holding the site |
| `ip` | IPv4 address of a server that answers for the name |
| `tls` | SHA-256 fingerprint of that server's certificate |
| `p` | An upstream URL the gateway reverse-proxies |
| `u` | A URL to redirect to |
| `h` | A page stored inline on the chain |
| `a` | A Bitcoin Cash address for payments |
Two names carry system records: `electrum.bch` lists Electrum servers (`el`), and `tlds.bch` carries the legacy top-level list that clients still read as the native set.
**Validity is computed, not declared.** Every client sorts the beacon's history by height and transaction, orders it so no transaction is indexed before one it spends from, and applies two rules: the first valid registration of a name wins, and an update counts only if one of its outputs carries the name's own certificate category. Uniqueness is therefore indexer policy rather than chain consensus: clients that apply the same rules to the same history reach the same index. The protocol has no expiry of its own. Whether names under a top-level domain are bought outright or leased, per year, per two years or not at all, is a term for that domain's certificate holder to set; how such a term is enforced is an open question below, and no name expires today. Front-running of a pending registration is possible today; a commit-reveal scheme is specified but not implemented.
**Top-level domains** are digital certificates of the same shape on a second beacon. A TLD's records set its policy: `open`, `cosign`, `covenant` or `frozen`; a minimum label length; reserved labels; a price the owner sets for names under it; and `hidden`, which takes the TLD off public listings and restricts registration to the owner. A registration under a `cosign` or `hidden` TLD is valid only if the transaction also spends and re-issues the TLD's certificate, so the TLD owner countersigns every name under it; the gateway keeps a seven-day queue of co-sign requests and signs nothing itself. Every policy change is a `TUPD` kept on a timeline, so a name is judged by the policy in force at its own height. Of these fields, every client's indexer enforces co-signing and hidden; the minimum length, reserved labels and the frozen and covenant policies are enforced by the registrar when a name is registered, not by every client.
**Ownership can be sold in one transaction.** A seller signs the certificate input and a payout output with the SIGHASH\_SINGLE | ANYONECANPAY flag; anyone can complete the sale by adding payment and receiving the certificate, and cancelling is spending the certificate once. USD-pegged listings move the certificate into a covenant that pays out `target / price`, clamped between a floor and a ceiling, against an oracle quote the gateway signs from the median of several public price sources.
## Resolution: from a name to content
Theseus treats every dotted host as a Bitcoin Cash name first and falls back to ordinary DNS only when the registry has no record for it. The registry does not privilege ICANN: `example.com` is looked up on the chain before it is looked up in DNS, and only `localhost`, IP literals and single-label hosts skip the chain. What the browser resolves against is not a remote service but a local index of the whole registry, rebuilt from sources that can be checked against each other.
&#91;embedded content: resolution · 4 sources, 1 index, 4 ways to serve\]
Every source writes the same index, so the browser never depends on one of them; the name's own record then decides where the bytes come from.
**Four sources feed the index.** They start in a fixed order at launch and none of them is trusted alone.
1. **Bundled snapshot.** Every build ships a snapshot of the registry's beacon history. The browser prefers a newer snapshot it has saved itself under the profile. Either way the snapshot is a floor: the index is marked stale the moment it loads, so the live sources overwrite it.
2. **Delta poll.** Every 30 seconds the browser opens an Electrum connection, asks for the beacon's transaction history, fetches only the transactions it has not seen, and rebuilds the index. The result is written back as the profile's snapshot, so the next launch starts where this one ended.
3. **Full walk.** If no snapshot can be read, the browser walks the beacon's entire history before answering.
4. **Sia snapshot.** A published name list is fetched from decentralized storage as a further floor. Today the poll's own snapshot overwrites it as soon as an Electrum server answers, so it matters only when none does.
The Electrum servers come from a seed list compiled into the resolver and from the chain itself: the `electrum.bch` name carries an `el` record listing servers, refreshed at most every 30 minutes. The browser connects by hostname first and, if that fails, by the server's pinned IP with the hostname as the TLS name, which defeats DNS interference against the resolver's own transport. With Tor on, these connections go through the SOCKS proxy.
**Reducing history to an index** is deterministic, so clients that see the same history compute the same index: the first valid registration of a name wins, and an update is honoured only when the transaction carries a proof output of the name's own certificate category, that is, only when the current holder signed it. A client that reaches the same history therefore computes the same index as every other client, with no resolver in between.
**Serving the name.** Once a host has an entry, the browser loads it through its own `bns://` scheme and chooses the source in this order:
1. A subdomain rule set by the owner (block or redirect), fetched from the gateway for hosts served directly.
2. A subdomain with an `ip` record is served from that IP.
3. Inline HTML stored on the chain (`h`), for the root path only.
4. A Sia site (`s3`), fetched through the public gateway's `/bns/` mount, which holds the storage keys so the browser never does.
5. A server address (`ip`), fetched directly. If the name also carries a `tls` record, the connection is pinned: the server certificate's SHA-256 fingerprint must equal the record or the socket is dropped, with no fallback to plain HTTP.
6. A proxied upstream (`p`), reverse-proxied by the browser with any path prefix kept.
7. A redirect (`u`).
On-chain records are authoritative for the name itself; a signed subdomain rule can block or redirect a subdomain the on-chain record would otherwise serve. Records for a name are fetched only after the name is known to be registered, so ordinary DNS hosts the user visits are never sent to the gateway.
**Collisions.** A name that exists both on the chain and in ICANN's root is a collision. The default policy is chain first; the user can choose ICANN first, or ask each time, in which case an in-tab page offers both and can remember the choice per name or per top-level domain. Names under the registry's native top-level domains, listed on the chain by the `tlds.bch` name, are never treated as collisions.
**Limits at the time of writing.** A lookup miss on an index older than eight seconds waits for one poll before answering, so an unknown host can pause a navigation by the length of one Electrum round trip. History is taken from one server at a time; the resolver library can union several servers but the browser does not yet use that path. The profile snapshot is validated by shape only, not by signature. And the transactions an Electrum server returns are taken in their decoded form, token data included, without a check against the raw transaction or a block header, so a lying server could present a registration that is not on the chain.
## Content and records
A name points at content in one of five ways, and the two that matter most, a site on Sia and a pinned server, need no host that can be switched off and no certificate authority that can be fooled.
**Sites on Sia.** The owner uploads a folder to the Sia network through an S3-compatible endpoint and puts its path in the name's `s3` record. The public gateway serves it at `navigate.st/bns/<name>/`, with the usual conveniences of a static host: `index.html` for folders, `/about` for `about.html`, and a single-page fallback to the root. Theseus reads the same mount through its `bns://` scheme, so the storage keys live on the gateway and never in the browser. Sirius Studio and Sirius Press publish straight into that folder through a gateway API where every upload is signed over the name, the path, the file's hash and a timestamp, and the signature must recover to the current holder of the certificate. No one else can write there through the gateway; the gateway itself holds the storage keys, which is the trust discussed under Security model.
**Pinned servers.** An `ip` record names a server; a `tls` record beside it names the SHA-256 fingerprint of that server's certificate. The browser connects directly, ignores the certificate chain, and accepts the connection only if the fingerprint matches. The owner's own key is the trust anchor, published on the chain they control, so no authority can issue a certificate the browser would accept for that name. If the fingerprint does not match, the socket is dropped and there is no fallback to plain HTTP.
**Inline, proxied and redirected.** A page small enough to fit the payload can live on the chain itself (`h`), served at the root path only. A `p` record makes the gateway or the browser reverse-proxy an upstream URL, keeping the path. A `u` record redirects. On the apex the order is inline, then Sia, then IP, then proxy, then redirect; for a subdomain an `ip` record wins first and a proxy is not inherited.
**Signed records without a transaction.** Everything a DNS zone would carry lives in a file the owner signs and stores beside the site: `_records.json`. It carries a sequence number, a timestamp and A, AAAA, MX, TXT, CNAME, NS, CAA and SRV records, canonicalised with sorted keys and signed with the same key that holds the name. The gateway verifies the signature against the current certificate holder on every read, rejects a sequence number that goes backwards, and answers `GET /api/dns/<name>` with the verified records and a 30-second cache. Publishing an update is a signed POST, no chain transaction and no fee. Version 2 adds a `hosts` map: each subdomain can have its own folder in the bucket, a redirect, a block, or its own record set, with a wildcard entry and a depth of up to eight labels. A block beats a redirect beats a folder, and an exact entry beats the wildcard.
On-chain records are authoritative for content, and the signed records extend them with everything DNS-shaped. Signed A records are not yet used for serving: the manifest lives beside a Sia site, and a name with a Sia site is served from it. The on-chain `tls` pin applies to whatever is served from an address. Theseus fetches these records only for names it already knows are registered, so ordinary hosts the user visits are never revealed to the gateway, and a page is not held for the fetch; only a subdomain served from an IP or from inline HTML waits, up to three seconds, for its host rule.
**Subdomain rules apply everywhere.** The browser asks the gateway for a host's verified rule before serving a subdomain from an IP or from inline HTML, the two paths it serves without the gateway. A subdomain the owner has blocked or redirected therefore behaves the same whether the name is served from Sia or from the owner's server, and a subdomain served from Sia gains no round trip.
**Certificates for other browsers.** Theseus needs no certificate authority because it checks the pin itself. Other browsers do, which is what the system-wide resolver described later provides: a local root constrained to the registry's top-level domains that mints a leaf per name on the user's own machine.
## Privacy and protections
The protections below are on from the first launch unless the table says otherwise. The table gives the mechanism and the default; the paragraphs after it give the detail that matters for a threat model.
| Protection | Mechanism | Default |
| --- | --- | --- |
| Shield | Network request blocking against EasyList and EasyPrivacy, refreshed daily | On, allow per site |
| Cookie Pop-ups | Answers consent banners with the DuckDuckGo autoconsent rules | On, reject all but essentials |
| DNS over HTTPS | Chromium's built-in resolver with an encrypted upstream | Automatic, Quad9 |
| Global Privacy Control | `Sec-GPC: 1` header and `navigator.globalPrivacyControl` | On |
| Camera, microphone, location | Denied for every site unless the global setting allows them; there is no per-site prompt | Denied |
| Hardware device APIs (USB, HID, serial, Bluetooth, MIDI) | Always denied | Denied |
| Media device enumeration | One blank entry per kind | Hidden |
| WebRTC | Public interface only, no non-proxied UDP under Tor | Public only |
| Timezone and language | Chromium emulation overrides; show, hide or spoof | Show |
| Browser identity | User agent and client hints shaped like the Chromium release inside | Always |
| Cookies, cache, history, site storage | Cleared on quit | Cleared |
| Tor | Bundled Tor, SOCKS proxy for the whole session | Off, one click |
**Shield** runs inside the browser's one request hook. The main process owns the single `onBeforeRequest` listener for the session and asks each registered filter for a verdict, so add-ons cannot install competing hooks. The engine is Ghostery's adblocker library with network filters only; cosmetic filtering is not yet applied. A blocked request is cancelled before it leaves the machine. Top-level navigations are never blocked, so a page always loads even when its trackers do not.
**Cookie Pop-ups** runs in the isolated world of every page and answers the consent dialog the way a careful user would, rejecting everything but the essentials. It never sends anything anywhere; the rule set ships with the browser.
**DNS over HTTPS** has three modes. Automatic uses the encrypted resolver and falls back to the system resolver when it is unreachable. Increased protection never falls back, so a network that blocks the resolver blocks browsing rather than downgrading it. Off uses the system resolver. Providers are Quad9, Cloudflare, Mullvad, AdGuard, or any HTTPS resolver URL. This covers ordinary DNS hosts; a chain name's lookup never touches DNS, though fetching its content through the gateway does. With Tor on, hostnames resolve at the exit, not locally.
**Identity.** Theseus does not announce itself. The user agent drops the Electron and application tokens, the client-hint brands and their order are computed the way Chromium computes them, and both the JavaScript surface and the request headers agree. This is what keeps commercial bot filters from rejecting the browser. A `window.chrome` shim fills the properties sites test for.
**Tor** is bundled, not downloaded. One click starts the bundled binary with its own data directory and geoip files and points the session's proxy at it; the Electrum connections, Sia fetches and pinned IP fetches follow the same proxy, and WebRTC is restricted to proxied paths so no UDP leaves directly. Whether Tor is on is not remembered across launches; Tor's own data directory is. The browser is explicit that this buys IP privacy, not anonymity: it does not change the fingerprinting surface the way Tor Browser does.
**VPN** is an add-on rather than a core feature. It runs a pinned build of sing-box speaking VLESS over REALITY with a Chrome TLS fingerprint, verifies the binary's hash before every start, and can use Silent Mode exits, a pasted `vless://` URL or a subscription URL. Exits with keys are not yet issued at the time of writing.
**What is not done.** There is no referrer-policy override, no Do Not Track header, and no third-party cookie blocking beyond what clearing on quit achieves. Several main-process fetches use the process's plain HTTP client and do not follow the Tor proxy: gateway record lookups, the Sia snapshot pull, proxied `p` upstreams, the update checks, add-on channel and package downloads, the Shield list refresh, the community catalogue and the home cards. Fingerprinting defences are opt-in emulation rather than a uniform fingerprint.
## Wallet and dapps
Aegis is the wallet built into Theseus as a plug-in. It holds keys for seven chains from one recovery phrase, keeps key material in memory only and re-derives it from the vault on every launch, and lets pages transact through injected providers that can do nothing without an approval drawn by the browser itself.
| Chain | Networks | Derivation |
| --- | --- | --- |
| Bitcoin Cash | mainnet, test network | BIP32 `m/44'/145'/0'` |
| Bitcoin | mainnet, testnet, signet | BIP44, 49, 84, 86 |
| DigiByte | mainnet | BIP84 native segwit, coin type 20 |
| Ethereum | mainnet, Sepolia, custom EIP-3085 chains | `m/44'/60'/0'/0/0` |
| Solana | mainnet, devnet | SLIP-0010 `m/44'/501'/0'/0'` |
| Tron | mainnet, Nile | `m/44'/195'/0'/0/0` |
| Siacoin | mainnet | walletd v2 keys, `blake2b(seed, index)` |
**Key material.** The recovery phrase becomes a 64-byte seed the standard way, and that seed is used only to derive per-purpose roots with HKDF, one for wallets and one for messaging. The vault, which is the same vault the password manager uses, stores only those roots, encrypted with 200,000 rounds of PBKDF2 and AES-GCM; the seed itself is never written to disk. Each wallet then derives its own 32-byte root from the purpose root with a fixed HKDF label, and each chain derives its keys from that. Changing any of these labels would orphan funds, so they are frozen. Imported wallets live in a second file under the same password with a different salt, so a leaked purpose root yields none of their keys. The host scopes derivation to the plug-in's own identifier and to legacy identifiers its manifest declares it absorbs; that absorb list is not yet gated, which the roadmap covers.
**Unlocking.** The vault unlocks with the master password, entered in the wallet's panel and handed to the main process for the unlock. Two opt-in conveniences keep a wrapped copy of it on disk: stay-signed-in encrypts it under the operating system's own store, with a 15-minute idle lock and a lock on close by default, and a quick-access PIN wraps it under a PBKDF2-derived key.
**The page bridge.** On .x sites Aegis injects `window.bitcoincash` with three calls: get an address, sign and send, sign a message. On every HTTPS page it injects an EIP-1193 `window.ethereum` provider (announced through EIP-6963 as `st.silentmode.aegis`), a `window.solana` provider, and the Tron provider surface, all speaking to the isolated world over tagged messages. Nothing injected can sign, read a balance or read an address on its own.
**Approval.** Every request from a page lands in an approval sheet the browser draws in its own view, never in the page: it names the plug-in and the requesting origin, one request at a time per origin, with focus on Cancel so Enter never approves a spend by accident. Permissions are remembered per origin: address access can be granted once, and a Bitcoin Cash spending allowance of 0.001, 0.01 or 0.1 BCH lets small payments through without a prompt until it is used up.
**Pairing.** For wallet-to-app sessions Aegis speaks WizardConnect, the LGPL protocol used by Bitcoin Cash applications, with an explicit pairing approval; a page is scanned for a pairing URI only when the user asks, and only matching URIs come back. Price lookups are off until the user turns them on, so a fresh install contacts no oracle.
**Limits at the time of writing.** Siacoin needs a walletd endpoint the user provides, with a public explorer as read-only fallback. WalletConnect is not implemented. The wallet is ordinary JavaScript running with the browser's privileges, which is the trust model of every plug-in and is discussed under Security model.
## Extensions and plug-ins
Theseus has its own add-on model rather than Chrome's, and it publishes community extensions the same way it publishes sites: signed by a name's owner and stored on Sia, with the browser checking the signature against its own copy of the registry.
**The host.** An add-on is a folder with a manifest and a main script. The manifest declares an identifier, a version, a category (extension by default, or plug-in for wallets and other trusted components), and the capabilities it needs. Eleven capabilities exist, each unlocking one API:
| Capability | Grants |
| --- | --- |
| `sidebar-panel` | A panel in the browser's sidebar |
| `page-inject` | Runs a script in matching pages, in the isolated world |
| `request-filter` | A verdict on every subresource request; top-level navigations are never blocked |
| `session-proxy` | Swaps the session's outbound proxy, the primitive Tor and VPN use |
| `vault-derive` | Keys derived from the vault under the add-on's own identifier only |
| `approval-modal` | An approval sheet drawn by the browser |
| `capture-tab` | Screenshots of the active tab |
| `scan-page` | Matching URIs from the active tab, never page content |
| `open-tab` | The add-on's own pages as full tabs |
| `toolbar-menu` | A dropdown on the toolbar |
| `context-menu-item` | Right-click items with a condition |
Unknown capabilities grant nothing. Storage, host module imports and messaging need no capability. Page scripts run in the isolated world with a bridge that offers only the add-on's identifier, the page's origin, a message channel and an explicit evaluate-in-page call. Messages from a page are accepted only from origins the manifest lists, and messages from a panel only from the add-on's own files.
**The trust model is stated plainly in the code:** the main script is loaded into the browser's main process with full privileges, the same model as a developer-mode browser extension. Capabilities scope what the host offers; they are not a sandbox. Installing a community extension shows a prompt naming the publisher and the site that asked, and says so in those words.
**The add-ons that ship with the browser.** Aegis (wallet, plug-in), Shield, Cookie Pop-ups, a Word editor for `.docx`, a PDF editor, a screenshot tool with annotation, a translator using LibreTranslate or Google, a VPN client, and a notepad that serves as the reference add-on. Bundled add-ons cannot be removed, only turned off, and are re-seeded from the build only when the bundled version is newer than the installed one.
**Updates are signed.** Each add-on has an update channel on Sia, reached through the gateway. A channel entry names a version, a tarball, its SHA-256 and a signature; the browser accepts it if the operator's Ed25519 key signs it, or if the publisher's signature verifies as described below. The tarball's hash must match, the extracted manifest's identifier and version must match the entry, size caps apply (16 MB for the package, 128 KB for the manifest), and only versions newer than the installed one are taken. Updates are staged and promoted at the next launch, or in place for extensions on request; a wallet mid-session is never hot-swapped. There is one operator key today, and it can sign an update for any identifier; a channel entry names its publisher but is not yet pinned to the publisher the add-on was installed from. Both are on the roadmap to narrow.
**Community publishing.** A publisher who owns a name uploads a package to the gateway with two signatures from that name's key, one over the upload and one over the catalogue entry, both required to recover to the name's current certificate holder. An extension identifier belongs to the first name that publishes it, and versions must increase. When Theseus installs or updates a community extension, it recovers the signer of the entry and compares it with the owner it finds for that name in its own index, not the gateway's, so the gateway cannot substitute a publisher. The publish page on `theseus.x` does the signing in the page from a recovery phrase that never leaves it, and installs are one click from that site through a `theseus://extensions/install/` link or a page-side bridge.
**Plug-ins** are the category for components that hold keys or route traffic: Aegis is one, and the system-wide resolver is managed from the same place. They are listed apart from extensions in Settings, their updates wait for a restart, and the toolbar chip that announces a ready update stays silent for them.
## Beyond the browser
Theseus needs nothing installed on the machine to resolve names, but everything else on the machine does. Three companions extend the same registry to other browsers, to Android, and to the people who register and publish names. All of them share the resolver code with Theseus, so the same rules reduce the same history everywhere.
**Ariadne's Thread** is a Windows service that makes the registry visible to every application. Its installer adds one Name Resolution Policy Table rule per routable top-level domain, so only those names are sent to a local daemon and everything else keeps the system's own DNS; a full mode that repoints the adapters exists but is not the default. The daemon answers DNS on the loopback address, serves HTTP and HTTPS for names, and keeps its own index of the chain, taken from a published snapshot first and from an Electrum walk when that fails, refreshed every 30 seconds, with the gateway asked about a name it does not know. For HTTPS it holds a root certificate generated on the machine itself with a critical name-constraints extension limiting it to the registry's top-level domains and excluding every IP range, and it mints a two-year leaf per name on first request; the installer refuses to trust any root without those constraints, and the private key never leaves the machine. Names with an `ip` and `tls` record are reverse-proxied through the same fingerprint check Theseus applies. The list of top-level domains is synchronised every six hours from the registry's routable list minus every TLD delegated by IANA, so `.com` and its kind stay with ordinary DNS; when the list changes, the rules are adjusted and the root is rotated. A collision policy file is re-read every five seconds, so the browser's setting and the system's can agree. Theseus offers install, update, on and off from its Settings and checks the installer's hash before it runs.
**Ariadne for Android** is a tabbed browser for phones with the same resolution stack: a bundled index, a snapshot the gateway refreshes every ten minutes, an on-device Electrum walk with a 60-second cache, and a gateway fast path that races the trustless path. It claims the registry's routable top-level domains at start and refreshes them from the gateway. An optional system-wide mode runs a DNS-only VPN service on the device so other apps resolve names too; it tunnels nothing else. The package is signed with Silent Mode's own key, its certificate hash is published in the release manifest, and it is distributed from the download host rather than a store.
**Sirius** is the registrar and the publishing desk, reachable at `sirius.x` and mirrored on the ICANN site. Registration runs entirely in the page: a built-in wallet unlocked, created or imported there, or a WizardConnect pairing with a mobile wallet, builds the one transaction that mints the certificate and pays the fees. The dashboard manages every name and TLD the wallet holds: DNS records and subdomain rules through the signed manifest at no cost, content and hosting, redirects, transfers, marketplace listings in BCH or USD, TLD policy and the owner's share of each registration, and the co-sign queue for gated TLDs. Sirius Studio is a page builder on GrapesJS that publishes straight to the name's Sia folder through signed uploads and sets the `s3` record with one transaction when needed; its optional assistant runs a small language model in the tab over WebGPU or against a local endpoint, and never executes model output. Sirius Press is a WordPress fork whose only change to core is 75 lines: accounts are Bitcoin Cash addresses, sign-in is a signed challenge, and every page is mirrored as static HTML to the name. The registrar speaks seven languages.
**Hephaestus** hosts the source. It is a Forgejo instance where the account is a Bitcoin Cash address and sign-in is a signed message through an OpenID Connect proxy, with repositories on the server and large files, attachments and packages on Sia. It answers on the ICANN name with a public certificate and on `hephaestus.x` with the project's own root.
&#91;embedded content: architecture · 4 clients, 1 chain, 1 gateway\]
The three resolvers read the chain themselves; the gateway is where bytes come from and where signed uploads land, and it reads the same chain to know who may write.
## Security model
The design goal is that no single party between the user and a page can change what the page is. The table says who is trusted for what today; the paragraphs after it say what an attacker in each position can and cannot do.
| Question | Who decides | What Theseus checks |
| --- | --- | --- |
| Who owns a name | The Bitcoin Cash chain | Beacon history from Electrum, reduced locally by fixed rules |
| What a name's records say | The owner, by an on-chain update | Same reduction; an update must carry the name's certificate |
| What a name's DNS records say | The owner, by a signed file | Signature recovered to the current certificate holder, by the gateway |
| Whether a pinned server is genuine | The owner's `tls` record | Certificate fingerprint compared in the browser, no chain of trust |
| What a Sia site contains | Whoever holds the name's signing key | Gateway checks the upload signature; the browser trusts the gateway for bytes |
| Which extension is genuine | The publisher's name, or the operator key | Publisher signature checked against the browser's own registry index |
| Which browser update is genuine | The ICANN mirror over TLS | SHA-256 from the manifest, no publisher signature yet |
**A hostile network** can block or delay but not redirect. It sees encrypted DNS to a chosen resolver, TLS to Electrum servers reached by pinned IP if hostnames fail, and, with Tor on, page traffic through Tor with the exceptions listed under Privacy. It cannot alter a name on the way through: the index is computed from history the browser fetches over TLS from servers it chose. It cannot forge a pinned site: the fingerprint is on the chain. It can still see which Electrum servers and which gateway the browser talks to when Tor is off.
**A hostile Electrum server** can lie by omission, returning a truncated history, which would make a recently registered name resolve as absent and fall through to ordinary DNS. It can also invent one: the browser takes the server's decoded transactions, token data included, without checking them against raw transactions or block headers. The defence today is thin, since the browser takes the first server that answers and the on-chain server list is consulted first; verifying decoded data against raw transactions and headers, and taking the union of several servers, are both on the roadmap.
**A hostile gateway** is the largest remaining trust. For Sia sites the browser takes the bytes from the gateway's mount and cannot verify them against the chain, because the site is not hashed on the chain. For signed DNS records the gateway does the verification and the browser trusts its answer. The gateway can therefore serve a substituted Sia site or a substituted A record to a browser, though not a substituted pinned site, and a substituted publisher for an extension still has to own a name in the browser's own index. Reducing this trust, by verifying manifests in the browser and by hashing site roots on the chain, is the main item on the roadmap.
**A hostile certificate authority** gains nothing against a pinned name and nothing against a Sia site reached through the gateway's own certificate, which is itself an ordinary TLS endpoint. For names without a `tls` record that are served from an IP, the connection is plain HTTP and offers no protection at all; owners are expected to set the pin.
**A hostile page** sees a browser shaped like the Chromium inside it, without the Electron tokens, with hardware and media APIs denied, and with wallet providers that can do nothing without an approval drawn outside the page. It can request fullscreen, notifications and the other benign permissions. It cannot reach the registry APIs beyond read-only lookups, and while it can see that a wallet provider is present, as with any wallet extension, it learns nothing about wallets or addresses until the user approves.
**A hostile add-on** is the user's own decision. Add-on code runs in the main process with the browser's privileges, and the capabilities scope what the host offers rather than what the code can reach. Bundled add-ons come from the build; community extensions come from a named publisher whose ownership the browser checks; anything else is a folder the user placed there. There is no sandbox, and this paper does not claim one.
**A hostile update** to the browser would have to come from the ICANN mirror over TLS, since the hash the browser checks is fetched from the same place as the file. A publisher signature on the manifest, verified against a key in the build, would close that; add-on updates already have it.
**What the chain does not enforce.** Name uniqueness and the co-sign rule are indexer rules, the other top-level-domain policies are registrar rules, and none are consensus rules of Bitcoin Cash. The co-sign rule is applied on the live walk and not yet on the snapshot path, which the roadmap covers. A name under a top-level domain nobody has registered still indexes today because the existence gate is off by default. There is no recovery for a lost key and no authority that can return a name.
## Distribution and updates
Theseus ships as an unsigned Windows installer and a portable executable, with SHA-256 checksums published beside them, from two mirrors: one reached through ordinary DNS and one through the name system, which today run on the same server and differ in how they are reached rather than where they live.
- **silentmode.st** on ordinary DNS, with stable links at `dl.silentmode.st/latest/` that redirect to the current versioned file. This is the mirror the updater reads.
- **silentmode.bch** on the name system, served from Sia through the gateway. A systemd timer mirrors the ICANN site into the Sia bucket every five minutes, so both surfaces carry the same release notes and hashes, and a user whose names resolve can fetch the browser from the name system's own surface.
The release page, the manifest and the `theseus.x` site all print the same two hashes, so a downloaded file can be checked against three surfaces.
**The updater** checks the release manifest at launch and every six hours, downloads the installer silently in the background, and refuses to install anything whose SHA-256 does not match the manifest: a mismatch or a missing hash deletes the file, and the toolbar chip falls back to offering a manual download. Only on success does the chip offer to install. Clicking it quits the browser and hands over to a detached helper script that waits for the process to exit, runs the installer silently, checks that the application archive is present afterwards and reruns the installer once if it is not, then deletes itself. That last step exists because an installer killed mid-update once left an install with no application archive.
The manifest travels over HTTPS from the ICANN mirror; it is not yet signed by a publisher key, so the trust in an update rests on TLS to that host. A signed manifest is on the roadmap.
**Add-ons update separately.** Bundled add-ons are seeded into the profile at first launch and re-seeded only when the build carries a newer one. Updates for them and for community extensions arrive over their own channel on Sia, are verified against the operator's key or a publisher's name key, staged, and promoted at the next launch, or in place for extensions on request; a four-hourly check and a toolbar chip cover the case where the user never restarts.
**Companion downloads.** The same manifest lists Ariadne's Thread for Windows and Ariadne for Android, and Theseus reads it to offer the system-wide resolver's install or update from Settings, with the same hash check before the installer runs.
## Roadmap and open questions
The system works end to end today; the items below are what stands between that and a system a stranger can rely on, in the order they matter.
1. **Less trust in the gateway.** Verify signed record manifests inside the browser instead of taking the gateway's word, and put a content hash for a Sia site on the chain so the bytes can be checked. Until then the gateway can substitute a Sia site or a DNS answer.
2. **Front-running.** The commit-reveal scheme for registrations is specified and not implemented.
3. **Enforcement gates.** Turn on the top-level-domain existence check by default so names under unregistered TLDs stop indexing, and bring the co-sign rule to the snapshot path and to Android.
4. **A signed release manifest** for browser updates, checked against a key in the build, so an update no longer rests on TLS to one host.
5. **Per-identifier update keys** for add-ons, so the single operator key cannot sign an update for any extension, and each installed add-on pinned to the publisher it came from, with the vault's absorb list gated the same way.
6. **Resolution robustness.** Verify the transactions Electrum servers return against raw transactions and block headers, use the union of several servers' history rather than one, and stop a lookup miss from waiting on a poll.
7. **Tor coverage** for the remaining main-process fetches: gateway record lookups, the Sia snapshot pull, proxied upstreams and the update check.
8. **Protections still missing:** an HTTPS-only mode, cosmetic filtering in Shield, a referrer policy, and third-party cookie controls.
9. **Packaging.** macOS and Linux builds, and a code-signing arrangement that does not depend on a certificate authority's approval.
**Open questions** the design has not settled:
- Whether name uniqueness and TLD policy should stay indexer rules or become covenant-enforced on the chain, which is the difference between "every honest client agrees" and "no client can disagree".
- How a top-level domain holder's rental terms, yearly, two-yearly or none, should be enforced on the chain rather than by the registrar, and what becomes of an abandoned name under a domain with no rent.
- Whether a sandbox for add-ons is worth the loss of capability, given that the wallet itself is an add-on.
## Glossary and references
| Term | Meaning |
| --- | --- |
| BCNR | Bitcoin Cash Name Registry: the name system as a whole |
| BNS1 | The payload protocol: `BNS1` plus JSON in one OP\_RETURN output |
| Beacon | A fixed address every registration pays dust to, so its history is the registry |
| Certificate | The immutable digital certificate on the chain whose commitment is the name; holding it is holding the name |
| REG, UPD, TREG, TUPD | Register or update a name; register or update a top-level domain |
| Routable TLDs | The top-level domains a client should resolve and certify: the public listing plus the advertised set |
| Co-sign | A registration that must also spend and re-issue the TLD's certificate |
| Signed records | `_records.json`, the owner-signed file on Sia that carries DNS-style records and subdomain rules |
| Gateway | The public HTTP service at navigate.st that serves Sia sites and verifies signed records; a convenience tier, not the trustless one |
| Pinned server | An `ip` record with a `tls` fingerprint; the browser accepts only that certificate |
| Collision | A name that exists both on the chain and in ICANN's root |
| Ariadne's Thread | The Windows system-wide resolver; Ariadne is also the Android browser |
| Sirius | The registrar, dashboard, Studio site builder and Press |
| Hephaestus | The wallet-authenticated code forge |
**Public surfaces**
- Theseus: [theseus.x](https://navigate.st/bns/theseus.x/) through the gateway, [silentmode.st/theseus](https://silentmode.st/theseus) on ICANN DNS
- Downloads and release manifest: [dl.silentmode.st](https://dl.silentmode.st/releases-manifest.json), mirrored at [silentmode.bch](https://navigate.st/bns/silentmode.bch/releases)
- Registrar: [sirius.x](https://navigate.st/bns/sirius.x/)
- Source: [code.silentmode.st/silentmode/theseus](https://code.silentmode.st/silentmode/theseus)
- Gateway API root: [navigate.st/api/tlds](https://navigate.st/api/tlds), `/api/registry`, `/api/dns/<name>`, `/api/records/<name>`, `/api/market`, `/api/cosign`, `/api/site`, `/api/ext`
**Design documents in the repository** (paths under the Silent Mode monorepo): `Decentralized.DNS/PROTOCOL.md`, `Decentralized.DNS/DESIGN-tld-registry.md`, `Decentralized.DNS/DESIGN-signed-records-manifest.md`, `Decentralized.DNS/INTEGRATION-signed-records-clients.md`, `Argus/DESIGN-collision-modes.md`, `TheseusNavigator/docs/ADDON-UPDATES.md`, `Argus/src/lib/resolver-web.js` (the reduction rules), `Argus/src/lib/manifest.js` (manifest validity), `Argus/src/lib/register-tx.js` (transaction shapes).
**Licensing.** Theseus Navigator, Ariadne's Thread and Ariadne for Android are published under the Mozilla Public License 2.0, the file-level copyleft Firefox and Brave use: changes to the project's own files stay open, and anything may be built on the browser. The resolver, gateway and protocol libraries are under the Apache License 2.0, so that other implementations of the registry can reuse them freely; the protocol documents and this paper are under Creative Commons Attribution 4.0. Sirius Press is GPL-2.0-or-later, as WordPress requires. Each build ships a third-party notices file: Chromium via Electron, Tor, DuckDuckGo autoconsent and the Ghostery adblocker (MPL-2.0), EasyList and EasyPrivacy, pdf.js, pdf-lib, mammoth, WizardConnect (LGPL-3.0-or-later), and the sing-box binary the VPN add-on fetches (GPL-3.0-or-later), whose complete source is published beside the binary. The project's names and marks are reserved and not covered by any of these licenses, so a fork must ship under its own name. The brand kits published at `theseus.x/brand` and `sirius.x/brand` carry the same note: their files may be used to refer to the projects, for example on a page that integrates with them, never as another product's mark.
All facts in this paper were read from the source tree and the live surfaces on 2026-10-01. The software moves faster than a paper; the repository and the release notes are the record of what has changed since.