Profile screen
- Missing safe-area top inset on the header — fixed by wrapping the
outer View in paddingTop: insets.top (matches the rest of the tab
screens).
- "Чат" button icon + text sat on two lines because the button used
column layout by default. Rewritten as a full-width CTA pill with
flexDirection: row and alignItems: center → chat-bubble icon and
label sit on one line.
- Dedicated "Копировать адрес" button removed. The address row in
the info card is now the tap target: pressing it copies to clipboard
and flips the row to "Скопировано" with a green check for 1.8s.
- Posts tab removed entirely. User's right — a "chat profile" has no
posts concept, just participant count + date + encryption. The
profile screen is now a single-view info card (address, encryption
status, added date, participants). Posts are discoverable via the
Feed tab; a dedicated /feed/author/{pub} screen is on the roadmap
for browsing a specific user's timeline.
Back-stack navigation
- app/(app)/profile/_layout.tsx + app/(app)/feed/_layout.tsx added,
each with a native <Stack>. The outer AnimatedSlot is stack-less
(intentional — it animates tab switches), so without these nested
stacks `router.back()` from a profile screen had no history to pop
and fell through to root. Now tapping an author in the feed → back
returns to feed; opening a profile from chat header → back returns
to the chat.
Dev feed seed
- lib/devSeedFeed.ts: 12 synthetic posts (text-only, mix of hashtags,
one with has_attachment, varying likes/views, timestamps from "1m
ago" to "36h ago"). Exposed via getDevSeedFeed() — stripped from
production via __DEV__ gate.
- Feed screen falls back to the dev seed only when the real API
returned zero posts. Gives something to scroll / tap / like-toggle
before the backend has real content; real data takes over as soon
as it arrives.
Note: tapping a mock post into detail will 404 against the real node
(the post_ids don't exist on-chain). Intentional — the seed is for
scroll + interaction feel, not deep linking.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DChain Messenger — React Native Client
E2E-encrypted mobile/desktop messenger built on the DChain blockchain stack.
Stack: React Native · Expo · NativeWind (Tailwind) · TweetNaCl · Zustand
Quick Start
cd client-app
npm install
npx expo start # opens Expo Dev Tools
# Press 'i' for iOS simulator, 'a' for Android, 'w' for web
Requirements
- Node.js 18+
- Expo Go on your phone (for Expo tunnel), or iOS/Android emulator
- A running DChain node (see root README for
docker compose up --build -d)
Project Structure
client-app/
├── app/
│ ├── _layout.tsx # Root layout — loads keys, sets up nav
│ ├── index.tsx # Welcome / onboarding
│ ├── (auth)/
│ │ ├── create.tsx # Generate new Ed25519 + X25519 keys
│ │ ├── created.tsx # Key created — export reminder
│ │ └── import.tsx # Import existing key.json
│ └── (app)/
│ ├── _layout.tsx # Tab bar — Chats · Wallet · Settings
│ ├── chats/
│ │ ├── index.tsx # Chat list with contacts
│ │ └── [id].tsx # Individual chat with E2E encryption
│ ├── requests.tsx # Incoming contact requests
│ ├── new-contact.tsx # Add contact by @username or address
│ ├── wallet.tsx # Balance + TX history + send
│ └── settings.tsx # Node URL, key export, profile
├── components/ui/ # shadcn-style components (Button, Card, Input…)
├── hooks/
│ ├── useMessages.ts # Poll relay inbox, decrypt messages
│ ├── useBalance.ts # Poll token balance
│ └── useContacts.ts # Load contacts + poll contact requests
└── lib/
├── api.ts # REST client for all DChain endpoints
├── crypto.ts # NaCl box encrypt/decrypt, Ed25519 sign
├── storage.ts # SecureStore (keys) + AsyncStorage (data)
├── store.ts # Zustand global state
├── types.ts # TypeScript interfaces
└── utils.ts # cn(), formatAmount(), relativeTime()
Cryptography
| Operation | Algorithm | Library |
|---|---|---|
| Transaction signing | Ed25519 | TweetNaCl sign |
| Key exchange | X25519 (Curve25519) | TweetNaCl box |
| Message encryption | NaCl box (XSalsa20-Poly1305) | TweetNaCl box |
| Key storage | Device secure enclave | expo-secure-store |
Messages are encrypted as:
Envelope {
sender_pub: <X25519 hex> // sender's public key
recipient_pub: <X25519 hex> // recipient's public key
nonce: <24-byte hex> // random per message
ciphertext: <hex> // NaCl box(plaintext, nonce, sender_priv, recipient_pub)
}
Connect to your node
- Start the DChain node:
docker compose up --build -d - Open the app → Settings → Node URL →
http://YOUR_IP:8081 - If using Expo Go on physical device: your PC and phone must be on the same network, or use
npx expo start --tunnel
Key File Format
The key.json exported/imported by the app:
{
"pub_key": "26018d40...", // Ed25519 public key (64 hex chars)
"priv_key": "...", // Ed25519 private key (128 hex chars)
"x25519_pub": "...", // X25519 public key (64 hex chars)
"x25519_priv": "..." // X25519 private key (64 hex chars)
}
This is the same format as the Go node's --key flag.