@mysten-incubation/dev-wallet 0.0.0 → 0.2.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 (182) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +189 -0
  3. package/dist/_virtual/{_@oxc-project_runtime@0.107.0 → _@oxc-project_runtime@0.127.0}/helpers/decorate.mjs +2 -3
  4. package/dist/adapters/base-adapter.mjs +6 -7
  5. package/dist/adapters/base-adapter.mjs.map +1 -1
  6. package/dist/adapters/browser.d.mts +4 -1
  7. package/dist/adapters/browser.mjs +4 -2
  8. package/dist/adapters/build-managed-account.mjs +8 -10
  9. package/dist/adapters/build-managed-account.mjs.map +1 -1
  10. package/dist/adapters/devstack-adapter.d.mts +111 -0
  11. package/dist/adapters/devstack-adapter.d.mts.map +1 -0
  12. package/dist/adapters/devstack-adapter.mjs +224 -0
  13. package/dist/adapters/devstack-adapter.mjs.map +1 -0
  14. package/dist/adapters/devstack-paths.d.mts +12 -0
  15. package/dist/adapters/devstack-paths.d.mts.map +1 -0
  16. package/dist/adapters/devstack-paths.mjs +12 -0
  17. package/dist/adapters/devstack-paths.mjs.map +1 -0
  18. package/dist/adapters/fork-relay.d.mts +109 -0
  19. package/dist/adapters/fork-relay.d.mts.map +1 -0
  20. package/dist/adapters/fork-relay.mjs +208 -0
  21. package/dist/adapters/fork-relay.mjs.map +1 -0
  22. package/dist/adapters/idb-store.mjs +12 -16
  23. package/dist/adapters/idb-store.mjs.map +1 -1
  24. package/dist/adapters/in-memory-adapter.mjs +4 -10
  25. package/dist/adapters/in-memory-adapter.mjs.map +1 -1
  26. package/dist/adapters/passkey-adapter.mjs +4 -12
  27. package/dist/adapters/passkey-adapter.mjs.map +1 -1
  28. package/dist/adapters/remote-cli-adapter.mjs +37 -72
  29. package/dist/adapters/remote-cli-adapter.mjs.map +1 -1
  30. package/dist/adapters/webcrypto-adapter.mjs +2 -4
  31. package/dist/adapters/webcrypto-adapter.mjs.map +1 -1
  32. package/dist/bin/cli.mjs +9 -22
  33. package/dist/bin/cli.mjs.map +1 -1
  34. package/dist/client/dev-wallet-client.mjs +13 -28
  35. package/dist/client/dev-wallet-client.mjs.map +1 -1
  36. package/dist/client/index.mjs +1 -2
  37. package/dist/client/request-handler.mjs +36 -42
  38. package/dist/client/request-handler.mjs.map +1 -1
  39. package/dist/index.mjs +1 -2
  40. package/dist/react/components.d.mts +9 -9
  41. package/dist/react/components.d.mts.map +1 -1
  42. package/dist/react/components.mjs +2 -5
  43. package/dist/react/components.mjs.map +1 -1
  44. package/dist/react/context.d.mts +3 -3
  45. package/dist/react/context.d.mts.map +1 -1
  46. package/dist/react/context.mjs +3 -8
  47. package/dist/react/context.mjs.map +1 -1
  48. package/dist/react/index.mjs +1 -2
  49. package/dist/react/useDevWallet.d.mts +3 -0
  50. package/dist/react/useDevWallet.d.mts.map +1 -1
  51. package/dist/react/useDevWallet.mjs +9 -14
  52. package/dist/react/useDevWallet.mjs.map +1 -1
  53. package/dist/server/cli-signing-middleware.mjs +7 -20
  54. package/dist/server/cli-signing-middleware.mjs.map +1 -1
  55. package/dist/server/index.mjs +1 -2
  56. package/dist/standalone/assets/{in-memory-adapter-CBF4h2KD.js → in-memory-adapter-qRkNfj9O.js} +1 -1
  57. package/dist/standalone/assets/main-DDltQnzR.js +3954 -0
  58. package/dist/standalone/assets/{webcrypto-adapter-DysAubFb.js → webcrypto-adapter-FejcrL9x.js} +1 -1
  59. package/dist/standalone/index.html +11 -12
  60. package/dist/ui/copy-controller.mjs +3 -3
  61. package/dist/ui/copy-controller.mjs.map +1 -1
  62. package/dist/ui/dev-wallet-account-selector.d.mts +3 -4
  63. package/dist/ui/dev-wallet-account-selector.d.mts.map +1 -1
  64. package/dist/ui/dev-wallet-account-selector.mjs +48 -25
  65. package/dist/ui/dev-wallet-account-selector.mjs.map +1 -1
  66. package/dist/ui/dev-wallet-accounts.d.mts +9 -4
  67. package/dist/ui/dev-wallet-accounts.d.mts.map +1 -1
  68. package/dist/ui/dev-wallet-accounts.mjs +62 -140
  69. package/dist/ui/dev-wallet-accounts.mjs.map +1 -1
  70. package/dist/ui/dev-wallet-balances.d.mts +10 -3
  71. package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
  72. package/dist/ui/dev-wallet-balances.mjs +241 -43
  73. package/dist/ui/dev-wallet-balances.mjs.map +1 -1
  74. package/dist/ui/dev-wallet-connect.d.mts +3 -3
  75. package/dist/ui/dev-wallet-connect.d.mts.map +1 -1
  76. package/dist/ui/dev-wallet-connect.mjs +24 -16
  77. package/dist/ui/dev-wallet-connect.mjs.map +1 -1
  78. package/dist/ui/dev-wallet-dropdown.d.mts +3 -3
  79. package/dist/ui/dev-wallet-dropdown.d.mts.map +1 -1
  80. package/dist/ui/dev-wallet-dropdown.mjs +37 -47
  81. package/dist/ui/dev-wallet-dropdown.mjs.map +1 -1
  82. package/dist/ui/dev-wallet-fork-panel.d.mts +43 -0
  83. package/dist/ui/dev-wallet-fork-panel.d.mts.map +1 -0
  84. package/dist/ui/dev-wallet-fork-panel.mjs +500 -0
  85. package/dist/ui/dev-wallet-fork-panel.mjs.map +1 -0
  86. package/dist/ui/dev-wallet-network-badge.d.mts +3 -4
  87. package/dist/ui/dev-wallet-network-badge.d.mts.map +1 -1
  88. package/dist/ui/dev-wallet-network-badge.mjs +52 -21
  89. package/dist/ui/dev-wallet-network-badge.mjs.map +1 -1
  90. package/dist/ui/dev-wallet-new-account.d.mts +3 -4
  91. package/dist/ui/dev-wallet-new-account.d.mts.map +1 -1
  92. package/dist/ui/dev-wallet-new-account.mjs +31 -105
  93. package/dist/ui/dev-wallet-new-account.mjs.map +1 -1
  94. package/dist/ui/dev-wallet-objects.d.mts +3 -3
  95. package/dist/ui/dev-wallet-objects.d.mts.map +1 -1
  96. package/dist/ui/dev-wallet-objects.mjs +76 -39
  97. package/dist/ui/dev-wallet-objects.mjs.map +1 -1
  98. package/dist/ui/dev-wallet-panel.d.mts +24 -4
  99. package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
  100. package/dist/ui/dev-wallet-panel.mjs +247 -64
  101. package/dist/ui/dev-wallet-panel.mjs.map +1 -1
  102. package/dist/ui/dev-wallet-popup.d.mts +3 -5
  103. package/dist/ui/dev-wallet-popup.d.mts.map +1 -1
  104. package/dist/ui/dev-wallet-popup.mjs +19 -15
  105. package/dist/ui/dev-wallet-popup.mjs.map +1 -1
  106. package/dist/ui/dev-wallet-settings.d.mts +3 -4
  107. package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
  108. package/dist/ui/dev-wallet-settings.mjs +149 -97
  109. package/dist/ui/dev-wallet-settings.mjs.map +1 -1
  110. package/dist/ui/dev-wallet-signing-modal.d.mts +11 -4
  111. package/dist/ui/dev-wallet-signing-modal.d.mts.map +1 -1
  112. package/dist/ui/dev-wallet-signing-modal.mjs +46 -18
  113. package/dist/ui/dev-wallet-signing-modal.mjs.map +1 -1
  114. package/dist/ui/dev-wallet-signing.d.mts +9 -3
  115. package/dist/ui/dev-wallet-signing.d.mts.map +1 -1
  116. package/dist/ui/dev-wallet-signing.mjs +119 -130
  117. package/dist/ui/dev-wallet-signing.mjs.map +1 -1
  118. package/dist/ui/dev-wallet-standalone.d.mts +8 -3
  119. package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
  120. package/dist/ui/dev-wallet-standalone.mjs +303 -38
  121. package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
  122. package/dist/ui/dev-wallet-tab-bar.d.mts +13 -5
  123. package/dist/ui/dev-wallet-tab-bar.d.mts.map +1 -1
  124. package/dist/ui/dev-wallet-tab-bar.mjs +143 -82
  125. package/dist/ui/dev-wallet-tab-bar.mjs.map +1 -1
  126. package/dist/ui/index.d.mts +3 -2
  127. package/dist/ui/index.mjs +2 -2
  128. package/dist/ui/mount.d.mts +14 -1
  129. package/dist/ui/mount.d.mts.map +1 -1
  130. package/dist/ui/mount.mjs +5 -2
  131. package/dist/ui/mount.mjs.map +1 -1
  132. package/dist/ui/styles.mjs +428 -66
  133. package/dist/ui/styles.mjs.map +1 -1
  134. package/dist/ui/transaction-analyzer.mjs +19 -30
  135. package/dist/ui/transaction-analyzer.mjs.map +1 -1
  136. package/dist/ui/utils.d.mts +59 -0
  137. package/dist/ui/utils.d.mts.map +1 -0
  138. package/dist/ui/utils.mjs +42 -15
  139. package/dist/ui/utils.mjs.map +1 -1
  140. package/dist/ui/wallet-controller.mjs +89 -20
  141. package/dist/ui/wallet-controller.mjs.map +1 -1
  142. package/dist/wallet/constants.d.mts +9 -0
  143. package/dist/wallet/constants.d.mts.map +1 -0
  144. package/dist/wallet/constants.mjs +1 -1
  145. package/dist/wallet/dev-wallet.mjs +42 -99
  146. package/dist/wallet/dev-wallet.mjs.map +1 -1
  147. package/dist/wallet/initializer.d.mts +3 -0
  148. package/dist/wallet/initializer.d.mts.map +1 -1
  149. package/dist/wallet/initializer.mjs +9 -14
  150. package/dist/wallet/initializer.mjs.map +1 -1
  151. package/dist/wallet/signing.mjs +8 -21
  152. package/dist/wallet/signing.mjs.map +1 -1
  153. package/package.json +125 -123
  154. package/src/adapters/browser.ts +21 -0
  155. package/src/adapters/devstack-adapter.ts +342 -0
  156. package/src/adapters/devstack-paths.ts +16 -0
  157. package/src/adapters/fork-relay.ts +321 -0
  158. package/src/app/index.html +10 -11
  159. package/src/react/useDevWallet.ts +5 -1
  160. package/src/ui/dev-wallet-account-selector.ts +44 -17
  161. package/src/ui/dev-wallet-accounts.ts +87 -124
  162. package/src/ui/dev-wallet-balances.ts +248 -27
  163. package/src/ui/dev-wallet-connect.ts +19 -8
  164. package/src/ui/dev-wallet-dropdown.ts +7 -5
  165. package/src/ui/dev-wallet-fork-panel.ts +591 -0
  166. package/src/ui/dev-wallet-network-badge.ts +49 -10
  167. package/src/ui/dev-wallet-new-account.ts +21 -62
  168. package/src/ui/dev-wallet-objects.ts +59 -19
  169. package/src/ui/dev-wallet-panel.ts +261 -42
  170. package/src/ui/dev-wallet-popup.ts +12 -6
  171. package/src/ui/dev-wallet-settings.ts +159 -94
  172. package/src/ui/dev-wallet-signing-modal.ts +47 -7
  173. package/src/ui/dev-wallet-signing.ts +124 -97
  174. package/src/ui/dev-wallet-standalone.ts +310 -22
  175. package/src/ui/dev-wallet-tab-bar.ts +132 -70
  176. package/src/ui/index.ts +3 -0
  177. package/src/ui/mount.ts +23 -1
  178. package/src/ui/styles.ts +434 -58
  179. package/src/ui/utils.ts +66 -0
  180. package/src/ui/wallet-controller.ts +103 -5
  181. package/src/wallet/initializer.ts +7 -1
  182. package/dist/standalone/assets/main-VE4olb-Q.js +0 -2841
@@ -0,0 +1,321 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // Browser-side relay for sui-fork admin RPCs. A compatible devstack
5
+ // wallet endpoint can proxy a curated subset of the fork-control surface
6
+ // (status read, advance-clock, advance-checkpoint, impersonation slot
7
+ // list) over HTTP so the fork panel does not have to ship a gRPC
8
+ // client into the browser bundle.
9
+
10
+ export const FORK_RELAY_HTTP_PATH = {
11
+ STATUS: '/api/v1/devstack/fork/status',
12
+ ADVANCE_CLOCK: '/api/v1/devstack/fork/advance-clock',
13
+ ADVANCE_CHECKPOINT: '/api/v1/devstack/fork/advance-checkpoint',
14
+ IMPERSONATIONS: '/api/v1/devstack/fork/impersonations',
15
+ } as const;
16
+
17
+ /** Snapshot of the fork's current head state.
18
+ *
19
+ * Mirrors the body of `ForkControl.getStatus()` minus the unused
20
+ * heartbeat fields. `clockMs` is a Unix-ms timestamp (the value
21
+ * `clock::timestamp_ms()` returns on chain); `checkpoint` is the
22
+ * latest sealed sequence number.
23
+ */
24
+ export interface ForkStatus {
25
+ readonly checkpoint: bigint;
26
+ readonly clockMs: bigint;
27
+ readonly autoTickMs?: number;
28
+ /** Best-effort label of the upstream the fork is pointed at, e.g.
29
+ * `'mainnet'` or `'testnet'`. Sourced from the devstack manifest. */
30
+ readonly upstream?: string;
31
+ }
32
+
33
+ /** A single impersonation slot resolved by devstack — typically every
34
+ * address that was passed to `Sui({fork:{impersonate:[…]}})`. */
35
+ export interface ForkImpersonationSlot {
36
+ readonly address: string;
37
+ readonly label?: string;
38
+ /** Whether the wallet currently routes user-driven tx signing through
39
+ * the `executeImpersonated` path for this address. The dev-wallet
40
+ * server stores the toggle so it persists across page reloads. */
41
+ readonly active: boolean;
42
+ }
43
+
44
+ export interface ForkRelayOptions {
45
+ /** Wallet-app origin (e.g. `http://localhost:9420`). Same value
46
+ * `DevstackSignerAdapter` consumes — read from the manifest entry's
47
+ * `app.wallet.url`. */
48
+ readonly serverOrigin: string;
49
+ /** Bearer token. Same parsing rules as `DevstackSignerAdapter`. */
50
+ readonly token?: string | null;
51
+ }
52
+
53
+ class ForkRelayHttpError extends Error {
54
+ constructor(
55
+ public readonly status: number,
56
+ message: string,
57
+ ) {
58
+ super(message);
59
+ this.name = 'ForkRelayHttpError';
60
+ }
61
+ }
62
+
63
+ /** Tagged result so callers can render an inline error without throwing. */
64
+ export type ForkRelayResult<T> =
65
+ | { readonly ok: true; readonly value: T }
66
+ | { readonly ok: false; readonly error: string; readonly status?: number };
67
+
68
+ function isForkStatusBody(
69
+ value: unknown,
70
+ ): value is { checkpoint: string; clockMs: string; autoTickMs?: number; upstream?: string } {
71
+ if (value === null || typeof value !== 'object') return false;
72
+ const v = value as Record<string, unknown>;
73
+ return typeof v.checkpoint === 'string' && typeof v.clockMs === 'string';
74
+ }
75
+
76
+ function isImpersonationsBody(
77
+ value: unknown,
78
+ ): value is { slots: { address: string; label?: string; active: boolean }[] } {
79
+ if (value === null || typeof value !== 'object') return false;
80
+ const slots = (value as { slots?: unknown }).slots;
81
+ return Array.isArray(slots);
82
+ }
83
+
84
+ /**
85
+ * Thin HTTP client for the fork admin relay. Stateless — each call
86
+ * issues a fresh `fetch`. The UI layer (`dev-wallet-fork-panel`)
87
+ * polls `getStatus` on a low cadence and only refreshes after action
88
+ * verbs land, so the lack of caching here is intentional.
89
+ *
90
+ * @example
91
+ * const relay = new ForkRelay({ serverOrigin, token });
92
+ * const status = await relay.getStatus();
93
+ * if (status.ok) console.log(`clock=${status.value.clockMs}ms`);
94
+ */
95
+ export class ForkRelay {
96
+ #serverOrigin: string;
97
+ #authToken: string | null;
98
+
99
+ constructor(options: ForkRelayOptions) {
100
+ try {
101
+ new URL(options.serverOrigin);
102
+ } catch {
103
+ throw new Error(`ForkRelay: invalid serverOrigin "${options.serverOrigin}"`);
104
+ }
105
+ this.#serverOrigin = options.serverOrigin;
106
+ this.#authToken = options.token ?? null;
107
+ }
108
+
109
+ get serverOrigin(): string {
110
+ return this.#serverOrigin;
111
+ }
112
+
113
+ async getStatus(): Promise<ForkRelayResult<ForkStatus>> {
114
+ return this.#request('GET', FORK_RELAY_HTTP_PATH.STATUS, undefined, (body) => {
115
+ if (!isForkStatusBody(body)) {
116
+ throw new Error('Malformed fork status response');
117
+ }
118
+ return {
119
+ checkpoint: BigInt(body.checkpoint),
120
+ clockMs: BigInt(body.clockMs),
121
+ autoTickMs: body.autoTickMs,
122
+ upstream: body.upstream,
123
+ };
124
+ });
125
+ }
126
+
127
+ async advanceClock(durationMs: number): Promise<ForkRelayResult<ForkStatus>> {
128
+ if (!Number.isFinite(durationMs) || durationMs <= 0 || !Number.isInteger(durationMs)) {
129
+ return {
130
+ ok: false,
131
+ error: `advanceClock: durationMs must be a positive integer (got ${durationMs})`,
132
+ };
133
+ }
134
+ return this.#request('POST', FORK_RELAY_HTTP_PATH.ADVANCE_CLOCK, { durationMs }, (body) => {
135
+ if (!isForkStatusBody(body)) {
136
+ throw new Error('Malformed advance-clock response');
137
+ }
138
+ return {
139
+ checkpoint: BigInt(body.checkpoint),
140
+ clockMs: BigInt(body.clockMs),
141
+ autoTickMs: body.autoTickMs,
142
+ upstream: body.upstream,
143
+ };
144
+ });
145
+ }
146
+
147
+ async advanceCheckpoint(count: number): Promise<ForkRelayResult<ForkStatus>> {
148
+ if (!Number.isFinite(count) || count <= 0 || !Number.isInteger(count)) {
149
+ return {
150
+ ok: false,
151
+ error: `advanceCheckpoint: count must be a positive integer (got ${count})`,
152
+ };
153
+ }
154
+ return this.#request('POST', FORK_RELAY_HTTP_PATH.ADVANCE_CHECKPOINT, { count }, (body) => {
155
+ if (!isForkStatusBody(body)) {
156
+ throw new Error('Malformed advance-checkpoint response');
157
+ }
158
+ return {
159
+ checkpoint: BigInt(body.checkpoint),
160
+ clockMs: BigInt(body.clockMs),
161
+ autoTickMs: body.autoTickMs,
162
+ upstream: body.upstream,
163
+ };
164
+ });
165
+ }
166
+
167
+ async listImpersonations(): Promise<ForkRelayResult<ForkImpersonationSlot[]>> {
168
+ return this.#request('GET', FORK_RELAY_HTTP_PATH.IMPERSONATIONS, undefined, (body) => {
169
+ if (!isImpersonationsBody(body)) {
170
+ throw new Error('Malformed impersonations response');
171
+ }
172
+ return body.slots.map((s) => ({
173
+ address: s.address,
174
+ label: s.label,
175
+ active: s.active,
176
+ }));
177
+ });
178
+ }
179
+
180
+ /** Toggle the impersonation slot identified by `address`. The server
181
+ * is the source of truth — the response carries the resulting slot
182
+ * list so the panel re-renders consistently. */
183
+ async setImpersonation(
184
+ address: string,
185
+ active: boolean,
186
+ ): Promise<ForkRelayResult<ForkImpersonationSlot[]>> {
187
+ return this.#request(
188
+ 'POST',
189
+ FORK_RELAY_HTTP_PATH.IMPERSONATIONS,
190
+ { address, active },
191
+ (body) => {
192
+ if (!isImpersonationsBody(body)) {
193
+ throw new Error('Malformed impersonations response');
194
+ }
195
+ return body.slots.map((s) => ({
196
+ address: s.address,
197
+ label: s.label,
198
+ active: s.active,
199
+ }));
200
+ },
201
+ );
202
+ }
203
+
204
+ async #request<T>(
205
+ method: 'GET' | 'POST',
206
+ path: string,
207
+ body: unknown,
208
+ parse: (raw: unknown) => T,
209
+ ): Promise<ForkRelayResult<T>> {
210
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
211
+ if (this.#authToken !== null) {
212
+ headers['Authorization'] = `Bearer ${this.#authToken}`;
213
+ }
214
+ const init: RequestInit = { method, headers };
215
+ if (method === 'POST' && body !== undefined) {
216
+ init.body = JSON.stringify(body);
217
+ }
218
+ let res: Response;
219
+ try {
220
+ res = await fetch(`${this.#serverOrigin}${path}`, init);
221
+ } catch (cause) {
222
+ return {
223
+ ok: false,
224
+ error: `Network error: ${cause instanceof Error ? cause.message : String(cause)}`,
225
+ };
226
+ }
227
+ if (!res.ok) {
228
+ const text = await res.text().catch(() => res.statusText);
229
+ return { ok: false, error: text || res.statusText, status: res.status };
230
+ }
231
+ let parsedBody: unknown;
232
+ try {
233
+ parsedBody = await res.json();
234
+ } catch (cause) {
235
+ return {
236
+ ok: false,
237
+ error: `Invalid JSON response: ${cause instanceof Error ? cause.message : String(cause)}`,
238
+ status: res.status,
239
+ };
240
+ }
241
+ try {
242
+ return { ok: true, value: parse(parsedBody) };
243
+ } catch (cause) {
244
+ return {
245
+ ok: false,
246
+ error: cause instanceof Error ? cause.message : String(cause),
247
+ status: res.status,
248
+ };
249
+ }
250
+ }
251
+ }
252
+
253
+ export { ForkRelayHttpError };
254
+
255
+ /** Narrow input mirroring `DevstackAdapterManifest` — enough to
256
+ * discover the wallet endpoint + fork-mode flag without pulling in
257
+ * the full devstack `Manifest` type (no reverse dependency edge,
258
+ * same reasoning as `devstack-adapter.ts`). */
259
+ export interface ForkRelayManifest {
260
+ readonly app?: {
261
+ readonly wallet?: {
262
+ readonly url: string;
263
+ readonly pairUrl?: string;
264
+ };
265
+ };
266
+ readonly meta?: {
267
+ /** Devstack's `meta.runtime` is `'bundled' | 'forked'`. Only
268
+ * `'forked'` triggers fork-controls construction. */
269
+ readonly runtime?: string;
270
+ /** Optional upstream label (`'mainnet'`, `'testnet'`, …) used
271
+ * by the panel banner. */
272
+ readonly upstream?: string;
273
+ };
274
+ }
275
+
276
+ /** Parse the bearer token off the manifest's `pairUrl`. Duplicates the
277
+ * logic in `devstack-adapter.ts` rather than importing it because the
278
+ * fork-relay module must remain dependency-free from the adapter
279
+ * layer (it's also consumed by codegen-emitted glue that doesn't pull
280
+ * in the signer adapter bundle). */
281
+ function tokenFromPairUrl(pairedUrl: string | undefined): string | null {
282
+ if (pairedUrl === undefined) return null;
283
+ try {
284
+ const url = new URL(pairedUrl);
285
+ if (url.hash.length > 1) {
286
+ const params = new URLSearchParams(url.hash.slice(1));
287
+ const fromHash = params.get('token');
288
+ if (fromHash !== null && fromHash !== '<redacted>') return fromHash;
289
+ }
290
+ const fromQuery = url.searchParams.get('token');
291
+ if (fromQuery !== null && fromQuery !== '<redacted>') return fromQuery;
292
+ return null;
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Build a configured {@link ForkRelay} from a devstack manifest, or
300
+ * `null` when fork-mode isn't active (bundled stack, no wallet entry,
301
+ * or the manifest hasn't loaded yet).
302
+ *
303
+ * Codegen-emitted browser glue calls this and forwards the result to
304
+ * `<dev-wallet-panel forkRelay={…}>`.
305
+ */
306
+ export function createForkRelayFromManifest(manifest: ForkRelayManifest): ForkRelay | null {
307
+ if (manifest.meta?.runtime !== 'forked') return null;
308
+ const wallet = manifest.app?.wallet;
309
+ if (wallet === undefined) return null;
310
+ try {
311
+ return new ForkRelay({
312
+ serverOrigin: wallet.url,
313
+ token: tokenFromPairUrl(wallet.pairUrl),
314
+ });
315
+ } catch {
316
+ // `new URL(…)` failure surfaces as a thrown Error — swallow it
317
+ // here rather than propagating, the calling app shouldn't
318
+ // blow up just because the manifest's wallet URL is malformed.
319
+ return null;
320
+ }
321
+ }
@@ -4,6 +4,12 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Dev Wallet</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap"
11
+ rel="stylesheet"
12
+ />
7
13
  <style>
8
14
  * {
9
15
  margin: 0;
@@ -12,29 +18,22 @@
12
18
  }
13
19
  body {
14
20
  font-family:
21
+ 'Geist',
15
22
  ui-sans-serif,
16
23
  system-ui,
17
24
  -apple-system,
18
25
  BlinkMacSystemFont,
19
26
  'Segoe UI',
20
- Roboto,
21
- 'Helvetica Neue',
22
- Arial,
23
27
  sans-serif;
24
- background: oklch(0.175 0.028 283);
25
- color: oklch(0.91 0 0);
28
+ background: #05080f;
29
+ color: #e6eefb;
26
30
  min-height: 100vh;
27
31
  }
28
32
  #app {
29
- display: flex;
30
- flex-direction: column;
31
- align-items: center;
32
- justify-content: center;
33
33
  min-height: 100vh;
34
- padding: 20px;
35
34
  }
36
35
  .loading {
37
- color: oklch(0.62 0.04 280);
36
+ color: #94a6c2;
38
37
  font-size: 14px;
39
38
  }
40
39
  </style>
@@ -4,6 +4,7 @@
4
4
  import { useEffect, useMemo, useRef, useState } from 'react';
5
5
 
6
6
  import { mountDevWallet } from '../ui/mount.js';
7
+ import type { DevWalletDockStyle } from '../ui/dev-wallet-panel.js';
7
8
  import { DevWallet, type DevWalletConfig } from '../wallet/dev-wallet.js';
8
9
 
9
10
  export interface UseDevWalletOptions extends DevWalletConfig {
@@ -13,6 +14,8 @@ export interface UseDevWalletOptions extends DevWalletConfig {
13
14
  createInitialAccount?: boolean;
14
15
  /** Whether to mount the floating wallet drawer UI. Defaults to true. */
15
16
  mountUI?: boolean;
17
+ /** Floating dock presentation when `mountUI` is true. Defaults to `corner-pill`. */
18
+ dockStyle?: DevWalletDockStyle;
16
19
  /** Container element for the UI drawer. Defaults to document.body. */
17
20
  container?: HTMLElement;
18
21
  }
@@ -51,6 +54,7 @@ export function useDevWallet(options: UseDevWalletOptions): UseDevWalletResult {
51
54
  autoInitialize = true,
52
55
  createInitialAccount = true,
53
56
  mountUI = true,
57
+ dockStyle,
54
58
  container,
55
59
  ...walletConfig
56
60
  } = optionsRef.current;
@@ -90,7 +94,7 @@ export function useDevWallet(options: UseDevWalletOptions): UseDevWalletResult {
90
94
  unregister = devWallet.register();
91
95
 
92
96
  if (mountUI && typeof document !== 'undefined') {
93
- unmountUI = mountDevWallet(devWallet, { container });
97
+ unmountUI = mountDevWallet(devWallet, { container, dockStyle });
94
98
  }
95
99
 
96
100
  if (!cancelled) {
@@ -32,17 +32,22 @@ export class DevWalletAccountSelector extends LitElement {
32
32
  .active-account {
33
33
  display: flex;
34
34
  align-items: center;
35
- gap: 8px;
35
+ gap: 10px;
36
36
  width: 100%;
37
- padding: 8px 10px;
38
- border-radius: var(--dev-wallet-radius-md);
39
- background: var(--dev-wallet-secondary);
37
+ min-height: 54px;
38
+ padding: 10px 12px;
39
+ border-radius: var(--dev-wallet-radius-lg);
40
+ border: 1px solid var(--dev-wallet-border);
41
+ background: var(--dev-wallet-surface);
40
42
  cursor: pointer;
41
- transition: background 0.15s;
43
+ transition:
44
+ background 120ms,
45
+ border-color 120ms;
42
46
  }
43
47
 
44
48
  .active-account:hover {
45
- background: var(--dev-wallet-border);
49
+ background: var(--dev-wallet-bg-hover);
50
+ border-color: var(--dev-wallet-border-strong);
46
51
  }
47
52
 
48
53
  .avatar {
@@ -73,26 +78,36 @@ export class DevWalletAccountSelector extends LitElement {
73
78
  text-overflow: ellipsis;
74
79
  }
75
80
 
81
+ .account-label-row {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 6px;
85
+ min-width: 0;
86
+ }
87
+
76
88
  .account-address {
77
89
  font-size: 11px;
78
90
  font-family: var(--dev-wallet-font-mono);
79
- color: var(--dev-wallet-muted-foreground);
91
+ color: var(--dev-wallet-text-3);
80
92
  }
81
93
 
82
94
  .copy-btn {
83
- width: 22px;
84
- height: 22px;
95
+ width: 36px;
96
+ height: 54px;
85
97
  display: flex;
86
98
  align-items: center;
87
99
  justify-content: center;
88
- border-radius: var(--dev-wallet-radius-xs);
100
+ border-radius: var(--dev-wallet-radius);
101
+ border: 1px solid var(--dev-wallet-border);
102
+ background: transparent;
89
103
  color: var(--dev-wallet-muted-foreground);
90
104
  flex-shrink: 0;
91
105
  font-size: 12px;
92
106
  }
93
107
 
94
108
  .copy-btn:hover {
95
- background: var(--dev-wallet-border);
109
+ background: var(--dev-wallet-bg-hover);
110
+ border-color: var(--dev-wallet-border-strong);
96
111
  color: var(--dev-wallet-foreground);
97
112
  }
98
113
 
@@ -108,7 +123,8 @@ export class DevWalletAccountSelector extends LitElement {
108
123
  }
109
124
 
110
125
  .dropdown-item[aria-selected='true'] {
111
- background: color-mix(in oklab, var(--dev-wallet-primary) 10%, transparent);
126
+ background: var(--dev-wallet-accent-fade);
127
+ border-color: var(--dev-wallet-border-strong);
112
128
  }
113
129
 
114
130
  .dropdown-item .avatar {
@@ -136,11 +152,16 @@ export class DevWalletAccountSelector extends LitElement {
136
152
  }
137
153
 
138
154
  .adapter-badge {
155
+ height: 16px;
156
+ display: inline-flex;
157
+ align-items: center;
158
+ padding: 0 5px;
159
+ border-radius: 999px;
160
+ border: 1px solid var(--dev-wallet-border);
161
+ background: var(--dev-wallet-bg-3);
162
+ color: var(--dev-wallet-text-3);
163
+ font-family: var(--dev-wallet-font-mono);
139
164
  font-size: 9px;
140
- padding: 1px 4px;
141
- border-radius: var(--dev-wallet-radius-2xs);
142
- background: var(--dev-wallet-secondary);
143
- color: var(--dev-wallet-muted-foreground);
144
165
  white-space: nowrap;
145
166
  }
146
167
 
@@ -188,6 +209,7 @@ export class DevWalletAccountSelector extends LitElement {
188
209
 
189
210
  const label = this.#getLabel(active);
190
211
  const initial = (label[0] ?? '?').toUpperCase();
212
+ const activeAdapterName = this.#getAdapterName(active.address);
191
213
 
192
214
  return html`
193
215
  <div class="selector-row">
@@ -207,7 +229,12 @@ export class DevWalletAccountSelector extends LitElement {
207
229
  >
208
230
  <span class="avatar">${initial}</span>
209
231
  <div class="account-info">
210
- <div class="account-label">${label}</div>
232
+ <div class="account-label-row">
233
+ <div class="account-label">${label}</div>
234
+ ${activeAdapterName
235
+ ? html`<span class="adapter-badge">${activeAdapterName}</span>`
236
+ : nothing}
237
+ </div>
211
238
  <div class="account-address">${formatAddress(active.address)}</div>
212
239
  </div>
213
240
  <svg