@mysten-incubation/dev-wallet 0.5.1 → 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 +33 -0
- package/README.md +7 -4
- 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/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/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/app/main.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Copyright (c) Mysten Labs, Inc.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import { getFaucetHost } from '@mysten/sui/faucet';
|
|
4
5
|
import { fromBase64, toBase64 } from '@mysten/sui/utils';
|
|
5
6
|
|
|
6
7
|
import { RemoteCliAdapter } from '../adapters/remote-cli-adapter.js';
|
|
7
8
|
import type { SignerAdapter } from '../types.js';
|
|
9
|
+
import { ConnectedAppsStore } from '../client/connected-apps.js';
|
|
8
10
|
import { parseWalletRequest } from '../client/request-handler.js';
|
|
9
11
|
import { getNetworkFromChain } from '../wallet/constants.js';
|
|
10
12
|
import { DevWallet } from '../wallet/dev-wallet.js';
|
|
@@ -97,7 +99,8 @@ async function createWallet(): Promise<DevWallet> {
|
|
|
97
99
|
return new DevWallet({
|
|
98
100
|
adapters,
|
|
99
101
|
activeNetwork: 'devnet',
|
|
100
|
-
|
|
102
|
+
persistState: true,
|
|
103
|
+
faucets: { devnet: getFaucetHost('devnet'), localnet: getFaucetHost('localnet') },
|
|
101
104
|
});
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -112,6 +115,7 @@ async function handlePopupRequest(hash: string) {
|
|
|
112
115
|
const request = parseWalletRequest({
|
|
113
116
|
adapters: [...wallet.adapters],
|
|
114
117
|
jwtSecretKey,
|
|
118
|
+
connectedApps: new ConnectedAppsStore(),
|
|
115
119
|
getClient: (network) => {
|
|
116
120
|
try {
|
|
117
121
|
return wallet.getClient(network);
|
|
@@ -201,7 +205,7 @@ async function showStandaloneUI() {
|
|
|
201
205
|
|
|
202
206
|
const el = document.createElement('dev-wallet-standalone') as DevWalletStandalone;
|
|
203
207
|
el.wallet = wallet;
|
|
204
|
-
el.
|
|
208
|
+
el.connectedApps = new ConnectedAppsStore();
|
|
205
209
|
app.appendChild(el);
|
|
206
210
|
} catch (error) {
|
|
207
211
|
showErrorMessage(app, 'Failed to initialize wallet', error);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { readPersisted } from '../wallet/persisted.js';
|
|
5
|
+
|
|
6
|
+
/** localStorage key for the connected-apps list. Bump the suffix on shape changes. */
|
|
7
|
+
export const CONNECTED_APPS_STORAGE_KEY = 'dev-wallet:connected-apps:v1';
|
|
8
|
+
|
|
9
|
+
/** A dApp the user approved a connection for in the standalone wallet. */
|
|
10
|
+
export interface ConnectedApp {
|
|
11
|
+
/** The dApp's origin — the identity sessions are scoped to. */
|
|
12
|
+
origin: string;
|
|
13
|
+
/** Display name the dApp sent (its page title). */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Addresses shared with the dApp. */
|
|
16
|
+
accounts: string[];
|
|
17
|
+
/** Milliseconds since epoch of the latest approval. */
|
|
18
|
+
connectedAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isConnectedApp(value: unknown): value is ConnectedApp {
|
|
22
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
23
|
+
const app = value as Record<string, unknown>;
|
|
24
|
+
return (
|
|
25
|
+
typeof app.origin === 'string' &&
|
|
26
|
+
typeof app.name === 'string' &&
|
|
27
|
+
Array.isArray(app.accounts) &&
|
|
28
|
+
app.accounts.every((a) => typeof a === 'string') &&
|
|
29
|
+
typeof app.connectedAt === 'number'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Connected dApps for a standalone wallet, stored in the wallet origin's
|
|
35
|
+
* localStorage so the popup (which records connections) and the wallet page
|
|
36
|
+
* (which lists them) share it. Removing an app makes the popup reject that
|
|
37
|
+
* origin's signing requests until it reconnects.
|
|
38
|
+
*/
|
|
39
|
+
export class ConnectedAppsStore {
|
|
40
|
+
readonly #storage: Storage;
|
|
41
|
+
|
|
42
|
+
constructor(storage: Storage = localStorage) {
|
|
43
|
+
this.#storage = storage;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Connected apps, most recently connected first. */
|
|
47
|
+
list(): ConnectedApp[] {
|
|
48
|
+
const apps = readPersisted(this.#storage, CONNECTED_APPS_STORAGE_KEY, (value) =>
|
|
49
|
+
Array.isArray(value) && value.every(isConnectedApp) ? value : null,
|
|
50
|
+
);
|
|
51
|
+
return (apps ?? []).sort((a, b) => b.connectedAt - a.connectedAt);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
has(origin: string): boolean {
|
|
55
|
+
return this.list().some((app) => app.origin === origin);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Add an app, or replace the entry for its origin. */
|
|
59
|
+
record(app: ConnectedApp): void {
|
|
60
|
+
this.#write([app, ...this.list().filter((a) => a.origin !== app.origin)]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
remove(origin: string): void {
|
|
64
|
+
this.#write(this.list().filter((a) => a.origin !== origin));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Call `callback` when another window (e.g. the signing popup) changes the list. */
|
|
68
|
+
subscribe(callback: () => void): () => void {
|
|
69
|
+
const onStorage = (e: StorageEvent) => {
|
|
70
|
+
if (e.key === CONNECTED_APPS_STORAGE_KEY || e.key === null) callback();
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener('storage', onStorage);
|
|
73
|
+
return () => window.removeEventListener('storage', onStorage);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#write(apps: ConnectedApp[]): void {
|
|
77
|
+
this.#storage.setItem(CONNECTED_APPS_STORAGE_KEY, JSON.stringify(apps));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -223,7 +223,8 @@ export class DevWalletClient implements Wallet {
|
|
|
223
223
|
|
|
224
224
|
#createChannel(): DappPostMessageChannel {
|
|
225
225
|
return new DappPostMessageChannel({
|
|
226
|
-
|
|
226
|
+
// Identifies the requesting dApp in the wallet's approval popup.
|
|
227
|
+
appName: globalThis.document?.title || globalThis.location?.host || this.#name,
|
|
227
228
|
hostOrigin: this.#origin,
|
|
228
229
|
});
|
|
229
230
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -5,3 +5,5 @@ export { DevWalletClient, devWalletClientInitializer } from './dev-wallet-client
|
|
|
5
5
|
export type { DevWalletClientOptions } from './dev-wallet-client.js';
|
|
6
6
|
export { parseWalletRequest } from './request-handler.js';
|
|
7
7
|
export type { PendingWalletRequest, HandleRequestOptions } from './request-handler.js';
|
|
8
|
+
export { ConnectedAppsStore, CONNECTED_APPS_STORAGE_KEY } from './connected-apps.js';
|
|
9
|
+
export type { ConnectedApp } from './connected-apps.js';
|
|
@@ -6,6 +6,7 @@ import { fromBase64, toBase64 } from '@mysten/sui/utils';
|
|
|
6
6
|
import { createJwtSession, WalletPostMessageChannel } from '@mysten/window-wallet-core';
|
|
7
7
|
|
|
8
8
|
import type { SignerAdapter } from '../types.js';
|
|
9
|
+
import type { ConnectedAppsStore } from './connected-apps.js';
|
|
9
10
|
import { getNetworkFromChain } from '../wallet/constants.js';
|
|
10
11
|
import { executeSigning } from '../wallet/signing.js';
|
|
11
12
|
|
|
@@ -41,6 +42,12 @@ export interface HandleRequestOptions {
|
|
|
41
42
|
getClient?: (network: string) => ClientWithCoreApi | undefined;
|
|
42
43
|
/** URL hash containing the encoded request. Defaults to window.location.hash. */
|
|
43
44
|
hash?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Tracks connected dApps. When set, approved connects are recorded here and
|
|
47
|
+
* signing requests from an origin that isn't in the store are rejected, so
|
|
48
|
+
* removing an app disconnects it.
|
|
49
|
+
*/
|
|
50
|
+
connectedApps?: ConnectedAppsStore;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export function parseWalletRequest(options: HandleRequestOptions): PendingWalletRequest {
|
|
@@ -53,6 +60,7 @@ export function parseWalletRequest(options: HandleRequestOptions): PendingWallet
|
|
|
53
60
|
const channel = WalletPostMessageChannel.fromUrlHash(hash);
|
|
54
61
|
const requestData = channel.getRequestData();
|
|
55
62
|
const payload = requestData.payload;
|
|
63
|
+
const appOrigin = new URL(requestData.appUrl).origin;
|
|
56
64
|
|
|
57
65
|
if (payload.type === 'connect') {
|
|
58
66
|
return {
|
|
@@ -78,10 +86,17 @@ export function parseWalletRequest(options: HandleRequestOptions): PendingWallet
|
|
|
78
86
|
secretKey: options.jwtSecretKey,
|
|
79
87
|
expirationTime: '7d',
|
|
80
88
|
issuer: 'dev-wallet',
|
|
81
|
-
audience:
|
|
89
|
+
audience: appOrigin,
|
|
82
90
|
},
|
|
83
91
|
);
|
|
84
92
|
|
|
93
|
+
options.connectedApps?.record({
|
|
94
|
+
origin: appOrigin,
|
|
95
|
+
name: requestData.appName,
|
|
96
|
+
accounts: accounts.map((a) => a.address),
|
|
97
|
+
connectedAt: Date.now(),
|
|
98
|
+
});
|
|
99
|
+
|
|
85
100
|
channel.sendMessage({
|
|
86
101
|
type: 'resolve',
|
|
87
102
|
data: { type: 'connect', session },
|
|
@@ -116,6 +131,14 @@ export function parseWalletRequest(options: HandleRequestOptions): PendingWallet
|
|
|
116
131
|
return;
|
|
117
132
|
}
|
|
118
133
|
|
|
134
|
+
if (options.connectedApps && !options.connectedApps.has(appOrigin)) {
|
|
135
|
+
channel.sendMessage({
|
|
136
|
+
type: 'reject',
|
|
137
|
+
reason: `${appOrigin} was disconnected from the wallet. Reconnect to continue.`,
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
119
142
|
let account;
|
|
120
143
|
for (const adapter of options.adapters) {
|
|
121
144
|
account = adapter.getAccount(payload.address);
|
|
@@ -117,7 +117,6 @@ export class DevWalletBalances extends LitElement {
|
|
|
117
117
|
margin-bottom: 0;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
.manage-btn,
|
|
121
120
|
.action-btn {
|
|
122
121
|
height: 30px;
|
|
123
122
|
padding: 0 10px;
|
|
@@ -129,18 +128,12 @@ export class DevWalletBalances extends LitElement {
|
|
|
129
128
|
font-weight: var(--dev-wallet-font-weight-medium);
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
.manage-btn:hover,
|
|
133
131
|
.action-btn:hover:not(:disabled) {
|
|
134
132
|
background: var(--dev-wallet-bg-hover);
|
|
135
133
|
border-color: var(--dev-wallet-border-strong);
|
|
136
134
|
color: var(--dev-wallet-foreground);
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
.manage-btn:disabled {
|
|
140
|
-
opacity: 0.48;
|
|
141
|
-
cursor: not-allowed;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
137
|
.balance-hero {
|
|
145
138
|
display: flex;
|
|
146
139
|
align-items: center;
|
|
@@ -210,6 +203,10 @@ export class DevWalletBalances extends LitElement {
|
|
|
210
203
|
color: var(--dev-wallet-foreground);
|
|
211
204
|
}
|
|
212
205
|
|
|
206
|
+
.empty-copy a {
|
|
207
|
+
color: var(--dev-wallet-foreground);
|
|
208
|
+
}
|
|
209
|
+
|
|
213
210
|
.empty-copy {
|
|
214
211
|
margin-top: 6px;
|
|
215
212
|
font-size: 12px;
|
|
@@ -271,14 +268,25 @@ export class DevWalletBalances extends LitElement {
|
|
|
271
268
|
}
|
|
272
269
|
|
|
273
270
|
/** Request SUI from the active network's faucet for the active address,
|
|
274
|
-
* then refresh balances. No-op
|
|
275
|
-
* mainnet / fork stacks have none). */
|
|
271
|
+
* wait for the faucet's transfers to land, then refresh balances. No-op
|
|
272
|
+
* when no `faucetHost` is configured (live mainnet / fork stacks have none). */
|
|
276
273
|
async #requestFromFaucet() {
|
|
277
274
|
if (!this.faucetHost || !this.address || this._funding) return;
|
|
278
275
|
this._funding = true;
|
|
279
276
|
this._fundError = null;
|
|
280
277
|
try {
|
|
281
|
-
await requestSuiFromFaucetV2({
|
|
278
|
+
const result = await requestSuiFromFaucetV2({
|
|
279
|
+
host: this.faucetHost,
|
|
280
|
+
recipient: this.address,
|
|
281
|
+
});
|
|
282
|
+
const client = this.client;
|
|
283
|
+
if (client) {
|
|
284
|
+
const digests = new Set(result.coins_sent?.map((c) => c.transferTxDigest) ?? []);
|
|
285
|
+
// Best effort: the funds were sent even if waiting times out.
|
|
286
|
+
await Promise.allSettled(
|
|
287
|
+
[...digests].map((digest) => client.core.waitForTransaction({ digest, timeout: 30_000 })),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
282
290
|
this.refresh();
|
|
283
291
|
} catch (error) {
|
|
284
292
|
this._fundError =
|
|
@@ -346,7 +354,6 @@ export class DevWalletBalances extends LitElement {
|
|
|
346
354
|
: nothing}
|
|
347
355
|
<div class="coins-header">
|
|
348
356
|
<h3 class="section-header">Coins · ${this._balances.length}</h3>
|
|
349
|
-
<button class="manage-btn" disabled>Manage</button>
|
|
350
357
|
</div>
|
|
351
358
|
${this._loading
|
|
352
359
|
? html`<div class="loading" part="loading" aria-live="polite">Loading...</div>`
|
|
@@ -361,8 +368,13 @@ export class DevWalletBalances extends LitElement {
|
|
|
361
368
|
${this.faucetHost
|
|
362
369
|
? html`Use the Faucet button above to fund ${formatAddress(this.address)} on
|
|
363
370
|
${networkLabel}, then refresh.`
|
|
364
|
-
:
|
|
365
|
-
|
|
371
|
+
: this.network === 'testnet'
|
|
372
|
+
? html`Get testnet SUI for ${formatAddress(this.address)} at
|
|
373
|
+
<a href="https://faucet.sui.io" target="_blank" rel="noopener noreferrer"
|
|
374
|
+
>faucet.sui.io</a
|
|
375
|
+
>, then refresh.`
|
|
376
|
+
: html`Fund ${formatAddress(this.address)} from your local faucet or devstack
|
|
377
|
+
seed, then refresh balances here.`}
|
|
366
378
|
</div>
|
|
367
379
|
</div>`
|
|
368
380
|
: html`
|