@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.
- package/CHANGELOG.md +41 -0
- package/README.md +12 -8
- package/dist/client/connected-apps.d.mts +35 -0
- package/dist/client/connected-apps.d.mts.map +1 -0
- package/dist/client/connected-apps.mjs +50 -0
- package/dist/client/connected-apps.mjs.map +1 -0
- package/dist/client/dev-wallet-client.mjs +1 -1
- package/dist/client/dev-wallet-client.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -1
- package/dist/client/index.mjs +2 -1
- package/dist/client/request-handler.d.mts +7 -0
- package/dist/client/request-handler.d.mts.map +1 -1
- package/dist/client/request-handler.mjs +17 -2
- package/dist/client/request-handler.mjs.map +1 -1
- package/dist/inject/index.d.mts +4 -0
- package/dist/inject/index.d.mts.map +1 -1
- package/dist/inject/index.mjs +31 -0
- package/dist/inject/index.mjs.map +1 -1
- package/dist/standalone/assets/{in-memory-adapter-Ban2kRYS.js → in-memory-adapter-uuWG4K4k.js} +1 -1
- package/dist/standalone/assets/main-CKYv0eR6.js +7 -0
- package/dist/standalone/assets/{webcrypto-adapter-Ddqn-RAu.js → webcrypto-adapter-5ShaPc_M.js} +1 -1
- package/dist/standalone/bookmarklet.js +1 -1
- package/dist/standalone/index.html +1 -1
- package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
- package/dist/ui/dev-wallet-balances.mjs +20 -13
- package/dist/ui/dev-wallet-balances.mjs.map +1 -1
- package/dist/ui/dev-wallet-connect-guide.d.mts +29 -0
- package/dist/ui/dev-wallet-connect-guide.d.mts.map +1 -0
- package/dist/ui/dev-wallet-connect-guide.mjs +386 -0
- package/dist/ui/dev-wallet-connect-guide.mjs.map +1 -0
- package/dist/ui/dev-wallet-connected-apps.d.mts +27 -0
- package/dist/ui/dev-wallet-connected-apps.d.mts.map +1 -0
- package/dist/ui/dev-wallet-connected-apps.mjs +164 -0
- package/dist/ui/dev-wallet-connected-apps.mjs.map +1 -0
- package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
- package/dist/ui/dev-wallet-panel.mjs +0 -9
- package/dist/ui/dev-wallet-panel.mjs.map +1 -1
- package/dist/ui/dev-wallet-settings.d.mts +8 -2
- package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
- package/dist/ui/dev-wallet-settings.mjs +86 -171
- package/dist/ui/dev-wallet-settings.mjs.map +1 -1
- package/dist/ui/dev-wallet-standalone.d.mts +7 -2
- package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
- package/dist/ui/dev-wallet-standalone.mjs +68 -222
- package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
- package/dist/ui/index.d.mts +3 -1
- package/dist/ui/index.mjs +3 -1
- package/dist/ui/wallet-controller.mjs +15 -3
- package/dist/ui/wallet-controller.mjs.map +1 -1
- package/dist/wallet/dev-wallet.d.mts +15 -3
- package/dist/wallet/dev-wallet.d.mts.map +1 -1
- package/dist/wallet/dev-wallet.mjs +94 -30
- package/dist/wallet/dev-wallet.mjs.map +1 -1
- package/dist/wallet/persisted.mjs +20 -0
- package/dist/wallet/persisted.mjs.map +1 -0
- package/package.json +6 -6
- package/src/app/main.ts +6 -2
- package/src/client/connected-apps.ts +79 -0
- package/src/client/dev-wallet-client.ts +2 -1
- package/src/client/index.ts +2 -0
- package/src/client/request-handler.ts +24 -1
- package/src/inject/index.ts +55 -0
- package/src/ui/dev-wallet-balances.ts +25 -13
- package/src/ui/dev-wallet-connect-guide.ts +428 -0
- package/src/ui/dev-wallet-connected-apps.ts +191 -0
- package/src/ui/dev-wallet-panel.ts +0 -9
- package/src/ui/dev-wallet-settings.ts +101 -179
- package/src/ui/dev-wallet-standalone.ts +80 -231
- package/src/ui/index.ts +2 -0
- package/src/ui/wallet-controller.ts +23 -3
- package/src/wallet/dev-wallet.ts +130 -39
- package/src/wallet/persisted.ts +24 -0
- package/dist/standalone/assets/main-xfzarPod.js +0 -7
package/src/wallet/dev-wallet.ts
CHANGED
|
@@ -24,10 +24,75 @@ import { getWallets, ReadonlyWalletAccount } from '@mysten/wallet-standard';
|
|
|
24
24
|
import { chainsForNetworks } from '../adapters/build-managed-account.js';
|
|
25
25
|
import type { SignerAdapter } from '../types.js';
|
|
26
26
|
import { DEFAULT_WALLET_ICON, getNetworkFromChain, type WalletEventsMap } from './constants.js';
|
|
27
|
+
import { readPersisted } from './persisted.js';
|
|
27
28
|
import { type SigningResult, executeSigning } from './signing.js';
|
|
28
29
|
|
|
29
30
|
const DEFAULT_WALLET_NAME = 'Dev Wallet';
|
|
30
|
-
|
|
31
|
+
/** localStorage key for {@link PersistedStateV1}. Bump the suffix on shape changes. */
|
|
32
|
+
export const STATE_STORAGE_KEY = 'dev-wallet:state:v1';
|
|
33
|
+
|
|
34
|
+
/** Wallet state persisted across reloads when `persistState` is enabled. */
|
|
35
|
+
interface PersistedStateV1 {
|
|
36
|
+
version: 1;
|
|
37
|
+
networks: Record<string, string>;
|
|
38
|
+
faucets?: Record<string, string>;
|
|
39
|
+
activeNetwork?: string;
|
|
40
|
+
activeAccount?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
44
|
+
return (
|
|
45
|
+
typeof value === 'object' &&
|
|
46
|
+
value !== null &&
|
|
47
|
+
!Array.isArray(value) &&
|
|
48
|
+
Object.values(value).every((v) => typeof v === 'string')
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Throws unless `url` is an http(s) URL. `what` names it in the error, e.g. `network "localnet"`. */
|
|
53
|
+
function assertHttpUrl(url: string, what: string): void {
|
|
54
|
+
let parsed: URL;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(url);
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error(`Invalid URL for ${what}: ${url}`);
|
|
59
|
+
}
|
|
60
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
61
|
+
throw new Error(`Invalid URL for ${what}: must use http or https (got ${parsed.protocol})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Config faucets overlaid with persisted ones; `''` marks a removed faucet. */
|
|
66
|
+
function mergeFaucets(
|
|
67
|
+
config: Record<string, string>,
|
|
68
|
+
persisted: Record<string, string> | undefined,
|
|
69
|
+
): Record<string, string> {
|
|
70
|
+
const merged = { ...config, ...persisted };
|
|
71
|
+
for (const [name, url] of Object.entries(merged)) {
|
|
72
|
+
if (url === '') delete merged[name];
|
|
73
|
+
}
|
|
74
|
+
return merged;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function decodePersistedState(parsed: unknown): PersistedStateV1 | null {
|
|
78
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
79
|
+
const { version, networks, faucets, activeNetwork, activeAccount } = parsed as Record<
|
|
80
|
+
string,
|
|
81
|
+
unknown
|
|
82
|
+
>;
|
|
83
|
+
if (version !== 1) return null;
|
|
84
|
+
if (!isStringRecord(networks)) return null;
|
|
85
|
+
if (faucets !== undefined && !isStringRecord(faucets)) return null;
|
|
86
|
+
if (activeNetwork !== undefined && typeof activeNetwork !== 'string') return null;
|
|
87
|
+
if (activeAccount !== undefined && typeof activeAccount !== 'string') return null;
|
|
88
|
+
return {
|
|
89
|
+
version,
|
|
90
|
+
networks,
|
|
91
|
+
faucets,
|
|
92
|
+
activeNetwork,
|
|
93
|
+
activeAccount,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
31
96
|
|
|
32
97
|
/**
|
|
33
98
|
* Default gRPC endpoint URLs for standard Sui networks (devnet, testnet, localnet).
|
|
@@ -100,10 +165,11 @@ export interface DevWalletConfig {
|
|
|
100
165
|
/** Factory to create a client for a given network. Defaults to SuiGrpcClient. */
|
|
101
166
|
clientFactory?: (network: string, url: string) => ClientWithCoreApi;
|
|
102
167
|
/**
|
|
103
|
-
* Persist network URLs
|
|
104
|
-
* and are shared with popup windows.
|
|
168
|
+
* Persist network URLs, the active network, and the UI's active account to
|
|
169
|
+
* localStorage so they survive page reloads and are shared with popup windows.
|
|
170
|
+
* Persisted values take precedence over `networks` and `activeNetwork`.
|
|
105
171
|
*/
|
|
106
|
-
|
|
172
|
+
persistState?: boolean;
|
|
107
173
|
}
|
|
108
174
|
|
|
109
175
|
/**
|
|
@@ -114,6 +180,8 @@ export class DevWallet implements Wallet {
|
|
|
114
180
|
readonly #adapters: SignerAdapter[];
|
|
115
181
|
#networkUrls: Record<string, string>;
|
|
116
182
|
#faucetUrls: Record<string, string>;
|
|
183
|
+
/** Faucets from config; persisted faucets layer over these. */
|
|
184
|
+
readonly #configFaucets: Record<string, string>;
|
|
117
185
|
#clients: Record<string, ClientWithCoreApi>;
|
|
118
186
|
#activeNetwork: string;
|
|
119
187
|
readonly #name: string;
|
|
@@ -121,7 +189,8 @@ export class DevWallet implements Wallet {
|
|
|
121
189
|
readonly #autoApprove: AutoApprovePolicy;
|
|
122
190
|
readonly #autoConnect: boolean;
|
|
123
191
|
readonly #clientFactory: (network: string, url: string) => ClientWithCoreApi;
|
|
124
|
-
readonly #
|
|
192
|
+
readonly #persistState: boolean;
|
|
193
|
+
#activeAccount: string | null;
|
|
125
194
|
#accounts: ReadonlyWalletAccount[];
|
|
126
195
|
#selectedAddress: string | null = null;
|
|
127
196
|
#events: Emitter<WalletEventsMap>;
|
|
@@ -143,11 +212,17 @@ export class DevWallet implements Wallet {
|
|
|
143
212
|
console.warn('[dev-wallet] No adapters provided. The wallet will have no accounts.');
|
|
144
213
|
}
|
|
145
214
|
this.#adapters = config.adapters;
|
|
146
|
-
this.#
|
|
147
|
-
|
|
148
|
-
this.#
|
|
215
|
+
this.#persistState = config.persistState ?? false;
|
|
216
|
+
const persisted = this.#loadState();
|
|
217
|
+
this.#networkUrls = persisted?.networks ?? { ...(config.networks ?? DEFAULT_NETWORK_URLS) };
|
|
218
|
+
this.#configFaucets = { ...config.faucets };
|
|
219
|
+
this.#faucetUrls = mergeFaucets(this.#configFaucets, persisted?.faucets);
|
|
149
220
|
this.#clients = {};
|
|
150
|
-
this.#activeNetwork =
|
|
221
|
+
this.#activeNetwork =
|
|
222
|
+
persisted?.activeNetwork && persisted.activeNetwork in this.#networkUrls
|
|
223
|
+
? persisted.activeNetwork
|
|
224
|
+
: (config.activeNetwork ?? Object.keys(this.#networkUrls)[0] ?? '');
|
|
225
|
+
this.#activeAccount = persisted?.activeAccount ?? null;
|
|
151
226
|
this.#name = config.name ?? DEFAULT_WALLET_NAME;
|
|
152
227
|
this.#icon = config.icon ?? DEFAULT_WALLET_ICON;
|
|
153
228
|
this.#autoApprove = config.autoApprove ?? false;
|
|
@@ -330,25 +405,37 @@ export class DevWallet implements Wallet {
|
|
|
330
405
|
);
|
|
331
406
|
}
|
|
332
407
|
this.#activeNetwork = network;
|
|
408
|
+
this.#saveState();
|
|
333
409
|
this.#events.emit('change', { accounts: this.#exposedAccounts() });
|
|
334
410
|
}
|
|
335
411
|
|
|
412
|
+
/** Address the wallet UI shows as active, or `null` when unset. Persisted
|
|
413
|
+
* with `persistState`. Unlike {@link setSelectedAccount}, this doesn't
|
|
414
|
+
* change which accounts dApps see. */
|
|
415
|
+
get activeAccount(): string | null {
|
|
416
|
+
return this.#activeAccount;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
setActiveAccount(address: string | null): void {
|
|
420
|
+
this.#activeAccount = address;
|
|
421
|
+
this.#saveState();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Add a network or replace its URL. `faucet` sets the network's faucet
|
|
426
|
+
* endpoint; pass `null` to remove it, or omit it to leave it unchanged.
|
|
427
|
+
*/
|
|
336
428
|
addNetwork(name: string, url: string, faucet?: string | null): void {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
parsed = new URL(url);
|
|
340
|
-
} catch {
|
|
341
|
-
throw new Error(`Invalid URL: ${url}`);
|
|
342
|
-
}
|
|
343
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
344
|
-
throw new Error(`Invalid URL: must use http or https (got ${parsed.protocol})`);
|
|
345
|
-
}
|
|
429
|
+
assertHttpUrl(url, `network "${name}"`);
|
|
430
|
+
if (faucet) assertHttpUrl(faucet, `the faucet of network "${name}"`);
|
|
346
431
|
this.#networkUrls[name] = url;
|
|
347
432
|
this.#clients[name] = this.#clientFactory(name, url);
|
|
348
|
-
if (faucet
|
|
433
|
+
if (faucet) {
|
|
349
434
|
this.#faucetUrls[name] = faucet;
|
|
435
|
+
} else if (faucet === null) {
|
|
436
|
+
delete this.#faucetUrls[name];
|
|
350
437
|
}
|
|
351
|
-
this.#
|
|
438
|
+
this.#saveState();
|
|
352
439
|
this.#events.emit('change', { accounts: this.#exposedAccounts() });
|
|
353
440
|
}
|
|
354
441
|
|
|
@@ -359,7 +446,7 @@ export class DevWallet implements Wallet {
|
|
|
359
446
|
if (this.#activeNetwork === name) {
|
|
360
447
|
this.#activeNetwork = Object.keys(this.#networkUrls)[0] ?? '';
|
|
361
448
|
}
|
|
362
|
-
this.#
|
|
449
|
+
this.#saveState();
|
|
363
450
|
this.#events.emit('change', { accounts: this.#exposedAccounts() });
|
|
364
451
|
}
|
|
365
452
|
|
|
@@ -576,27 +663,31 @@ export class DevWallet implements Wallet {
|
|
|
576
663
|
return this.#ensureClient(network);
|
|
577
664
|
}
|
|
578
665
|
|
|
579
|
-
#
|
|
580
|
-
if (!this.#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
} catch {
|
|
592
|
-
// Corrupted data — fall through to defaults
|
|
666
|
+
#loadState(): PersistedStateV1 | null {
|
|
667
|
+
if (!this.#persistState || typeof localStorage === 'undefined') return null;
|
|
668
|
+
return readPersisted(localStorage, STATE_STORAGE_KEY, decodePersistedState);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Current faucets plus an empty-string marker for each config faucet the
|
|
672
|
+
* user removed, so the removal survives a reload. */
|
|
673
|
+
#persistableFaucets(): Record<string, string> {
|
|
674
|
+
const faucets = { ...this.#faucetUrls };
|
|
675
|
+
for (const name of Object.keys(this.#configFaucets)) {
|
|
676
|
+
if (!(name in faucets)) faucets[name] = '';
|
|
593
677
|
}
|
|
594
|
-
return
|
|
678
|
+
return faucets;
|
|
595
679
|
}
|
|
596
680
|
|
|
597
|
-
#
|
|
598
|
-
if (!this.#
|
|
599
|
-
|
|
681
|
+
#saveState(): void {
|
|
682
|
+
if (!this.#persistState || typeof localStorage === 'undefined') return;
|
|
683
|
+
const state: PersistedStateV1 = {
|
|
684
|
+
version: 1,
|
|
685
|
+
networks: this.#networkUrls,
|
|
686
|
+
faucets: this.#persistableFaucets(),
|
|
687
|
+
activeNetwork: this.#activeNetwork,
|
|
688
|
+
...(this.#activeAccount ? { activeAccount: this.#activeAccount } : {}),
|
|
689
|
+
};
|
|
690
|
+
localStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(state));
|
|
600
691
|
}
|
|
601
692
|
|
|
602
693
|
#ensureClient(network: string): ClientWithCoreApi {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read a versioned JSON value from storage. A missing key is a plain miss;
|
|
6
|
+
* unparseable data, or data `decode` rejects (returns `null`), is a miss plus
|
|
7
|
+
* a warning, so a bad write never crashes the wallet.
|
|
8
|
+
*/
|
|
9
|
+
export function readPersisted<T>(
|
|
10
|
+
storage: Storage,
|
|
11
|
+
key: string,
|
|
12
|
+
decode: (value: unknown) => T | null,
|
|
13
|
+
): T | null {
|
|
14
|
+
const raw = storage.getItem(key);
|
|
15
|
+
if (raw === null) return null;
|
|
16
|
+
let value: T | null = null;
|
|
17
|
+
try {
|
|
18
|
+
value = decode(JSON.parse(raw));
|
|
19
|
+
} catch {
|
|
20
|
+
// Invalid JSON — handled below.
|
|
21
|
+
}
|
|
22
|
+
if (value === null) console.warn(`[dev-wallet] Ignoring invalid data in "${key}".`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|