@www.hyperlinks.space/program-kit 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/api/ai.ts +111 -0
- package/api/base.ts +117 -0
- package/api/blockchain.ts +58 -0
- package/api/bot.ts +19 -0
- package/api/ping.ts +41 -0
- package/api/releases.ts +162 -0
- package/api/telegram.ts +65 -0
- package/api/tsconfig.json +17 -0
- package/app/_layout.tsx +135 -0
- package/app/ai.tsx +39 -0
- package/app/components/GlobalBottomBar.tsx +447 -0
- package/app/components/GlobalBottomBarWeb.tsx +362 -0
- package/app/components/GlobalLogoBar.tsx +108 -0
- package/app/components/GlobalLogoBarFallback.tsx +66 -0
- package/app/components/GlobalLogoBarWithFallback.tsx +24 -0
- package/app/components/HyperlinksSpaceLogo.tsx +29 -0
- package/app/components/Telegram.tsx +648 -0
- package/app/components/telegramWebApp.ts +359 -0
- package/app/fonts.ts +12 -0
- package/app/index.tsx +102 -0
- package/app/theme.ts +117 -0
- package/app.json +60 -0
- package/assets/icon.ico +0 -0
- package/assets/images/favicon.png +0 -0
- package/blockchain/coffee.ts +217 -0
- package/blockchain/router.ts +44 -0
- package/bot/format.ts +143 -0
- package/bot/grammy.ts +52 -0
- package/bot/responder.ts +620 -0
- package/bot/webhook.ts +262 -0
- package/database/messages.ts +128 -0
- package/database/start.ts +133 -0
- package/database/users.ts +46 -0
- package/docs/ai_and_search_bar_input.md +94 -0
- package/docs/ai_bot_messages.md +124 -0
- package/docs/backlogs/medium_term_backlog.md +26 -0
- package/docs/backlogs/short_term_backlog.md +39 -0
- package/docs/blue_bar_tackling.md +143 -0
- package/docs/bot_async_streaming.md +174 -0
- package/docs/build_and_install.md +129 -0
- package/docs/database_messages.md +34 -0
- package/docs/fonts.md +18 -0
- package/docs/releases.md +201 -0
- package/docs/releases_github_actions.md +188 -0
- package/docs/scalability.md +34 -0
- package/docs/security_plan_raw.md +244 -0
- package/docs/security_raw.md +345 -0
- package/docs/timing_raw.md +63 -0
- package/docs/tma_logo_bar_jump_investigation.md +69 -0
- package/docs/update.md +205 -0
- package/docs/wallets_hosting_architecture.md +257 -0
- package/eas.json +47 -0
- package/eslint.config.js +10 -0
- package/fullREADME.md +159 -0
- package/global.css +67 -0
- package/npmReadMe.md +53 -0
- package/package.json +214 -0
- package/scripts/load-env.ts +17 -0
- package/scripts/migrate-db.ts +16 -0
- package/scripts/program-kit-init.cjs +58 -0
- package/scripts/run-bot-local.ts +30 -0
- package/scripts/set-webhook.ts +67 -0
- package/scripts/test-api-base.ts +12 -0
- package/telegram/post.ts +328 -0
- package/tsconfig.json +17 -0
- package/vercel.json +7 -0
- package/windows/after-sign-windows-icon.cjs +13 -0
- package/windows/build-layout.cjs +72 -0
- package/windows/build-with-progress.cjs +88 -0
- package/windows/build.cjs +2247 -0
- package/windows/cleanup-legacy-appdata-installs.ps1 +91 -0
- package/windows/cleanup-legacy-windows-shortcuts.ps1 +46 -0
- package/windows/cleanup.cjs +200 -0
- package/windows/embed-windows-exe-icon.cjs +55 -0
- package/windows/extractAppPackage.nsh +150 -0
- package/windows/forge/README.md +41 -0
- package/windows/forge/forge.config.js +138 -0
- package/windows/forge/make-with-stamp.cjs +65 -0
- package/windows/forge-cleanup.cjs +255 -0
- package/windows/hsp-app-process.ps1 +63 -0
- package/windows/installer-hooks.nsi +373 -0
- package/windows/product-brand.cjs +42 -0
- package/windows/remove-orphan-uninstall-registry.ps1 +67 -0
- package/windows/run-installed-with-icon-debug.cmd +20 -0
- package/windows/run-win-electron-builder.cjs +46 -0
- package/windows/updater-dialog.html +143 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# TMA logo bar jump on input focus – fix
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
When the user focuses the AI & Search input in the Telegram Mini App, the keyboard opens and the logo bar jumps (shifts down then up). The whole column (logo bar + main + bottom bar) was also scrollable.
|
|
6
|
+
|
|
7
|
+
**Cause:** Viewport height shrinks when the keyboard opens; if root/body height follows that, the layout reflows and the host may scroll the window → logo bar moves. Reacting on every browser resize (e.g. Visual Viewport) caused intermediate reflows before the final state.
|
|
8
|
+
|
|
9
|
+
## What we tried (dismissed or replaced)
|
|
10
|
+
|
|
11
|
+
| Approach | Outcome |
|
|
12
|
+
|----------|--------|
|
|
13
|
+
| **Position fixed for logo bar** | Reverted. Did not fix the shift inside TMA. |
|
|
14
|
+
| **Root height from `viewport.stableHeight()`** (pin layout to stable height so it doesn’t change on keyboard) | Not used. We chose “single update when TMA reports” instead. |
|
|
15
|
+
| **Visual Viewport API** (`window.visualViewport` resize + `body.style.height` + `--vh` + `scrollTo(0,0)` on every resize) | Replaced. Caused intermediate reflows; keyboard open triggered multiple resize events before the final size. |
|
|
16
|
+
| **Scroll lock only** (prevent window scroll) | Necessary but not sufficient on its own; layout still shifted. |
|
|
17
|
+
|
|
18
|
+
## Solution
|
|
19
|
+
|
|
20
|
+
Use **only the TMA viewport API**. When the keyboard opens, change nothing until TMA sends **viewport_changed**; then apply the new height and scroll reset in one turn.
|
|
21
|
+
|
|
22
|
+
### 1. TMA viewport (Telegram.tsx)
|
|
23
|
+
|
|
24
|
+
- After `viewport.mount()`:
|
|
25
|
+
- **`viewport.bindCssVars()`** – SDK sets/updates `--tg-viewport-height`. Layout height is driven by this.
|
|
26
|
+
- **`on("viewport_changed", handler)`** – Run **`window.scrollTo(0, 0)` only when `payload.is_state_stable`** (or `payload.isStateStable`). Do not reset scroll during drag or animation.
|
|
27
|
+
- **Window scroll lock** – `window.addEventListener("scroll", …)` → if `scrollY > 0` then `scrollTo(0, 0)`.
|
|
28
|
+
- **viewport-fit=cover** on the viewport meta (iOS).
|
|
29
|
+
|
|
30
|
+
### 2. CSS (global.css)
|
|
31
|
+
|
|
32
|
+
- **html**: `height: 100%`, `overflow: hidden`, `overscroll-behavior: none`.
|
|
33
|
+
- **body**, **#root** / **[data-expo-root]**: `height: var(--tg-viewport-height, 100%)`, `min-height: var(--tg-viewport-height, 100%)`, `overflow: hidden` (body also `overscroll-behavior: none`).
|
|
34
|
+
|
|
35
|
+
### 3. Layout
|
|
36
|
+
|
|
37
|
+
- **Root View** (_layout.tsx): `overflow: "hidden"`, flex column.
|
|
38
|
+
- **Logo bar** (GlobalLogoBar): `flexShrink: 0`.
|
|
39
|
+
- **Main**: `flex: 1`, `minHeight: 0`.
|
|
40
|
+
|
|
41
|
+
### 4. TMA (telegramWebApp.ts)
|
|
42
|
+
|
|
43
|
+
- **readyAndExpand()**: `expand()`, `setHeaderColor("#000000")`, `setupSwipeBehavior({ allow_vertical_swipe: false })`, `disableVerticalSwipes?.()`.
|
|
44
|
+
|
|
45
|
+
## viewport_changed payload
|
|
46
|
+
|
|
47
|
+
| Field | Type | Description |
|
|
48
|
+
|-------|------|-------------|
|
|
49
|
+
| `height` | number | Viewport height. |
|
|
50
|
+
| `width` | number (optional) | Viewport width. |
|
|
51
|
+
| `is_expanded` | boolean | Viewport expanded. |
|
|
52
|
+
| `is_state_stable` | boolean | State stable (no change in the next moment). |
|
|
53
|
+
|
|
54
|
+
Only call `scrollTo(0, 0)` when **is_state_stable** is true. Support both `is_state_stable` and `isStateStable` (bridge payload shape).
|
|
55
|
+
|
|
56
|
+
## Summary: keep vs dismiss
|
|
57
|
+
|
|
58
|
+
**Keep in the stack:**
|
|
59
|
+
|
|
60
|
+
- TMA-only viewport logic: `viewport.bindCssVars()`, `on("viewport_changed", …)` with **is_state_stable** check before `scrollTo(0, 0)`, window scroll lock, viewport-fit=cover.
|
|
61
|
+
- CSS: `--tg-viewport-height` for body and #root, overflow/overscroll locked on html/body and root.
|
|
62
|
+
- Root `overflow: "hidden"` and logo bar `flexShrink: 0`.
|
|
63
|
+
- readyAndExpand (expand, setHeaderColor, setupSwipeBehavior, disableVerticalSwipes).
|
|
64
|
+
|
|
65
|
+
**Dismiss as try-outs:**
|
|
66
|
+
|
|
67
|
+
- Any use of **Visual Viewport API** for TMA (resize/scroll listeners, `body.style.height`, `--vh`) — replaced by TMA viewport + bindCssVars + viewport_changed.
|
|
68
|
+
- **Position fixed** for the logo bar — reverted.
|
|
69
|
+
- **stableHeight**-based root height — not used; we use bindCssVars (current height) and rely on **is_state_stable** to avoid resetting scroll until the viewport is stable.
|
package/docs/update.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Instant App Updates Plan
|
|
2
|
+
|
|
3
|
+
This plan defines how to deliver near-instant updates for the app with minimal user friction, while keeping binary releases for native-level changes.
|
|
4
|
+
|
|
5
|
+
## 1) Update Strategy (Two Lanes)
|
|
6
|
+
|
|
7
|
+
- **Lane A: OTA updates (default, fast path)**
|
|
8
|
+
- Use Expo EAS Update to deliver JavaScript/TypeScript and asset changes.
|
|
9
|
+
- No reinstall required.
|
|
10
|
+
- Target latency: minutes or less after publish.
|
|
11
|
+
- **Lane B: Binary releases (slow path)**
|
|
12
|
+
- Use installer/store release only when native/runtime changes are required.
|
|
13
|
+
- Triggered through `app/releases/**` and GitHub Release workflow.
|
|
14
|
+
|
|
15
|
+
Decision rule:
|
|
16
|
+
|
|
17
|
+
- JS/UI/business logic/assets changes -> OTA
|
|
18
|
+
- Native dependency/plugin/permission/runtime changes -> binary release
|
|
19
|
+
|
|
20
|
+
## 2) Current Project Baseline
|
|
21
|
+
|
|
22
|
+
Already configured:
|
|
23
|
+
|
|
24
|
+
- `app/app.json` has `updates.url`.
|
|
25
|
+
- `app/app.json` has `runtimeVersion.policy = appVersion`.
|
|
26
|
+
- `app/eas.json` has channels:
|
|
27
|
+
- preview profile -> `main`
|
|
28
|
+
- production profile -> `production`
|
|
29
|
+
|
|
30
|
+
Implication:
|
|
31
|
+
|
|
32
|
+
- Binaries built on a channel only receive updates from that same channel and compatible runtime version.
|
|
33
|
+
|
|
34
|
+
## 3) Target Architecture for "Instant" Updates
|
|
35
|
+
|
|
36
|
+
1. Developer merges code to branch.
|
|
37
|
+
2. CI publishes OTA update to EAS (`main` or `production` branch/channel).
|
|
38
|
+
3. App receives update check trigger (foreground/resume and optional push signal).
|
|
39
|
+
4. App fetches update, then applies at safe point (next app restart or immediate reload based on policy).
|
|
40
|
+
|
|
41
|
+
For binary-only updates:
|
|
42
|
+
|
|
43
|
+
1. `app/releases/builder/build_MMDDYYYY_HHMM/` (or Forge under `app/releases/forge/...`) changes.
|
|
44
|
+
2. GitHub workflow dedupes and creates Release if new.
|
|
45
|
+
3. Workflow calls `POST /api/releases` webhook.
|
|
46
|
+
4. Backend notifies clients that binary update is available.
|
|
47
|
+
|
|
48
|
+
## 4) Server and CI Setup
|
|
49
|
+
|
|
50
|
+
### 4.1 GitHub Secrets
|
|
51
|
+
|
|
52
|
+
Add repository secrets:
|
|
53
|
+
|
|
54
|
+
- `EXPO_TOKEN` (required for EAS CLI in CI)
|
|
55
|
+
- `RELEASE_WEBHOOK_URL` (for binary-release notification)
|
|
56
|
+
- `RELEASE_WEBHOOK_TOKEN` (for endpoint auth)
|
|
57
|
+
|
|
58
|
+
### 4.2 OTA Workflow (GitHub Actions)
|
|
59
|
+
|
|
60
|
+
Create workflow `.github/workflows/eas-update.yml`:
|
|
61
|
+
|
|
62
|
+
- Trigger:
|
|
63
|
+
- `push` to selected branches (`main`, optionally `release/*`)
|
|
64
|
+
- Optional `workflow_dispatch`
|
|
65
|
+
- Path filters to avoid unnecessary publishes (example):
|
|
66
|
+
- include app source and config paths
|
|
67
|
+
- exclude `app/releases/**`
|
|
68
|
+
- Steps:
|
|
69
|
+
1. checkout
|
|
70
|
+
2. setup node
|
|
71
|
+
3. install deps (`npm ci` in `app/`)
|
|
72
|
+
4. publish OTA:
|
|
73
|
+
- branch `main` for preview/internal
|
|
74
|
+
- branch `production` for production
|
|
75
|
+
5. output EAS update group id and URL
|
|
76
|
+
|
|
77
|
+
Recommended publish command:
|
|
78
|
+
|
|
79
|
+
- `npx eas update --branch <branch> --non-interactive --message "<commit message>"`
|
|
80
|
+
|
|
81
|
+
### 4.3 Binary Workflow (Already Planned)
|
|
82
|
+
|
|
83
|
+
Keep `app/releases/**` workflow with dedupe:
|
|
84
|
+
|
|
85
|
+
- If release exists, skip.
|
|
86
|
+
- If new, create GitHub Release and upload assets.
|
|
87
|
+
- Call `POST /api/releases`.
|
|
88
|
+
|
|
89
|
+
## 5) App Runtime Behavior
|
|
90
|
+
|
|
91
|
+
Implement client update behavior with `expo-updates`:
|
|
92
|
+
|
|
93
|
+
1. On app start and on foreground:
|
|
94
|
+
- call `checkForUpdateAsync()`
|
|
95
|
+
2. If update exists:
|
|
96
|
+
- call `fetchUpdateAsync()`
|
|
97
|
+
3. Apply policy:
|
|
98
|
+
- silent + apply on next launch (default safe)
|
|
99
|
+
- or prompt user and reload now (`reloadAsync()`)
|
|
100
|
+
|
|
101
|
+
Suggested policy:
|
|
102
|
+
|
|
103
|
+
- **Critical fixes:** prompt and reload now
|
|
104
|
+
- **Normal updates:** fetch silently and apply next launch
|
|
105
|
+
|
|
106
|
+
Minimum checks:
|
|
107
|
+
|
|
108
|
+
- On cold start
|
|
109
|
+
- On foreground if last check older than threshold (for example 10 minutes)
|
|
110
|
+
|
|
111
|
+
## 6) Push-Accelerated Detection (Optional but Preferred)
|
|
112
|
+
|
|
113
|
+
OTA itself is already fast, but to reduce time further:
|
|
114
|
+
|
|
115
|
+
- Add backend push trigger when CI publishes OTA.
|
|
116
|
+
- App receives push and immediately runs update check.
|
|
117
|
+
|
|
118
|
+
Channels you can use:
|
|
119
|
+
|
|
120
|
+
- Expo Push Notifications
|
|
121
|
+
- Firebase Cloud Messaging
|
|
122
|
+
- WebSocket/SSE (if app has persistent session)
|
|
123
|
+
|
|
124
|
+
Fallback remains foreground polling.
|
|
125
|
+
|
|
126
|
+
## 7) Versioning and Compatibility Rules
|
|
127
|
+
|
|
128
|
+
With `runtimeVersion.policy = appVersion`:
|
|
129
|
+
|
|
130
|
+
- OTA is only delivered to binaries with matching `expo.version`.
|
|
131
|
+
- When native changes occur:
|
|
132
|
+
- bump app version
|
|
133
|
+
- ship new binary
|
|
134
|
+
- publish OTA for that new runtime afterward
|
|
135
|
+
|
|
136
|
+
Team rule:
|
|
137
|
+
|
|
138
|
+
- Do not bump app version for pure JS hotfixes unless intentionally creating a new runtime.
|
|
139
|
+
|
|
140
|
+
## 8) Security and Reliability
|
|
141
|
+
|
|
142
|
+
- Protect `/api/releases` using token and optional HMAC.
|
|
143
|
+
- Make webhook handler idempotent by `release_id`.
|
|
144
|
+
- Retry webhook delivery in workflow with backoff.
|
|
145
|
+
- Log update lifecycle:
|
|
146
|
+
- CI publish success/failure
|
|
147
|
+
- release webhook accepted/rejected
|
|
148
|
+
- app check/fetch/apply events (client telemetry)
|
|
149
|
+
|
|
150
|
+
## 9) Rollout Plan
|
|
151
|
+
|
|
152
|
+
Phase 1 - Staging:
|
|
153
|
+
|
|
154
|
+
1. Implement OTA workflow for `main` channel only.
|
|
155
|
+
2. Add app-side check/fetch on startup + foreground.
|
|
156
|
+
3. Test with internal build on `main`.
|
|
157
|
+
|
|
158
|
+
Phase 2 - Production:
|
|
159
|
+
|
|
160
|
+
1. Add production publish workflow/manual gate.
|
|
161
|
+
2. Add push-accelerated trigger.
|
|
162
|
+
3. Monitor crash-free sessions and update adoption rate.
|
|
163
|
+
|
|
164
|
+
Phase 3 - Hardening:
|
|
165
|
+
|
|
166
|
+
1. Add kill switch/rollback procedure.
|
|
167
|
+
2. Add release dashboard with latest OTA and binary status.
|
|
168
|
+
|
|
169
|
+
## 10) Operational Playbook
|
|
170
|
+
|
|
171
|
+
### Publish OTA update
|
|
172
|
+
|
|
173
|
+
From `app/`:
|
|
174
|
+
|
|
175
|
+
- `npx eas update --branch main --message "fix: ..."`
|
|
176
|
+
- for production:
|
|
177
|
+
- `npx eas update --branch production --message "fix: ..."`
|
|
178
|
+
|
|
179
|
+
### Publish binary update
|
|
180
|
+
|
|
181
|
+
1. Create `app/releases/builder/build_MMDDYYYY_HHMM/` with installer at root and supporting files under `dev/`.
|
|
182
|
+
2. Push changes.
|
|
183
|
+
3. Workflow creates (or skips duplicate) GitHub Release and calls `/api/releases`.
|
|
184
|
+
|
|
185
|
+
### Rollback OTA
|
|
186
|
+
|
|
187
|
+
- Republish a known-good commit to the same branch/channel with a new OTA message.
|
|
188
|
+
- If needed, temporarily disable update checks via remote config flag.
|
|
189
|
+
|
|
190
|
+
## 11) Acceptance Criteria
|
|
191
|
+
|
|
192
|
+
- OTA updates reach active users without reinstall for JS/UI changes.
|
|
193
|
+
- Median time from CI publish to client fetch is within target SLA.
|
|
194
|
+
- Binary updates only occur for native/runtime changes.
|
|
195
|
+
- Duplicate `release_id` never creates duplicate GitHub Release.
|
|
196
|
+
- `/api/releases` accepts only authenticated requests.
|
|
197
|
+
- App always has fallback update detection even if push signal fails.
|
|
198
|
+
|
|
199
|
+
## 12) Next Implementation Tasks
|
|
200
|
+
|
|
201
|
+
1. Create `app/api/releases.ts` endpoint.
|
|
202
|
+
2. Add `.github/workflows/eas-update.yml`.
|
|
203
|
+
3. Add client update service wrapper (`check/fetch/apply` policy).
|
|
204
|
+
4. Add docs section in `build_and_install.md` linking OTA vs binary rules.
|
|
205
|
+
5. Add monitoring events for update checks and apply outcomes.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# HyperlinksSpace — Wallet Architecture Doc
|
|
2
|
+
|
|
3
|
+
> High-level system design, UI flow principles, and implementation thoughts.
|
|
4
|
+
> Target: non-custodial TON wallet built into the HyperlinksSpace app (Flutter/TMA/Web).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Guiding Principles
|
|
9
|
+
|
|
10
|
+
- **Non-custodial by default.** The mnemonic (seed phrase) never touches the server. Key generation and signing happen on-device.
|
|
11
|
+
- **Telegram-first identity.** Inside Telegram, the user's Telegram account is the identity anchor. Outside Telegram (Windows desktop, Web), Telegram Login is used as the identity bridge.
|
|
12
|
+
- **Small, working steps.** Each wallet feature ships as an isolated, backward-compatible unit that can be deployed without touching existing bot or app flows.
|
|
13
|
+
- **Stable coin built-in.** The wallet is designed from day one to display and eventually allocate DLLR (or another locked stable) alongside the TON balance.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Wallet Types and Entry Points
|
|
18
|
+
|
|
19
|
+
The app supports two wallet modes:
|
|
20
|
+
|
|
21
|
+
| Mode | Description | Who uses it |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| **Unhosted (non-custodial)** | Created inside the app, keys live on device | New users with no existing wallet |
|
|
24
|
+
| **TON Connect** | Connects an existing external wallet (Tonkeeper, MyTonWallet, etc.) | Power users with existing wallets |
|
|
25
|
+
|
|
26
|
+
TON Connect is treated as an **additional/advanced feature**, not the default. The primary flow is always the unhosted wallet.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 3. Wallet State Machine
|
|
31
|
+
|
|
32
|
+
Every wallet instance moves through these states:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[ No Wallet ]
|
|
36
|
+
│
|
|
37
|
+
├──── "Create wallet" ──────► [ Generating ]
|
|
38
|
+
│ │
|
|
39
|
+
└──── "Restore wallet" ────────────►│
|
|
40
|
+
▼
|
|
41
|
+
[ Created ]
|
|
42
|
+
(address shown instantly, QR + copy)
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
[ Deploying ]
|
|
46
|
+
(SMC deploy call in progress)
|
|
47
|
+
│
|
|
48
|
+
┌─────────┴─────────┐
|
|
49
|
+
▼ ▼
|
|
50
|
+
[ Ready ] [ Deploy Failed ]
|
|
51
|
+
(fully operational) (retry available)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**State storage key:** `awallet_v1` (encrypted blob in SecureStorage)
|
|
55
|
+
**Metadata stored separately (plaintext):**
|
|
56
|
+
```
|
|
57
|
+
wallet_address
|
|
58
|
+
created_at
|
|
59
|
+
last_seen_at
|
|
60
|
+
deploy_status ← "pending" | "deployed" | "failed"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 4. First Launch — Unhosted Wallet
|
|
66
|
+
|
|
67
|
+
**Goal:** User has no wallet. They open the app for the first time (any platform).
|
|
68
|
+
|
|
69
|
+
### UI Flow
|
|
70
|
+
|
|
71
|
+
1. **Wallet tab / panel opens** → app checks `awallet_v1` in local SecureStorage.
|
|
72
|
+
2. Nothing found → show two CTAs:
|
|
73
|
+
- **"Create new wallet"** (primary, prominent)
|
|
74
|
+
- **"Restore wallet"** (secondary, text link — visible only if device has an encrypted blob from a previous session or the user chooses to enter a mnemonic)
|
|
75
|
+
3. User taps **"Create new wallet"**:
|
|
76
|
+
- Spinner shown: _"Creating wallet…"_
|
|
77
|
+
- App generates a BIP39 mnemonic + TON keypair **entirely in-memory** (no server call at this step).
|
|
78
|
+
- Address is computed and displayed **instantly**.
|
|
79
|
+
- Encrypted blob is written to SecureStorage.
|
|
80
|
+
4. App shows **"Your wallet is ready"** screen:
|
|
81
|
+
- Address (truncated + copy button)
|
|
82
|
+
- QR code
|
|
83
|
+
- DLLR status: `Allocated / Locked / Available` (placeholders until deploy completes)
|
|
84
|
+
- `"Deploying…"` status badge
|
|
85
|
+
5. SMC deploy call fires in the background (POST `/wallet/deploy`). When complete, status badge updates to ✅ `"Active"`.
|
|
86
|
+
6. A **"Back up your seed phrase"** nudge is shown — non-blocking, dismissible, but persistent until the user acknowledges.
|
|
87
|
+
|
|
88
|
+
### Key decisions
|
|
89
|
+
- Address generation is instant and shown before deploy. This is intentional UX — users can copy the address to receive funds immediately, even before the contract is deployed.
|
|
90
|
+
- Mnemonic is shown **once**, at creation time. After acknowledgement it is never shown again (only recoverable via "Export seed phrase" with auth).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 5. Second Launch — Loading Existing Wallet
|
|
95
|
+
|
|
96
|
+
**Goal:** User returns to the app on the same device. Wallet was created previously.
|
|
97
|
+
|
|
98
|
+
### UI Flow
|
|
99
|
+
|
|
100
|
+
1. App reads `awallet_v1` from SecureStorage on startup.
|
|
101
|
+
2. Encrypted blob found → decrypt using device key (Secure Enclave / Android Keystore).
|
|
102
|
+
3. Wallet panel loads directly to **Ready state** — no mnemonic prompt needed.
|
|
103
|
+
4. App polls `/wallet/status?address=...` to refresh balances and deploy status.
|
|
104
|
+
5. If `deploy_status === "pending"` (e.g., deploy was interrupted), app auto-retries the deploy silently.
|
|
105
|
+
|
|
106
|
+
### Key decisions
|
|
107
|
+
- The device key (used to decrypt the blob) **never leaves the device**. It is stored in the OS-level secure keystore.
|
|
108
|
+
- No mnemonic entry is needed on the same device — the encrypted blob handles re-authentication transparently.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 6. New Device / Re-connect Scenario (Mnemonic Required)
|
|
113
|
+
|
|
114
|
+
**Goal:** User opens the app on a new phone, new PC, or after clearing app data.
|
|
115
|
+
|
|
116
|
+
### UI Flow
|
|
117
|
+
|
|
118
|
+
1. App finds no `awallet_v1` in SecureStorage (it's a fresh install).
|
|
119
|
+
2. Wallet panel shows:
|
|
120
|
+
- **"Create new wallet"** (primary)
|
|
121
|
+
- **"Restore wallet"** (secondary, prominent here)
|
|
122
|
+
3. User taps **"Restore wallet"** → input field: _"Enter your 24-word seed phrase"_
|
|
123
|
+
4. Words entered → app validates mnemonic → re-derives keypair → re-encrypts blob into the new device's SecureStorage.
|
|
124
|
+
5. App calls `/wallet/restore` to confirm address matches expectation.
|
|
125
|
+
6. Wallet loads at **Ready state** (deploy was already done on the original device).
|
|
126
|
+
|
|
127
|
+
### Key decisions
|
|
128
|
+
- The mnemonic is the **single recovery factor**. There is no server-side recovery.
|
|
129
|
+
- Mnemonic input should use a native secure text field (no clipboard suggestions, no autocorrect logging).
|
|
130
|
+
- CloudStorage (Telegram's `telegram.cloudStorage` API) can optionally store the **ciphertext** (not the key), so restore can be assisted without the user typing all 24 words. The device key is still required to decrypt it — CloudStorage alone is not sufficient to reconstruct the wallet.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 7. Telegram Wallet — Inside Telegram (TMA)
|
|
135
|
+
|
|
136
|
+
**Goal:** User accesses the wallet via the Telegram Mini App (TMA).
|
|
137
|
+
|
|
138
|
+
### How identity and storage work inside TMA
|
|
139
|
+
|
|
140
|
+
- **Identity:** `initData.user.id` (Telegram user ID) is the identity anchor. No separate login needed.
|
|
141
|
+
- **Storage:**
|
|
142
|
+
- `telegram.cloudStorage` is used for the encrypted wallet blob — it is synced across Telegram sessions automatically.
|
|
143
|
+
- The device key (to decrypt the blob) is stored in the browser's IndexedDB / WebCrypto key store scoped to the TMA origin.
|
|
144
|
+
- **First launch in TMA:** Same as Section 4, but blob goes to `cloudStorage` instead of native SecureStorage.
|
|
145
|
+
- **Re-open in TMA on same device:** Blob in `cloudStorage` + device key in IndexedDB → loads wallet silently.
|
|
146
|
+
- **TMA on a new device:** CloudStorage has the ciphertext, but the device key is absent → user is prompted to enter mnemonic once. New device key is generated and stored. From then on, that device works silently.
|
|
147
|
+
|
|
148
|
+
### Key decisions
|
|
149
|
+
- `telegram.cloudStorage` is **not end-to-end encrypted by Telegram** — it is accessible to the bot's server-side in theory. This is why we store only the **ciphertext** there, not the plaintext key or mnemonic.
|
|
150
|
+
- The split: _ciphertext in CloudStorage, key in device_ ensures neither half alone can reconstruct the wallet.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 8. Outside Telegram — Web / Windows Setup
|
|
155
|
+
|
|
156
|
+
**Goal:** User uses the app via browser or Windows desktop (Electron/Tauri wrapper), outside of Telegram context.
|
|
157
|
+
|
|
158
|
+
### How identity works outside TMA
|
|
159
|
+
|
|
160
|
+
Since there is no `initData` from Telegram, identity is established via **Telegram Login Widget** (OAuth-style flow):
|
|
161
|
+
|
|
162
|
+
1. User is shown a "Log in with Telegram" button.
|
|
163
|
+
2. Telegram sends a signed hash to the app confirming the user's Telegram ID and username.
|
|
164
|
+
3. App uses the Telegram ID as the identity anchor — same as inside TMA.
|
|
165
|
+
|
|
166
|
+
### Storage outside Telegram
|
|
167
|
+
|
|
168
|
+
- **Web browser:** Encrypted blob is stored in `localStorage` (or IndexedDB for larger payloads). Device key in WebCrypto non-extractable key store.
|
|
169
|
+
- **Windows desktop:** Encrypted blob stored in the OS credential manager or app's local data directory. Device key in Windows DPAPI or Keychain equivalent.
|
|
170
|
+
|
|
171
|
+
### First launch on Windows
|
|
172
|
+
|
|
173
|
+
1. App opens → Telegram Login flow → identity confirmed.
|
|
174
|
+
2. App checks local storage for `awallet_v1`.
|
|
175
|
+
3. **No wallet found:** Same "Create / Restore" UI as Section 4.
|
|
176
|
+
4. **Wallet found (transferred or restored):** Loads to Ready state.
|
|
177
|
+
|
|
178
|
+
### Re-launch on Windows
|
|
179
|
+
|
|
180
|
+
Same device → blob in local storage + device key still present → silent load, no mnemonic needed.
|
|
181
|
+
|
|
182
|
+
### Key decisions
|
|
183
|
+
- Windows is primarily a **reading/management surface** — send, receive, view balances. Heavy transaction flows remain on mobile/TMA first.
|
|
184
|
+
- The GitHub auto-updater (workflow-based releases) is already in place for the Windows build. Wallet state must survive app updates — storage paths must not change across versions, or migration logic must be included.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 9. API Contract (Wallet Endpoints)
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
POST /wallet/create
|
|
192
|
+
Body: { address, encrypted_blob, public_key }
|
|
193
|
+
Response: { ok: true }
|
|
194
|
+
|
|
195
|
+
POST /wallet/deploy
|
|
196
|
+
Body: { address }
|
|
197
|
+
Response: { status: "pending" | "deployed" | "failed" }
|
|
198
|
+
|
|
199
|
+
GET /wallet/status?address=...
|
|
200
|
+
Response: { deployed: bool, dllr_status: {...}, balances: { ton, dllr } }
|
|
201
|
+
|
|
202
|
+
POST /wallet/restore
|
|
203
|
+
Body: { encrypted_blob }
|
|
204
|
+
Response: { address }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The backend is **optional** for the read path — balances can be fetched directly from TON APIs (toncenter/tonapi) on the client side. The backend is primarily needed for:
|
|
208
|
+
- SMC deploy coordination
|
|
209
|
+
- DLLR allocation logic
|
|
210
|
+
- Caching and rate-limit protection for API calls
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 10. WalletService Abstraction (Flutter)
|
|
215
|
+
|
|
216
|
+
The Flutter app uses a `WalletService` interface so the UI never depends directly on whether we're in mock, front-only (direct TON API), or backend-connected mode:
|
|
217
|
+
|
|
218
|
+
```dart
|
|
219
|
+
abstract class WalletService {
|
|
220
|
+
Future<WalletState> loadFromStorage();
|
|
221
|
+
Stream<WalletStatus> watchStatus(String address);
|
|
222
|
+
Future<WalletInfo> createWallet();
|
|
223
|
+
Future<WalletInfo> restoreWallet(String mnemonic);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Swap between implementations via a flag (`kUseMockWalletState` for dev, env-driven for prod). No UI refactor needed when switching providers.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 11. Security Summary
|
|
232
|
+
|
|
233
|
+
| Concern | Approach |
|
|
234
|
+
|---|---|
|
|
235
|
+
| Mnemonic exposure | Shown once at creation, never stored in plaintext |
|
|
236
|
+
| Key storage | OS Secure Enclave / Android Keystore / WebCrypto non-extractable |
|
|
237
|
+
| Server-side secrets | None — server never sees mnemonic or private key |
|
|
238
|
+
| Cross-device restore | Mnemonic re-entry required OR ciphertext from CloudStorage + device key |
|
|
239
|
+
| Telegram identity outside TMA | Telegram Login Widget (signed hash verification) |
|
|
240
|
+
| Duplicate deploy | State machine prevents re-deploy if `deploy_status === "deployed"` |
|
|
241
|
+
| App update survivability | Storage keys versioned (`awallet_v1`), migration path required for `v2` |
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 12. Phase Roadmap
|
|
246
|
+
|
|
247
|
+
| Phase | Scope |
|
|
248
|
+
|---|---|
|
|
249
|
+
| **Phase 1** | Unhosted wallet: create, display address, deploy SMC, show TON balance |
|
|
250
|
+
| **Phase 2** | DLLR integration: allocated/locked/available display, stable coin status |
|
|
251
|
+
| **Phase 3** | TON Connect (connect existing wallets as secondary option) |
|
|
252
|
+
| **Phase 4** | Send/receive flows, transaction history |
|
|
253
|
+
| **Phase 5** | Cross-device CloudStorage assist, backup/export UX hardening |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
*Last updated: April 2026. Maintained in repo at `app/docs/wallet_architecture.md`.*
|
package/eas.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli": {
|
|
3
|
+
"version": ">= 18.0.5",
|
|
4
|
+
"appVersionSource": "remote"
|
|
5
|
+
},
|
|
6
|
+
"build": {
|
|
7
|
+
"development": {
|
|
8
|
+
"developmentClient": true,
|
|
9
|
+
"distribution": "internal",
|
|
10
|
+
"android": {
|
|
11
|
+
"image": "latest"
|
|
12
|
+
},
|
|
13
|
+
"ios": {
|
|
14
|
+
"image": "latest"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"development-simulator": {
|
|
18
|
+
"extends": "development",
|
|
19
|
+
"ios": {
|
|
20
|
+
"simulator": true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"preview": {
|
|
24
|
+
"distribution": "internal",
|
|
25
|
+
"channel": "main",
|
|
26
|
+
"android": {
|
|
27
|
+
"image": "latest"
|
|
28
|
+
},
|
|
29
|
+
"ios": {
|
|
30
|
+
"image": "latest"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"production": {
|
|
34
|
+
"channel": "production",
|
|
35
|
+
"autoIncrement": true,
|
|
36
|
+
"android": {
|
|
37
|
+
"image": "latest"
|
|
38
|
+
},
|
|
39
|
+
"ios": {
|
|
40
|
+
"image": "latest"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"submit": {
|
|
45
|
+
"production": {}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/eslint.config.js
ADDED