Part of PR #3. Pairing flow still to come.
Devices screen — app/(app)/devices.tsx:
* Lists every active device from /api/devices/{self}.
* "THIS DEVICE" badge on our own row, Unlink button on every other.
* Unlink confirms + submits UNLINK_DEVICE tx, optimistic local removal.
* Pull-to-refresh; empty state when balance is too low for auto-link.
* Placeholder row for "Link new device" — wired in next commit.
Settings → Devices entry row: added under a new "Devices" section.
Self-wipe on revoke — lib/storage.ts + app/(app)/_layout.tsx:
* New AsyncStorage marker `dchain_device_registered` tracks whether
this install ever made it into the on-chain registry.
* wipeAllLocalState() zeroes secure-store key + contacts + settings +
chats cache + marker. Safe-idempotent.
* Bootstrap effect in app layout splits three branches by
(our_pub in chain's active list × marker_set):
- in list → mark registered, done.
- not in list + was registered → REVOKED → wipe + redirect to auth.
- not in list + never registered → first boot, LINK_DEVICE.
* Network errors never trigger wipe — only an explicit "pub missing
from chain response" decides it. Belt-and-suspenders against a
misbehaving node spuriously dropping records.
Next: pairing flow so a second device (desktop, tablet, new phone)
can come online, show a 6-digit code, receive master priv via a
one-shot relay envelope encrypted to its fresh device X25519 pub,
then self-link.
PR #2 of the multi-device roadmap — wires the messenger pipeline
against the on-chain registry landed in v2.2.0-alpha1.
lib/api.ts:
- DeviceInfo type mirroring blockchain.DeviceInfo.
- IdentityInfo.device_count (optional; populated from /api/identity).
- fetchDevices(masterPub) → /api/devices/{master_pub}, returns [].
Errors swallowed so a downed endpoint doesn't block messaging.
- resolveRecipientKeys(masterPub) — the routing primitive. Returns
devices[] if registered, else falls back to IdentityInfo.x25519_pub
(pre-v2.2.0 path). Empty only when recipient has published nothing.
- buildLinkDeviceTx / buildUnlinkDeviceTx — signed by master Ed25519,
min-fee cost, canonical JSON payload matching the chain-side
LinkDevicePayload / UnlinkDevicePayload.
app/(app)/chats/[id].tsx:
- sendCore now fans out: encrypts once per recipient device pub
(Promise.all, any failure rejects the batch), falls back to the
cached contact.x25519Pub if the registry lookup returns nothing.
- Saved Messages short-circuit preserved; no devices lookup for self.
app/(app)/_layout.tsx:
- On every sign-in, auto-submit LINK_DEVICE for this device if its
X25519 pub isn't already in the master's registry. Device name
picks "iPhone" / "Android phone" / "Device" by Platform. Errors
(insufficient balance / legacy chain without LINK_DEVICE support)
are silent — next launch retries.
Backward compatibility: senders fall back to identity.x25519_pub when
the recipient has no registry entries, so pre-v2.2.0 clients still
receive messages. Chain-side already gates new validation on the event
types existing; old clients simply never emit LINK_DEVICE and keep
working with a single X25519.
Next — PR #3 (Settings → Devices screen + QR pairing flow + receive-side
self-wipe on revoke detection).
Node flags (cmd/node/main.go):
--max-cpu / --max-ram-mb — Go runtime caps (GOMAXPROCS / GOMEMLIMIT)
--feed-disk-limit-mb — hard 507 refusal for new post bodies over quota
--chain-disk-limit-mb — advisory watcher (can't reject blocks without
breaking consensus; logs WARN every minute)
Client — Saved Messages (self-chat):
- Auto-created on sign-in, pinned top of chat list, blue bookmark avatar
- Send short-circuits the relay (no encrypt, no fee, no mailbox hop)
- Empty state rendered outside inverted FlatList — fixes the mirrored
"say hi…" on Android RTL-aware layout builds
- PostCard shows "You" for own posts instead of the self-contact alias
Client — user walls:
- New route /(app)/feed/author/[pub] with infinite-scroll via
`created_at` cursor and pull-to-refresh
- Profile screen gains "View posts" button (universal) next to
"Open chat" (contact-only)
Feed pipeline:
- Bump client JPEG quality 0.5 → 0.75 to match server scrubber (Q=75),
so a 60 KiB compose doesn't balloon past 256 KiB after server re-encode
- ErrPostTooLarge now wraps with the actual size vs cap, errors.Is
preserved in the HTTP layer
- FeedMailbox quota + DiskUsage surface — supports new CLI flag
README:
- Step-by-step "first node / joiner" section on the landing page,
full flag tables incl. the new resource-cap group, minimal
checklists for open/private/low-end deployments
Users can now tap any row in the wallet history and see the full
transaction detail, matching what the block explorer shows for the
same tx. Covers every visible activity — transfers, contact
requests, likes, posts, follows, relay proofs, contract calls.
Components
lib/api.ts
- New TxDetail interface mirroring node/api_explorer.go's
txDetail JSON (id, type, from/to + their DC addresses, µT
amount + display string, fee, block coords, gas, payload,
signature hex).
- getTxDetail(txID) with 404→null handling.
app/(app)/tx/[id].tsx — new screen
- Hero row: icon + type label + local-time timestamp
- Big amount pill (only for txs that move tokens) — signed by
the viewer's perspective (+ when you received, − when you
paid, neutral when it's someone else's tx or a non-transfer)
- Info card rows with tap-to-copy on hashes and addresses:
Tx ID, From (highlighted "you" when it's the signed-in user),
To (same), Block, Fee, Gas used (when > 0), Memo (when set)
- Collapsible Payload section — renders JSON with 2-space
indent if the node could decode it, otherwise the raw hex
- Signature copy row at the bottom (useful for debugging / audits)
- txMeta() covers all EventTypes from blockchain/types.go
(TRANSFER, CONTACT_REQUEST/ACCEPT/BLOCK, REGISTER_KEY/RELAY,
BIND_WALLET, RELAY_PROOF, BLOCK_REWARD, HEARTBEAT, CREATE_POST,
DELETE_POST, LIKE_POST/UNLIKE_POST, FOLLOW/UNFOLLOW,
CALL_CONTRACT, DEPLOY_CONTRACT, STAKE/UNSTAKE) with
distinct icons + in/out/neutral tone.
- Nested Stack layout so router.back() pops to the caller;
safeBack() fallback when entered via deep link.
app/(app)/wallet.tsx
- TxTile's outer Pressable was a no-op onPress handler; now
router.push(`/(app)/tx/${tx.hash}`). Entire row is the
touch target (icon + type + addr + time + amount).
app/(app)/_layout.tsx
- /tx/* added to hideNav regex so the detail screen is
full-screen without the 5-icon bar at the bottom.
Translation quirk
The screen is English to match the rest of the UI (what the user
just asked for in the previous commit). Handles copying via
expo-clipboard — tapping an address/hash shows "Copied" for 1.5s
with a green check, then reverts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two dev-only seed modules removed now that the app talks to a real
backend:
- lib/devSeed.ts — fake 15+ contacts with mock chat histories,
mounted via useDevSeed() in (app)/_layout.tsx on empty store.
Was useful during client-first development; now it fights real
contact sync and confuses operators bringing up fresh nodes
("why do I see NBA scores and a dchain_updates channel in my
chat list?").
- lib/devSeedFeed.ts — 12 synthetic feed posts surfaced when the
real API returned empty. Same reasoning: operator imports genesis
key on a fresh node, opens Feed, sees 12 mock posts that aren't on
their chain. "Test data" that looks real is worse than an honest
empty state.
Feed screen now shows its proper empty state ("Пока нет
рекомендаций", etc.) when the API returns zero items OR on network
error. Chat screen starts empty until real contacts + messages
arrive via WS / storage cache.
Also cleaned a stale comment in chats/[id].tsx that referenced
devSeed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships the client side of the v2.0.0 feed feature. Folds client-app/
into the monorepo (was previously .gitignored as "tracked separately"
but no separate repo ever existed — for v2.0.0 the client is
first-class).
Feed screens
app/(app)/feed.tsx — Feed tab
- Three-way tab strip: Подписки / Для вас / В тренде backed by
/feed/timeline, /feed/foryou, /feed/trending respectively
- Default landing tab is "Для вас" — surfaces discovery without
requiring the user to follow anyone first
- FlatList with pull-to-refresh + viewability-driven view counter
bump (posts visible ≥ 60% for ≥ 1s trigger POST /feed/post/…/view)
- Floating blue compose button → /compose
- Per-post liked_by_me fetched in batches of 6 after list load
app/(app)/compose.tsx — post composer modal
- Fullscreen, Twitter-like header (✕ left, Опубликовать right)
- Auto-focused multiline TextInput, 4000 char cap
- Hashtag preview chips that auto-update as you type
- expo-image-picker + expo-image-manipulator pipeline: resize to
1080px max-dim, JPEG Q=50 (client-side first-pass compression
before the mandatory server-side scrub)
- Live fee estimate + balance guard with a confirmation modal
("Опубликовать пост? Цена: 0.00X T · Размер: N KB")
- Exif: false passed to ImagePicker as an extra privacy layer
app/(app)/feed/[id].tsx — post detail
- Full PostCard rendering + detailed info panel (views, likes,
size, fee, hosting relay, hashtags as tappable chips)
- Triggers bumpView on mount
- 410 (on-chain soft-delete) routes back to the feed
app/(app)/feed/tag/[tag].tsx — hashtag feed
app/(app)/profile/[address].tsx — rebuilt
- Twitter-ish profile: avatar, name, address short-form, post count
- Posts | Инфо tab strip
- Follow / Unfollow button for non-self profiles (optimistic UI)
- Edit button on self profile → settings
- Secondary actions (chat, copy address) when viewing a known contact
Supporting library
lib/feed.ts — HTTP wrappers + tx builders for every /feed/* endpoint:
- publishPost (POST /feed/publish, signed)
- publishAndCommit (publish → on-chain CREATE_POST)
- fetchPost / fetchStats / bumpView
- fetchAuthorPosts / fetchTimeline / fetchForYou / fetchTrending /
fetchHashtag
- buildCreatePostTx / buildDeletePostTx
- buildFollowTx / buildUnfollowTx
- buildLikePostTx / buildUnlikePostTx
- likePost / unlikePost / followUser / unfollowUser / deletePost
(high-level helpers that bundle build + submitTx)
- formatFee, formatRelativeTime, formatCount — Twitter-like display
helpers
components/feed/PostCard.tsx — core card component
- Memoised for performance (N-row re-render on every like elsewhere
would cost a lot otherwise)
- Optimistic like toggle with heart-bounce spring animation
- Hashtag highlighting in body text (tappable → hashtag feed)
- Long-press context menu (Delete, owner-only)
- Views / likes / share-link / reply icons in footer row
Navigation cleanup
- NavBar: removed the SOON pill on the Feed tab (it's shipped now)
- (app)/_layout: hide NavBar on /compose and /feed/* sub-routes
- AnimatedSlot: treat /feed/<id>, /feed/tag/<t>, /compose as
sub-routes so back-swipe-right closes them
Channel removal (client side)
- lib/types.ts: ContactKind stripped to 'direct' | 'group'; legacy
'channel' flag removed. `kind` field kept for backward compat with
existing AsyncStorage records.
- lib/devSeed.ts: dropped the 5 channel seed contacts.
- components/ChatTile.tsx: removed channel kindIcon branch.
Dependencies
- expo-image-manipulator added for client-side image compression.
- expo-file-system/legacy used for readAsStringAsync (SDK 54 moved
that API to the legacy sub-path; the new streaming API isn't yet
stable).
Type check
- npx tsc --noEmit — clean, 0 errors.
Next (not in this commit)
- Direct attachment-bytes endpoint on the server so post-detail can
actually render the image (currently shows placeholder with URL)
- Cross-relay body fetch via /api/relays + hosting_relay pubkey
- Mentions (@username) with notifications
- Full-text search
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Excluded from release bundle:
- CONTEXT.md, CHANGELOG.md (agent/project working notes)
- client-app/ (React Native messenger — tracked separately)
- contracts/hello_go/ (unused standalone example)
Kept contracts/counter/ and contracts/name_registry/ as vm-test fixtures
(referenced by vm/vm_test.go; NOT production contracts).
Docs refactor:
- docs/README.md — new top-level index with cross-references
- docs/quickstart.md — rewrite around single-node as primary path
- docs/node/README.md — full rewrite, all CLI flags, schema table
- docs/api/README.md — add /api/well-known-version, /api/update-check
- docs/contracts/README.md — split native (Go) vs WASM (user-deployable)
- docs/update-system.md — new, full 5-layer update system design
- README.md — link into docs/, drop CHANGELOG/client-app references
Build-time version system (inherited from earlier commits this branch):
- node --version / client --version with ldflags-injected metadata
- /api/well-known-version with {build, protocol_version, features[]}
- Peer-version gossip on dchain/version/v1
- /api/update-check against Gitea release API
- deploy/single/update.sh with semver guard + 15-min systemd jitter