@mysten-incubation/dev-wallet 0.0.1 → 0.3.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.
- package/CHANGELOG.md +139 -0
- package/LICENSE +189 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.107.0 → _@oxc-project_runtime@0.127.0}/helpers/decorate.mjs +2 -3
- package/dist/adapters/base-adapter.mjs +6 -7
- package/dist/adapters/base-adapter.mjs.map +1 -1
- package/dist/adapters/browser.d.mts +4 -1
- package/dist/adapters/browser.mjs +4 -2
- package/dist/adapters/build-managed-account.mjs +8 -10
- package/dist/adapters/build-managed-account.mjs.map +1 -1
- package/dist/adapters/devstack-adapter.d.mts +111 -0
- package/dist/adapters/devstack-adapter.d.mts.map +1 -0
- package/dist/adapters/devstack-adapter.mjs +224 -0
- package/dist/adapters/devstack-adapter.mjs.map +1 -0
- package/dist/adapters/devstack-paths.d.mts +11 -0
- package/dist/adapters/devstack-paths.d.mts.map +1 -0
- package/dist/adapters/devstack-paths.mjs +11 -0
- package/dist/adapters/devstack-paths.mjs.map +1 -0
- package/dist/adapters/fork-relay.d.mts +109 -0
- package/dist/adapters/fork-relay.d.mts.map +1 -0
- package/dist/adapters/fork-relay.mjs +208 -0
- package/dist/adapters/fork-relay.mjs.map +1 -0
- package/dist/adapters/idb-store.mjs +12 -16
- package/dist/adapters/idb-store.mjs.map +1 -1
- package/dist/adapters/in-memory-adapter.mjs +4 -10
- package/dist/adapters/in-memory-adapter.mjs.map +1 -1
- package/dist/adapters/passkey-adapter.mjs +4 -12
- package/dist/adapters/passkey-adapter.mjs.map +1 -1
- package/dist/adapters/remote-cli-adapter.mjs +37 -72
- package/dist/adapters/remote-cli-adapter.mjs.map +1 -1
- package/dist/adapters/webcrypto-adapter.mjs +2 -4
- package/dist/adapters/webcrypto-adapter.mjs.map +1 -1
- package/dist/bin/cli.mjs +9 -22
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/client/dev-wallet-client.mjs +13 -28
- package/dist/client/dev-wallet-client.mjs.map +1 -1
- package/dist/client/index.mjs +1 -2
- package/dist/client/request-handler.mjs +36 -42
- package/dist/client/request-handler.mjs.map +1 -1
- package/dist/index.mjs +1 -2
- package/dist/react/components.d.mts +9 -9
- package/dist/react/components.d.mts.map +1 -1
- package/dist/react/components.mjs +2 -5
- package/dist/react/components.mjs.map +1 -1
- package/dist/react/context.d.mts +3 -3
- package/dist/react/context.d.mts.map +1 -1
- package/dist/react/context.mjs +3 -8
- package/dist/react/context.mjs.map +1 -1
- package/dist/react/index.mjs +1 -2
- package/dist/react/useDevWallet.d.mts +3 -0
- package/dist/react/useDevWallet.d.mts.map +1 -1
- package/dist/react/useDevWallet.mjs +9 -14
- package/dist/react/useDevWallet.mjs.map +1 -1
- package/dist/server/cli-signing-middleware.mjs +7 -20
- package/dist/server/cli-signing-middleware.mjs.map +1 -1
- package/dist/server/index.mjs +1 -2
- package/dist/standalone/assets/{in-memory-adapter-CBF4h2KD.js → in-memory-adapter-qRkNfj9O.js} +1 -1
- package/dist/standalone/assets/main-DDltQnzR.js +3954 -0
- package/dist/standalone/assets/{webcrypto-adapter-DysAubFb.js → webcrypto-adapter-FejcrL9x.js} +1 -1
- package/dist/standalone/index.html +11 -12
- package/dist/ui/copy-controller.mjs +3 -3
- package/dist/ui/copy-controller.mjs.map +1 -1
- package/dist/ui/dev-wallet-account-selector.d.mts +3 -4
- package/dist/ui/dev-wallet-account-selector.d.mts.map +1 -1
- package/dist/ui/dev-wallet-account-selector.mjs +48 -25
- package/dist/ui/dev-wallet-account-selector.mjs.map +1 -1
- package/dist/ui/dev-wallet-accounts.d.mts +9 -4
- package/dist/ui/dev-wallet-accounts.d.mts.map +1 -1
- package/dist/ui/dev-wallet-accounts.mjs +62 -140
- package/dist/ui/dev-wallet-accounts.mjs.map +1 -1
- package/dist/ui/dev-wallet-balances.d.mts +10 -3
- package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
- package/dist/ui/dev-wallet-balances.mjs +241 -43
- package/dist/ui/dev-wallet-balances.mjs.map +1 -1
- package/dist/ui/dev-wallet-connect.d.mts +3 -3
- package/dist/ui/dev-wallet-connect.d.mts.map +1 -1
- package/dist/ui/dev-wallet-connect.mjs +24 -16
- package/dist/ui/dev-wallet-connect.mjs.map +1 -1
- package/dist/ui/dev-wallet-dropdown.d.mts +3 -3
- package/dist/ui/dev-wallet-dropdown.d.mts.map +1 -1
- package/dist/ui/dev-wallet-dropdown.mjs +37 -47
- package/dist/ui/dev-wallet-dropdown.mjs.map +1 -1
- package/dist/ui/dev-wallet-fork-panel.d.mts +43 -0
- package/dist/ui/dev-wallet-fork-panel.d.mts.map +1 -0
- package/dist/ui/dev-wallet-fork-panel.mjs +500 -0
- package/dist/ui/dev-wallet-fork-panel.mjs.map +1 -0
- package/dist/ui/dev-wallet-network-badge.d.mts +3 -4
- package/dist/ui/dev-wallet-network-badge.d.mts.map +1 -1
- package/dist/ui/dev-wallet-network-badge.mjs +52 -21
- package/dist/ui/dev-wallet-network-badge.mjs.map +1 -1
- package/dist/ui/dev-wallet-new-account.d.mts +3 -4
- package/dist/ui/dev-wallet-new-account.d.mts.map +1 -1
- package/dist/ui/dev-wallet-new-account.mjs +31 -105
- package/dist/ui/dev-wallet-new-account.mjs.map +1 -1
- package/dist/ui/dev-wallet-objects.d.mts +3 -3
- package/dist/ui/dev-wallet-objects.d.mts.map +1 -1
- package/dist/ui/dev-wallet-objects.mjs +76 -39
- package/dist/ui/dev-wallet-objects.mjs.map +1 -1
- package/dist/ui/dev-wallet-panel.d.mts +24 -4
- package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
- package/dist/ui/dev-wallet-panel.mjs +247 -64
- package/dist/ui/dev-wallet-panel.mjs.map +1 -1
- package/dist/ui/dev-wallet-popup.d.mts +3 -5
- package/dist/ui/dev-wallet-popup.d.mts.map +1 -1
- package/dist/ui/dev-wallet-popup.mjs +19 -15
- package/dist/ui/dev-wallet-popup.mjs.map +1 -1
- package/dist/ui/dev-wallet-settings.d.mts +3 -4
- package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
- package/dist/ui/dev-wallet-settings.mjs +149 -97
- package/dist/ui/dev-wallet-settings.mjs.map +1 -1
- package/dist/ui/dev-wallet-signing-modal.d.mts +11 -4
- package/dist/ui/dev-wallet-signing-modal.d.mts.map +1 -1
- package/dist/ui/dev-wallet-signing-modal.mjs +46 -18
- package/dist/ui/dev-wallet-signing-modal.mjs.map +1 -1
- package/dist/ui/dev-wallet-signing.d.mts +9 -3
- package/dist/ui/dev-wallet-signing.d.mts.map +1 -1
- package/dist/ui/dev-wallet-signing.mjs +119 -130
- package/dist/ui/dev-wallet-signing.mjs.map +1 -1
- package/dist/ui/dev-wallet-standalone.d.mts +8 -3
- package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
- package/dist/ui/dev-wallet-standalone.mjs +303 -38
- package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
- package/dist/ui/dev-wallet-tab-bar.d.mts +13 -5
- package/dist/ui/dev-wallet-tab-bar.d.mts.map +1 -1
- package/dist/ui/dev-wallet-tab-bar.mjs +143 -82
- package/dist/ui/dev-wallet-tab-bar.mjs.map +1 -1
- package/dist/ui/index.d.mts +3 -2
- package/dist/ui/index.mjs +2 -2
- package/dist/ui/mount.d.mts +14 -1
- package/dist/ui/mount.d.mts.map +1 -1
- package/dist/ui/mount.mjs +5 -2
- package/dist/ui/mount.mjs.map +1 -1
- package/dist/ui/styles.mjs +428 -66
- package/dist/ui/styles.mjs.map +1 -1
- package/dist/ui/transaction-analyzer.mjs +19 -30
- package/dist/ui/transaction-analyzer.mjs.map +1 -1
- package/dist/ui/utils.d.mts +59 -0
- package/dist/ui/utils.d.mts.map +1 -0
- package/dist/ui/utils.mjs +42 -15
- package/dist/ui/utils.mjs.map +1 -1
- package/dist/ui/wallet-controller.mjs +89 -20
- package/dist/ui/wallet-controller.mjs.map +1 -1
- package/dist/wallet/constants.d.mts +9 -0
- package/dist/wallet/constants.d.mts.map +1 -0
- package/dist/wallet/constants.mjs +1 -1
- package/dist/wallet/dev-wallet.mjs +42 -99
- package/dist/wallet/dev-wallet.mjs.map +1 -1
- package/dist/wallet/initializer.d.mts +3 -0
- package/dist/wallet/initializer.d.mts.map +1 -1
- package/dist/wallet/initializer.mjs +9 -14
- package/dist/wallet/initializer.mjs.map +1 -1
- package/dist/wallet/signing.mjs +8 -21
- package/dist/wallet/signing.mjs.map +1 -1
- package/package.json +8 -6
- package/src/adapters/browser.ts +21 -0
- package/src/adapters/devstack-adapter.ts +342 -0
- package/src/adapters/devstack-paths.ts +15 -0
- package/src/adapters/fork-relay.ts +321 -0
- package/src/app/index.html +10 -11
- package/src/react/useDevWallet.ts +5 -1
- package/src/ui/dev-wallet-account-selector.ts +44 -17
- package/src/ui/dev-wallet-accounts.ts +87 -124
- package/src/ui/dev-wallet-balances.ts +248 -27
- package/src/ui/dev-wallet-connect.ts +19 -8
- package/src/ui/dev-wallet-dropdown.ts +7 -5
- package/src/ui/dev-wallet-fork-panel.ts +591 -0
- package/src/ui/dev-wallet-network-badge.ts +49 -10
- package/src/ui/dev-wallet-new-account.ts +21 -62
- package/src/ui/dev-wallet-objects.ts +59 -19
- package/src/ui/dev-wallet-panel.ts +261 -42
- package/src/ui/dev-wallet-popup.ts +12 -6
- package/src/ui/dev-wallet-settings.ts +159 -94
- package/src/ui/dev-wallet-signing-modal.ts +47 -7
- package/src/ui/dev-wallet-signing.ts +124 -97
- package/src/ui/dev-wallet-standalone.ts +310 -22
- package/src/ui/dev-wallet-tab-bar.ts +132 -70
- package/src/ui/index.ts +3 -0
- package/src/ui/mount.ts +23 -1
- package/src/ui/styles.ts +434 -58
- package/src/ui/utils.ts +66 -0
- package/src/ui/wallet-controller.ts +103 -5
- package/src/wallet/initializer.ts +7 -1
- 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
|
+
}
|
package/src/app/index.html
CHANGED
|
@@ -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:
|
|
25
|
-
color:
|
|
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:
|
|
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:
|
|
35
|
+
gap: 10px;
|
|
36
36
|
width: 100%;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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:
|
|
43
|
+
transition:
|
|
44
|
+
background 120ms,
|
|
45
|
+
border-color 120ms;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
.active-account:hover {
|
|
45
|
-
background: var(--dev-wallet-
|
|
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-
|
|
91
|
+
color: var(--dev-wallet-text-3);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
.copy-btn {
|
|
83
|
-
width:
|
|
84
|
-
height:
|
|
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
|
|
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-
|
|
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:
|
|
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"
|
|
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
|