sui.ski 0.1.0

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 (73) hide show
  1. package/AGENTS.md +311 -0
  2. package/CLAUDE.md +292 -0
  3. package/CODEBASE_GUIDE.md +217 -0
  4. package/README.md +77 -0
  5. package/biome.json +28 -0
  6. package/package.json +73 -0
  7. package/scripts/deploy-messaging-mainnet.sh +184 -0
  8. package/scripts/extract-suins-object.ts +180 -0
  9. package/scripts/full-deploy.sh +26 -0
  10. package/scripts/obsidian.ts +243 -0
  11. package/scripts/set-suins-contenthash.ts +130 -0
  12. package/scripts/setup-ika-dwallet.ts +338 -0
  13. package/scripts/transfer-upgrade-cap-from-nft.ts +86 -0
  14. package/src/durable-objects/wallet-session.ts +333 -0
  15. package/src/handlers/app.ts +1430 -0
  16. package/src/handlers/authenticated-events.ts +267 -0
  17. package/src/handlers/dashboard.ts +1659 -0
  18. package/src/handlers/landing.ts +6751 -0
  19. package/src/handlers/mcp.ts +556 -0
  20. package/src/handlers/messaging-sdk.ts +220 -0
  21. package/src/handlers/profile.css.ts +9332 -0
  22. package/src/handlers/profile.ts +12640 -0
  23. package/src/handlers/register2.ts +2811 -0
  24. package/src/handlers/ski-sign.ts +1901 -0
  25. package/src/handlers/ski.ts +314 -0
  26. package/src/handlers/thunder.ts +940 -0
  27. package/src/handlers/vault.ts +284 -0
  28. package/src/handlers/wallet-api.ts +169 -0
  29. package/src/handlers/x402-register.ts +601 -0
  30. package/src/index.test.ts +55 -0
  31. package/src/index.ts +512 -0
  32. package/src/resolvers/content.ts +231 -0
  33. package/src/resolvers/rpc.ts +222 -0
  34. package/src/resolvers/suins.ts +266 -0
  35. package/src/sdk/messaging.ts +279 -0
  36. package/src/types.ts +230 -0
  37. package/src/utils/agent-keypair.ts +40 -0
  38. package/src/utils/authenticated-events.ts +280 -0
  39. package/src/utils/cache.ts +82 -0
  40. package/src/utils/media-pack.ts +27 -0
  41. package/src/utils/mmr.ts +181 -0
  42. package/src/utils/ns-price.ts +529 -0
  43. package/src/utils/og-image.ts +141 -0
  44. package/src/utils/onchain-activity.ts +211 -0
  45. package/src/utils/onchain-listing.ts +39 -0
  46. package/src/utils/premium.ts +29 -0
  47. package/src/utils/pricing.ts +291 -0
  48. package/src/utils/pyth-price-info.ts +63 -0
  49. package/src/utils/response.ts +204 -0
  50. package/src/utils/rpc.ts +25 -0
  51. package/src/utils/shared-wallet-js.ts +166 -0
  52. package/src/utils/social.ts +152 -0
  53. package/src/utils/status.ts +39 -0
  54. package/src/utils/subdomain.ts +116 -0
  55. package/src/utils/surflux-grpc.ts +241 -0
  56. package/src/utils/swap-transactions.ts +1222 -0
  57. package/src/utils/thunder-css.ts +1341 -0
  58. package/src/utils/thunder-js.ts +5046 -0
  59. package/src/utils/transactions.ts +65 -0
  60. package/src/utils/vault.ts +18 -0
  61. package/src/utils/wallet-kit-js.ts +2312 -0
  62. package/src/utils/wallet-session-js.ts +192 -0
  63. package/src/utils/wallet-tx-js.ts +2287 -0
  64. package/src/utils/wallet-ui-js.ts +3057 -0
  65. package/src/utils/x402-middleware.ts +428 -0
  66. package/src/utils/x402-sui.ts +171 -0
  67. package/src/utils/zksend-js.ts +166 -0
  68. package/tsconfig.json +22 -0
  69. package/workers/x402-multichain/src/index.ts +237 -0
  70. package/workers/x402-multichain/src/types.ts +80 -0
  71. package/workers/x402-multichain/tsconfig.json +20 -0
  72. package/workers/x402-multichain/wrangler.toml +11 -0
  73. package/wrangler.toml +84 -0
package/AGENTS.md ADDED
@@ -0,0 +1,311 @@
1
+ # Agent Guidelines for Sui-ski Gateway
2
+
3
+ ## Thunder-First Development
4
+
5
+ Every feature touches Thunder. The `#primary` channel is the source of truth for a user's on-chain activity through the gateway. If you're building a new action (registration, swap, transfer, etc.), it MUST emit a Thunder message.
6
+
7
+ **Core principle:** The WebMCP format ensures all messages are machine-readable. AI agents can parse the `#primary` channel to reconstruct a user's complete activity history.
8
+
9
+ **When building any PTB-producing feature:**
10
+ 1. Define the `ThunderAction` for the action (tool name, input fields)
11
+ 2. Call `appendThunderMessage()` as the last step in PTB construction
12
+ 3. Account for ~0.001 SUI gas overhead from the message call
13
+ 4. Handle the no-channel case (first action triggers channel creation instead)
14
+
15
+ ---
16
+
17
+ ## Core Architecture
18
+
19
+ ### Subdomain Routing (`src/utils/subdomain.ts`)
20
+
21
+ | Pattern | Route | Handler |
22
+ | ------- | ----- | ------- |
23
+ | `sui.ski` | root | Landing page, API routes |
24
+ | `my.sui.ski` | dashboard | User's names management |
25
+ | `rpc.sui.ski` | rpc | Read-only JSON-RPC proxy |
26
+ | `{name}.sui.ski` | suins | SuiNS profile + Thunder feed |
27
+ | `{pkg}--{name}.sui.ski` | mvr | MVR package resolution |
28
+ | `ipfs-{cid}.sui.ski` | content | IPFS gateway |
29
+ | `walrus-{blobId}.sui.ski` | content | Walrus aggregator |
30
+ | `app.sui.ski` | app | Messaging/chat application |
31
+ | `.t.` prefix | testnet | Override to testnet |
32
+ | `.d.` prefix | devnet | Override to devnet |
33
+
34
+ ---
35
+
36
+ ## Wallet Bridge Policy
37
+
38
+ All wallet interaction on any `*.sui.ski` subdomain MUST be routed through `https://sui.ski/sign` using an invisible iframe bridge.
39
+
40
+ - Subdomains may perform local wallet discovery only to collect wallet name/icon hints.
41
+ - Subdomains MUST forward those hints to the bridge (`postMessage`) and let the bridge own connect/sign/disconnect.
42
+ - Subdomains MUST NOT directly call extension connect/sign APIs for trad wallets.
43
+ - New wallet flows must keep the bridge path as the only signing authority outside `sui.ski`.
44
+ - **Solution: If a wallet is unavailable in iframe context, use a same-tab top-frame handoff to `https://sui.ski/sign` for connect/sign and then return to the subdomain. Do not use popup fallback.**
45
+
46
+ ---
47
+
48
+ ## Thunder Integration Points
49
+
50
+ | File | Thunder Role |
51
+ | ---- | ------------ |
52
+ | `src/handlers/ski-sign.ts` | Appends Thunder message to every PTB before signing |
53
+ | `src/handlers/ski.ts` | Triggers `#primary` channel creation on first .SKI |
54
+ | `src/durable-objects/wallet-session.ts` | Stores `channelId` + `memberCapId` in session |
55
+ | `src/handlers/thunder.ts` | Reads `#primary` channel for activity display |
56
+ | `src/handlers/profile.ts` | Renders Thunder activity feed on profile pages |
57
+ | `src/utils/thunder.ts` | **(NEW)** Thunder message builder + PTB composer |
58
+ | `contracts/storm/sources/registry.move` | Storm on-chain canonical channel registry (`SuiNS NFT -> channel`) |
59
+ | `src/handlers/app.ts` | Exposes Storm config at `/api/app/subscriptions/config` |
60
+ | `src/utils/thunder-js.ts` | Reads/writes Storm mapping; enforces primary channel identity |
61
+
62
+ ---
63
+
64
+ ## PTB Composition Checklist
65
+
66
+ When adding or modifying any PTB that goes through `ski-sign.ts`:
67
+
68
+ - [ ] Define the `ThunderAction` with correct `tool` name (`sui:` prefix) and `input` fields
69
+ - [ ] Serialize the action to the WebMCP envelope format (`v: 1`, `method: 'tools/call'`)
70
+ - [ ] Encrypt the serialized bytes with the channel's DEK via Seal
71
+ - [ ] Append `channel::send_message()` as the **last** MoveCall in the PTB
72
+ - [ ] Verify the PTB handles the no-channel edge case (first .SKI creates channel instead)
73
+ - [ ] Test that gas budget includes the message overhead (~0.001 SUI)
74
+ - [ ] Confirm atomicity: action + log both succeed or both revert
75
+
76
+ ---
77
+
78
+ ## WebMCP Message Catalog
79
+
80
+ All known Thunder action types:
81
+
82
+ | Tool | Trigger | Input Fields |
83
+ | ---- | ------- | ------------ |
84
+ | `sui:ski` | First key-in | `name`, `address` |
85
+ | `sui:register` | SuiNS registration | `name`, `years`, `paymentMethod` |
86
+ | `sui:transfer` | Object transfer | `objectId`, `recipient` |
87
+ | `sui:swap` | DeepBook swap | `fromCoin`, `toCoin`, `amount` |
88
+ | `sui:stake` | Validator staking | `validator`, `amount` |
89
+ | `sui:message` | Direct message send | `channel`, `recipient` |
90
+
91
+ When adding a new action type: add a row here, implement the `ThunderAction` in `src/utils/thunder.ts`, and wire it into the relevant handler's PTB construction.
92
+
93
+ ---
94
+
95
+ ## Deployment
96
+
97
+ **CRITICAL: Use `npx wrangler deploy` after every change.**
98
+
99
+ Do not use `bun run deploy` (fails with auth token errors). Do not consider work complete until deployment succeeds.
100
+
101
+ ```bash
102
+ npx wrangler deploy # Deploy (MANDATORY after changes)
103
+ bun run dev # Local dev server
104
+ bun test # Run tests
105
+ bun run typecheck # Type checking
106
+ bun run lint # Biome linter
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Code Style
112
+
113
+ Full standards in `CLAUDE.md`. Key points:
114
+
115
+ - **No comments** — self-documenting code
116
+ - **No dead code** — git has history
117
+ - **Fail fast** — `invariant(condition, actionable message)`
118
+ - **Parallel async** — `Promise.all` for independent operations
119
+ - **Single-pass** — avoid chained array methods
120
+ - Naming: constants `UPPER_SNAKE`, functions `camelCase`, types `PascalCase`, files `kebab-case`
121
+ - Formatting: tabs, single quotes, no semicolons
122
+
123
+ ---
124
+
125
+ ## Key Files
126
+
127
+ ### Thunder Path (action flow)
128
+
129
+ | File | Purpose |
130
+ | ---- | ------- |
131
+ | `src/handlers/ski.ts` | .SKI key-in entry point, wallet connection |
132
+ | `src/handlers/ski-sign.ts` | PTB signing bridge, Thunder message injection |
133
+ | `src/handlers/thunder.ts` | Thunder API, MCP bridge, channel/message endpoints |
134
+ | `src/utils/thunder-js.ts` | Thunder chat runtime UI + SDK transaction orchestration |
135
+ | `src/utils/thunder-css.ts` | Thunder UI styling |
136
+ | `src/durable-objects/wallet-session.ts` | Session state: address, channel/member caps, auth |
137
+
138
+ ### Transaction Building
139
+
140
+ | File | Purpose |
141
+ | ---- | ------- |
142
+ | `src/utils/swap-transactions.ts` | DeepBook swap + SuiNS registration PTBs |
143
+ | `src/utils/transactions.ts` | Shared transaction helpers |
144
+ | `src/utils/ns-price.ts` | NS token pricing via DeepBook pools |
145
+ | `src/utils/pricing.ts` | SuiNS pricing + renewal calculations |
146
+ | `src/utils/grace-vault-transactions.ts` | Grace Vault transaction assembly |
147
+
148
+ ### Messaging + App Surfaces
149
+
150
+ | File | Purpose |
151
+ | ---- | ------- |
152
+ | `src/handlers/app.ts` | `app.sui.ski` shell + `/api/app/*` config/messaging helpers |
153
+ | `src/handlers/profile.ts` | Profile page rendering + Thunder embed |
154
+ | `src/handlers/messaging-sdk.ts` | Messaging SDK status/config API |
155
+ | `src/sdk/messaging.ts` | Shared SDK constants/config/version pins |
156
+
157
+ ### Resolution Layer
158
+
159
+ | File | Purpose |
160
+ | ---- | ------- |
161
+ | `src/resolvers/suins.ts` | SuiNS resolution |
162
+ | `src/resolvers/content.ts` | IPFS/Walrus content resolution |
163
+ | `src/resolvers/rpc.ts` | Read-only RPC proxy |
164
+
165
+ ---
166
+
167
+ ## Current Progress Log (2026-02-17)
168
+
169
+ ### Thunder stability and UX
170
+
171
+ - Done (2026-02-18): Enforced bridge-only wallet interaction on subdomains (`sui.ski/sign` iframe) and added local wallet hint forwarding from subdomains to bridge discovery/connect/sign paths.
172
+ - Done: Robust Sui + messaging SDK module resolution in browser init path.
173
+ - Done: Fixed package-ID mismatch to mainnet messaging package `0xbcdf...39f9` in live runtime/config paths.
174
+ - Done: Added signer result hydration to recover missing transaction effects/object changes.
175
+ - Done: Removed channel-label collapsing that mixed independent channels under one label.
176
+ - Done: Added local cache reset control for channel meta/dismissed keys.
177
+
178
+ ### Hardening completed
179
+
180
+ - Done: Duplicate-channel prevention at source.
181
+ - Added on-chain membership guard before any bootstrap/create flow, so existing membership blocks new channel creation.
182
+ - Removed “dismiss on failed burn” behavior to prevent hidden channels from spawning replacement channels.
183
+ - Shifted duplicate channels into explicit “extras” management UI, with on-chain cleanup action.
184
+ - Done: Reduced first-message signing path.
185
+ - Added bootstrap path using `createChannelFlow()` + append `sendMessage()` to key-attach PTB, reducing first send from 3 signatures to 2 when SDK builders are available.
186
+ - Done: Channel-key self-heal for broken legacy channels.
187
+ - Added on-chain `add_encrypted_key` repair flow as an explicit user action (`Repair key`) when signer has required member capability.
188
+ - Added send fallback routing to next public channel with a valid encrypted key when active channel is unrecoverable.
189
+ - Added member-cap permission selection logic so repair uses a cap with `EditEncryptionKey` instead of arbitrary owned caps.
190
+ - Disabled automatic repair during send to keep one send-click to one transaction attempt.
191
+ - Done: Strict send path (no implicit fallback/switching).
192
+ - Removed automatic channel switching during send.
193
+ - Removed “best sendable channel” fallback logic.
194
+ - Added explicit burn wording and scope for non-primary channels only.
195
+
196
+ ### Storm registry rollout
197
+
198
+ - Done: Added `contracts/storm` Move package with shared `Registry` dynamic fields keyed by SuiNS NFT object ID.
199
+ - `set_channel_for_nft<T: key>(registry, nft, channel_id)`
200
+ - `clear_channel_for_nft<T: key>(registry, nft)`
201
+ - Done: Added worker env wiring for Storm (`STORM_PACKAGE_ID`, `STORM_REGISTRY_ID`) and included Storm config in `/api/app/subscriptions/config`.
202
+ - Done: Thunder runtime now resolves canonical channel from Storm mapping first and pins primary channel identity to it.
203
+ - Done: Added channel-gear action `Set primary` to update Storm mapping explicitly on-chain.
204
+ - Done: Bootstrap create+attach+send PTB now appends Storm mapping write when configured, so channel creation + first message + canonical registry update are bundled into one signed finalize PTB.
205
+ - Done: Extra-channel burn paths attempt Storm mapping clear when burned channel matches current mapped primary.
206
+
207
+ ### Cleanup policy
208
+
209
+ - Rule: “Remove” means on-chain delete (`member_cap::transfer_to_recipient`) whenever capability requirements are met.
210
+ - Rule: If deletion caps are missing, do not silently hide; return explicit undeletable state and recovery guidance.
211
+
212
+ ---
213
+
214
+ ## Full App Surface Map
215
+
216
+ ### Entry + routing
217
+
218
+ | File | Surface |
219
+ | ---- | ------- |
220
+ | `src/index.ts` | Global routing, host/subdomain dispatch, API wiring, media/OG routes |
221
+ | `src/types.ts` | Worker env/type contracts |
222
+ | `src/utils/subdomain.ts` | Host parsing + network override logic |
223
+
224
+ ### Durable object
225
+
226
+ | File | Surface |
227
+ | ---- | ------- |
228
+ | `src/durable-objects/wallet-session.ts` | Wallet session lifecycle, wallet linkage, messaging caps/session state |
229
+
230
+ ### Handlers
231
+
232
+ | File | Surface |
233
+ | ---- | ------- |
234
+ | `src/handlers/landing.ts` | Root landing page + shared API routes |
235
+ | `src/handlers/profile.ts` | SuiNS profile pages + embedded Thunder |
236
+ | `src/handlers/app.ts` | App shell + API namespaces (`/api/app`, `/api/agents`, `/api/ika`, `/api/llm`) |
237
+ | `src/handlers/thunder.ts` | Thunder API + MCP proxy + x402 hints |
238
+ | `src/handlers/ski.ts` | `.SKI` action UI |
239
+ | `src/handlers/ski-sign.ts` | Wallet sign bridge and PTB payload signing |
240
+ | `src/handlers/dashboard.ts` | `my.sui.ski` dashboard |
241
+ | `src/handlers/vault.ts` | Vault routes |
242
+ | `src/handlers/grace-vault-agent.ts` | Grace Vault agent routes |
243
+ | `src/handlers/x402-register.ts` | x402 register agent routes |
244
+ | `src/handlers/mcp.ts` | Sui MCP server wiring |
245
+ | `src/handlers/messaging-sdk.ts` | Messaging SDK informational endpoints |
246
+ | `src/handlers/authenticated-events.ts` | Authenticated events ingestion |
247
+ | `src/handlers/register2.ts` | SuiNS register tx build/submit |
248
+ | `src/handlers/wallet-api.ts` | Wallet challenge/connect/check/disconnect |
249
+
250
+ ### Resolvers
251
+
252
+ | File | Surface |
253
+ | ---- | ------- |
254
+ | `src/resolvers/suins.ts` | Name lookup / owner/address resolution |
255
+ | `src/resolvers/content.ts` | IPFS/Walrus direct blob routing |
256
+ | `src/resolvers/rpc.ts` | RPC passthrough with env controls |
257
+
258
+ ### SDK + protocol utilities
259
+
260
+ | File | Surface |
261
+ | ---- | ------- |
262
+ | `src/sdk/messaging.ts` | Messaging SDK pinning/config/bootstrap URLs |
263
+ | `src/utils/x402-middleware.ts` | x402 request middleware |
264
+ | `src/utils/x402-sui.ts` | x402 Sui settlement helpers |
265
+ | `src/utils/vault.ts` | Vault utility operations |
266
+ | `src/utils/agent-keypair.ts` | Agent keypair derivation/helpers |
267
+
268
+ ### Wallet + client runtime
269
+
270
+ | File | Surface |
271
+ | ---- | ------- |
272
+ | `src/utils/wallet-kit-js.ts` | Wallet integration bootstrap |
273
+ | `src/utils/wallet-session-js.ts` | Session sync in browser |
274
+ | `src/utils/wallet-ui-js.ts` | Wallet modal/UI behavior |
275
+ | `src/utils/wallet-tx-js.ts` | Browser tx helper layer |
276
+ | `src/utils/shared-wallet-js.ts` | Shared wallet mount script |
277
+ | `src/utils/thunder-js.ts` | Thunder interaction + messaging SDK write/read loop |
278
+ | `src/utils/thunder-css.ts` | Thunder presentation layer |
279
+ | `src/utils/zksend-js.ts` | zk-send utilities |
280
+
281
+ ### Data, pricing, status, and cache
282
+
283
+ | File | Surface |
284
+ | ---- | ------- |
285
+ | `src/utils/cache.ts` | KV/cache helpers |
286
+ | `src/utils/status.ts` | Gateway status assembly |
287
+ | `src/utils/rpc.ts` | RPC URL/env helpers |
288
+ | `src/utils/response.ts` | response helpers |
289
+ | `src/utils/pricing.ts` | registration/renewal pricing |
290
+ | `src/utils/ns-price.ts` | NS market pricing |
291
+ | `src/utils/pyth-price-info.ts` | external price feeds |
292
+ | `src/utils/mmr.ts` | misc on-chain helpers |
293
+
294
+ ### On-chain contracts (workspace)
295
+
296
+ | File | Surface |
297
+ | ---- | ------- |
298
+ | `contracts/storm/sources/registry.move` | Storm canonical channel registry for Thunder |
299
+ | `contracts/seal_messaging/sources/access.move` | Seal messaging/access control package |
300
+ | `contracts/mvr/sources/registry.move` | Move package registry |
301
+
302
+ ### Media, social, and activity
303
+
304
+ | File | Surface |
305
+ | ---- | ------- |
306
+ | `src/utils/media-pack.ts` | generated static media assets |
307
+ | `src/utils/og-image.ts` | SVG/PNG OG rendering |
308
+ | `src/utils/social.ts` | social metadata + bot detection |
309
+ | `src/utils/onchain-activity.ts` | profile on-chain activity feeds |
310
+ | `src/utils/onchain-listing.ts` | on-chain listing fetch path |
311
+ | `src/utils/surflux-grpc.ts` | gRPC data integration helpers |
package/CLAUDE.md ADDED
@@ -0,0 +1,292 @@
1
+ # CLAUDE.md
2
+
3
+ ## The Garbage Goobler
4
+
5
+ You are a garbage goobler. You love to eat dead code and squash bugs. Your preference is to remove unused code, but you are incredibly cautious because if you eat functionality or code that is actually used, YOU DIE.
6
+
7
+ **Garbage Goobler Rules:**
8
+ 1. Always verify code is unused before removing (check imports, dynamic imports, type usage)
9
+ 2. When in doubt, leave it - death awaits the careless goobler
10
+ 3. Run `npx ts-prune --project tsconfig.json` to find potential dead exports
11
+ 4. Check for dynamic imports with `grep -r "import\(" src/`
12
+ 5. Type-only exports are safe to remove if the type is never imported
13
+ 6. After removing code, run `npx wrangler deploy` to verify bundle still works
14
+
15
+ ---
16
+
17
+ ## What is Sui-ski
18
+
19
+ Cloudflare Worker gateway for the Sui blockchain. Resolves wildcard subdomains (`*.sui.ski`) to SuiNS names, MVR packages, IPFS/Walrus content, and a read-only RPC proxy. Thunder is the activity spine — every on-chain action is journaled to the user's `#primary` channel.
20
+
21
+ ---
22
+
23
+ ## Thunder
24
+
25
+ Thunder is the structured activity journal for every `.SKI` user. It turns the Sui Stack Messaging SDK into an atomic, encrypted, machine-readable log of every on-chain action a user takes through the gateway.
26
+
27
+ ### #primary Channel
28
+
29
+ Every SuiNS name gets exactly one `#primary` channel — an on-chain Sui Stack Messaging channel (shared object) that serves as the user's activity journal.
30
+
31
+ | Property | Value |
32
+ | -------- | ----- |
33
+ | Ownership | Owner holds `CreatorCap` + `MemberCap` with all permissions |
34
+ | Encryption | Seal-encrypted; only owner can read by default |
35
+ | Write access | Owner-only (journal mode — no replies, no conversation) |
36
+ | Read access | Invited members can read but not write |
37
+ | Storage | Channel ID + MemberCap ID stored in wallet session (KV + cookie) |
38
+ | Package | Sui Stack Messaging mainnet: `0x74e34e2e4a2ba60d935db245c0ed93070bbbe23bf1558ae5c6a2a8590c8ad470` |
39
+
40
+ The `#primary` channel is **write-only by owner**. It is a structured, machine-parseable activity log. AI agents can read it to understand user activity. Invited members observe but never write.
41
+
42
+ ### .SKI → Thunder Bootstrap
43
+
44
+ First `.SKI` key-in triggers `#primary` channel creation:
45
+
46
+ 1. Connect wallet → sign challenge → verify signature
47
+ 2. Check for existing `#primary` channel in session
48
+ 3. If none exists, build creation PTB:
49
+ - `channel::new()` → `channel::share()` → transfer caps → attach encryption key → send genesis message
50
+ 4. Requires 2 transactions: create channel + attach Seal encryption key
51
+ 5. Subsequent `.SKI` sessions skip creation, load existing `channelId`/`memberCapId` from session
52
+
53
+ ### Action Messages (WebMCP Format)
54
+
55
+ Every Thunder message uses the MCP `tools/call` envelope with `sui:` namespace:
56
+
57
+ ```typescript
58
+ interface ThunderAction {
59
+ v: 1
60
+ method: 'tools/call'
61
+ tool: string // "sui:register", "sui:transfer", "sui:swap", etc.
62
+ input: Record<string, unknown>
63
+ origin: string // subdomain that initiated (e.g., "alice.sui.ski")
64
+ }
65
+ ```
66
+
67
+ The message is serialized to bytes, encrypted with the channel's DEK, and passed to `channel::send_message()` as ciphertext within the same PTB as the action.
68
+
69
+ **Message Catalog:**
70
+
71
+ | Tool | Trigger | Key Input Fields |
72
+ | ---- | ------- | ---------------- |
73
+ | `sui:register` | SuiNS registration | name, years, paymentMethod |
74
+ | `sui:transfer` | Object transfer | objectId, recipient |
75
+ | `sui:swap` | DeepBook swap | fromCoin, toCoin, amount |
76
+ | `sui:stake` | Validator staking | validator, amount |
77
+ | `sui:ski` | First key-in | name, address |
78
+ | `sui:message` | Direct message send | channel, recipient |
79
+
80
+ ### PTB Composition Rules
81
+
82
+ 1. Every PTB built by `ski-sign.ts` MUST include a Thunder `send_message` call
83
+ 2. The Thunder message is appended as the **last** MoveCall in the PTB
84
+ 3. If the user has no `#primary` channel yet (first action), the PTB creates the channel instead
85
+ 4. Gas budget accounts for the extra message call (~0.001 SUI overhead)
86
+ 5. Action and log are **atomic** — both succeed or both fail
87
+
88
+ The composer function signature (to be implemented in `src/utils/thunder.ts`):
89
+
90
+ ```typescript
91
+ appendThunderMessage(tx: Transaction, channelId: string, memberCapId: string, action: ThunderAction): void
92
+ ```
93
+
94
+ ---
95
+
96
+ ## **IMPORTANT: Wallet Session Architecture — Cookies, NOT Bridges**
97
+
98
+ **The same Cloudflare Worker serves `sui.ski` AND `*.sui.ski`. Session state flows through `.sui.ski` domain cookies — NOT iframes, NOT popups, NOT postMessage bridges.**
99
+
100
+ ### How It Works
101
+
102
+ 1. **Connect** — User connects wallet on any page → `POST /api/wallet/connect` with signed challenge → server verifies signature via `verifyPersonalMessageSignature` → creates session in `WalletSession` Durable Object
103
+ 2. **Cookies** — Server sets three cookies on **`.sui.ski` domain** (available to ALL subdomains):
104
+ - `session_id` — DO session key
105
+ - `wallet_address` — `0x...` address
106
+ - `wallet_name` — e.g. "Slush"
107
+ 3. **Subdomain restore** — Browser sends `.sui.ski` cookies automatically → Worker middleware reads them → calls `DO.getSessionInfo(sessionId)` → injects verified session into rendered HTML
108
+ 4. **Client hydration** — Page JS calls `SuiWalletKit.initFromSession(address, walletName)` with server-injected data → wallet UI shows connected state immediately
109
+
110
+ ### Rules
111
+
112
+ - **Subdomain wallet connect goes through the hidden iframe bridge (`sui.ski/sign`) so the wallet dialog shows "From sui.ski".** The iframe is invisible — no popups, no visible windows.
113
+ - **NEVER use popups (`window.open`) for wallet connect.** Popups get blocked after async delays (user gesture expires) and are bad UX.
114
+ - **The hidden iframe bridge handles BOTH connect AND transaction signing on subdomains.** The bridge loads `sui.ski/sign` in a 1px offscreen iframe, relays messages via `postMessage`.
115
+ - **On root domain (`sui.ski`), connect goes directly to the extension** — no bridge needed.
116
+ - Session source of truth is the `WalletSession` Durable Object, not browser cookies (cookies are broadcast cache).
117
+ - Sessions expire after 30 days, auto-extend on activity.
118
+
119
+ ### Session Files
120
+
121
+ | File | Role |
122
+ |------|------|
123
+ | `src/durable-objects/wallet-session.ts` | Backend DO — session CRUD, challenge creation, verification |
124
+ | `src/handlers/wallet-api.ts` | API routes — `/api/wallet/challenge`, `/connect`, `/disconnect` |
125
+ | `src/utils/wallet-session-js.ts` | Client-side JS — cookie reading, `challengeAndConnect()`, `disconnectWalletSession()` |
126
+ | `src/utils/wallet-kit-js.ts` | Wallet extension connect — `SuiWalletKit.connect()`, `initFromSession()` |
127
+ | `src/index.ts` | Middleware — reads cookies, calls DO, injects session into handler context |
128
+
129
+ ---
130
+
131
+ ## Directory Structure
132
+
133
+ ```
134
+ src/
135
+ ├── index.ts # Worker entry, Hono router
136
+ ├── types.ts # Shared types (Env, SuiNSRecord, etc.)
137
+ ├── handlers/
138
+ │ ├── landing.ts # Root domain, /api/* routes
139
+ │ ├── profile.ts # SuiNS profile pages + Thunder activity feed
140
+ │ ├── app.ts # Messaging/chat WebSocket + REST APIs
141
+ │ ├── ski.ts, ski-sign.ts # .SKI key-in + Thunder bootstrap
142
+ │ ├── thunder.ts # Thunder API routes (reads #primary)
143
+ │ ├── dashboard.ts # my.sui.ski names management
144
+ │ └── mcp.ts # MCP server for AI tools
145
+ ├── resolvers/ # SuiNS, MVR, IPFS/Walrus, RPC proxy
146
+ ├── utils/
147
+ │ ├── thunder.ts # (NEW) Thunder message builder + PTB composer
148
+ │ ├── swap-transactions.ts # DeepBook swap + registration PTBs
149
+ │ ├── ns-price.ts # NS token pricing via DeepBook
150
+ │ ├── pricing.ts # SuiNS registration pricing
151
+ │ └── wallet-*.ts # Wallet connection UI/JS
152
+ ├── durable-objects/
153
+ │ └── wallet-session.ts # WalletSession DO (stores channelId, memberCapId)
154
+ └── client/
155
+ └── wallet-session.ts # Client-side session helper
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Deployment
161
+
162
+ **Use `npx wrangler deploy` NOT `bun run deploy`.** The `bun run deploy` command fails with auth token errors.
163
+
164
+ **Deploy after every change.** Run `npx wrangler deploy` after each code change so the user can test live immediately.
165
+
166
+ ```bash
167
+ npx wrangler deploy # Deploy (MANDATORY after changes)
168
+ bun run dev # Local dev server
169
+ bun test # Run tests
170
+ bun run typecheck # Type checking
171
+ bun run lint # Biome linter
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Code Standards
177
+
178
+ ### Rules
179
+
180
+ | Rule | Detail |
181
+ | ---- | ------ |
182
+ | No comments | Code is self-documenting through clear naming |
183
+ | No dead code | Delete unused code; git has history |
184
+ | No magic values | Named constants at module level |
185
+ | No ignored errors | Handle or propagate every error |
186
+ | Fail fast | Validate inputs at function entry with `invariant` |
187
+
188
+ ### Patterns
189
+
190
+ - **Parallel async** — `Promise.all` for independent operations, never sequential `await`
191
+ - **Single-pass** — one iteration, not chained `.map().filter().reduce()`
192
+ - **Bitwise byte conversion** — no string intermediates
193
+ - **Pre-compute constants** — computation out of hot paths
194
+ - **Uint8Array** — for all internal binary data
195
+ - **Actionable errors** — include what failed AND what's expected
196
+
197
+ ### Naming
198
+
199
+ | Type | Convention | Example |
200
+ | ---- | ---------- | ------- |
201
+ | Constants | `UPPER_SNAKE` | `MAX_RETRIES` |
202
+ | Functions | `camelCase` | `fetchUser` |
203
+ | Types | `PascalCase` | `UserProfile` |
204
+ | Variables | `camelCase` | `userName` |
205
+ | Files | `kebab-case` | `user-profile.ts` |
206
+
207
+ ### Anti-Patterns
208
+
209
+ - `as any` or `@ts-ignore` — use proper types
210
+ - `export { x }` at end of file — export inline
211
+ - `console.log` in production — use structured logging
212
+ - Catching errors without handling or re-throwing
213
+ - Mutable state in module scope
214
+
215
+ ---
216
+
217
+ ## Git Conventions
218
+
219
+ ```
220
+ emoji type(scope): subject
221
+ ```
222
+
223
+ | Emoji | Type | Use For |
224
+ | ----- | ---- | ------- |
225
+ | ✨ | feat | New feature |
226
+ | 🐛 | fix | Bug fix |
227
+ | 📝 | docs | Documentation |
228
+ | ♻️ | refactor | Code restructure |
229
+ | ⚡ | perf | Performance |
230
+ | ✅ | test | Tests |
231
+ | 📦 | build | Build/dependencies |
232
+ | 🔧 | chore | Maintenance |
233
+
234
+ Lowercase subject, no period, one logical change per commit.
235
+
236
+ ---
237
+
238
+ ## Key Constants
239
+
240
+ ### Token Decimals (Critical)
241
+
242
+ | Token | Decimals | Conversion |
243
+ | ----- | -------- | ---------- |
244
+ | NS | 6 | `nsTokens * 1e6 = nsMist` |
245
+ | SUI | 9 | `suiAmount * 1e9 = suiMist` |
246
+
247
+ When calculating SUI needed to buy NS tokens:
248
+ ```typescript
249
+ const nsTokens = Number(nsMist) / 1e6;
250
+ const suiMist = BigInt(Math.ceil(nsTokens * suiPerNs * 1e9));
251
+ ```
252
+
253
+ ### Package Addresses
254
+
255
+ | Package | Mainnet ID |
256
+ | ------- | ---------- |
257
+ | Sui Stack Messaging | `0x74e34e2e4a2ba60d935db245c0ed93070bbbe23bf1558ae5c6a2a8590c8ad470` |
258
+ | SuiNS Core V3 | `0x00c2f85e07181b90c140b15c5ce27d863f93c4d9159d2a4e7bdaeb40e286d6f5` |
259
+ | SuiNS Core Object | `0x6e0ddefc0ad98889c04bab9639e512c21766c5e6366f89e696956d9be6952871` |
260
+ | Seal (mainnet vault) | `0xfabfc63a1d67c37d9c0250bc3efeb96a3c56fb20b9a6d92e3b89dd151f54af5c` |
261
+ | Seal Core (mainnet) | `0xcb83a248bda5f7a0a431e6bf9e96d184e604130ec5218696e3f1211113b447b7` |
262
+ | Decay Auction | `0x10dbff33383bdb68d0bbf6aadeed5e2d3911c45b03664130ebb16437954a2f40` |
263
+
264
+ ### Subdomain Routing
265
+
266
+ | Pattern | Route |
267
+ | ------- | ----- |
268
+ | `sui.ski` | Landing page |
269
+ | `rpc.sui.ski` | Read-only RPC proxy |
270
+ | `{name}.sui.ski` | SuiNS profile + Thunder feed |
271
+ | `{pkg}--{name}.sui.ski` | MVR package |
272
+ | `ipfs-{cid}.sui.ski` | IPFS content |
273
+ | `walrus-{blobId}.sui.ski` | Walrus blob |
274
+ | `my.sui.ski` | Names dashboard |
275
+ | `app.sui.ski` | Messaging app |
276
+
277
+ ---
278
+
279
+ ## SDK References
280
+
281
+ **Sui Stack Messaging SDK (local fork):**
282
+ `/home/brandon/Dev/Contributor/sui-stack-messaging-sdk` — built at `packages/messaging/dist/`. Client-side only; server serves config, all operations go direct to Sui RPC + Walrus.
283
+
284
+ **CDN manifest:** `https://cdn.jsdelivr.net/gh/arbuthnot-eth/sui-stack-messaging-sdk@mainnet-messaging-v2-2026-02-16/cdn/messaging-mainnet.json`
285
+
286
+ **Sui Client (v2.x):** `CoreClient` is abstract — use `SuiGraphQLClient` from `@mysten/sui/graphql` (preferred) or `SuiJsonRpcClient` from `@mysten/sui/jsonRpc`. JSON-RPC deprecated April 2026.
287
+
288
+ **Seal:** Every app deploys its own `seal_approve` Move contract. See `docs/SEAL_UPGRADE_GUIDE.md`.
289
+
290
+ **SuiNS:** Resolution via gRPC-Web (primary) + JSON-RPC fallback. See [docs.suins.io/developer](https://docs.suins.io/developer).
291
+
292
+ **MVR:** See `docs/MVR_IMPROVEMENTS.md`. SDK plugin: `namedPackagesPlugin` from `@mysten/sui/transactions`.