ChainlessChain
无链之链 · 让数据主权回归个人
CHAINLESSCHAIN · Xiamen Chainlesschain Technology
ChainlessChain chainless·chain
ANDROID · v5.0.3.54 · GA

Phones aren't tiny desktops.
They're a key, a capture rig, a remote.

Android v1.0 is repositioned around three jobs: StrongBox hardware DID wallet, five mobile-capture pieces (voice / camera / location / share / push), and REMOTE invocation of the 139 desktop skills. Mirrors Claude Desktop / Mobile's split — desktop is the workstation, phone is its extension.

Desktop and mobile ship on the same version, same tag: v5.0.3.54. Each release tag carries 8 desktop bundles + 4 Android bundles together.
Three layers

L1 · L2 · L3 — each layer earns its keep.

We stopped competing with desktop on skill count. The phone does the three things it's actually best at: guard the private key, capture from the field, drive the desktop remotely.

L1 · WALLET

StrongBox DID wallet

Hardware key store (Android Keystore + StrongBox HSM) guards W3C DID v2 private keys. BIP-39 mnemonic, biometric (fingerprint / face) unlock, multi-DID switching, automatic migration from any legacy plaintext store.

68 unit tests · M2 landed
L2 · CAPTURE

Field capture, five pieces

All five wired: VoiceMode (continuous SeedASR → LLM → TTS), CameraOCR (photo → OCR → note), LocationTagger (GPS foreground service), ShareReceiver (5 SharePayload kinds → KB), PushNotifier (4 notification channels + FCM scaffold).

130 unit tests · M3 landed (code 5/5)
L3 · REMOTE

REMOTE control

Phone → desktop, 23 REMOTE commands (audit of 795 suspend fns). RemoteSkillRegistry with file + method dual-granularity whitelist · ApprovalUI in 4 categories (Sign / Cowork / Marketplace / SystemCritical) · ProgressViewer for long tasks · §8.3 alias compat window.

152 unit tests · M4 landed
Hardware isolation
StrongBox HSM · private keys never leave · biometric unlock · auto-lock on retry.
SignAsService
M5 reverse signing: macOS/Linux desktop calls the phone for hardware sigs — cross-platform U-Key replacement. 33 tests.
28 Skills
12 Kotlin handlers + 8 doc-only + 8 REMOTE (delegate to desktop) — curated for mobile.
W3.7 · 2026-05-12 · Xiaomi real-device verified

Flow B QR pairing (default)

Desktop shows the QR, phone camera scans the desktop screen — same UX as WeChat / Alipay / Discord / WhatsApp Web. Phone-scans-desktop has dramatically better recognition rates than desktop-webcam-scans-tiny-phone-screen. Xiaomi 24115RA8EC E2E walked the full chain: desktop QR → ML Kit scan → signaling pair-ack → CLI writes SQLite → Vue list refreshes.

  • · Entry: phone Settings → "Scan desktop QR" (recommended) / desktop V5, V6, web-shell all three
  • · Tests: ScanDesktopPairingViewModel 10 · desktop-pair-handlers 19 · ZXing / ML Kit pass-through · adb-reverse E2E proven
  • · Advanced path: Flow A (phone-generates / desktop-scans, Signal e2ee) kept as opt-in

Design doc: Android repositioning v0.2 →

Phase 3d · 2026-05-09

Desktop ↔ Android two-way sync

Five resource types — Note / Conversation / DID / Community / Channel — plus tombstones, bidirectional walker. dagger.Lazy untangles 4 Hilt cycles; Room persists SyncRemoteCursor; sync.* JSON-RPC handlers wire transport. Gates 1–4 are strict Ed25519 verify. Private keys stay on phone, passwords never on the wire.

  • · M2 → v1.2, 12 commits: desktop side 5 ResourceType walker + 52 tests · Android side SocialSyncWalker fills handlePullRpc + DID auth
  • · Entry: desktop Settings → Sync → SyncMobile device manager + manual pairing; mobile Settings → Sync
  • · Protocol: CommandRequest.auth nullable · listTombstones SQL filter · Android substitutes P2P session DID for device registry
Remote Operate Plan C · v5.0.3.50 · 2026-05-13

Remote first: signaling-forward RPC.

Phone as remote — call desktop skills / list / status from the phone, low-bandwidth path first. signaling-relay forwards JSON-RPC 2.0; desktop side runs RelayClient + handlePairAckFromRelay, Android side runs SignalingRpcClient + RemoteOperateScreen. LAN direct first, falls back to public relay; PairedDesktopsStore persists paired peers.

  • · Three-path decision: Plan C signal-forward (shipped first) / Plan A WebRTC DC (throughput) / Plan B STUN+TURN (NAT traversal) — three tiers compose
  • · Measured ping: 100–400ms (relay region dependent); fine for everyday remote, Plan A migrates the hot path to DC
  • · 20 new unit tests · key fixes: runCurrent over advanceUntilIdle for SharedFlow race / org.json over kotlinx-serialization to slim deps

Design doc: Android Remote Operate Plan C →

Plan A + B infrastructure · v5.0.3.51

WebRTC forwarding + STUN/TURN fallback.

signaling-relay forwards offer/answer/ice-candidate and injects the from field (from ws.peerId), so LAN and relay paths are interchangeable in mobileBridge.handleSignalingMessage. coturn 4.6 deployed at turn.chainlesschain.com (3478 UDP/TCP + 5349 TLS + 49152-65535 relay UDP), Let's Encrypt acme.sh auto-renew, HMAC-SHA1 use-auth-secret 24h-TTL ephemeral creds.

  • · iceServers don't ship in the QR: 650 chars + 280px + error-H stalls 30s in practice. Pushed async after scan — desktop pair-ack matched ⇒ pushIceServersToMobile dual-send LAN + relay chainlesschain:ice:config
  • · Android persistence: WebRTCClient.setOnForwardedMessageReceived intercepts ⇒ PairedDesktopsStore.iceServersJson · SignalingRpcClient mirror race-tolerant
  • · No hardcoded secrets: CC_TURN_SECRET strictly required · Alibaba Cloud SG must open UDP/TCP 3478 / TCP 5349 / UDP 49152-65535 to 0.0.0.0/0

Design doc: Android Remote Operate Plan A+B →

Plan A.1 Remote Terminal · v5.0.3.53 · 2026-05-14

Same day, upgraded: 4-hop signaling collapsed to a 1-hop DataChannel.

v5.0.3.52 Plan A real-device validation (Xiaomi 24115RA8EC × Win desktop dev) surfaced one architectural issue: a 4-hop signaling chain (phone → router → public relay → desktop RelayClient) is fragile under NAT idle / cellular carrier-side TCP RST — any hop down kills the whole chain. Plan A.1 moves high-frequency / high-throughput terminal traffic onto a WebRTC DataChannel direct connection, bypassing every middle hop; signaling stays as a fallback.

  • · Phase 1 Trap 1 fix: SignalClient.forwardedMessages migrated to multi-subscribe SharedFlow replacing the single-listener setOnForwardedMessageReceived → the ice:config interceptor was no longer silently overwritten when TerminalRpcClient.start() ran, fixing the bug where iceServers expired in 24h and cross-NAT became unreachable; + new WebRTCClient.dataChannelReady StateFlow (READY truly means DC OPEN)
  • · Phase 2 DC fast path: SignalingRpcClient.invoke embeds a transport selector — connectionState==READY → send via DC; throws or not-ready → fallback signaling. Two listeners consume both streams; same requestId → same CompletableDeferred (second complete is a no-op, dual delivery is safe without explicit dedup)
  • · Phase 3 handshake trigger + UI indicator: TerminalListViewModel async-triggers RemoteConnectionManager.connect on entry; chip shows "P2P direct" (green) vs "Relay path" (yellow)
  • · Phase 4 bidirectional LRU dedup: Android dedups stdout by (sessionId|seq) with 256-LRU + exit by sessionId with 64-LRU on dual streams; desktop mobile-bridge.bridgeToLibp2p gets a 128-LRU / 30s-TTL keyed by payload.id for mobile→desktop command requests, guarding against duplicate PtyManager side effects
  • · Phase 5 zero new code: DC failure fallback / auto-reconnect / recovery auto-switch / live UI mapping all fall out of Phase 2 + P2PClient's existing wiring for free
  • · Perf: RTT p50 200-500ms → 30-80ms LAN / 50-200ms TURN · p99 1.5-30s with timeouts → 200-800ms · stability 20s-2min outages → hours-long sustained
  • · Tests: Android +8 (TerminalRpcClient 3 dedup / SignalingRpcClient 4 transport / WebRTCClient 1 Trap 1) + desktop +14 (LRU dedup 5 + sendToMobile 5 + guard rails 2) · all 3 suites green (11 + 15 + 21 Android + 14 desktop) · 12-test regression fix (mockk relaxed StateFlow generic erasure)

Design doc: Android Remote Terminal Plan A.1 → · Real-device E2E §5.3 5-scenario matrix (LAN / cellular / double-NAT / DC forced-down / DC recovered) is on the user.

Plan A Remote Terminal · v5.0.3.52 · 2026-05-14

See your desktop terminals on your phone.

User pain: "I have many terminals open on the PC, can I see their output and type commands from Android?". Hard constraint — Windows externally-running terminals can't be attached by another process (OS handles are private). Solution: ChainlessChain desktop spawns new terminals via node-pty and streams stdin/stdout through signaling-relay into an Android xterm.js WebView.

  • · Desktop Phase 1: PtyManager (lazy node-pty + 256 KB ring buffer + 24h idle kill + shell allowlist pwsh/cmd/bash/wsl + 8-session cap) · 8 WS topics (create/list/stdin/resize/close/history + server-push stdout/exit) · dangerous-keyword Electron messageBox confirm + permanent trust per-cmd cache
  • · Phase 1.5 three shells: V5/V6 web-shell + V6 plugin widget + cc ui mirror (agent-runtime.startUiServer reuses dispatcher) · /terminal route + sidebar menu + slash command
  • · Android Phase 3: TerminalRpcClient reuses envelope · WebView ↔ Kotlin JS bridge · xterm.js vendored into assets/terminal/ · softkey toolbar Ctrl/Tab/Esc/arrows/Ctrl+C/D
  • · Phase 4 resilience: paired_devices/permission-gate · mobile-bridge stdout/exit per-peer subscription fan-out · reconnect terminal.history backfill
  • · 162 new unit tests, all green · Desktop 61 / CLI 21 / Web Panel 17 + 3 real e2e (cc ui subprocess + real WS + shell stdin/stdout round-trip) / Android 10 · including real cmd.exe spawn streaming probe stdout back

Design doc: Android Remote Terminal Plan A → · User guide: Remote terminal →

Social wiring · 2026-05-13

Social goes from demo to production.

The 14-screen + 9-ViewModel + 4-Repository social scaffold (≈10K LOC) was built long ago, but only MyQRCode / QRCodeScanner were actually wired; the other seven routes were registerPlaceholder("temporarily simplified"), and the SocialScreen Friends/Timeline tabs were hardcoded placeholder strings. Closed in one pass: all routes bound to real composables, two new routes for NotificationCenter / BlockedUsers, PostReportDao landed (the entity was in schema, DAO was missing), and PROFILE_QUERY/RESPONSE protocol added for remote DID profile lookup.

  • · NavGraph: 7 placeholders → real screens + 2 new routes · PublishPost / PostDetail / FriendDetail / UserProfile / AddFriend / CommentDetail / EditPost / NotificationCenter / BlockedUsers · spinner shown while the DID document loads
  • · SocialScreen three tabs upgraded · Friends → FriendListScreen · Timeline → TimelineScreen (myDid via DIDViewModel) · Notifications → NotificationCenterScreen (filter / mark-all-read / cleanup menus)
  • · PostReportDao · reportPost was building entities without inserting + getUserReports returned a hardcoded empty list. New DAO adds idempotent dedupe + status transitions + 8 in-memory Room tests
  • · PROFILE_QUERY/RESPONSE protocol · RealtimeEventManager.queryProfile with 5s timeout · onSubscription clears the SharedFlow replay=0 subscribe-race · DefaultSelfProfileProvider wired in Application.delayedInit
  • · 39 new tests, all green · 6 core-p2p / 14 feature-p2p / 8 core-database Room / 11 NavGraph + tab structural regressions · no emulator required

Design doc: Android Social Wiring 2026-05 →

Quality baseline

383+ unit tests · GA grade.

M1–M5 JVM-side fully green. Real-device M3 / M4 D2 / M6 perf / FCM credentials / docs sync — the five user-driven items — are tracked in the v1.0 GA checklist.

M1
REMOTE inventory
795 fn audit
M2
L1 wallet
68 tests
M3
L2 capture (5/5)
130 tests
M4
L3 REMOTE
152 tests
M5
SignAsService
33 tests

Stack: Jetpack Compose · Kotlin · Hilt DI · Room · MockK + Robolectric · Play Services (FusedLocationProvider / FCM scaffold) · StrongBox Keystore · ML Kit / ZXing scanner · Volcengine SeedASR 16 kHz streaming.

Known limitations · scheduled for v1.1

What v1.0 doesn't ship.

  • · FCM in mainland China: unreliable behind the GFW · v1.1 will unify OPPO / Xiaomi / Huawei push channels
  • · Single-peer pairing: v1.0 pairs one desktop · multi-device N-end in v1.1
  • · Offline message queue: REMOTE requests aren't queued while desktop is offline · v1.1 adds replay
  • · Real-device M3 / M4 D2, M6 perf, FCM creds, docs sync: 5 user-driven steps tracked in the GA checklist
Download · Android v5.0.3.54

Pick the right package.

Most devices since 2019 want arm64-v8a. If unsure or older, go universal. Google Play uploads use the AAB (developer-only). Every artifact is signed by GitHub Actions release.yml.

Install note: first-time sideloading on most regional ROMs requires enabling "install from this source" in system settings. In-app self-update lives at Settings → Check for updates — it walks GitHub Releases v5.0.3.54 → DownloadManager → REQUEST_INSTALL_PACKAGES. iOS coming.

Want the full notes? See GitHub Release v5.0.3.54 ↗ or the Android CHANGELOG ↗