@www.hyperlinks.space/program-kit 18.18.18 → 123.123.123

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.
Files changed (52) hide show
  1. package/.eas/workflows/create-development-builds.yml +21 -0
  2. package/.eas/workflows/create-draft.yml +15 -0
  3. package/.eas/workflows/deploy-to-production.yml +68 -0
  4. package/.gitattributes +48 -0
  5. package/.gitignore +52 -0
  6. package/.nvmrc +1 -0
  7. package/.vercelignore +6 -0
  8. package/README.md +17 -2
  9. package/ai/openai.ts +202 -0
  10. package/ai/transmitter.ts +367 -0
  11. package/backlogs/medium_term_backlog.md +26 -0
  12. package/backlogs/short_term_backlog.md +42 -0
  13. package/eslint.config.cjs +10 -0
  14. package/npmReadMe.md +17 -2
  15. package/npmrc.example +1 -0
  16. package/package.json +3 -28
  17. package/polyfills/buffer.ts +9 -0
  18. package/research & docs/ai_and_search_bar_input.md +94 -0
  19. package/research & docs/ai_bot_messages.md +124 -0
  20. package/research & docs/auth-and-centralized-encrypted-keys-plan.md +440 -0
  21. package/research & docs/blue_bar_tackling.md +143 -0
  22. package/research & docs/bot_async_streaming.md +174 -0
  23. package/research & docs/build_and_install.md +129 -0
  24. package/research & docs/database_messages.md +34 -0
  25. package/research & docs/fonts.md +18 -0
  26. package/research & docs/github-gitlab-bidirectional-mirroring.md +154 -0
  27. package/research & docs/keys-retrieval-console-scripts.js +131 -0
  28. package/research & docs/npm-release.md +46 -0
  29. package/research & docs/releases.md +201 -0
  30. package/research & docs/releases_github_actions.md +188 -0
  31. package/research & docs/scalability.md +34 -0
  32. package/research & docs/security_plan_raw.md +244 -0
  33. package/research & docs/security_raw.md +354 -0
  34. package/research & docs/storage-availability-console-script.js +152 -0
  35. package/research & docs/storage-lifetime.md +33 -0
  36. package/research & docs/telegram-raw-keys-cloud-storage-risks.md +31 -0
  37. package/research & docs/timing_raw.md +63 -0
  38. package/research & docs/tma_logo_bar_jump_investigation.md +69 -0
  39. package/research & docs/update.md +205 -0
  40. package/research & docs/wallet_telegram_standalone_multichain_proposal.md +192 -0
  41. package/research & docs/wallets_hosting_architecture.md +403 -0
  42. package/services/wallet/tonWallet.ts +73 -0
  43. package/ui/components/GlobalBottomBar.tsx +447 -0
  44. package/ui/components/GlobalBottomBarWeb.tsx +362 -0
  45. package/ui/components/GlobalLogoBar.tsx +108 -0
  46. package/ui/components/GlobalLogoBarFallback.tsx +66 -0
  47. package/ui/components/GlobalLogoBarWithFallback.tsx +24 -0
  48. package/ui/components/HyperlinksSpaceLogo.tsx +29 -0
  49. package/ui/components/Telegram.tsx +677 -0
  50. package/ui/components/telegramWebApp.ts +359 -0
  51. package/ui/fonts.ts +12 -0
  52. package/ui/theme.ts +117 -0
@@ -0,0 +1,124 @@
1
+ # Plan: Implement messages table in the bot
2
+
3
+ **Goal:** Persist bot messages in the `messages` table and use the DB for "no message mixing" (serverless). Optionally use thread history for AI context later.
4
+
5
+ **Existing:** `app/database/messages.ts` has `insertMessage`, `getThreadHistory`, `getMaxTelegramUpdateIdForThread`. Schema in `start.ts`; table created by `db:migrate`. Bot: `responder.ts` handles text/caption, gets `ctx.from`, `message_thread_id`, calls `transmit`/`transmitStream`, replies. No DB persistence today; no thread history for AI.
6
+
7
+ ---
8
+
9
+ ## Best possible implementation (target design)
10
+
11
+ **Split of responsibility**
12
+
13
+ - **AI layer** owns all message persistence and context. **Bot** (and later TMA) own only transport and, for the bot, mixing prevention.
14
+
15
+ **AI side (single place for persistence and context)**
16
+
17
+ - Receives every request with: `input`, `user_telegram`, `thread_id`, `type` (`'bot'` | `'app'`), and optionally `telegram_update_id` (bot only).
18
+ - **Claim / user message:** Inserts the user message (with `telegram_update_id` when provided). If insert returns `null` (unique violation), returns a **skipped** result so the caller does not send anything.
19
+ - **Context:** Loads `getThreadHistory(...)` for that thread, converts to the format the model expects (e.g. `messages[]`), and passes current `input` + history to the model.
20
+ - **Assistant message:** After a successful model response, inserts the assistant message (no `telegram_update_id`).
21
+ - **Result:** One code path for “what gets stored” and “what context the model sees”. Bot and TMA both call this same layer; no duplicate insert logic in each client.
22
+
23
+ **Bot side (mixing only)**
24
+
25
+ - Resolves `user_telegram`, `thread_id`, `update_id` from `ctx`, and passes them into the AI call (including `telegram_update_id`).
26
+ - If AI returns **skipped** (claim insert failed), returns without calling AI again and without sending any reply or draft.
27
+ - Before **each** draft send and before the **final** reply: calls `getMaxTelegramUpdateIdForThread(user_telegram, thread_id, 'bot')`. If `max !== our update_id`, aborts (does not send). No message writes in the bot; only this read for mixing.
28
+ - Sends drafts and final reply as today; does not call `insertMessage` itself.
29
+
30
+ **TMA**
31
+
32
+ - Calls the same AI layer with `user_telegram`, `thread_id`, `type: 'app'`. No `telegram_update_id`. Same persistence (user + assistant) and same history loading. No mixing logic unless we add a TMA-specific mechanism later (e.g. client request id + uniqueness).
33
+
34
+ **Data flow (bot)**
35
+
36
+ 1. User sends a message → webhook → bot handler.
37
+ 2. Bot: resolve `user_telegram`, `thread_id`, `update_id`; call AI with `input`, `user_telegram`, `thread_id`, `type: 'bot'`, `telegram_update_id`.
38
+ 3. AI: insert user message (with `telegram_update_id`). If `null` → return skipped. Else: load thread history, call model with history + current input, insert assistant message, return response (and for streaming: stream + insert assistant when done).
39
+ 4. Bot: if skipped → return. Else: for each draft and for final reply, check `getMaxTelegramUpdateIdForThread`; if not ours, abort. Else send draft/reply.
40
+
41
+ **Why this is best**
42
+
43
+ - **Single source of truth:** All message rows and model context are created in the AI layer. Bot and TMA stay thin and consistent.
44
+ - **No mixing in bot:** Mixing is entirely “check before send” + “skipped when claim fails”; no message writes in the bot.
45
+ - **History by default:** AI always loads thread history and uses it for context, so conversations are coherent across turns.
46
+ - **TMA-ready:** Same API for TMA (no `telegram_update_id`); mixing can be added later if needed.
47
+
48
+ ---
49
+
50
+ ## 1. Resolve thread identity and update_id in the bot
51
+
52
+ - **user_telegram:** `normalizeUsername(ctx.from?.username)` (same as grammy upsert). If empty, we can skip persistence or still reply (plan: skip DB only when username missing).
53
+ - **thread_id:** `ctx.message?.message_thread_id ?? 0` (already used in responder for `replyOptions`).
54
+ - **type:** `'bot'`.
55
+ - **update_id:** `ctx.update.update_id` (Grammy context has it). Must be passed into the handler or read from `ctx.update` in responder.
56
+
57
+ **Where:** `responder.ts` (and optionally grammy if we need to pass update_id explicitly). Ensure we have access to `ctx.update.update_id` in `handleBotAiResponse`.
58
+
59
+ ---
60
+
61
+ ## 2. Insert user message first; skip if duplicate (claim by insert)
62
+
63
+ - At the start of the AI flow (after we have `text`, `user_telegram`, `thread_id`), call:
64
+ `insertMessage({ user_telegram, thread_id, type: 'bot', role: 'user', content: text, telegram_update_id })`.
65
+ - If `insertMessage` returns `null` (unique violation → another instance or duplicate webhook), **return without calling AI or replying** (so only one handler "owns" this update).
66
+
67
+ **Where:** `responder.ts`, right after we have `text` and before we set up streaming/cancellation. Requires `user_telegram` and `update_id`; user must exist in `users` (grammy already upserts before calling the handler).
68
+
69
+ ---
70
+
71
+ ## 3. Check "max update_id" before each send (no mixing)
72
+
73
+ - Before each **draft** send and before the **final reply**, call:
74
+ `getMaxTelegramUpdateIdForThread(user_telegram, thread_id, 'bot')`.
75
+ - If the returned max is not equal to our `update_id`, another instance has already processed a newer user message → **abort** (do not send draft or reply). Same idea as current in-memory `isCancelled()`, but DB-backed so it works across serverless instances.
76
+
77
+ **Where:** In `responder.ts`, inside `sendDraftOnce` / before `ctx.reply`: call the DB; if `max !== ourUpdateId`, treat as cancelled (return / skip send).
78
+
79
+ ---
80
+
81
+ ## 4. Persist assistant reply after successful send
82
+
83
+ - After we send the final reply with `ctx.reply(result.output_text, replyOptions)` (and only when we actually send, not when we aborted or errored), call:
84
+ `insertMessage({ user_telegram, thread_id, type: 'bot', role: 'assistant', content: result.output_text })` (no `telegram_update_id`).
85
+
86
+ **Where:** `responder.ts`, after the successful `ctx.reply(...)`.
87
+
88
+ ---
89
+
90
+ ## 5. (Optional) Use thread history for AI context
91
+
92
+ - Load history: `getThreadHistory({ user_telegram, thread_id, type: 'bot', limit })`.
93
+ - Convert to the format expected by the AI (e.g. OpenAI `messages`: `{ role, content }[]`).
94
+ - Pass this into the AI layer. Today `transmit`/`transmitStream` and `callOpenAiChat`/`callOpenAiChatStream` take a single `input` string; we’d need to extend the API to accept an optional `history` (or `messages`) and send a multi-turn request instead of a single user message.
95
+
96
+ **Where:** New or changed code in `openai.ts` / `transmitter.ts` and call from `responder.ts` when in `chat` mode. Can be a follow-up step after 1–4.
97
+
98
+ ---
99
+
100
+ ## Implementation order (recommended)
101
+
102
+ | Step | What | Files |
103
+ |------|------|--------|
104
+ | 1 | Resolve and pass `user_telegram`, `thread_id`, `update_id` in responder | `responder.ts` |
105
+ | 2 | Insert user message at start; if `null`, return (no AI, no reply) | `responder.ts`, `database/messages.ts` (already has API) |
106
+ | 3 | Before each draft and before final reply: check `getMaxTelegramUpdateIdForThread`; if max ≠ our `update_id`, abort send | `responder.ts` |
107
+ | 4 | After successful `ctx.reply`, insert assistant message | `responder.ts` |
108
+ | 5 | (Later) Load thread history and pass to AI | `responder.ts`, `ai/openai.ts`, `ai/transmitter.ts` |
109
+
110
+ ---
111
+
112
+ ## Edge cases
113
+
114
+ - **No username:** If `user_telegram` is empty (no `ctx.from.username`), we can skip all DB calls and keep current behavior (reply without persisting), or refuse to reply; plan suggests skip persistence only.
115
+ - **User not in DB:** `insertMessage` uses FK to `users(telegram_username)`. Grammy already upserts on message, so the user should exist. If we ever process before upsert, we’d get an FK error; keep upsert as first step in grammy (current behavior).
116
+ - **Schema not run:** Ensure `ensureSchema()` runs before handlers (e.g. at deploy via `db:migrate`); no change needed if already in place.
117
+
118
+ ---
119
+
120
+ ## Summary
121
+
122
+ 1. **Tell first:** This document is the plan.
123
+ 2. **Implement 1–4** so the bot persists user and assistant messages and uses the DB for "only latest wins" (no mixing in serverless).
124
+ 3. **Implement 5** later to add thread history to the AI.
@@ -0,0 +1,440 @@
1
+ # Auth + Centralized Encrypted Keys Plan (Supabase)
2
+
3
+ This document defines a practical implementation plan for:
4
+
5
+ - Multi-provider login: **Google**, **Telegram**, **GitHub**, and **email + protection code (OTP)**
6
+ - Centralized storage in **Supabase**
7
+ - Wallet secrets stored as **encrypted blobs only** (no plaintext mnemonic/private keys server-side)
8
+
9
+ ---
10
+
11
+ ## 1) Product Goal
12
+
13
+ Enable users to sign in from multiple platforms and recover wallet access from Supabase by using account credentials plus a user-held decryption secret model.
14
+
15
+ ## 2) Security Goal
16
+
17
+ - Supabase/backend stores only ciphertext envelopes and metadata.
18
+ - Decryption happens client-side.
19
+ - Server does not receive plaintext mnemonic/private keys.
20
+
21
+ ---
22
+
23
+ ## 3) Trust Model Decision
24
+
25
+ To avoid custodial key handling by backend, choose one of these decryption models:
26
+
27
+ 1. **Password-derived key model (recommended for centralized sync)**
28
+ - User sets a wallet passphrase.
29
+ - Client derives key with Argon2id.
30
+ - Ciphertext stored in Supabase.
31
+ 2. **Device-key model only**
32
+ - Better local UX, weaker cross-device recovery unless mnemonic re-entry is required.
33
+
34
+ For this plan, use model (1) as canonical centralized recovery path.
35
+
36
+ ---
37
+
38
+ ## 3.1) What "wrapped decrypt key" means (plain language)
39
+
40
+ This phrase means we do not store the real decryption key directly in the database.
41
+
42
+ - **DEK (Data Encryption Key):** key that encrypts wallet secret/mnemonic.
43
+ - **KEK (Key Encryption Key):** key that encrypts ("wraps") the DEK.
44
+
45
+ So database stores:
46
+ - wallet ciphertext (encrypted by DEK)
47
+ - wrapped DEK (encrypted by KEK)
48
+
49
+ Database does **not** store:
50
+ - plaintext mnemonic
51
+ - plaintext DEK
52
+ - plaintext KEK
53
+
54
+ ### Why KMS/HSM is mentioned
55
+
56
+ `KEK` should live in a managed key system (AWS KMS, GCP KMS, Azure Key Vault HSM, etc.), not in app code or DB columns.
57
+
58
+ When app needs decrypt flow:
59
+ 1. User authenticates (Google/Telegram/GitHub/email OTP).
60
+ 2. Backend loads `wrapped_dek` + wallet `ciphertext` from DB.
61
+ 3. Backend asks KMS/HSM to unwrap DEK (or returns a short-lived tokenized decrypt result, depending on policy).
62
+ 4. Decrypt happens in the chosen boundary (client-side or controlled backend flow).
63
+
64
+ ### Tiny analogy
65
+
66
+ - Wallet data = document in locked box (ciphertext).
67
+ - DEK = key to that box.
68
+ - KEK = key to a safe that contains the DEK.
69
+ - KMS/HSM = guarded safe room.
70
+
71
+ This way, stealing only DB rows is not enough to decrypt user wallets.
72
+
73
+ ---
74
+
75
+ ## 3.2) Newbie note: "But ciphertext and wrapped key are in the same DB"
76
+
77
+ This is a common concern and the short answer is:
78
+
79
+ - Yes, they can be stored in the same row.
80
+ - No, that does not automatically break security.
81
+
82
+ ### Why this can still be safe
83
+
84
+ Think of three pieces:
85
+
86
+ 1. `ciphertext` (locked wallet data)
87
+ 2. `wrapped_dek` (the key to the lock, but itself locked)
88
+ 3. `kek` in KMS/HSM (the key that unlocks `wrapped_dek`)
89
+
90
+ If attacker steals DB only, they get (1) and (2), but not (3).
91
+ Without (3), they cannot recover plaintext keys.
92
+
93
+ ### What actually protects the system
94
+
95
+ The real protection is **access control to KMS/HSM**, not hiding DB relations.
96
+
97
+ - Keep KEK outside DB.
98
+ - Restrict KMS permissions to minimum required service path.
99
+ - Audit and rate-limit unwrap operations.
100
+
101
+ ### Optional extra hardening
102
+
103
+ You can split storage across two databases (ciphertext in one, wrapped key in another), but this is additional defense-in-depth. It does not replace KMS/HSM boundary.
104
+
105
+ Rule of thumb for beginners:
106
+ **Do not rely on "they cannot match rows"; rely on "they cannot access KEK".**
107
+
108
+ ---
109
+
110
+ ## 3.3) How user session works with KEK (Google/Telegram/GitHub/email OTP)
111
+
112
+ Important: user login session is an **authorization signal**, not the KEK itself.
113
+
114
+ - Google/Telegram/GitHub/email OTP proves "this user is authenticated".
115
+ - Backend then decides whether to allow key unwrap path.
116
+ - KEK remains in KMS/HSM and is never replaced by OAuth/session token.
117
+
118
+ Typical flow:
119
+
120
+ 1. User logs in and gets a valid app session.
121
+ 2. App requests wallet unlock/decrypt operation.
122
+ 3. Backend verifies session + policy checks (device/risk/rate limits).
123
+ 4. Backend reads `wrapped_dek + ciphertext` from DB.
124
+ 5. Backend calls KMS/HSM to unwrap DEK.
125
+ 6. Decrypt/sign path continues under selected trust boundary.
126
+
127
+ This is why people say "session gates KEK usage", not "session is KEK".
128
+
129
+ ### Two deployment variants
130
+
131
+ - **Variant A (more custodial):**
132
+ - Backend unwraps DEK and performs decrypt/sign server-side.
133
+ - User gets signed result/tx hash.
134
+ - **Variant B (hybrid):**
135
+ - Backend authorizes and returns short-lived decrypt material/session token.
136
+ - Client resolves/decrypts locally for signing.
137
+
138
+ Choose Variant A only if you explicitly accept custodial responsibility.
139
+
140
+ ---
141
+
142
+ ## 3.4) Why keep both `wrapped_dek` and `ciphertext` in DB (not one encrypted entity)
143
+
144
+ You can think "why not one giant blob encrypted by KEK directly?".
145
+ Short answer: envelope encryption with separate DEK is safer and more operationally practical.
146
+
147
+ Reasons:
148
+
149
+ 1. **KMS/HSM usage limits and performance**
150
+ - KMS is best for wrapping small keys, not encrypting large/high-volume payloads repeatedly.
151
+ - DEK handles data encryption efficiently.
152
+
153
+ 2. **Key rotation without re-encrypting all wallet data**
154
+ - Rotate KEK by re-wrapping DEKs.
155
+ - No need to decrypt/re-encrypt every wallet ciphertext each time KEK rotates.
156
+
157
+ 3. **Cryptographic separation of duties**
158
+ - DEK protects wallet payload.
159
+ - KEK protects DEK.
160
+ - Cleaner blast-radius control and auditing.
161
+
162
+ 4. **Metadata and versioning flexibility**
163
+ - You can evolve ciphertext formats (AEAD params/version) independently from KEK lifecycle.
164
+
165
+ 5. **Standard industry pattern**
166
+ - This is standard "envelope encryption" used by major cloud security systems.
167
+
168
+ So storing both `ciphertext` and `wrapped_dek` is expected architecture, not duplication.
169
+
170
+ ---
171
+
172
+ ## 3.5) KMS/HSM section: what it is and how to operate it
173
+
174
+ ## What is KMS?
175
+
176
+ **KMS (Key Management Service)** is a managed service that stores and uses cryptographic master keys with strict access controls, logging, and rotation features.
177
+
178
+ Examples:
179
+ - AWS KMS
180
+ - Google Cloud KMS
181
+ - Azure Key Vault (with HSM-backed options)
182
+
183
+ ## What is HSM?
184
+
185
+ **HSM (Hardware Security Module)** is specialized hardware designed to keep key material protected and perform crypto operations with strong tamper resistance.
186
+
187
+ In practice:
188
+ - Many cloud KMS offerings can use HSM-backed keys.
189
+ - Teams usually start with managed KMS and move to stricter HSM policies if required by risk/compliance.
190
+
191
+ ## Why use KMS/HSM here
192
+
193
+ - Keep `KEK` out of application DB and source code.
194
+ - Centralize key policy and access control.
195
+ - Get immutable audit logs for unwrap/encrypt/decrypt operations.
196
+ - Support controlled key rotation and key disable/emergency revoke.
197
+
198
+ ## How to deal with it (practical checklist)
199
+
200
+ 1. **Create KEK in KMS/HSM**
201
+ - Mark as non-exportable when available.
202
+ - Separate key per environment (`dev/stage/prod`).
203
+
204
+ 2. **Restrict IAM permissions**
205
+ - App service can only call required operations (typically `Decrypt/Unwrap`, maybe `Encrypt/Wrap`).
206
+ - No broad admin access from runtime services.
207
+
208
+ 3. **Use envelope encryption pattern**
209
+ - Generate DEK for wallet payload encryption.
210
+ - Store `ciphertext + wrapped_dek` in DB.
211
+ - Never store plaintext KEK/DEK at rest.
212
+
213
+ 4. **Add policy checks before unwrap**
214
+ - Require valid session and account state.
215
+ - Apply risk checks (IP/device anomalies, rate limits, cooldowns).
216
+ - Log every sensitive operation.
217
+
218
+ 5. **Implement rotation**
219
+ - Rotate KEK on schedule.
220
+ - Re-wrap DEKs in background jobs.
221
+ - Keep key version metadata in DB.
222
+
223
+ 6. **Prepare incident controls**
224
+ - Ability to disable key version quickly.
225
+ - Emergency freeze for high-risk accounts.
226
+ - Recovery runbook for key compromise scenarios.
227
+
228
+ ## Common mistakes to avoid
229
+
230
+ - Putting KEK plaintext in `.env` and calling it "KMS-ready".
231
+ - Giving app runtime full KMS admin permissions.
232
+ - Missing unwrap rate limits and anomaly detection.
233
+ - No audit review pipeline for key operations.
234
+ - Designing without key-rotation path from day one.
235
+
236
+ ## Newbie rule
237
+
238
+ If DB is stolen, attacker should still need **separate KMS/HSM access** to decrypt anything.
239
+ If DB theft alone can decrypt wallets, architecture is wrong.
240
+
241
+ ---
242
+
243
+ ## 4) Authentication Architecture
244
+
245
+ Use **Supabase Auth** as identity layer:
246
+
247
+ - Google OAuth
248
+ - GitHub OAuth
249
+ - Email + protection code (OTP / magic code)
250
+ - Telegram login bridge (custom verifier service; link to Supabase user)
251
+
252
+ ### Identity Linking
253
+
254
+ A single user can link multiple auth methods to one account.
255
+
256
+ - Primary identity key: `user_id` (Supabase Auth UUID)
257
+ - Linked providers table stores provider identifiers (`google_sub`, `github_id`, `telegram_id`, etc.)
258
+
259
+ ---
260
+
261
+ ## 5) Wallet Encryption Envelope
262
+
263
+ Store one or more encrypted wallet envelopes per user.
264
+
265
+ ### Envelope format (server-stored)
266
+
267
+ - `ciphertext` (base64)
268
+ - `nonce/iv`
269
+ - `kdf` params:
270
+ - `algorithm = argon2id`
271
+ - `salt`
272
+ - `memory_kib`
273
+ - `iterations`
274
+ - `parallelism`
275
+ - `dk_len`
276
+ - `aead`:
277
+ - `algorithm = aes-256-gcm` (or xchacha20-poly1305)
278
+ - optional `aad`
279
+ - `version`
280
+ - `created_at`, `updated_at`
281
+
282
+ ### Crypto rules
283
+
284
+ - KDF and encryption run **client-side only**.
285
+ - Never send passphrase to server.
286
+ - Never log secrets/ciphertext in verbose logs.
287
+
288
+ ---
289
+
290
+ ## 6) Supabase Data Model
291
+
292
+ ## `profiles`
293
+ - `id` (uuid, fk -> auth.users.id)
294
+ - `username` (nullable unique)
295
+ - `display_name`
296
+ - `created_at`
297
+
298
+ ## `auth_identities`
299
+ - `id` (uuid)
300
+ - `user_id` (uuid)
301
+ - `provider` (`google|github|telegram|email_otp`)
302
+ - `provider_subject` (string)
303
+ - unique (`provider`, `provider_subject`)
304
+
305
+ ## `wallet_envelopes`
306
+ - `id` (uuid)
307
+ - `user_id` (uuid)
308
+ - `wallet_label`
309
+ - `ciphertext`
310
+ - `kdf_json`
311
+ - `aead_json`
312
+ - `envelope_version`
313
+ - `is_active`
314
+ - timestamps
315
+
316
+ ## `security_events`
317
+ - `id`, `user_id`
318
+ - `event_type`
319
+ - `ip_hash`, `ua_hash`
320
+ - timestamps
321
+
322
+ ---
323
+
324
+ ## 7) Authorization and RLS
325
+
326
+ Enable Row Level Security:
327
+
328
+ - user can read/write only rows where `user_id = auth.uid()`.
329
+ - admin/service role separated for backend-only tasks.
330
+ - strict policies for envelope update/delete.
331
+
332
+ Add rate limits and abuse controls at API edge:
333
+
334
+ - login attempt throttling
335
+ - envelope fetch/update throttling
336
+ - device/IP anomaly checks
337
+
338
+ ---
339
+
340
+ ## 8) Provider-Specific Notes
341
+
342
+ ## Google / GitHub
343
+ - Use Supabase OAuth providers.
344
+ - Standard callback + session issuance.
345
+
346
+ ## Email + Protection Code (OTP)
347
+ - Use Supabase OTP flow (`signInWithOtp`) with short code expiry.
348
+ - Rate-limit OTP requests and verify attempts.
349
+ - Add anti-abuse checks (per-IP/per-email cooldown).
350
+ - Optional: require email verification before provider linking actions.
351
+
352
+ ## Telegram
353
+ - Verify Telegram login payload (`id_token`/signed data) in backend function.
354
+ - On success, link Telegram identity to existing `user_id` or create new profile.
355
+ - Telegram auth is identity only, not decryption secret.
356
+
357
+ ---
358
+
359
+ ## 9) Client Flows
360
+
361
+ ## Registration
362
+ 1. User signs up via any provider.
363
+ 2. App prompts wallet passphrase setup (if no envelope exists).
364
+ 3. Client generates/imports mnemonic.
365
+ 4. Client encrypts mnemonic -> envelope.
366
+ 5. Upload envelope to Supabase.
367
+
368
+ ## Login on new device
369
+ 1. User authenticates via provider.
370
+ 2. App fetches envelope from Supabase.
371
+ 3. User enters wallet passphrase.
372
+ 4. Client decrypts locally and unlocks wallet.
373
+
374
+ ## Passphrase change
375
+ 1. Unlock with current passphrase.
376
+ 2. Re-encrypt with new Argon2id salt/params.
377
+ 3. Replace envelope atomically.
378
+
379
+ ## Email protection-code recovery
380
+ 1. User requests login code by email.
381
+ 2. Enters code and gets authenticated session.
382
+ 3. If wallet envelope exists, user enters wallet passphrase to decrypt.
383
+ 4. If passphrase is forgotten, user must recover with mnemonic and set a new passphrase.
384
+
385
+ ---
386
+
387
+ ## 10) Operational Security Requirements
388
+
389
+ - CSP hardening and dependency pinning for web/TMA.
390
+ - Secrets scanning and signed CI artifacts.
391
+ - Audit trail for sensitive actions.
392
+ - Incident playbook for account takeover and suspicious activity.
393
+ - User alerts (new login, passphrase changed, provider linked/unlinked).
394
+ - User alerts (new OTP login, passphrase changed, provider linked/unlinked).
395
+
396
+ ---
397
+
398
+ ## 11) Migration Plan (from current model)
399
+
400
+ 1. Keep existing TMA SecureStorage/DeviceStorage path active.
401
+ 2. Add optional centralized envelope creation for users.
402
+ 3. Backfill on next successful unlock/sign event.
403
+ 4. After adoption, use centralized envelope as cross-device recovery path.
404
+ 5. Maintain mnemonic-first emergency recovery.
405
+
406
+ ---
407
+
408
+ ## 12) Scope, Non-goals, and Warnings
409
+
410
+ Non-goals:
411
+ - Backend plaintext key custody
412
+ - Signing in backend with user mnemonic/private key
413
+
414
+ Warnings:
415
+ - If user forgets both mnemonic and passphrase, recovery is impossible by design.
416
+ - Centralized ciphertext still increases account-takeover pressure; defense must focus on OTP hardening, rate limits, and alerts.
417
+
418
+ ---
419
+
420
+ ## 13) Phase Breakdown
421
+
422
+ ## Phase A: Identity foundation
423
+ - Configure Supabase Auth providers.
424
+ - Add account linking UX.
425
+ - Add RLS and identity tables.
426
+
427
+ ## Phase B: Envelope crypto
428
+ - Implement client Argon2id + AEAD module.
429
+ - Add `wallet_envelopes` APIs and tests.
430
+
431
+ ## Phase C: Recovery UX
432
+ - New device unlock flow.
433
+ - Passphrase reset/re-encrypt flow.
434
+
435
+ ## Phase D: Hardening
436
+ - Abuse controls, telemetry, alerts, and audits.
437
+
438
+ ## Phase E: Rollout
439
+ - Feature flags, gradual rollout, and migration metrics.
440
+