Monorepo for Aesthetic.Computer
aesthetic.computer
1# Keeps Wallet — Beacon Protocol Implementation Plan
2
3**Goal:** Make Keeps Wallet a drop-in replacement for Temple Wallet by implementing the Beacon SDK postMessage protocol. Any dApp using Beacon (keep.kidlisp.com, objkt.com, etc.) will see Keeps Wallet as a connectable wallet — no custom integration needed.
4
5---
6
7## 1. How Beacon Extension Communication Works
8
9The Beacon SDK (used by dApps) discovers and communicates with browser extension wallets via `window.postMessage`. The protocol has three phases:
10
11### Discovery (Ping/Pong)
12```
13dApp → window.postMessage({ target: "toExtension", payload: "ping" })
14extension → window.postMessage({ target: "toPage", payload: "pong", sender: { id, name, iconURL } })
15```
16The dApp's `DappPostMessageTransport` sends a `ping` and collects `pong` responses to build a list of available wallets.
17
18### Pairing (Key Exchange)
19```
20dApp → PostMessagePairingRequest { id, name, publicKey, version, icon?, appUrl? }
21extension → PostMessagePairingResponse { id, name, publicKey, version, type: "postmessage-pairing-response", extensionId }
22```
23Both sides exchange Ed25519 public keys. All subsequent messages are encrypted using `crypto_box` (libsodium shared secret derived from both keypairs).
24
25### Encrypted Message Exchange
26```
27dApp → { target: "toExtension", encryptedPayload: hex(nonce + ciphertext), targetId: extensionId }
28extension → { target: "toPage", encryptedPayload: hex(nonce + ciphertext) }
29```
30Payloads are Beacon protocol messages (JSON) encrypted with the shared secret.
31
32### Message Types (ExtensionMessageTarget)
33```typescript
34enum ExtensionMessageTarget {
35 BACKGROUND = 'toBackground',
36 PAGE = 'toPage',
37 EXTENSION = 'toExtension'
38}
39
40interface ExtensionMessage<T> {
41 target: ExtensionMessageTarget
42 targetId?: string // extension ID for routing
43 sender?: { id, name, iconURL }
44 payload: T // 'ping' | 'pong' | encrypted payload string
45}
46```
47
48### Beacon Message Types (inside encrypted payloads)
49```
50PermissionRequest → PermissionResponse
51OperationRequest → OperationResponse
52SignPayloadRequest → SignPayloadResponse
53BroadcastRequest → (optional, can decline)
54Disconnect
55Acknowledge (sent immediately on receipt of any request)
56Error (sent when request fails)
57```
58
59---
60
61## 2. Implementation Steps
62
63### Step 1: Add libsodium for Beacon Crypto
64
65**File:** `wallet/extension/lib/beacon-crypto.mjs`
66
67Beacon uses libsodium `crypto_box` for encrypted communication between dApp and wallet. We need:
68- Ed25519 keypair generation (for wallet's Beacon identity — separate from Tezos keys)
69- `crypto_box` / `crypto_box_open` for encrypting/decrypting messages
70- Key exchange: convert Ed25519 keys to Curve25519 for `crypto_box`
71- Nonce generation (24 bytes, prepended to ciphertext)
72
73We already have libsodium in `lib/crypto.mjs` — extend or import from there.
74
75**Persistent Beacon keypair:** Store a separate Ed25519 keypair seed in `chrome.storage.local` under `beacon_keypair_seed`. This is the wallet's Beacon identity (not the Tezos signing key). Generate on first use.
76
77### Step 2: Beacon Protocol Handler in Background
78
79**File:** `wallet/extension/beacon.mjs` (new)
80
81This module handles the Beacon protocol logic:
82
83```javascript
84// Core functions needed:
85getOrCreateBeaconKeypair() // Persistent Beacon identity
86encryptMessage(msg, peerPK) // crypto_box with shared secret
87decryptMessage(payload, peerPK) // crypto_box_open
88handleBeaconRequest(msg) // Route to permission/operation/sign handlers
89buildBeaconResponse(request, data) // Construct proper response with id, version, senderId
90```
91
92**Request handlers:**
93
94| Beacon Request | What Keeps Wallet Does |
95|---|---|
96| `PermissionRequest` | Return account address + public key. Scope: `sign`, `operation_request`. Opens confirmation popup. |
97| `OperationRequest` | Forge operations via Tezos RPC, sign with wallet key, inject. Return `opHash`. Opens confirmation popup. |
98| `SignPayloadRequest` | Sign raw bytes with ed25519 key. Return `edsig...` signature. Opens confirmation popup. |
99| `BroadcastRequest` | Inject pre-signed operation to RPC node. Return `opHash`. |
100| `Disconnect` | Clear active dApp session. |
101
102**For every request:** immediately send an `Acknowledge` response (unencrypted `{ type: "acknowledge", id: request.id }`), then process and send the real response.
103
104### Step 3: Rewrite Content Script for Beacon
105
106**File:** `wallet/extension/content.js`
107
108The content script becomes the message relay between the page and background:
109
110```javascript
111// 1. Listen for messages FROM the page (dApp → extension)
112window.addEventListener('message', (event) => {
113 if (event.source !== window) return;
114 const msg = event.data;
115
116 // Beacon ping
117 if (msg?.target === 'toExtension' && msg?.payload === 'ping') {
118 // Respond with pong + wallet info
119 window.postMessage({
120 target: 'toPage',
121 payload: 'pong',
122 sender: { id: EXTENSION_ID, name: 'Keeps Wallet', iconURL: ICON_URL }
123 }, '*');
124 return;
125 }
126
127 // Beacon encrypted message or pairing request
128 if (msg?.target === 'toExtension') {
129 chrome.runtime.sendMessage({ type: 'BEACON_MESSAGE', data: msg })
130 .then(response => {
131 if (response) {
132 window.postMessage({ target: 'toPage', ...response }, '*');
133 }
134 });
135 return;
136 }
137
138 // Keep existing KEEPS_* custom protocol as fallback
139 if (msg?.type?.startsWith('KEEPS_')) { /* existing relay logic */ }
140});
141
142// 2. Listen for messages FROM background (extension → page)
143chrome.runtime.onMessage.addListener((message) => {
144 if (message.type === 'BEACON_RESPONSE') {
145 window.postMessage({ target: 'toPage', ...message.data }, '*');
146 }
147 if (message.type === 'KEEPS_LOCKED') {
148 window.postMessage({ type: 'KEEPS_LOCKED' }, '*');
149 }
150});
151```
152
153### Step 4: Update Inpage Script
154
155**File:** `wallet/extension/inpage.js`
156
157Keep the existing `window.keeps` API but also announce via Beacon's expected mechanism. The inpage script doesn't need to do much for Beacon — the content script handles the postMessage relay directly. But we should:
158
1591. Dispatch a custom event announcing wallet availability:
160```javascript
161window.dispatchEvent(new CustomEvent('keeps:ready'));
162```
163
1642. Optionally set `window.tezos` or a similar global that some dApps check (Temple sets this too).
165
166### Step 5: UI — Only Keeps, Alive
167
168**Design philosophy:** The wallet is not a finance app. It's a living collection. You open it and you see your keeps running. Everything else is invisible until needed.
169
170**Style guide:** Match `keep.kidlisp.com` — same CSS variables, fonts, and feel.
171
172```css
173/* Fonts */
174--font-mono: 'Noto Sans Mono', 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
175--font-display: 'YWFTProcessing-Regular', monospace; /* headings / logo */
176--font-fun: 'Comic Relief', 'Comic Sans MS', cursive;
177
178/* Light */
179--bg-primary: #f7f7f7;
180--bg-secondary: #e8e8e8;
181--bg-tertiary: white;
182--text-primary: #111;
183--text-secondary: #333;
184--text-tertiary: #666;
185--border-color: #ddd;
186--ac-purple: rgb(205, 92, 155);
187
188/* Dark */
189--bg-primary: #1e1e1e;
190--bg-secondary: #252526;
191--bg-tertiary: #2d2d30;
192--text-primary: #d4d4d4;
193--text-tertiary: #858585;
194--border-color: #3e3e42;
195
196/* Links in ac-purple, cursor from aesthetic.computer */
197```
198
199Auto theme (follows system `prefers-color-scheme`), with manual override via `data-theme`. Font files loaded from `https://aesthetic.computer/type/webfonts/`.
200
201**One view, three states:**
202
203**No wallet yet:**
204A single demo keep runs full-bleed as a live `<canvas>`. Two words overlaid at the bottom: "Create" / "Import". Password fields appear inline when tapped — no separate view.
205
206Import accepts seed phrases from **Temple** or **Kukai** (same BIP39 → ed25519 derivation). Your `aesthetic.tez` identity comes with you — same tz1 address, same keeps collection. The wallet shows your keeps immediately after import.
207
208**Locked:**
209Your most recent keep runs blurred behind a single password field centered on screen. Type, hit enter. No title, no logo, no chrome.
210
211**Unlocked (the only real view):**
212Your keeps in a grid. Each thumbnail is a live `<canvas>` running the KidLisp source code (~30KB evaluator). That's the whole UI. No transaction history, no settings pages.
213
214- Balance hidden behind a small `$` icon in the corner — tap to reveal, tap again to hide
215- Tap a keep → it expands to fill the panel, interactive (touch/click works)
216- Tap edge or swipe → back to grid
217- Connected dApps are invisible dots along the bottom edge (tap to disconnect)
218- Network badge (ghostnet/mainnet) is a tiny pill in the corner, tap to toggle
219
220**Confirmation overlay:**
221When a Beacon request arrives, a translucent sheet slides up over whatever keep is currently displayed:
222- dApp name
223- What it wants (connect / sign / send)
224- Approve / Reject
225- No navigation away from the collection — it's an overlay
226
227**File:** `wallet/extension/popup/popup.html` (rewrite)
228**File:** `wallet/extension/popup/popup.js` (rewrite)
229
230The popup loads the KidLisp evaluator (`kidlisp.mjs`) and creates a `<canvas>` per keep. Keeps metadata (including source `$code`) is fetched from TzKT. The evaluator runs each piece at thumbnail resolution (~100x100) for the grid, full resolution when expanded.
231
232### Step 6: Update Manifest
233
234**File:** `wallet/extension/manifest.json`
235
236```json
237{
238 "content_scripts": [{
239 "matches": [
240 "https://aesthetic.computer/*",
241 "https://*.aesthetic.computer/*",
242 "https://keep.kidlisp.com/*",
243 "https://*.kidlisp.com/*",
244 "https://objkt.com/*",
245 "http://localhost:8888/*",
246 "https://localhost:8888/*",
247 "<all_urls>"
248 ]
249 }]
250}
251```
252
253For a true Temple replacement, we need `<all_urls>` so the wallet works on any Tezos dApp. Add `"permissions": ["storage", "activeTab"]` (already present).
254
255### Step 7: dApp Session Management
256
257**File:** `wallet/extension/beacon.mjs` (extends Step 2)
258
259Track connected dApps:
260```javascript
261// Store in chrome.storage.local:
262{
263 beacon_peers: {
264 [dAppId]: {
265 name: "keep.kidlisp.com",
266 publicKey: "...",
267 connectedAt: timestamp,
268 permissions: ["sign", "operation_request"]
269 }
270 }
271}
272```
273
274On `Disconnect` message: remove peer. On lock: notify all connected dApps.
275Connected dApps appear as small dots at the bottom edge of the keeps grid — tap to see name + disconnect. No separate view.
276
277---
278
279## 3. File Changes Summary
280
281| File | Action | Description |
282|---|---|---|
283| `wallet/extension/lib/beacon-crypto.mjs` | **New** | Beacon-specific crypto (crypto_box, key exchange) |
284| `wallet/extension/beacon.mjs` | **New** | Beacon protocol handler (request routing, responses) |
285| `wallet/extension/content.js` | **Modify** | Add Beacon toExtension/toPage relay alongside existing KEEPS_ relay |
286| `wallet/extension/inpage.js` | **Modify** | Keep window.keeps, add wallet announcement |
287| `wallet/extension/background.js` | **Modify** | Add BEACON_MESSAGE handler, pending request queue, confirmation flow |
288| `wallet/extension/popup/popup.html` | **Rewrite** | Living keeps grid — no finance UI, just running canvases |
289| `wallet/extension/popup/popup.js` | **Rewrite** | KidLisp evaluator integration, keeps grid, confirmation overlay |
290| `wallet/extension/manifest.json` | **Modify** | Add `<all_urls>` to content scripts, add kidlisp.com |
291| `wallet/extension/package.json` | **Modify** | Add libsodium-wrappers if not already present |
292
293---
294
295## 4. Dependencies
296
297Already have in `wallet/extension/package.json`:
298- `tweetnacl` (ed25519 signing)
299- `bip39` (mnemonic)
300- `bs58check` (base58)
301
302Need to add or verify:
303- `libsodium-wrappers` — for `crypto_box` (Beacon uses this, not tweetnacl's box). Or use tweetnacl's `nacl.box` which is compatible (same algorithm: x25519-xsalsa20-poly1305).
304
305**Decision:** Use `tweetnacl.box` since it's already a dependency. Same underlying crypto as libsodium's `crypto_box`. This avoids adding another dependency.
306
307---
308
309## 5. Testing Plan
310
3111. **Unit test Beacon crypto** — encrypt/decrypt roundtrip with known test vectors
3122. **Ping/pong discovery** — Load extension, open a page with Beacon SDK, verify wallet appears in wallet list
3133. **Permission request** — Connect from keep.kidlisp.com, verify address returned matches wallet
3144. **Operation request** — Mint a keep on ghostnet, verify confirmation popup appears, transaction succeeds
3155. **Sign payload** — Sign arbitrary bytes, verify signature is valid ed25519
3166. **Disconnect** — Disconnect from dApp, verify session cleared
3177. **Auto-lock** — Verify Beacon requests fail gracefully when wallet is locked
3188. **Multi-dApp** — Connect to two dApps simultaneously, verify independent sessions
319
320---
321
322## 6. Implementation Order
323
3241. **Beacon crypto layer** (Step 1) — foundation
3252. **Content script Beacon relay** (Step 3) — ping/pong first
3263. **Background Beacon handler** (Step 2) — permission request flow
3274. **Confirmation popup** (Step 5) — security gate
3285. **Manifest update** (Step 6) — broader site support
3296. **Operation & sign handlers** (Step 2 continued) — full transaction support
3307. **Session management** (Step 7) — track connected dApps
3318. **Popup UI updates** (Step 8) — connected dApps view
3329. **Testing on keep.kidlisp.com** — end-to-end validation
333
334---
335
336## 7. Key References
337
338- **Beacon SDK source:** `github.com/airgap-it/beacon-sdk` (branch: master)
339 - `packages/beacon-transport-postmessage/src/` — PostMessage transport
340 - `packages/beacon-types/src/types/ExtensionMessage.ts` — Message format
341 - `packages/beacon-types/src/types/ExtensionMessageTarget.ts` — `toPage`/`toExtension` enum
342 - `packages/beacon-types/src/types/beacon/BeaconMessageType.ts` — All 19 message types
343 - `packages/beacon-types/src/types/PostMessagePairingRequest.ts` — Pairing request format
344 - `packages/beacon-types/src/types/PostMessagePairingResponse.ts` — Pairing response format
345- **Temple Wallet source:** `github.com/madfish-solutions/templewallet-extension` (branch: development)
346 - `src/content-scripts/main.ts` — Content script with Beacon relay
347 - `src/lib/temple/beacon.ts` — Beacon protocol handler (encrypt/decrypt, message routing)
348- **Spire (reference wallet):** `github.com/airgap-it/spire` — AirGap's own Beacon extension wallet
349- **TZIP-10 standard:** Wallet interaction standard that Beacon implements
350- **Beacon docs:** `docs.walletbeacon.io`