@mysten-incubation/dev-wallet 0.5.0 → 0.6.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/CHANGELOG.md +41 -0
  2. package/README.md +12 -8
  3. package/dist/client/connected-apps.d.mts +35 -0
  4. package/dist/client/connected-apps.d.mts.map +1 -0
  5. package/dist/client/connected-apps.mjs +50 -0
  6. package/dist/client/connected-apps.mjs.map +1 -0
  7. package/dist/client/dev-wallet-client.mjs +1 -1
  8. package/dist/client/dev-wallet-client.mjs.map +1 -1
  9. package/dist/client/index.d.mts +2 -1
  10. package/dist/client/index.mjs +2 -1
  11. package/dist/client/request-handler.d.mts +7 -0
  12. package/dist/client/request-handler.d.mts.map +1 -1
  13. package/dist/client/request-handler.mjs +17 -2
  14. package/dist/client/request-handler.mjs.map +1 -1
  15. package/dist/inject/index.d.mts +4 -0
  16. package/dist/inject/index.d.mts.map +1 -1
  17. package/dist/inject/index.mjs +31 -0
  18. package/dist/inject/index.mjs.map +1 -1
  19. package/dist/standalone/assets/{in-memory-adapter-Ban2kRYS.js → in-memory-adapter-uuWG4K4k.js} +1 -1
  20. package/dist/standalone/assets/main-CKYv0eR6.js +7 -0
  21. package/dist/standalone/assets/{webcrypto-adapter-Ddqn-RAu.js → webcrypto-adapter-5ShaPc_M.js} +1 -1
  22. package/dist/standalone/bookmarklet.js +1 -1
  23. package/dist/standalone/index.html +1 -1
  24. package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
  25. package/dist/ui/dev-wallet-balances.mjs +20 -13
  26. package/dist/ui/dev-wallet-balances.mjs.map +1 -1
  27. package/dist/ui/dev-wallet-connect-guide.d.mts +29 -0
  28. package/dist/ui/dev-wallet-connect-guide.d.mts.map +1 -0
  29. package/dist/ui/dev-wallet-connect-guide.mjs +386 -0
  30. package/dist/ui/dev-wallet-connect-guide.mjs.map +1 -0
  31. package/dist/ui/dev-wallet-connected-apps.d.mts +27 -0
  32. package/dist/ui/dev-wallet-connected-apps.d.mts.map +1 -0
  33. package/dist/ui/dev-wallet-connected-apps.mjs +164 -0
  34. package/dist/ui/dev-wallet-connected-apps.mjs.map +1 -0
  35. package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
  36. package/dist/ui/dev-wallet-panel.mjs +0 -9
  37. package/dist/ui/dev-wallet-panel.mjs.map +1 -1
  38. package/dist/ui/dev-wallet-settings.d.mts +8 -2
  39. package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
  40. package/dist/ui/dev-wallet-settings.mjs +86 -171
  41. package/dist/ui/dev-wallet-settings.mjs.map +1 -1
  42. package/dist/ui/dev-wallet-standalone.d.mts +7 -2
  43. package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
  44. package/dist/ui/dev-wallet-standalone.mjs +68 -222
  45. package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
  46. package/dist/ui/index.d.mts +3 -1
  47. package/dist/ui/index.mjs +3 -1
  48. package/dist/ui/wallet-controller.mjs +15 -3
  49. package/dist/ui/wallet-controller.mjs.map +1 -1
  50. package/dist/wallet/dev-wallet.d.mts +15 -3
  51. package/dist/wallet/dev-wallet.d.mts.map +1 -1
  52. package/dist/wallet/dev-wallet.mjs +94 -30
  53. package/dist/wallet/dev-wallet.mjs.map +1 -1
  54. package/dist/wallet/persisted.mjs +20 -0
  55. package/dist/wallet/persisted.mjs.map +1 -0
  56. package/package.json +6 -6
  57. package/src/app/main.ts +6 -2
  58. package/src/client/connected-apps.ts +79 -0
  59. package/src/client/dev-wallet-client.ts +2 -1
  60. package/src/client/index.ts +2 -0
  61. package/src/client/request-handler.ts +24 -1
  62. package/src/inject/index.ts +55 -0
  63. package/src/ui/dev-wallet-balances.ts +25 -13
  64. package/src/ui/dev-wallet-connect-guide.ts +428 -0
  65. package/src/ui/dev-wallet-connected-apps.ts +191 -0
  66. package/src/ui/dev-wallet-panel.ts +0 -9
  67. package/src/ui/dev-wallet-settings.ts +101 -179
  68. package/src/ui/dev-wallet-standalone.ts +80 -231
  69. package/src/ui/index.ts +2 -0
  70. package/src/ui/wallet-controller.ts +23 -3
  71. package/src/wallet/dev-wallet.ts +130 -39
  72. package/src/wallet/persisted.ts +24 -0
  73. package/dist/standalone/assets/main-xfzarPod.js +0 -7
@@ -0,0 +1,428 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { css, html, LitElement, nothing } from 'lit';
5
+ import { customElement, property, state } from 'lit/decorators.js';
6
+
7
+ import { CopyController } from './copy-controller.js';
8
+
9
+ /**
10
+ * How to point a dApp at a hosted/standalone dev wallet: a draggable
11
+ * bookmarklet, a console script, and a dApp Kit snippet — all derived from
12
+ * the wallet's `origin`. Rendered beside the standalone wallet on wide
13
+ * screens and at the top of its Settings tab.
14
+ */
15
+ type Platform = 'ios' | 'android' | 'android-firefox' | 'desktop';
16
+
17
+ function detectPlatform(): Platform {
18
+ const nav = globalThis.navigator;
19
+ if (!nav) return 'desktop';
20
+ const ua = nav.userAgent;
21
+ if (/Android/.test(ua)) return /Firefox/.test(ua) ? 'android-firefox' : 'android';
22
+ // iPadOS reports itself as a Mac; touch support gives it away.
23
+ if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && nav.maxTouchPoints > 1)) {
24
+ return 'ios';
25
+ }
26
+ return 'desktop';
27
+ }
28
+
29
+ const BOOKMARKS_BAR_SHORTCUT = /Mac|iPhone|iPad/.test(globalThis.navigator?.platform ?? '')
30
+ ? '⌘ Shift B'
31
+ : 'Ctrl Shift B';
32
+
33
+ type TabId = 'bookmarklet' | 'dapp-kit';
34
+ const TABS: { id: TabId; label: string }[] = [
35
+ { id: 'bookmarklet', label: 'Bookmarklet' },
36
+ { id: 'dapp-kit', label: 'dApp Kit' },
37
+ ];
38
+
39
+ @customElement('dev-wallet-connect-guide')
40
+ export class DevWalletConnectGuide extends LitElement {
41
+ static override styles = css`
42
+ :host {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 10px;
46
+ font-size: 12px;
47
+ line-height: 1.55;
48
+ color: var(--dev-wallet-muted-foreground);
49
+ }
50
+
51
+ .tabs {
52
+ display: flex;
53
+ gap: 2px;
54
+ padding: 2px;
55
+ border-radius: var(--dev-wallet-radius-sm);
56
+ border: 1px solid var(--dev-wallet-border);
57
+ background: var(--dev-wallet-bg-1);
58
+ }
59
+
60
+ .tab {
61
+ flex: 1;
62
+ height: 26px;
63
+ border: 0;
64
+ border-radius: var(--dev-wallet-radius-xs);
65
+ background: transparent;
66
+ color: var(--dev-wallet-muted-foreground);
67
+ font: inherit;
68
+ font-weight: var(--dev-wallet-font-weight-medium);
69
+ cursor: pointer;
70
+ }
71
+
72
+ .tab:hover {
73
+ color: var(--dev-wallet-foreground);
74
+ }
75
+
76
+ .tab[aria-selected='true'] {
77
+ background: var(--dev-wallet-bg-3);
78
+ color: var(--dev-wallet-foreground);
79
+ }
80
+
81
+ .panel {
82
+ display: flex;
83
+ flex-direction: column;
84
+ gap: 10px;
85
+ padding-top: 4px;
86
+ }
87
+
88
+ .guide-title {
89
+ margin: 0;
90
+ font-family: var(--dev-wallet-font-mono);
91
+ font-size: 10px;
92
+ font-weight: var(--dev-wallet-font-weight-medium);
93
+ letter-spacing: 0.14em;
94
+ text-transform: uppercase;
95
+ color: var(--dev-wallet-text-3);
96
+ }
97
+
98
+ .guide p {
99
+ margin: 0;
100
+ }
101
+
102
+ .guide strong {
103
+ color: var(--dev-wallet-foreground);
104
+ font-weight: var(--dev-wallet-font-weight-medium);
105
+ }
106
+
107
+ .steps {
108
+ margin: 0;
109
+ padding-left: 18px;
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 8px;
113
+ }
114
+
115
+ .steps li::marker {
116
+ color: var(--dev-wallet-text-3);
117
+ font-family: var(--dev-wallet-font-mono);
118
+ font-size: 11px;
119
+ }
120
+
121
+ .steps .bookmarklet-row {
122
+ margin-top: 6px;
123
+ }
124
+
125
+ kbd {
126
+ padding: 1px 5px;
127
+ border-radius: var(--dev-wallet-radius-2xs);
128
+ border: 1px solid var(--dev-wallet-border-strong);
129
+ background: var(--dev-wallet-bg-2);
130
+ font-family: var(--dev-wallet-font-mono);
131
+ font-size: 10.5px;
132
+ color: var(--dev-wallet-foreground);
133
+ }
134
+
135
+ .bookmarklet-row {
136
+ display: flex;
137
+ align-items: center;
138
+ flex-wrap: wrap;
139
+ gap: 10px;
140
+ }
141
+
142
+ .bookmarklet {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 6px;
146
+ padding: 6px 12px;
147
+ border-radius: var(--dev-wallet-radius-sm);
148
+ border: 1px solid var(--dev-wallet-border-strong);
149
+ background: var(--dev-wallet-bg-2);
150
+ color: var(--dev-wallet-foreground);
151
+ font-weight: var(--dev-wallet-font-weight-medium);
152
+ text-decoration: none;
153
+ cursor: grab;
154
+ user-select: none;
155
+ }
156
+
157
+ .bookmarklet:hover {
158
+ background: var(--dev-wallet-bg-hover);
159
+ }
160
+
161
+ .bookmarklet:active {
162
+ cursor: grabbing;
163
+ }
164
+
165
+ .bookmarklet svg {
166
+ width: 13px;
167
+ height: 13px;
168
+ }
169
+
170
+ .hint {
171
+ font-size: 11px;
172
+ color: var(--dev-wallet-text-3);
173
+ }
174
+
175
+ .hint.nudge {
176
+ color: var(--dev-wallet-warning);
177
+ }
178
+
179
+ .link-btn {
180
+ align-self: flex-start;
181
+ text-align: left;
182
+ padding: 0;
183
+ border: 0;
184
+ background: none;
185
+ color: var(--dev-wallet-muted-foreground);
186
+ font: inherit;
187
+ text-decoration: underline;
188
+ text-underline-offset: 2px;
189
+ cursor: pointer;
190
+ }
191
+
192
+ .link-btn.inline {
193
+ display: inline;
194
+ }
195
+
196
+ .link-btn:hover {
197
+ color: var(--dev-wallet-foreground);
198
+ }
199
+
200
+ .snippet {
201
+ position: relative;
202
+ border-radius: var(--dev-wallet-radius);
203
+ border: 1px solid var(--dev-wallet-border);
204
+ background: var(--dev-wallet-bg-1);
205
+ }
206
+
207
+ .snippet pre,
208
+ .snippet code {
209
+ font-family: var(--dev-wallet-font-mono);
210
+ }
211
+
212
+ .snippet pre {
213
+ margin: 0;
214
+ padding: 10px 12px;
215
+ overflow-x: auto;
216
+ font-size: 10.5px;
217
+ line-height: 1.6;
218
+ color: var(--dev-wallet-foreground);
219
+ }
220
+
221
+ .snippet .btn-copy {
222
+ position: absolute;
223
+ top: 6px;
224
+ right: 6px;
225
+ }
226
+
227
+ .btn-copy {
228
+ height: 22px;
229
+ padding: 0 8px;
230
+ border-radius: var(--dev-wallet-radius-xs);
231
+ border: 1px solid var(--dev-wallet-border);
232
+ background: var(--dev-wallet-bg-2);
233
+ color: var(--dev-wallet-foreground);
234
+ font-family: var(--dev-wallet-font-sans);
235
+ font-size: 10.5px;
236
+ font-weight: var(--dev-wallet-font-weight-medium);
237
+ cursor: pointer;
238
+ }
239
+
240
+ .btn-copy:hover {
241
+ background: var(--dev-wallet-bg-hover);
242
+ border-color: var(--dev-wallet-border-strong);
243
+ }
244
+ `;
245
+
246
+ /** Origin the wallet is served from (e.g. `https://sui-dev-wallet.vercel.app`). */
247
+ @property({ type: String })
248
+ origin = '';
249
+
250
+ @state()
251
+ private _dragNudge = false;
252
+
253
+ @state()
254
+ private _tab: TabId = 'bookmarklet';
255
+
256
+ /** Phones can't drag bookmarklets, so they get copy-and-edit steps instead. */
257
+ @property({ attribute: false })
258
+ platform: Platform = detectPlatform();
259
+
260
+ #copy = new CopyController(this);
261
+
262
+ override render() {
263
+ const origin = this.origin;
264
+ const scriptUrl = `${origin}/bookmarklet.js`;
265
+ const bookmarkletHref = `javascript:void(document.head.appendChild(Object.assign(document.createElement('script'),{src:'${scriptUrl}'})))`;
266
+ const consoleScript = `var s=document.createElement('script');s.src='${scriptUrl}';document.head.appendChild(s);`;
267
+ const dappKitSnippet = `import {
268
+ devWalletClientInitializer,
269
+ } from '@mysten-incubation/dev-wallet/client';
270
+
271
+ createDAppKit({
272
+ networks: ['devnet'],
273
+ walletInitializers: [
274
+ devWalletClientInitializer({
275
+ origin: '${origin}',
276
+ }),
277
+ ],
278
+ });`;
279
+
280
+ return html`
281
+ <h2 class="guide-title">Connect an app</h2>
282
+ <div class="tabs" role="tablist" aria-label="Connection method">
283
+ ${TABS.map(
284
+ ({ id, label }) =>
285
+ html`<button
286
+ role="tab"
287
+ class="tab"
288
+ aria-selected=${this._tab === id}
289
+ @click=${() => (this._tab = id)}
290
+ >
291
+ ${label}
292
+ </button>`,
293
+ )}
294
+ </div>
295
+ <div role="tabpanel" class="panel">
296
+ ${this._tab === 'bookmarklet'
297
+ ? this.platform === 'desktop'
298
+ ? this.#renderBookmarklet(bookmarkletHref, consoleScript)
299
+ : this.#renderMobileBookmarklet(bookmarkletHref)
300
+ : this.#renderDappKit(dappKitSnippet)}
301
+ </div>
302
+ `;
303
+ }
304
+
305
+ #renderBookmarklet(bookmarkletHref: string, consoleScript: string) {
306
+ return html`
307
+ <p>Add this wallet to any dApp without changing its code.</p>
308
+ <ol class="steps">
309
+ <li>Show your browser's bookmarks bar (<kbd>${BOOKMARKS_BAR_SHORTCUT}</kbd>).</li>
310
+ <li>
311
+ Drag this button onto the bookmarks bar:
312
+ <div class="bookmarklet-row">
313
+ <a
314
+ class="bookmarklet"
315
+ href=${bookmarkletHref}
316
+ title="Drag to your bookmarks bar"
317
+ @click=${this.#nudgeDrag}
318
+ >${this.#walletIcon} Dev Wallet</a
319
+ >
320
+ ${this._dragNudge
321
+ ? html`<span class="hint nudge">Drag it, don't click it</span>`
322
+ : nothing}
323
+ </div>
324
+ </li>
325
+ <li>
326
+ Open your dApp and click the bookmark. Then choose
327
+ <strong>Dev Wallet (Web)</strong> in the dApp's wallet picker.
328
+ </li>
329
+ </ol>
330
+ <p class="hint">
331
+ The bookmark only lasts until the page reloads. After a reload, click it again.
332
+ </p>
333
+ <button class="link-btn" type="button" @click=${() => this.#copy.copy(consoleScript)}>
334
+ ${this.#copy.isCopied(consoleScript)
335
+ ? 'Copied. Paste it into the dApp’s browser console.'
336
+ : 'Can’t use bookmarks? Copy a script for the dApp’s console instead.'}
337
+ </button>
338
+ `;
339
+ }
340
+
341
+ #renderMobileBookmarklet(bookmarkletHref: string) {
342
+ if (this.platform === 'android-firefox') {
343
+ return html`
344
+ <p>
345
+ Firefox for Android doesn't run bookmarklets. Open the dApp in Chrome, or use the
346
+ <button class="link-btn inline" type="button" @click=${() => (this._tab = 'dapp-kit')}>
347
+ dApp Kit
348
+ </button>
349
+ option if you're building the app.
350
+ </p>
351
+ `;
352
+ }
353
+ const copied = this.#copy.isCopied(bookmarkletHref);
354
+ return html`
355
+ <p>Add this wallet to any dApp without changing its code. Phones can't drag bookmarks, so:</p>
356
+ <ol class="steps">
357
+ <li>
358
+ <button class="btn-copy" type="button" @click=${() => this.#copy.copy(bookmarkletHref)}>
359
+ ${copied ? 'Copied' : 'Copy bookmarklet code'}
360
+ </button>
361
+ </li>
362
+ ${this.platform === 'ios'
363
+ ? html`
364
+ <li>Bookmark this page, then open Bookmarks and edit the new bookmark.</li>
365
+ <li>Replace its address with the copied code and tap Done.</li>
366
+ <li>
367
+ On your dApp, open Bookmarks and tap the bookmark. Then choose
368
+ <strong>Dev Wallet (Web)</strong> in the dApp's wallet picker.
369
+ </li>
370
+ `
371
+ : html`
372
+ <li>
373
+ Bookmark this page, then edit the bookmark: name it "Dev Wallet" and replace its URL
374
+ with the copied code.
375
+ </li>
376
+ <li>
377
+ On your dApp, type "Dev Wallet" in the address bar and tap the bookmark suggestion
378
+ (opening it from the bookmarks list won't work). Then choose
379
+ <strong>Dev Wallet (Web)</strong> in the dApp's wallet picker.
380
+ </li>
381
+ `}
382
+ </ol>
383
+ <p class="hint">
384
+ The bookmark only lasts until the page reloads. After a reload, run it again.
385
+ </p>
386
+ `;
387
+ }
388
+
389
+ #renderDappKit(dappKitSnippet: string) {
390
+ return html`
391
+ <p>Register the wallet with dApp Kit so it's always in your app's wallet picker.</p>
392
+ <div class="snippet">
393
+ <button
394
+ class="btn-copy"
395
+ type="button"
396
+ aria-label="Copy dApp Kit snippet"
397
+ @click=${() => this.#copy.copy(dappKitSnippet)}
398
+ >
399
+ ${this.#copy.isCopied(dappKitSnippet) ? 'Copied' : 'Copy'}
400
+ </button>
401
+ <pre><code>${dappKitSnippet}</code></pre>
402
+ </div>
403
+ `;
404
+ }
405
+
406
+ #nudgeDrag = (e: MouseEvent) => {
407
+ e.preventDefault();
408
+ this._dragNudge = true;
409
+ setTimeout(() => {
410
+ this._dragNudge = false;
411
+ }, 2500);
412
+ };
413
+
414
+ get #walletIcon() {
415
+ return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
416
+ <rect x="2" y="6" width="20" height="14" rx="2.5" />
417
+ <path d="M2 10h20" />
418
+ <rect x="15" y="12" width="5" height="4" rx="1" />
419
+ <circle cx="17.5" cy="14" r="0.5" fill="currentColor" stroke="none" />
420
+ </svg>`;
421
+ }
422
+ }
423
+
424
+ declare global {
425
+ interface HTMLElementTagNameMap {
426
+ 'dev-wallet-connect-guide': DevWalletConnectGuide;
427
+ }
428
+ }
@@ -0,0 +1,191 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { css, html, LitElement } from 'lit';
5
+ import { customElement, property, state } from 'lit/decorators.js';
6
+
7
+ import type { ConnectedApp, ConnectedAppsStore } from '../client/connected-apps.js';
8
+ import { formatAddress } from './utils.js';
9
+
10
+ /**
11
+ * Lists the dApps connected to a standalone wallet, with a Disconnect action
12
+ * per app. Disconnecting makes the wallet reject that app's signing requests
13
+ * until it connects again.
14
+ */
15
+ @customElement('dev-wallet-connected-apps')
16
+ export class DevWalletConnectedApps extends LitElement {
17
+ static override styles = css`
18
+ :host {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 10px;
22
+ font-size: 12px;
23
+ line-height: 1.5;
24
+ color: var(--dev-wallet-muted-foreground);
25
+ }
26
+
27
+ .title {
28
+ margin: 0;
29
+ font-family: var(--dev-wallet-font-mono);
30
+ font-size: 10px;
31
+ font-weight: var(--dev-wallet-font-weight-medium);
32
+ letter-spacing: 0.14em;
33
+ text-transform: uppercase;
34
+ color: var(--dev-wallet-text-3);
35
+ }
36
+
37
+ .empty {
38
+ margin: 0;
39
+ color: var(--dev-wallet-text-3);
40
+ }
41
+
42
+ ul {
43
+ margin: 0;
44
+ padding: 0;
45
+ list-style: none;
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 6px;
49
+ }
50
+
51
+ li {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 10px;
55
+ padding: 8px 8px 8px 10px;
56
+ border-radius: var(--dev-wallet-radius-lg);
57
+ border: 1px solid var(--dev-wallet-border);
58
+ background: var(--dev-wallet-bg-1);
59
+ }
60
+
61
+ .info {
62
+ flex: 1;
63
+ min-width: 0;
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+
68
+ .name {
69
+ color: var(--dev-wallet-foreground);
70
+ font-weight: var(--dev-wallet-font-weight-medium);
71
+ overflow: hidden;
72
+ text-overflow: ellipsis;
73
+ white-space: nowrap;
74
+ }
75
+
76
+ .meta {
77
+ font-family: var(--dev-wallet-font-mono);
78
+ font-size: 10.5px;
79
+ color: var(--dev-wallet-text-3);
80
+ overflow: hidden;
81
+ text-overflow: ellipsis;
82
+ white-space: nowrap;
83
+ }
84
+
85
+ .disconnect {
86
+ flex-shrink: 0;
87
+ height: 24px;
88
+ padding: 0 8px;
89
+ border-radius: var(--dev-wallet-radius-xs);
90
+ border: 1px solid var(--dev-wallet-border);
91
+ background: transparent;
92
+ color: var(--dev-wallet-text-2);
93
+ font: inherit;
94
+ font-size: 11px;
95
+ cursor: pointer;
96
+ }
97
+
98
+ .disconnect:hover {
99
+ border-color: var(--dev-wallet-destructive);
100
+ color: var(--dev-wallet-destructive);
101
+ }
102
+ `;
103
+
104
+ @property({ attribute: false })
105
+ store: ConnectedAppsStore | null = null;
106
+
107
+ @state()
108
+ private _apps: ConnectedApp[] = [];
109
+
110
+ #unsubscribe: (() => void) | null = null;
111
+
112
+ override connectedCallback() {
113
+ super.connectedCallback();
114
+ this.#bind();
115
+ }
116
+
117
+ override disconnectedCallback() {
118
+ super.disconnectedCallback();
119
+ this.#unsubscribe?.();
120
+ this.#unsubscribe = null;
121
+ }
122
+
123
+ override willUpdate(changed: Map<string, unknown>) {
124
+ if (changed.has('store') && this.isConnected) this.#bind();
125
+ }
126
+
127
+ #bind() {
128
+ this.#unsubscribe?.();
129
+ this.#unsubscribe = this.store?.subscribe(() => this.#load()) ?? null;
130
+ this.#load();
131
+ }
132
+
133
+ #load() {
134
+ this._apps = this.store?.list() ?? [];
135
+ }
136
+
137
+ #disconnect(origin: string) {
138
+ this.store?.remove(origin);
139
+ this.#load();
140
+ }
141
+
142
+ override render() {
143
+ return html`
144
+ <h2 class="title">Connected apps</h2>
145
+ ${this._apps.length === 0
146
+ ? html`<p class="empty">No apps connected yet.</p>`
147
+ : html`<ul>
148
+ ${this._apps.map(
149
+ (app) =>
150
+ html`<li>
151
+ <div class="info">
152
+ <span class="name" title=${app.name}>${app.name || safeHost(app.origin)}</span>
153
+ <span class="meta" title=${app.origin}>${this.#describe(app)}</span>
154
+ </div>
155
+ <button
156
+ class="disconnect"
157
+ type="button"
158
+ aria-label=${`Disconnect ${app.origin}`}
159
+ @click=${() => this.#disconnect(app.origin)}
160
+ >
161
+ Disconnect
162
+ </button>
163
+ </li>`,
164
+ )}
165
+ </ul>`}
166
+ `;
167
+ }
168
+
169
+ #describe(app: ConnectedApp) {
170
+ const host = safeHost(app.origin);
171
+ const accounts =
172
+ app.accounts.length === 1
173
+ ? formatAddress(app.accounts[0])
174
+ : `${app.accounts.length} accounts`;
175
+ return `${host} · ${accounts}`;
176
+ }
177
+ }
178
+
179
+ function safeHost(origin: string): string {
180
+ try {
181
+ return new URL(origin).host;
182
+ } catch {
183
+ return origin;
184
+ }
185
+ }
186
+
187
+ declare global {
188
+ interface HTMLElementTagNameMap {
189
+ 'dev-wallet-connected-apps': DevWalletConnectedApps;
190
+ }
191
+ }
@@ -193,14 +193,6 @@ export class DevWalletPanel extends LitElement {
193
193
  text-overflow: ellipsis;
194
194
  }
195
195
 
196
- .version {
197
- font-family: var(--dev-wallet-font-mono);
198
- font-size: 9px;
199
- letter-spacing: 0.12em;
200
- text-transform: uppercase;
201
- color: var(--dev-wallet-text-3);
202
- }
203
-
204
196
  .header-spacer {
205
197
  flex: 1;
206
198
  min-width: 8px;
@@ -363,7 +355,6 @@ export class DevWalletPanel extends LitElement {
363
355
  <span class="logo-mark">${this.#walletIcon}</span>
364
356
  <span class="title-stack">
365
357
  <span class="sidebar-title">${this.wallet?.name ?? 'Dev Wallet'}</span>
366
- <span class="version">v0.1.0</span>
367
358
  </span>
368
359
  </div>
369
360
  <span class="header-spacer"></span>