@mysten-incubation/dev-wallet 0.2.0 → 0.4.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 +167 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.127.0/helpers → _@oxc-project_runtime@0.135.0/helpers/esm}/decorate.mjs +1 -1
- package/dist/adapters/base-adapter.d.mts.map +1 -1
- package/dist/adapters/base-adapter.mjs.map +1 -1
- package/dist/adapters/build-managed-account.mjs.map +1 -1
- package/dist/adapters/devstack-adapter.d.mts.map +1 -1
- package/dist/adapters/devstack-adapter.mjs.map +1 -1
- package/dist/adapters/devstack-paths.d.mts +0 -1
- package/dist/adapters/devstack-paths.d.mts.map +1 -1
- package/dist/adapters/devstack-paths.mjs +1 -2
- package/dist/adapters/devstack-paths.mjs.map +1 -1
- package/dist/adapters/fork-relay.d.mts.map +1 -1
- package/dist/adapters/fork-relay.mjs.map +1 -1
- package/dist/adapters/idb-store.mjs.map +1 -1
- package/dist/adapters/in-memory-adapter.d.mts.map +1 -1
- package/dist/adapters/in-memory-adapter.mjs.map +1 -1
- package/dist/adapters/passkey-adapter.d.mts.map +1 -1
- package/dist/adapters/passkey-adapter.mjs.map +1 -1
- package/dist/adapters/remote-cli-adapter.d.mts.map +1 -1
- package/dist/adapters/remote-cli-adapter.mjs.map +1 -1
- package/dist/adapters/webcrypto-adapter.d.mts.map +1 -1
- package/dist/adapters/webcrypto-adapter.mjs.map +1 -1
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/client/dev-wallet-client.d.mts.map +1 -1
- package/dist/client/dev-wallet-client.mjs.map +1 -1
- package/dist/client/request-handler.d.mts.map +1 -1
- package/dist/client/request-handler.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/inject/index.d.mts +61 -0
- package/dist/inject/index.d.mts.map +1 -0
- package/dist/inject/index.mjs +61 -0
- package/dist/inject/index.mjs.map +1 -0
- package/dist/react/components.d.mts +6 -8
- package/dist/react/components.d.mts.map +1 -1
- package/dist/react/components.mjs.map +1 -1
- package/dist/react/context.d.mts +2 -3
- package/dist/react/context.d.mts.map +1 -1
- package/dist/react/context.mjs.map +1 -1
- package/dist/react/useDevWallet.d.mts +3 -11
- package/dist/react/useDevWallet.d.mts.map +1 -1
- package/dist/react/useDevWallet.mjs +14 -36
- package/dist/react/useDevWallet.mjs.map +1 -1
- package/dist/server/cli-signing-middleware.d.mts.map +1 -1
- package/dist/server/cli-signing-middleware.mjs.map +1 -1
- package/dist/standalone/assets/base-adapter-DET6CP7z.js +1 -0
- package/dist/standalone/assets/in-memory-adapter-CX7xym4e.js +1 -0
- package/dist/standalone/assets/main-CuOnBmaa.js +7 -0
- package/dist/standalone/assets/webcrypto-adapter-CUQmbHDy.js +1 -0
- package/dist/standalone/bookmarklet.js +5 -5
- package/dist/standalone/index.html +2 -1
- package/dist/types.d.mts.map +1 -1
- package/dist/ui/copy-controller.mjs.map +1 -1
- package/dist/ui/dev-wallet-account-selector.d.mts +2 -3
- package/dist/ui/dev-wallet-account-selector.d.mts.map +1 -1
- package/dist/ui/dev-wallet-account-selector.mjs +1 -1
- package/dist/ui/dev-wallet-account-selector.mjs.map +1 -1
- package/dist/ui/dev-wallet-accounts.d.mts +2 -3
- package/dist/ui/dev-wallet-accounts.d.mts.map +1 -1
- package/dist/ui/dev-wallet-accounts.mjs +1 -1
- package/dist/ui/dev-wallet-accounts.mjs.map +1 -1
- package/dist/ui/dev-wallet-balances.d.mts +2 -3
- package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
- package/dist/ui/dev-wallet-balances.mjs +1 -1
- package/dist/ui/dev-wallet-balances.mjs.map +1 -1
- package/dist/ui/dev-wallet-connect.d.mts +2 -3
- package/dist/ui/dev-wallet-connect.d.mts.map +1 -1
- package/dist/ui/dev-wallet-connect.mjs +1 -1
- package/dist/ui/dev-wallet-connect.mjs.map +1 -1
- package/dist/ui/dev-wallet-dropdown.d.mts +2 -3
- package/dist/ui/dev-wallet-dropdown.d.mts.map +1 -1
- package/dist/ui/dev-wallet-dropdown.mjs +1 -1
- package/dist/ui/dev-wallet-dropdown.mjs.map +1 -1
- package/dist/ui/dev-wallet-fork-panel.d.mts +2 -3
- package/dist/ui/dev-wallet-fork-panel.d.mts.map +1 -1
- package/dist/ui/dev-wallet-fork-panel.mjs +1 -1
- package/dist/ui/dev-wallet-fork-panel.mjs.map +1 -1
- package/dist/ui/dev-wallet-network-badge.d.mts +2 -3
- package/dist/ui/dev-wallet-network-badge.d.mts.map +1 -1
- package/dist/ui/dev-wallet-network-badge.mjs +1 -1
- package/dist/ui/dev-wallet-network-badge.mjs.map +1 -1
- package/dist/ui/dev-wallet-new-account.d.mts +2 -3
- package/dist/ui/dev-wallet-new-account.d.mts.map +1 -1
- package/dist/ui/dev-wallet-new-account.mjs +1 -1
- package/dist/ui/dev-wallet-new-account.mjs.map +1 -1
- package/dist/ui/dev-wallet-objects.d.mts +2 -3
- package/dist/ui/dev-wallet-objects.d.mts.map +1 -1
- package/dist/ui/dev-wallet-objects.mjs +1 -1
- package/dist/ui/dev-wallet-objects.mjs.map +1 -1
- package/dist/ui/dev-wallet-panel.d.mts +2 -3
- package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
- package/dist/ui/dev-wallet-panel.mjs +1 -1
- package/dist/ui/dev-wallet-panel.mjs.map +1 -1
- package/dist/ui/dev-wallet-popup.d.mts +2 -3
- package/dist/ui/dev-wallet-popup.d.mts.map +1 -1
- package/dist/ui/dev-wallet-popup.mjs +1 -1
- package/dist/ui/dev-wallet-popup.mjs.map +1 -1
- package/dist/ui/dev-wallet-settings.d.mts +2 -3
- package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
- package/dist/ui/dev-wallet-settings.mjs +1 -1
- package/dist/ui/dev-wallet-settings.mjs.map +1 -1
- package/dist/ui/dev-wallet-signing-modal.d.mts +2 -3
- package/dist/ui/dev-wallet-signing-modal.d.mts.map +1 -1
- package/dist/ui/dev-wallet-signing-modal.mjs +1 -1
- package/dist/ui/dev-wallet-signing-modal.mjs.map +1 -1
- package/dist/ui/dev-wallet-signing.d.mts +2 -3
- package/dist/ui/dev-wallet-signing.d.mts.map +1 -1
- package/dist/ui/dev-wallet-signing.mjs +2 -1
- package/dist/ui/dev-wallet-signing.mjs.map +1 -1
- package/dist/ui/dev-wallet-standalone.d.mts +2 -3
- package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
- package/dist/ui/dev-wallet-standalone.mjs +1 -1
- package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
- package/dist/ui/dev-wallet-tab-bar.d.mts +1 -2
- package/dist/ui/dev-wallet-tab-bar.d.mts.map +1 -1
- package/dist/ui/dev-wallet-tab-bar.mjs +1 -1
- package/dist/ui/dev-wallet-tab-bar.mjs.map +1 -1
- package/dist/ui/mount.mjs.map +1 -1
- package/dist/ui/styles.mjs.map +1 -1
- package/dist/ui/transaction-analyzer.mjs.map +1 -1
- package/dist/ui/utils.d.mts.map +1 -1
- package/dist/ui/utils.mjs.map +1 -1
- package/dist/ui/wallet-controller.mjs.map +1 -1
- package/dist/wallet/constants.d.mts.map +1 -1
- package/dist/wallet/constants.mjs.map +1 -1
- package/dist/wallet/dev-wallet.d.mts +19 -0
- package/dist/wallet/dev-wallet.d.mts.map +1 -1
- package/dist/wallet/dev-wallet.mjs +58 -2
- package/dist/wallet/dev-wallet.mjs.map +1 -1
- package/dist/wallet/initializer.d.mts.map +1 -1
- package/dist/wallet/initializer.mjs +10 -26
- package/dist/wallet/initializer.mjs.map +1 -1
- package/dist/wallet/mount-and-register.d.mts +44 -0
- package/dist/wallet/mount-and-register.d.mts.map +1 -0
- package/dist/wallet/mount-and-register.mjs +46 -0
- package/dist/wallet/mount-and-register.mjs.map +1 -0
- package/dist/wallet/signing.d.mts.map +1 -1
- package/dist/wallet/signing.mjs.map +1 -1
- package/package.json +26 -22
- package/src/adapters/devstack-paths.ts +0 -1
- package/src/index.ts +5 -0
- package/src/inject/index.ts +183 -0
- package/src/react/useDevWallet.ts +26 -71
- package/src/ui/dev-wallet-signing.ts +2 -0
- package/src/wallet/dev-wallet.ts +75 -2
- package/src/wallet/initializer.ts +16 -42
- package/src/wallet/mount-and-register.ts +93 -0
- package/dist/standalone/assets/in-memory-adapter-qRkNfj9O.js +0 -1
- package/dist/standalone/assets/main-DDltQnzR.js +0 -3954
- package/dist/standalone/assets/webcrypto-adapter-FejcrL9x.js +0 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DevWallet } from "./dev-wallet.mjs";
|
|
2
|
+
//#region src/wallet/mount-and-register.ts
|
|
3
|
+
/**
|
|
4
|
+
* Construct a {@link DevWallet} from adapters, initialize the adapters, mount
|
|
5
|
+
* the drawer UI, register it with the wallet-standard registry, and return the
|
|
6
|
+
* wallet plus a single `dispose()` that tears all of that down again.
|
|
7
|
+
*
|
|
8
|
+
* This is the imperative "bring a dev wallet up on the page" path shared by the
|
|
9
|
+
* React hook (`useDevWallet`), the dApp-Kit initializer (`devWalletInitializer`),
|
|
10
|
+
* and the devstack page-register entry (`src/inject`). Those call sites only
|
|
11
|
+
* differ in HOW they obtain `adapters` / `networks` — the construct → init →
|
|
12
|
+
* mount → register → dispose sequence is identical, so it lives here once.
|
|
13
|
+
*/
|
|
14
|
+
async function mountAndRegisterDevWallet(options) {
|
|
15
|
+
const { autoInitialize = true, createInitialAccount = false, mountUI = true, dockStyle, container, onWalletCreated, ...walletConfig } = options;
|
|
16
|
+
if (autoInitialize) await Promise.all(walletConfig.adapters.map((a) => a.initialize()));
|
|
17
|
+
if (createInitialAccount) {
|
|
18
|
+
if (!walletConfig.adapters.some((a) => a.getAccounts().length > 0)) {
|
|
19
|
+
const creatableAdapter = walletConfig.adapters.find((a) => a.createAccount && a.getAccounts().length === 0);
|
|
20
|
+
if (creatableAdapter?.createAccount) await creatableAdapter.createAccount({ label: "Dev Account" });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const wallet = new DevWallet(walletConfig);
|
|
24
|
+
onWalletCreated?.(wallet);
|
|
25
|
+
let unmountUI;
|
|
26
|
+
if (mountUI && typeof document !== "undefined") {
|
|
27
|
+
const { mountDevWallet } = await import("../ui/mount.mjs");
|
|
28
|
+
unmountUI = mountDevWallet(wallet, {
|
|
29
|
+
container,
|
|
30
|
+
dockStyle
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const unregister = wallet.register();
|
|
34
|
+
return {
|
|
35
|
+
wallet,
|
|
36
|
+
dispose() {
|
|
37
|
+
unmountUI?.();
|
|
38
|
+
unregister();
|
|
39
|
+
wallet.destroy();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { mountAndRegisterDevWallet };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=mount-and-register.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-and-register.mjs","names":[],"sources":["../../src/wallet/mount-and-register.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { DevWalletDockStyle } from '../ui/dev-wallet-panel.js';\nimport { DevWallet, type DevWalletConfig } from './dev-wallet.js';\n\nexport interface MountAndRegisterDevWalletOptions extends DevWalletConfig {\n\t/** Call `adapter.initialize()` on every adapter before registering. Defaults to true. */\n\tautoInitialize?: boolean;\n\t/**\n\t * Create an initial account after initialization when no adapter has any\n\t * accounts (uses the first adapter that supports `createAccount`). Defaults\n\t * to false — most managed/remote adapters (devstack, CLI) bring their own\n\t * accounts and must NOT have a throwaway one fabricated.\n\t */\n\tcreateInitialAccount?: boolean;\n\t/** Mount the floating wallet drawer UI (no-op when `document` is undefined). Defaults to true. */\n\tmountUI?: boolean;\n\t/** Floating dock presentation when `mountUI` is true. */\n\tdockStyle?: DevWalletDockStyle;\n\t/** Container element for the UI drawer. Defaults to `document.body`. */\n\tcontainer?: HTMLElement;\n\t/** Called with the `DevWallet` once constructed, before the UI is mounted\n\t * and the wallet is registered. */\n\tonWalletCreated?: (wallet: DevWallet) => void;\n}\n\nexport interface MountAndRegisterDevWalletResult {\n\treadonly wallet: DevWallet;\n\t/** Unmount the UI, unregister from wallet-standard, and destroy the wallet (+ adapters). */\n\treadonly dispose: () => void;\n}\n\n/**\n * Construct a {@link DevWallet} from adapters, initialize the adapters, mount\n * the drawer UI, register it with the wallet-standard registry, and return the\n * wallet plus a single `dispose()` that tears all of that down again.\n *\n * This is the imperative \"bring a dev wallet up on the page\" path shared by the\n * React hook (`useDevWallet`), the dApp-Kit initializer (`devWalletInitializer`),\n * and the devstack page-register entry (`src/inject`). Those call sites only\n * differ in HOW they obtain `adapters` / `networks` — the construct → init →\n * mount → register → dispose sequence is identical, so it lives here once.\n */\nexport async function mountAndRegisterDevWallet(\n\toptions: MountAndRegisterDevWalletOptions,\n): Promise<MountAndRegisterDevWalletResult> {\n\tconst {\n\t\tautoInitialize = true,\n\t\tcreateInitialAccount = false,\n\t\tmountUI = true,\n\t\tdockStyle,\n\t\tcontainer,\n\t\tonWalletCreated,\n\t\t...walletConfig\n\t} = options;\n\n\tif (autoInitialize) {\n\t\tawait Promise.all(walletConfig.adapters.map((a) => a.initialize()));\n\t}\n\n\tif (createInitialAccount) {\n\t\tconst hasAccounts = walletConfig.adapters.some((a) => a.getAccounts().length > 0);\n\t\tif (!hasAccounts) {\n\t\t\tconst creatableAdapter = walletConfig.adapters.find(\n\t\t\t\t(a) => a.createAccount && a.getAccounts().length === 0,\n\t\t\t);\n\t\t\tif (creatableAdapter?.createAccount) {\n\t\t\t\tawait creatableAdapter.createAccount({ label: 'Dev Account' });\n\t\t\t}\n\t\t}\n\t}\n\n\tconst wallet = new DevWallet(walletConfig);\n\tonWalletCreated?.(wallet);\n\n\tlet unmountUI: (() => void) | undefined;\n\tif (mountUI && typeof document !== 'undefined') {\n\t\tconst { mountDevWallet } = await import('../ui/mount.js');\n\t\tunmountUI = mountDevWallet(wallet, { container, dockStyle });\n\t}\n\n\tconst unregister = wallet.register();\n\n\treturn {\n\t\twallet,\n\t\tdispose() {\n\t\t\tunmountUI?.();\n\t\t\tunregister();\n\t\t\twallet.destroy();\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;AA4CA,eAAsB,0BACrB,SAC2C;CAC3C,MAAM,EACL,iBAAiB,MACjB,uBAAuB,OACvB,UAAU,MACV,WACA,WACA,iBACA,GAAG,iBACA;CAEJ,IAAI,gBACH,MAAM,QAAQ,IAAI,aAAa,SAAS,KAAK,MAAM,EAAE,WAAW,CAAC,CAAC;CAGnE,IAAI,sBAEH;MAAI,CADgB,aAAa,SAAS,MAAM,MAAM,EAAE,YAAY,CAAC,CAAC,SAAS,CAC1E,GAAa;GACjB,MAAM,mBAAmB,aAAa,SAAS,MAC7C,MAAM,EAAE,iBAAiB,EAAE,YAAY,CAAC,CAAC,WAAW,CACtD;GACA,IAAI,kBAAkB,eACrB,MAAM,iBAAiB,cAAc,EAAE,OAAO,cAAc,CAAC;EAE/D;;CAGD,MAAM,SAAS,IAAI,UAAU,YAAY;CACzC,kBAAkB,MAAM;CAExB,IAAI;CACJ,IAAI,WAAW,OAAO,aAAa,aAAa;EAC/C,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,YAAY,eAAe,QAAQ;GAAE;GAAW;EAAU,CAAC;CAC5D;CAEA,MAAM,aAAa,OAAO,SAAS;CAEnC,OAAO;EACN;EACA,UAAU;GACT,YAAY;GACZ,WAAW;GACX,OAAO,QAAQ;EAChB;CACD;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing.d.mts","names":[],"sources":["../../src/wallet/signing.ts"],"mappings":";;;;KAQK,kBAAA;AAAA,UAKK,qBAAA;EACT,IAAA,EAAM,kBAAA;EACN,MAAA,EAAQ,MAAA;EACR,IAAA,WAAe,UAAA;EACf,MAAA,GAAS,iBAAA;AAAA;AAAA,KAGE,aAAA;EACP,IAAA;EAA+B,KAAA;EAAe,SAAA;AAAA;EAC9C,IAAA;EAA0B,KAAA;EAAe,SAAA;AAAA;EAE3C,IAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,iBAImB,cAAA
|
|
1
|
+
{"version":3,"file":"signing.d.mts","names":[],"sources":["../../src/wallet/signing.ts"],"mappings":";;;;KAQK,kBAAA;AAAA,UAKK,qBAAA;EACT,IAAA,EAAM,kBAAA;EACN,MAAA,EAAQ,MAAA;EACR,IAAA,WAAe,UAAA;EACf,MAAA,GAAS,iBAAA;AAAA;AAAA,KAGE,aAAA;EACP,IAAA;EAA+B,KAAA;EAAe,SAAA;AAAA;EAC9C,IAAA;EAA0B,KAAA;EAAe,SAAA;AAAA;EAE3C,IAAA;EACA,KAAA;EACA,SAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,iBAImB,cAAA;EACrB,IAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACE,qBAAA,GAAwB,OAAA,CAAQ,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing.mjs","names":[],"sources":["../../src/wallet/signing.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { Transaction } from '@mysten/sui/transactions';\nimport { fromBase64, toBase64 } from '@mysten/sui/utils';\n\ntype SigningRequestType =\n\t| 'sign-personal-message'\n\t| 'sign-transaction'\n\t| 'sign-and-execute-transaction';\n\ninterface ExecuteSigningOptions {\n\ttype: SigningRequestType;\n\tsigner: Signer;\n\tdata: string | Uint8Array;\n\tclient?: ClientWithCoreApi;\n}\n\nexport type SigningResult =\n\t| { type: 'sign-personal-message'; bytes: string; signature: string }\n\t| { type: 'sign-transaction'; bytes: string; signature: string }\n\t| {\n\t\t\ttype: 'sign-and-execute-transaction';\n\t\t\tbytes: string;\n\t\t\tsignature: string;\n\t\t\tdigest: string;\n\t\t\teffects: string;\n\t };\n\n// Shared between the embedded DevWallet and the standalone popup request-handler.\nexport async function executeSigning({\n\ttype,\n\tsigner,\n\tdata,\n\tclient,\n}: ExecuteSigningOptions): Promise<SigningResult> {\n\tswitch (type) {\n\t\tcase 'sign-personal-message': {\n\t\t\tif (!(data instanceof Uint8Array)) {\n\t\t\t\tthrow new Error('sign-personal-message expects data to be a Uint8Array');\n\t\t\t}\n\t\t\tconst { bytes, signature } = await signer.signPersonalMessage(data);\n\t\t\treturn { type, bytes, signature };\n\t\t}\n\t\tcase 'sign-transaction': {\n\t\t\tif (!client) throw new Error('No client provided for sign-transaction');\n\t\t\tif (typeof data !== 'string') {\n\t\t\t\tthrow new Error('sign-transaction expects data to be a JSON string');\n\t\t\t}\n\t\t\tconst tx = Transaction.from(data);\n\t\t\tconst { bytes, signature } = await tx.sign({ client, signer });\n\t\t\treturn { type, bytes, signature };\n\t\t}\n\t\tcase 'sign-and-execute-transaction': {\n\t\t\tif (!client) throw new Error('No client provided for sign-and-execute-transaction');\n\t\t\tif (typeof data !== 'string') {\n\t\t\t\tthrow new Error('sign-and-execute-transaction expects data to be a JSON string');\n\t\t\t}\n\t\t\tconst tx = Transaction.from(data);\n\t\t\tconst { bytes, signature } = await tx.sign({ client, signer });\n\t\t\tconst result = await client.core.executeTransaction({\n\t\t\t\ttransaction: fromBase64(bytes),\n\t\t\t\tsignatures: [signature],\n\t\t\t\tinclude: { effects: true },\n\t\t\t});\n\t\t\tconst txResult = result.Transaction ?? result.FailedTransaction;\n\t\t\tif (!txResult) {\n\t\t\t\tthrow new Error('Transaction execution returned empty result');\n\t\t\t}\n\t\t\tif (!txResult.status.success) {\n\t\t\t\tthrow new Error(txResult.status.error?.message ?? 'Transaction execution failed');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype,\n\t\t\t\tbytes,\n\t\t\t\tsignature,\n\t\t\t\tdigest: txResult.digest,\n\t\t\t\t// Empty string when effects BCS is unavailable — the wallet-standard protocol requires a string\n\t\t\t\teffects: txResult.effects?.bcs ? toBase64(txResult.effects.bcs) : '',\n\t\t\t};\n\t\t}\n\t}\n}\n"],"mappings":";;;AAgCA,eAAsB,eAAe,EACpC,MACA,QACA,MACA,UACiD;
|
|
1
|
+
{"version":3,"file":"signing.mjs","names":[],"sources":["../../src/wallet/signing.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport type { Signer } from '@mysten/sui/cryptography';\nimport { Transaction } from '@mysten/sui/transactions';\nimport { fromBase64, toBase64 } from '@mysten/sui/utils';\n\ntype SigningRequestType =\n\t| 'sign-personal-message'\n\t| 'sign-transaction'\n\t| 'sign-and-execute-transaction';\n\ninterface ExecuteSigningOptions {\n\ttype: SigningRequestType;\n\tsigner: Signer;\n\tdata: string | Uint8Array;\n\tclient?: ClientWithCoreApi;\n}\n\nexport type SigningResult =\n\t| { type: 'sign-personal-message'; bytes: string; signature: string }\n\t| { type: 'sign-transaction'; bytes: string; signature: string }\n\t| {\n\t\t\ttype: 'sign-and-execute-transaction';\n\t\t\tbytes: string;\n\t\t\tsignature: string;\n\t\t\tdigest: string;\n\t\t\teffects: string;\n\t };\n\n// Shared between the embedded DevWallet and the standalone popup request-handler.\nexport async function executeSigning({\n\ttype,\n\tsigner,\n\tdata,\n\tclient,\n}: ExecuteSigningOptions): Promise<SigningResult> {\n\tswitch (type) {\n\t\tcase 'sign-personal-message': {\n\t\t\tif (!(data instanceof Uint8Array)) {\n\t\t\t\tthrow new Error('sign-personal-message expects data to be a Uint8Array');\n\t\t\t}\n\t\t\tconst { bytes, signature } = await signer.signPersonalMessage(data);\n\t\t\treturn { type, bytes, signature };\n\t\t}\n\t\tcase 'sign-transaction': {\n\t\t\tif (!client) throw new Error('No client provided for sign-transaction');\n\t\t\tif (typeof data !== 'string') {\n\t\t\t\tthrow new Error('sign-transaction expects data to be a JSON string');\n\t\t\t}\n\t\t\tconst tx = Transaction.from(data);\n\t\t\tconst { bytes, signature } = await tx.sign({ client, signer });\n\t\t\treturn { type, bytes, signature };\n\t\t}\n\t\tcase 'sign-and-execute-transaction': {\n\t\t\tif (!client) throw new Error('No client provided for sign-and-execute-transaction');\n\t\t\tif (typeof data !== 'string') {\n\t\t\t\tthrow new Error('sign-and-execute-transaction expects data to be a JSON string');\n\t\t\t}\n\t\t\tconst tx = Transaction.from(data);\n\t\t\tconst { bytes, signature } = await tx.sign({ client, signer });\n\t\t\tconst result = await client.core.executeTransaction({\n\t\t\t\ttransaction: fromBase64(bytes),\n\t\t\t\tsignatures: [signature],\n\t\t\t\tinclude: { effects: true },\n\t\t\t});\n\t\t\tconst txResult = result.Transaction ?? result.FailedTransaction;\n\t\t\tif (!txResult) {\n\t\t\t\tthrow new Error('Transaction execution returned empty result');\n\t\t\t}\n\t\t\tif (!txResult.status.success) {\n\t\t\t\tthrow new Error(txResult.status.error?.message ?? 'Transaction execution failed');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttype,\n\t\t\t\tbytes,\n\t\t\t\tsignature,\n\t\t\t\tdigest: txResult.digest,\n\t\t\t\t// Empty string when effects BCS is unavailable — the wallet-standard protocol requires a string\n\t\t\t\teffects: txResult.effects?.bcs ? toBase64(txResult.effects.bcs) : '',\n\t\t\t};\n\t\t}\n\t}\n}\n"],"mappings":";;;AAgCA,eAAsB,eAAe,EACpC,MACA,QACA,MACA,UACiD;CACjD,QAAQ,MAAR;EACC,KAAK,yBAAyB;GAC7B,IAAI,EAAE,gBAAgB,aACrB,MAAM,IAAI,MAAM,uDAAuD;GAExE,MAAM,EAAE,OAAO,cAAc,MAAM,OAAO,oBAAoB,IAAI;GAClE,OAAO;IAAE;IAAM;IAAO;GAAU;EACjC;EACA,KAAK,oBAAoB;GACxB,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yCAAyC;GACtE,IAAI,OAAO,SAAS,UACnB,MAAM,IAAI,MAAM,mDAAmD;GAGpE,MAAM,EAAE,OAAO,cAAc,MADlB,YAAY,KAAK,IACO,CAAA,CAAG,KAAK;IAAE;IAAQ;GAAO,CAAC;GAC7D,OAAO;IAAE;IAAM;IAAO;GAAU;EACjC;EACA,KAAK,gCAAgC;GACpC,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,qDAAqD;GAClF,IAAI,OAAO,SAAS,UACnB,MAAM,IAAI,MAAM,+DAA+D;GAGhF,MAAM,EAAE,OAAO,cAAc,MADlB,YAAY,KAAK,IACO,CAAA,CAAG,KAAK;IAAE;IAAQ;GAAO,CAAC;GAC7D,MAAM,SAAS,MAAM,OAAO,KAAK,mBAAmB;IACnD,aAAa,WAAW,KAAK;IAC7B,YAAY,CAAC,SAAS;IACtB,SAAS,EAAE,SAAS,KAAK;GAC1B,CAAC;GACD,MAAM,WAAW,OAAO,eAAe,OAAO;GAC9C,IAAI,CAAC,UACJ,MAAM,IAAI,MAAM,6CAA6C;GAE9D,IAAI,CAAC,SAAS,OAAO,SACpB,MAAM,IAAI,MAAM,SAAS,OAAO,OAAO,WAAW,8BAA8B;GAEjF,OAAO;IACN;IACA;IACA;IACA,QAAQ,SAAS;IAEjB,SAAS,SAAS,SAAS,MAAM,SAAS,SAAS,QAAQ,GAAG,IAAI;GACnE;EACD;CACD;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mysten-incubation/dev-wallet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A modular dev wallet for Sui",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Mysten Labs <build@mystenlabs.com>",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./dist/adapters/browser.d.mts",
|
|
20
20
|
"import": "./dist/adapters/browser.mjs"
|
|
21
21
|
},
|
|
22
|
+
"./inject": {
|
|
23
|
+
"types": "./dist/inject/index.d.mts",
|
|
24
|
+
"import": "./dist/inject/index.mjs"
|
|
25
|
+
},
|
|
22
26
|
"./ui": {
|
|
23
27
|
"types": "./dist/ui/index.d.mts",
|
|
24
28
|
"import": "./dist/ui/index.mjs"
|
|
@@ -63,34 +67,34 @@
|
|
|
63
67
|
"homepage": "https://github.com/MystenLabs/ts-sdks-incubation/tree/main/packages/dev-wallet#readme",
|
|
64
68
|
"devDependencies": {
|
|
65
69
|
"@lit/react": "^1.0.8",
|
|
66
|
-
"@mysten/signers": "^1.0
|
|
67
|
-
"@mysten/sui": "^2.
|
|
70
|
+
"@mysten/signers": "^1.1.0",
|
|
71
|
+
"@mysten/sui": "^2.18.0",
|
|
68
72
|
"@testing-library/dom": "^10.4.1",
|
|
69
73
|
"@testing-library/react": "^16.3.1",
|
|
70
|
-
"@types/node": "^25.
|
|
71
|
-
"@types/react": "^19.2.
|
|
72
|
-
"@vitest/browser": "^4.1.
|
|
73
|
-
"@vitest/browser-playwright": "^4.1.
|
|
74
|
-
"happy-dom": "^20.
|
|
75
|
-
"playwright": "
|
|
76
|
-
"react": "^19.2.
|
|
77
|
-
"react-dom": "^19.2.
|
|
78
|
-
"typescript": "^
|
|
79
|
-
"vite": "^
|
|
80
|
-
"vitest": "^4.1.
|
|
74
|
+
"@types/node": "^25.9.3",
|
|
75
|
+
"@types/react": "^19.2.17",
|
|
76
|
+
"@vitest/browser": "^4.1.8",
|
|
77
|
+
"@vitest/browser-playwright": "^4.1.8",
|
|
78
|
+
"happy-dom": "^20.10.3",
|
|
79
|
+
"playwright": "^1.60.0",
|
|
80
|
+
"react": "^19.2.7",
|
|
81
|
+
"react-dom": "^19.2.7",
|
|
82
|
+
"typescript": "^6.0.3",
|
|
83
|
+
"vite": "^8.0.16",
|
|
84
|
+
"vitest": "^4.1.8"
|
|
81
85
|
},
|
|
82
86
|
"dependencies": {
|
|
83
|
-
"@hono/node-server": "^
|
|
84
|
-
"@mysten/utils": "^0.
|
|
85
|
-
"@mysten/wallet-sdk": "^0.
|
|
86
|
-
"@mysten/wallet-standard": "^0.
|
|
87
|
-
"@mysten/window-wallet-core": "^0.
|
|
88
|
-
"hono": "^4.12.
|
|
89
|
-
"lit": "^3.3.
|
|
87
|
+
"@hono/node-server": "^2.0.4",
|
|
88
|
+
"@mysten/utils": "^0.4.0",
|
|
89
|
+
"@mysten/wallet-sdk": "^0.4.0",
|
|
90
|
+
"@mysten/wallet-standard": "^0.21.0",
|
|
91
|
+
"@mysten/window-wallet-core": "^0.2.0",
|
|
92
|
+
"hono": "^4.12.25",
|
|
93
|
+
"lit": "^3.3.3"
|
|
90
94
|
},
|
|
91
95
|
"peerDependencies": {
|
|
92
96
|
"@lit/react": "^1.0.8",
|
|
93
|
-
"@mysten/signers": "^1.0
|
|
97
|
+
"@mysten/signers": "^1.1.0",
|
|
94
98
|
"@mysten/sui": "^2.17.0",
|
|
95
99
|
"@types/react": ">=17.0.0",
|
|
96
100
|
"react": ">=17.0.0"
|
|
@@ -9,7 +9,6 @@ export const DEVSTACK_WALLET_HTTP_PATH = {
|
|
|
9
9
|
ACCOUNTS: '/api/v1/devstack/accounts',
|
|
10
10
|
SIGN_TRANSACTION: '/api/v1/devstack/sign-transaction',
|
|
11
11
|
SIGN_PERSONAL_MESSAGE: '/api/v1/devstack/sign-personal-message',
|
|
12
|
-
EXECUTE: '/api/v1/devstack/execute',
|
|
13
12
|
} as const;
|
|
14
13
|
|
|
15
14
|
export type DevstackWalletHttpPathValue =
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
export { DevWallet, DEFAULT_NETWORK_URLS } from './wallet/dev-wallet.js';
|
|
5
|
+
export { mountAndRegisterDevWallet } from './wallet/mount-and-register.js';
|
|
6
|
+
export type {
|
|
7
|
+
MountAndRegisterDevWalletOptions,
|
|
8
|
+
MountAndRegisterDevWalletResult,
|
|
9
|
+
} from './wallet/mount-and-register.js';
|
|
5
10
|
export { devWalletInitializer } from './wallet/initializer.js';
|
|
6
11
|
export type { DevWalletInitializerConfig } from './wallet/initializer.js';
|
|
7
12
|
export type { SigningResult } from './wallet/signing.js';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Page-register entry for the devstack dev wallet.
|
|
5
|
+
//
|
|
6
|
+
// This is the single function the devstack Vite plugin injects into a dev
|
|
7
|
+
// page. It builds the token-based `DevstackSignerAdapter` (headless — signs
|
|
8
|
+
// over HTTP, no popup) + the routed-RPC network map, then hands those to the
|
|
9
|
+
// shared `mountAndRegisterDevWallet` core helper, which constructs the
|
|
10
|
+
// `DevWallet`, registers it via wallet-standard (so dApp Kit auto-discovers
|
|
11
|
+
// it), and mounts the floating wallet UI. The ONLY logic that lives here is
|
|
12
|
+
// the devstack-integration glue: the adapter-from-config and the routed-RPC
|
|
13
|
+
// network map.
|
|
14
|
+
//
|
|
15
|
+
// The wallet does NOT pre-connect or seed dApp Kit storage: a fresh page
|
|
16
|
+
// loads disconnected, dApp Kit's own `autoConnect` only re-connects a genuine
|
|
17
|
+
// prior session, and the Playwright `connectAs` helper performs an explicit
|
|
18
|
+
// connection through dApp Kit's public API. That test bridge is registered
|
|
19
|
+
// app-side via `@mysten-incubation/devstack/dapp-kit`'s
|
|
20
|
+
// `registerDAppKitForTesting(dAppKit)` (DEV-only) — it needs the app's dApp
|
|
21
|
+
// Kit instance, which the wallet has no reference to.
|
|
22
|
+
|
|
23
|
+
import type { AutoApprovePolicy, DevWallet } from '../wallet/dev-wallet.js';
|
|
24
|
+
import { mountAndRegisterDevWallet } from '../wallet/mount-and-register.js';
|
|
25
|
+
import { DevstackSignerAdapter } from '../adapters/devstack-adapter.js';
|
|
26
|
+
import { WebCryptoSignerAdapter } from '../adapters/webcrypto-adapter.js';
|
|
27
|
+
|
|
28
|
+
/** Shape of a single entry in the generated `accounts` map
|
|
29
|
+
* (`generated-extras/accounts.ts`). Only `address` is consumed here. */
|
|
30
|
+
export interface DevstackAccountInfo {
|
|
31
|
+
readonly address: string;
|
|
32
|
+
readonly name?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RegisterDevstackDevWalletConfig {
|
|
36
|
+
/** Wallet-app origin (`devWallet.walletUrl`). */
|
|
37
|
+
readonly serverOrigin: string;
|
|
38
|
+
/** Bearer token (`parseDevstackToken(devWallet.pairUrl)`), or null. */
|
|
39
|
+
readonly token?: string | null;
|
|
40
|
+
/** Generated name→account map (`accounts` from `generated-extras/accounts.ts`). */
|
|
41
|
+
readonly accounts: Readonly<Record<string, DevstackAccountInfo>>;
|
|
42
|
+
/** RPC endpoint the wallet uses to execute `signAndExecuteTransaction`
|
|
43
|
+
* (and simulate). MUST be the SAME routed RPC the app's dApp Kit client
|
|
44
|
+
* uses (`config.networks[config.network].rpc`) — a raw `127.0.0.1:9000`
|
|
45
|
+
* is CORS-blocked from the routed page origin. When omitted the wallet
|
|
46
|
+
* falls back to localnet defaults (only correct for non-routed setups). */
|
|
47
|
+
readonly rpcUrl?: string;
|
|
48
|
+
/** Network name the wallet's accounts are scoped to (e.g. `'localnet'`
|
|
49
|
+
* from `devWallet.network`). The wallet advertises the wallet-standard
|
|
50
|
+
* chain `sui:<network>` derived from it; defaults to `localnet`. */
|
|
51
|
+
readonly network?: string;
|
|
52
|
+
/** Auto-approve all signing requests (headless e2e). Defaults to false. */
|
|
53
|
+
readonly autoApprove?: AutoApprovePolicy;
|
|
54
|
+
/** Mount the floating wallet drawer UI. Defaults to true. */
|
|
55
|
+
readonly mountUI?: boolean;
|
|
56
|
+
/** Wallet display name. Defaults to `'Devstack'`. */
|
|
57
|
+
readonly name?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RegisterDevstackDevWalletResult {
|
|
61
|
+
readonly wallet: DevWallet;
|
|
62
|
+
/** Unregister the wallet, unmount the UI, and tear down the adapter. */
|
|
63
|
+
readonly dispose: () => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Global marker so tests / the plugin can assert injection happened. */
|
|
67
|
+
declare global {
|
|
68
|
+
// eslint-disable-next-line no-var
|
|
69
|
+
var __DEV_WALLET_INJECTED__: boolean | undefined;
|
|
70
|
+
/** In-flight (or settled) registration promise. Published BEFORE the first
|
|
71
|
+
* `await` so concurrent evaluations (HMR / double import) coalesce onto a
|
|
72
|
+
* single registration instead of each constructing + registering + mounting
|
|
73
|
+
* their own wallet. See `registerDevstackDevWallet`. */
|
|
74
|
+
// eslint-disable-next-line no-var
|
|
75
|
+
var __devstackDevWalletPromise__: Promise<RegisterDevstackDevWalletResult> | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Construct + register the devstack dev wallet on the current page and wire
|
|
80
|
+
* the Playwright `connectAs` slot. Idempotent: a second call is a no-op and
|
|
81
|
+
* returns the existing instance.
|
|
82
|
+
*
|
|
83
|
+
* Idempotency is enforced SYNCHRONOUSLY: an in-flight registration promise is
|
|
84
|
+
* published on `globalThis.__devstackDevWalletPromise__` before the first
|
|
85
|
+
* `await`, so two evaluations that race in before the first one finishes both
|
|
86
|
+
* resolve to the SAME wallet instead of double-registering.
|
|
87
|
+
*/
|
|
88
|
+
export function registerDevstackDevWallet(
|
|
89
|
+
config: RegisterDevstackDevWalletConfig,
|
|
90
|
+
): Promise<RegisterDevstackDevWalletResult> {
|
|
91
|
+
const g = globalThis as {
|
|
92
|
+
__devstackDevWallet__?: RegisterDevstackDevWalletResult;
|
|
93
|
+
__devstackDevWalletPromise__?: Promise<RegisterDevstackDevWalletResult>;
|
|
94
|
+
};
|
|
95
|
+
// Fast path: a prior call already completed.
|
|
96
|
+
if (g.__devstackDevWallet__ !== undefined) return Promise.resolve(g.__devstackDevWallet__);
|
|
97
|
+
// In-flight path: a prior call is still booting — share its promise. This
|
|
98
|
+
// guard runs before any `await`, so it closes the double-init window that a
|
|
99
|
+
// post-registration-only marker leaves open under HMR / double import.
|
|
100
|
+
if (g.__devstackDevWalletPromise__ !== undefined) return g.__devstackDevWalletPromise__;
|
|
101
|
+
|
|
102
|
+
const promise = registerDevstackDevWalletImpl(config);
|
|
103
|
+
g.__devstackDevWalletPromise__ = promise;
|
|
104
|
+
// On failure, clear the in-flight promise so a later call can retry instead
|
|
105
|
+
// of being stuck awaiting a rejected registration.
|
|
106
|
+
promise.catch(() => {
|
|
107
|
+
if (g.__devstackDevWalletPromise__ === promise) {
|
|
108
|
+
g.__devstackDevWalletPromise__ = undefined;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return promise;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function registerDevstackDevWalletImpl(
|
|
115
|
+
config: RegisterDevstackDevWalletConfig,
|
|
116
|
+
): Promise<RegisterDevstackDevWalletResult> {
|
|
117
|
+
const { mountUI = true, autoApprove = false } = config;
|
|
118
|
+
globalThis.__DEV_WALLET_INJECTED__ = true;
|
|
119
|
+
|
|
120
|
+
const adapter = new DevstackSignerAdapter({
|
|
121
|
+
serverOrigin: config.serverOrigin,
|
|
122
|
+
token: config.token ?? null,
|
|
123
|
+
name: config.name ?? 'Devstack',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// The wallet EXECUTES (and simulates) `signAndExecuteTransaction` with
|
|
127
|
+
// its OWN client — adapter signing is HTTP (server-side keys), but
|
|
128
|
+
// execution goes through the wallet's network client. That client MUST
|
|
129
|
+
// hit the same routed RPC the app's dApp Kit uses (a raw 127.0.0.1 RPC
|
|
130
|
+
// is CORS-blocked from the routed page origin).
|
|
131
|
+
//
|
|
132
|
+
// Expose a SINGLE network — the active stack's — so the wallet's network
|
|
133
|
+
// switcher shows one entry that matches dApp Kit, not a list of unused
|
|
134
|
+
// devnet/testnet/mainnet. `mountAndRegisterDevWallet` advertises the
|
|
135
|
+
// wallet-standard chain `sui:<network>` derived from this key, matching the
|
|
136
|
+
// `sui:<network>` dApp Kit forwards for signing — the `sui:` prefix lives
|
|
137
|
+
// only here, at the wallet-standard boundary.
|
|
138
|
+
const rpcUrl = config.rpcUrl ?? 'http://127.0.0.1:9000';
|
|
139
|
+
const activeNetwork = config.network ?? 'localnet';
|
|
140
|
+
const networks: Record<string, string> = { [activeNetwork]: rpcUrl };
|
|
141
|
+
|
|
142
|
+
// Delegate the construct → init-adapter → mount-UI → register → dispose
|
|
143
|
+
// sequence to the shared dev-wallet core helper. Two adapters: the
|
|
144
|
+
// `DevstackSignerAdapter` brings the stack's server-resolved accounts
|
|
145
|
+
// (alice/bob/carol — headless HTTP signing), and a `WebCryptoSignerAdapter`
|
|
146
|
+
// lets the user create their OWN accounts in the wallet UI, persisted in
|
|
147
|
+
// IndexedDB across reloads (non-extractable WebCrypto keys, NOT in-memory).
|
|
148
|
+
// `createInitialAccount` stays off — the devstack adapter already supplies
|
|
149
|
+
// accounts, so we never fabricate a throwaway key; the WebCrypto adapter
|
|
150
|
+
// starts empty until the user adds one.
|
|
151
|
+
const { wallet, dispose: disposeWallet } = await mountAndRegisterDevWallet({
|
|
152
|
+
adapters: [adapter, new WebCryptoSignerAdapter()],
|
|
153
|
+
name: config.name ?? 'Devstack',
|
|
154
|
+
autoApprove,
|
|
155
|
+
// Auto-approve `standard:connect` whenever signing is auto-approved — both
|
|
156
|
+
// are the headless-e2e signal (`DEVSTACK_AUTO_APPROVE`). The test bridge's
|
|
157
|
+
// explicit `connectWallet(...)` invokes the wallet's connect; without this
|
|
158
|
+
// it would queue a pending request and block on UI approval that never
|
|
159
|
+
// comes. In normal dev (no auto-approve) a human approves the connect.
|
|
160
|
+
autoConnect: Boolean(autoApprove),
|
|
161
|
+
networks,
|
|
162
|
+
activeNetwork,
|
|
163
|
+
mountUI,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const result: RegisterDevstackDevWalletResult = {
|
|
167
|
+
wallet,
|
|
168
|
+
dispose() {
|
|
169
|
+
disposeWallet();
|
|
170
|
+
globalThis.__DEV_WALLET_INJECTED__ = false;
|
|
171
|
+
delete (globalThis as { __devstackDevWallet__?: unknown }).__devstackDevWallet__;
|
|
172
|
+
// Clear the in-flight/settled registration promise too, so a
|
|
173
|
+
// subsequent `registerDevstackDevWallet` re-initializes rather than
|
|
174
|
+
// handing back the disposed instance.
|
|
175
|
+
delete (globalThis as { __devstackDevWalletPromise__?: unknown })
|
|
176
|
+
.__devstackDevWalletPromise__;
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
(
|
|
180
|
+
globalThis as { __devstackDevWallet__?: RegisterDevstackDevWalletResult }
|
|
181
|
+
).__devstackDevWallet__ = result;
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
@@ -3,21 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
6
|
+
import type { DevWallet } from '../wallet/dev-wallet.js';
|
|
7
|
+
import {
|
|
8
|
+
mountAndRegisterDevWallet,
|
|
9
|
+
type MountAndRegisterDevWalletOptions,
|
|
10
|
+
} from '../wallet/mount-and-register.js';
|
|
9
11
|
|
|
10
|
-
export interface UseDevWalletOptions extends
|
|
11
|
-
/** Whether to call adapter.initialize() automatically. Defaults to true. */
|
|
12
|
-
autoInitialize?: boolean;
|
|
12
|
+
export interface UseDevWalletOptions extends MountAndRegisterDevWalletOptions {
|
|
13
13
|
/** Whether to create an initial account after initialization (if adapter supports it). Defaults to true. */
|
|
14
14
|
createInitialAccount?: boolean;
|
|
15
|
-
/** Whether to mount the floating wallet drawer UI. Defaults to true. */
|
|
16
|
-
mountUI?: boolean;
|
|
17
|
-
/** Floating dock presentation when `mountUI` is true. Defaults to `corner-pill`. */
|
|
18
|
-
dockStyle?: DevWalletDockStyle;
|
|
19
|
-
/** Container element for the UI drawer. Defaults to document.body. */
|
|
20
|
-
container?: HTMLElement;
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
export interface UseDevWalletResult {
|
|
@@ -50,71 +44,32 @@ export function useDevWallet(options: UseDevWalletOptions): UseDevWalletResult {
|
|
|
50
44
|
optionsRef.current = options;
|
|
51
45
|
|
|
52
46
|
useEffect(() => {
|
|
53
|
-
const {
|
|
54
|
-
autoInitialize = true,
|
|
55
|
-
createInitialAccount = true,
|
|
56
|
-
mountUI = true,
|
|
57
|
-
dockStyle,
|
|
58
|
-
container,
|
|
59
|
-
...walletConfig
|
|
60
|
-
} = optionsRef.current;
|
|
61
|
-
|
|
62
47
|
let cancelled = false;
|
|
63
|
-
let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function setup() {
|
|
76
|
-
if (autoInitialize) {
|
|
77
|
-
await Promise.all(walletConfig.adapters.map((a) => a.initialize()));
|
|
78
|
-
}
|
|
79
|
-
if (cancelled) return;
|
|
80
|
-
|
|
81
|
-
if (createInitialAccount) {
|
|
82
|
-
const hasAccounts = walletConfig.adapters.some((a) => a.getAccounts().length > 0);
|
|
83
|
-
if (!hasAccounts) {
|
|
84
|
-
const creatableAdapter = walletConfig.adapters.find(
|
|
85
|
-
(a) => a.createAccount && a.getAccounts().length === 0,
|
|
86
|
-
);
|
|
87
|
-
if (creatableAdapter?.createAccount) {
|
|
88
|
-
await creatableAdapter.createAccount({ label: 'Dev Account' });
|
|
89
|
-
}
|
|
48
|
+
let dispose: (() => void) | undefined;
|
|
49
|
+
|
|
50
|
+
// The hook keeps `createInitialAccount` defaulting to TRUE: its typical
|
|
51
|
+
// adapters (WebCrypto / InMemory) start empty and need a starter account,
|
|
52
|
+
// whereas the core helper defaults it off for managed/remote adapters.
|
|
53
|
+
mountAndRegisterDevWallet({ createInitialAccount: true, ...optionsRef.current })
|
|
54
|
+
.then((result) => {
|
|
55
|
+
if (cancelled) {
|
|
56
|
+
result.dispose();
|
|
57
|
+
return;
|
|
90
58
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
unregister = devWallet.register();
|
|
95
|
-
|
|
96
|
-
if (mountUI && typeof document !== 'undefined') {
|
|
97
|
-
unmountUI = mountDevWallet(devWallet, { container, dockStyle });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (!cancelled) {
|
|
101
|
-
setWallet(devWallet);
|
|
59
|
+
dispose = result.dispose;
|
|
60
|
+
setWallet(result.wallet);
|
|
102
61
|
setLoading(false);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
});
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
if (!cancelled) {
|
|
65
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
112
69
|
|
|
113
70
|
return () => {
|
|
114
71
|
cancelled = true;
|
|
115
|
-
|
|
116
|
-
unregister?.();
|
|
117
|
-
devWallet.destroy();
|
|
72
|
+
dispose?.();
|
|
118
73
|
};
|
|
119
74
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
120
75
|
|
|
@@ -596,6 +596,8 @@ export class DevWalletSigning extends LitElement {
|
|
|
596
596
|
return `${arg.bytes.slice(0, 16)}${arg.bytes.length > 16 ? '...' : ''}`;
|
|
597
597
|
case 'Object':
|
|
598
598
|
return formatAddress(arg.object.objectId);
|
|
599
|
+
case 'Withdrawal':
|
|
600
|
+
return `Withdraw ${arg.amount} ${getCoinSymbol(arg.coinType)} from ${arg.withdrawFrom}`;
|
|
599
601
|
case 'Unknown':
|
|
600
602
|
return 'unknown';
|
|
601
603
|
}
|
package/src/wallet/dev-wallet.ts
CHANGED
|
@@ -115,12 +115,19 @@ export class DevWallet implements Wallet {
|
|
|
115
115
|
readonly #clientFactory: (network: string, url: string) => ClientWithCoreApi;
|
|
116
116
|
readonly #persistNetworks: boolean;
|
|
117
117
|
#accounts: ReadonlyWalletAccount[];
|
|
118
|
+
#selectedAddress: string | null = null;
|
|
118
119
|
#events: Emitter<WalletEventsMap>;
|
|
119
120
|
#unsubscribeAdapters: (() => void)[];
|
|
120
121
|
#pendingRequest: WalletRequest | null;
|
|
121
122
|
#requestListeners: Set<(request: PendingSigningRequest | null) => void>;
|
|
122
123
|
#pendingConnect: ConnectRequest | null;
|
|
123
124
|
#connectListeners: Set<(request: PendingConnectRequest | null) => void>;
|
|
125
|
+
/** Fired every time a dApp invokes `standard:connect` (the wallet's
|
|
126
|
+
* authoritative "a dApp is connecting" signal). Distinct from
|
|
127
|
+
* `#connectListeners`, which track the *pending-request* lifecycle for the
|
|
128
|
+
* approval UI. Consumers (e.g. the inject's `selectAccount`) use this to
|
|
129
|
+
* observe that the dApp has reached the wallet rather than racing a timer. */
|
|
130
|
+
#connectedListeners: Set<() => void>;
|
|
124
131
|
#destroyed = false;
|
|
125
132
|
|
|
126
133
|
constructor(config: DevWalletConfig) {
|
|
@@ -145,6 +152,7 @@ export class DevWallet implements Wallet {
|
|
|
145
152
|
this.#requestListeners = new Set();
|
|
146
153
|
this.#pendingConnect = null;
|
|
147
154
|
this.#connectListeners = new Set();
|
|
155
|
+
this.#connectedListeners = new Set();
|
|
148
156
|
|
|
149
157
|
this.#setupAdapterListeners();
|
|
150
158
|
}
|
|
@@ -166,7 +174,42 @@ export class DevWallet implements Wallet {
|
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
get accounts(): readonly ReadonlyWalletAccount[] {
|
|
169
|
-
return this.#
|
|
177
|
+
return this.#exposedAccounts();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Narrow the accounts this wallet exposes to dApps (via the
|
|
182
|
+
* wallet-standard `accounts` array + `change` events) to a single
|
|
183
|
+
* address, or pass `null` to expose all aggregated accounts again.
|
|
184
|
+
*
|
|
185
|
+
* Used by the devstack page-register entry to drive headless account
|
|
186
|
+
* selection: dApp Kit reflects the `change` event by switching its
|
|
187
|
+
* connected account to the now-single exposed account (or, if not yet
|
|
188
|
+
* connected, auto-connects to it). No reference to the app's dApp Kit
|
|
189
|
+
* instance is required — selection rides the wallet-standard protocol.
|
|
190
|
+
*/
|
|
191
|
+
setSelectedAccount(address: string | null): void {
|
|
192
|
+
if (address !== null) {
|
|
193
|
+
const match = this.#accounts.find((a) => a.address.toLowerCase() === address.toLowerCase());
|
|
194
|
+
if (match === undefined) {
|
|
195
|
+
const available = this.#accounts.map((a) => a.address).join(', ');
|
|
196
|
+
throw new Error(
|
|
197
|
+
`No dev-wallet account for address "${address}". Available: ${available || '(none)'}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
this.#selectedAddress = match.address;
|
|
201
|
+
} else {
|
|
202
|
+
this.#selectedAddress = null;
|
|
203
|
+
}
|
|
204
|
+
this.#events.emit('change', { accounts: this.#exposedAccounts() });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Accounts exposed to dApps — narrowed to the selected address when set. */
|
|
208
|
+
#exposedAccounts(): ReadonlyWalletAccount[] {
|
|
209
|
+
if (this.#selectedAddress === null) return this.#accounts;
|
|
210
|
+
return this.#accounts.filter(
|
|
211
|
+
(a) => a.address.toLowerCase() === this.#selectedAddress!.toLowerCase(),
|
|
212
|
+
);
|
|
170
213
|
}
|
|
171
214
|
|
|
172
215
|
get features(): StandardConnectFeature &
|
|
@@ -311,6 +354,7 @@ export class DevWallet implements Wallet {
|
|
|
311
354
|
}
|
|
312
355
|
this.#requestListeners.clear();
|
|
313
356
|
this.#connectListeners.clear();
|
|
357
|
+
this.#connectedListeners.clear();
|
|
314
358
|
this.#events.all.clear();
|
|
315
359
|
}
|
|
316
360
|
|
|
@@ -410,6 +454,29 @@ export class DevWallet implements Wallet {
|
|
|
410
454
|
}
|
|
411
455
|
}
|
|
412
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Subscribe to the "a dApp invoked `standard:connect`" edge. Returns an
|
|
459
|
+
* unsubscribe function. Used by the page-register entry to gate its
|
|
460
|
+
* narrow→widen account-selection on an OBSERVED connect rather than a fixed
|
|
461
|
+
* timer (see `src/inject/index.ts`).
|
|
462
|
+
*/
|
|
463
|
+
onDAppConnected(callback: () => void): () => void {
|
|
464
|
+
this.#connectedListeners.add(callback);
|
|
465
|
+
return () => {
|
|
466
|
+
this.#connectedListeners.delete(callback);
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
#notifyConnectedListeners() {
|
|
471
|
+
for (const listener of this.#connectedListeners) {
|
|
472
|
+
try {
|
|
473
|
+
listener();
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.error('[dev-wallet] onDAppConnected listener threw:', error);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
413
480
|
#aggregateAccounts(): ReadonlyWalletAccount[] {
|
|
414
481
|
return this.#adapters.flatMap((a) => a.getAccounts().map((acc) => acc.walletAccount));
|
|
415
482
|
}
|
|
@@ -418,7 +485,7 @@ export class DevWallet implements Wallet {
|
|
|
418
485
|
for (const adapter of this.#adapters) {
|
|
419
486
|
const unsub = adapter.onAccountsChanged(() => {
|
|
420
487
|
this.#accounts = this.#aggregateAccounts();
|
|
421
|
-
this.#events.emit('change', { accounts: this.#
|
|
488
|
+
this.#events.emit('change', { accounts: this.#exposedAccounts() });
|
|
422
489
|
});
|
|
423
490
|
this.#unsubscribeAdapters.push(unsub);
|
|
424
491
|
}
|
|
@@ -430,6 +497,12 @@ export class DevWallet implements Wallet {
|
|
|
430
497
|
};
|
|
431
498
|
|
|
432
499
|
#connect: StandardConnectMethod = async () => {
|
|
500
|
+
// Notify observers that a dApp has invoked `standard:connect`. This is
|
|
501
|
+
// the wallet's authoritative "a dApp reached us" edge — fired before we
|
|
502
|
+
// branch on auto/queued so it covers both the silent auto-connect path
|
|
503
|
+
// and an interactive connect.
|
|
504
|
+
this.#notifyConnectedListeners();
|
|
505
|
+
|
|
433
506
|
// Auto-connect: return all accounts immediately
|
|
434
507
|
if (this.#autoConnect) {
|
|
435
508
|
return { accounts: this.accounts };
|