@mysten-incubation/dev-wallet 0.4.0 → 0.5.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 +43 -0
- package/dist/adapters/build-managed-account.d.mts +15 -2
- package/dist/adapters/build-managed-account.d.mts.map +1 -1
- package/dist/adapters/build-managed-account.mjs +28 -4
- package/dist/adapters/build-managed-account.mjs.map +1 -1
- package/dist/adapters/devstack-adapter.d.mts +19 -7
- package/dist/adapters/devstack-adapter.d.mts.map +1 -1
- package/dist/adapters/devstack-adapter.mjs +16 -8
- package/dist/adapters/devstack-adapter.mjs.map +1 -1
- package/dist/inject/index.d.mts +37 -16
- package/dist/inject/index.d.mts.map +1 -1
- package/dist/inject/index.mjs +20 -6
- package/dist/inject/index.mjs.map +1 -1
- package/dist/standalone/assets/{base-adapter-DET6CP7z.js → base-adapter-C5w5q7XZ.js} +1 -1
- package/dist/standalone/assets/{in-memory-adapter-CX7xym4e.js → in-memory-adapter-Ban2kRYS.js} +1 -1
- package/dist/standalone/assets/{main-CuOnBmaa.js → main-xfzarPod.js} +3 -3
- package/dist/standalone/assets/{webcrypto-adapter-CUQmbHDy.js → webcrypto-adapter-Ddqn-RAu.js} +1 -1
- package/dist/standalone/index.html +2 -2
- package/dist/ui/dev-wallet-balances.d.mts +7 -0
- package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
- package/dist/ui/dev-wallet-balances.mjs +50 -3
- package/dist/ui/dev-wallet-balances.mjs.map +1 -1
- package/dist/ui/wallet-controller.mjs +2 -1
- package/dist/ui/wallet-controller.mjs.map +1 -1
- package/dist/wallet/dev-wallet.d.mts +26 -2
- package/dist/wallet/dev-wallet.d.mts.map +1 -1
- package/dist/wallet/dev-wallet.mjs +35 -6
- package/dist/wallet/dev-wallet.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/build-managed-account.ts +28 -2
- package/src/adapters/devstack-adapter.ts +28 -8
- package/src/inject/index.ts +98 -33
- package/src/ui/dev-wallet-balances.ts +62 -3
- package/src/ui/wallet-controller.ts +2 -1
- package/src/wallet/dev-wallet.ts +47 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-wallet.mjs","names":["#adapters","#name","#icon","#autoApprove","#autoConnect","#clientFactory","#persistNetworks","#networkUrls","#loadNetworkUrls","#clients","#activeNetwork","#accounts","#aggregateAccounts","#events","#unsubscribeAdapters","#pendingRequest","#requestListeners","#pendingConnect","#connectListeners","#connectedListeners","#setupAdapterListeners","#exposedAccounts","#selectedAddress","#connect","#on","#disconnect","#signPersonalMessage","#signTransaction","#signAndExecuteTransaction","#ensureClient","#saveNetworkUrls","#destroyed","#executeSigning","#notifyRequestListeners","#notifyConnectListeners","#notifyConnectedListeners","#getSigner","#getClientForChain","#shouldAutoApprove","#enqueueRequest"],"sources":["../../src/wallet/dev-wallet.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { mitt, type Emitter } from '@mysten/utils';\nimport type {\n\tStandardConnectFeature,\n\tStandardConnectMethod,\n\tStandardDisconnectFeature,\n\tStandardDisconnectMethod,\n\tStandardEventsFeature,\n\tStandardEventsOnMethod,\n\tSuiFeatures,\n\tSuiSignAndExecuteTransactionMethod,\n\tSuiSignPersonalMessageMethod,\n\tSuiSignTransactionMethod,\n\tWallet,\n\tWalletAccount,\n\tWalletIcon,\n} from '@mysten/wallet-standard';\nimport { getWallets, ReadonlyWalletAccount, SUI_CHAINS } from '@mysten/wallet-standard';\n\nimport type { SignerAdapter } from '../types.js';\nimport { DEFAULT_WALLET_ICON, getNetworkFromChain, type WalletEventsMap } from './constants.js';\nimport { type SigningResult, executeSigning } from './signing.js';\n\nconst DEFAULT_WALLET_NAME = 'Dev Wallet';\nconst NETWORK_STORAGE_KEY = 'dev-wallet:networks';\n\n/**\n * Default gRPC endpoint URLs for standard Sui networks (devnet, testnet, localnet).\n */\nexport const DEFAULT_NETWORK_URLS = {\n\tdevnet: 'https://fullnode.devnet.sui.io:443',\n\ttestnet: 'https://fullnode.testnet.sui.io:443',\n\tlocalnet: 'http://127.0.0.1:9000',\n} as const;\n\n/** A pending signing request waiting for user approval via the wallet UI. */\nexport interface WalletRequest {\n\tid: string;\n\ttype: 'sign-transaction' | 'sign-and-execute-transaction' | 'sign-personal-message';\n\taccount: WalletAccount;\n\tchain: string;\n\t/** Transaction JSON string for sign/execute, Uint8Array for personal messages. */\n\tdata: string | Uint8Array;\n\tresolve: (result: SigningResult) => void;\n\treject: (error: Error) => void;\n}\n\n/** Public view of a pending signing request, omitting internal resolve/reject callbacks. */\nexport type PendingSigningRequest = Omit<WalletRequest, 'resolve' | 'reject'>;\n\nexport interface ConnectRequest {\n\tid: string;\n\tresolve: (result: { accounts: readonly WalletAccount[] }) => void;\n\treject: (error: Error) => void;\n}\n\n/** Public view of a pending connect request, omitting internal resolve/reject callbacks. */\nexport type PendingConnectRequest = Omit<ConnectRequest, 'resolve' | 'reject'>;\n\n/**\n * Auto-approval policy for signing requests.\n * `true` approves all requests; a function is called per-request and returns `true` to approve.\n */\nexport type AutoApprovePolicy =\n\t| boolean\n\t| ((request: {\n\t\t\ttype: WalletRequest['type'];\n\t\t\taccount: WalletAccount;\n\t\t\tchain: string;\n\t\t\tdata: string | Uint8Array;\n\t }) => boolean);\n\nexport interface DevWalletConfig {\n\tadapters: SignerAdapter[];\n\t/** Network URLs by name (network → gRPC endpoint). Defaults to devnet, testnet, and localnet. */\n\tnetworks?: Record<string, string>;\n\t/** Display name for the wallet. Defaults to 'Dev Wallet'. */\n\tname?: string;\n\ticon?: WalletIcon;\n\t/** Initially active network. Defaults to the first key in `networks`. */\n\tactiveNetwork?: string;\n\t/**\n\t * When set, matching requests are signed immediately without queuing for user approval.\n\t * `true` approves everything; a function is called per-request for fine-grained control.\n\t */\n\tautoApprove?: AutoApprovePolicy;\n\t/** When true, connect requests resolve immediately with all accounts. */\n\tautoConnect?: boolean;\n\t/** Factory to create a client for a given network. Defaults to SuiGrpcClient. */\n\tclientFactory?: (network: string, url: string) => ClientWithCoreApi;\n\t/**\n\t * Persist network URLs to localStorage so custom configurations survive page reloads\n\t * and are shared with popup windows.\n\t */\n\tpersistNetworks?: boolean;\n}\n\n/**\n * wallet-standard Wallet implementation for development. All signing requests are queued\n * and require explicit approval — either via the UI or programmatically via `approveRequest()`.\n */\nexport class DevWallet implements Wallet {\n\treadonly #adapters: SignerAdapter[];\n\t#networkUrls: Record<string, string>;\n\t#clients: Record<string, ClientWithCoreApi>;\n\t#activeNetwork: string;\n\treadonly #name: string;\n\treadonly #icon: WalletIcon;\n\treadonly #autoApprove: AutoApprovePolicy;\n\treadonly #autoConnect: boolean;\n\treadonly #clientFactory: (network: string, url: string) => ClientWithCoreApi;\n\treadonly #persistNetworks: boolean;\n\t#accounts: ReadonlyWalletAccount[];\n\t#selectedAddress: string | null = null;\n\t#events: Emitter<WalletEventsMap>;\n\t#unsubscribeAdapters: (() => void)[];\n\t#pendingRequest: WalletRequest | null;\n\t#requestListeners: Set<(request: PendingSigningRequest | null) => void>;\n\t#pendingConnect: ConnectRequest | null;\n\t#connectListeners: Set<(request: PendingConnectRequest | null) => void>;\n\t/** Fired every time a dApp invokes `standard:connect` (the wallet's\n\t * authoritative \"a dApp is connecting\" signal). Distinct from\n\t * `#connectListeners`, which track the *pending-request* lifecycle for the\n\t * approval UI. Consumers (e.g. the inject's `selectAccount`) use this to\n\t * observe that the dApp has reached the wallet rather than racing a timer. */\n\t#connectedListeners: Set<() => void>;\n\t#destroyed = false;\n\n\tconstructor(config: DevWalletConfig) {\n\t\tif (config.adapters.length === 0) {\n\t\t\tconsole.warn('[dev-wallet] No adapters provided. The wallet will have no accounts.');\n\t\t}\n\t\tthis.#adapters = config.adapters;\n\t\tthis.#persistNetworks = config.persistNetworks ?? false;\n\t\tthis.#networkUrls = this.#loadNetworkUrls(config.networks ?? DEFAULT_NETWORK_URLS);\n\t\tthis.#clients = {};\n\t\tthis.#activeNetwork = config.activeNetwork ?? Object.keys(this.#networkUrls)[0] ?? '';\n\t\tthis.#name = config.name ?? DEFAULT_WALLET_NAME;\n\t\tthis.#icon = config.icon ?? DEFAULT_WALLET_ICON;\n\t\tthis.#autoApprove = config.autoApprove ?? false;\n\t\tthis.#autoConnect = config.autoConnect ?? false;\n\t\tthis.#clientFactory =\n\t\t\tconfig.clientFactory ?? ((network, url) => new SuiGrpcClient({ baseUrl: url, network }));\n\t\tthis.#accounts = this.#aggregateAccounts();\n\t\tthis.#events = mitt();\n\t\tthis.#unsubscribeAdapters = [];\n\t\tthis.#pendingRequest = null;\n\t\tthis.#requestListeners = new Set();\n\t\tthis.#pendingConnect = null;\n\t\tthis.#connectListeners = new Set();\n\t\tthis.#connectedListeners = new Set();\n\n\t\tthis.#setupAdapterListeners();\n\t}\n\n\tget version() {\n\t\treturn '1.0.0' as const;\n\t}\n\n\tget name() {\n\t\treturn this.#name;\n\t}\n\n\tget icon() {\n\t\treturn this.#icon;\n\t}\n\n\tget chains() {\n\t\treturn SUI_CHAINS;\n\t}\n\n\tget accounts(): readonly ReadonlyWalletAccount[] {\n\t\treturn this.#exposedAccounts();\n\t}\n\n\t/**\n\t * Narrow the accounts this wallet exposes to dApps (via the\n\t * wallet-standard `accounts` array + `change` events) to a single\n\t * address, or pass `null` to expose all aggregated accounts again.\n\t *\n\t * Used by the devstack page-register entry to drive headless account\n\t * selection: dApp Kit reflects the `change` event by switching its\n\t * connected account to the now-single exposed account (or, if not yet\n\t * connected, auto-connects to it). No reference to the app's dApp Kit\n\t * instance is required — selection rides the wallet-standard protocol.\n\t */\n\tsetSelectedAccount(address: string | null): void {\n\t\tif (address !== null) {\n\t\t\tconst match = this.#accounts.find((a) => a.address.toLowerCase() === address.toLowerCase());\n\t\t\tif (match === undefined) {\n\t\t\t\tconst available = this.#accounts.map((a) => a.address).join(', ');\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No dev-wallet account for address \"${address}\". Available: ${available || '(none)'}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#selectedAddress = match.address;\n\t\t} else {\n\t\t\tthis.#selectedAddress = null;\n\t\t}\n\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t}\n\n\t/** Accounts exposed to dApps — narrowed to the selected address when set. */\n\t#exposedAccounts(): ReadonlyWalletAccount[] {\n\t\tif (this.#selectedAddress === null) return this.#accounts;\n\t\treturn this.#accounts.filter(\n\t\t\t(a) => a.address.toLowerCase() === this.#selectedAddress!.toLowerCase(),\n\t\t);\n\t}\n\n\tget features(): StandardConnectFeature &\n\t\tStandardEventsFeature &\n\t\tStandardDisconnectFeature &\n\t\tSuiFeatures {\n\t\treturn {\n\t\t\t'standard:connect': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\tconnect: this.#connect,\n\t\t\t},\n\t\t\t'standard:events': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\ton: this.#on,\n\t\t\t},\n\t\t\t'standard:disconnect': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\tdisconnect: this.#disconnect,\n\t\t\t},\n\t\t\t'sui:signPersonalMessage': {\n\t\t\t\tversion: '1.1.0',\n\t\t\t\tsignPersonalMessage: this.#signPersonalMessage,\n\t\t\t},\n\t\t\t'sui:signTransaction': {\n\t\t\t\tversion: '2.0.0',\n\t\t\t\tsignTransaction: this.#signTransaction,\n\t\t\t},\n\t\t\t'sui:signAndExecuteTransaction': {\n\t\t\t\tversion: '2.0.0',\n\t\t\t\tsignAndExecuteTransaction: this.#signAndExecuteTransaction,\n\t\t\t},\n\t\t};\n\t}\n\n\t/** The current pending signing request, or null if none. */\n\tget pendingRequest(): PendingSigningRequest | null {\n\t\treturn this.#pendingRequest;\n\t}\n\n\tget adapters(): readonly SignerAdapter[] {\n\t\treturn this.#adapters;\n\t}\n\n\tgetAdapterForAccount(address: string): SignerAdapter | undefined {\n\t\treturn this.#adapters.find((a) => a.getAccount(address) !== undefined);\n\t}\n\n\t/** Eagerly creates and returns clients for all configured networks. Prefer `getClient()` or `activeClient` for single-network access. */\n\tget clients(): Record<string, ClientWithCoreApi> {\n\t\tfor (const name of Object.keys(this.#networkUrls)) {\n\t\t\tthis.#ensureClient(name);\n\t\t}\n\t\treturn { ...this.#clients };\n\t}\n\n\tgetClient(network: string): ClientWithCoreApi {\n\t\treturn this.#ensureClient(network);\n\t}\n\n\tget networkUrls(): Record<string, string> {\n\t\treturn { ...this.#networkUrls };\n\t}\n\n\tget activeNetwork(): string {\n\t\treturn this.#activeNetwork;\n\t}\n\n\tget activeClient(): ClientWithCoreApi | null {\n\t\tif (!this.#activeNetwork || !this.#networkUrls[this.#activeNetwork]) return null;\n\t\treturn this.#ensureClient(this.#activeNetwork);\n\t}\n\n\tget availableNetworks(): string[] {\n\t\treturn Object.keys(this.#networkUrls);\n\t}\n\n\t/** Switch the active network. Emits a `change` event. */\n\tsetActiveNetwork(network: string): void {\n\t\tif (!this.#networkUrls[network]) {\n\t\t\tthrow new Error(\n\t\t\t\t`No client for network \"${network}\". Available: ${this.availableNetworks.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tthis.#activeNetwork = network;\n\t\tthis.#events.emit('change', { accounts: this.#accounts });\n\t}\n\n\taddNetwork(name: string, url: string): void {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(url);\n\t\t} catch {\n\t\t\tthrow new Error(`Invalid URL: ${url}`);\n\t\t}\n\t\tif (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n\t\t\tthrow new Error(`Invalid URL: must use http or https (got ${parsed.protocol})`);\n\t\t}\n\t\tthis.#networkUrls[name] = url;\n\t\tthis.#clients[name] = this.#clientFactory(name, url);\n\t\tthis.#saveNetworkUrls();\n\t\tthis.#events.emit('change', { accounts: this.#accounts });\n\t}\n\n\tremoveNetwork(name: string): void {\n\t\tdelete this.#networkUrls[name];\n\t\tdelete this.#clients[name];\n\t\tif (this.#activeNetwork === name) {\n\t\t\tthis.#activeNetwork = Object.keys(this.#networkUrls)[0] ?? '';\n\t\t}\n\t\tthis.#saveNetworkUrls();\n\t\tthis.#events.emit('change', { accounts: this.#accounts });\n\t}\n\n\t/** Register this wallet with the wallet-standard registry. Returns an unregister function. */\n\tregister(): () => void {\n\t\tif (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {\n\t\t\tconsole.warn(\n\t\t\t\t'[@mysten-incubation/dev-wallet] DevWallet is intended for development only. Do not use in production.',\n\t\t\t);\n\t\t}\n\t\tconst walletsApi = getWallets();\n\t\treturn walletsApi.register(this);\n\t}\n\n\t/** Reject any pending requests, unsubscribe from adapters, destroy them, and clear all listeners. */\n\tdestroy(): void {\n\t\tthis.#destroyed = true;\n\t\tfor (const unsub of this.#unsubscribeAdapters) {\n\t\t\tunsub();\n\t\t}\n\t\tthis.#unsubscribeAdapters = [];\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tadapter.destroy();\n\t\t}\n\t\tif (this.#pendingRequest) {\n\t\t\tthis.#pendingRequest.reject(new Error('Wallet destroyed.'));\n\t\t\tthis.#pendingRequest = null;\n\t\t}\n\t\tif (this.#pendingConnect) {\n\t\t\tthis.#pendingConnect.reject(new Error('Wallet destroyed.'));\n\t\t\tthis.#pendingConnect = null;\n\t\t}\n\t\tthis.#requestListeners.clear();\n\t\tthis.#connectListeners.clear();\n\t\tthis.#connectedListeners.clear();\n\t\tthis.#events.all.clear();\n\t}\n\n\t/** Sign the pending request using the account's adapter signer and resolve the promise. */\n\tasync approveRequest(): Promise<void> {\n\t\tconst request = this.#pendingRequest;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending request to approve.');\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.#executeSigning(\n\t\t\t\trequest.type,\n\t\t\t\trequest.account,\n\t\t\t\trequest.chain,\n\t\t\t\trequest.data,\n\t\t\t);\n\t\t\tthis.#pendingRequest = null;\n\t\t\tthis.#notifyRequestListeners();\n\t\t\trequest.resolve(result);\n\t\t} catch (error) {\n\t\t\tthis.#pendingRequest = null;\n\t\t\tthis.#notifyRequestListeners();\n\t\t\trequest.reject(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n\n\trejectRequest(reason?: string): void {\n\t\tconst request = this.#pendingRequest;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending request to reject.');\n\t\t}\n\t\tthis.#pendingRequest = null;\n\t\tthis.#notifyRequestListeners();\n\t\trequest.reject(new Error(reason ?? 'Request rejected by user.'));\n\t}\n\n\t/** Subscribe to pending request changes. Returns an unsubscribe function. */\n\tonRequestChange(callback: (request: PendingSigningRequest | null) => void): () => void {\n\t\tthis.#requestListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#requestListeners.delete(callback);\n\t\t};\n\t}\n\n\t/** The current pending connect request, or null if none. */\n\tget pendingConnect(): PendingConnectRequest | null {\n\t\treturn this.#pendingConnect;\n\t}\n\n\t/** Expose the given addresses to the dApp. Pass an empty array to expose all accounts. */\n\tapproveConnect(selectedAddresses: string[]): void {\n\t\tconst request = this.#pendingConnect;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending connect request to approve.');\n\t\t}\n\n\t\tconst selected =\n\t\t\tselectedAddresses.length > 0\n\t\t\t\t? this.#accounts.filter((a) => selectedAddresses.includes(a.address))\n\t\t\t\t: [...this.#accounts];\n\n\t\tthis.#pendingConnect = null;\n\t\tthis.#notifyConnectListeners();\n\t\trequest.resolve({ accounts: selected });\n\t}\n\n\trejectConnect(reason?: string): void {\n\t\tconst request = this.#pendingConnect;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending connect request to reject.');\n\t\t}\n\t\tthis.#pendingConnect = null;\n\t\tthis.#notifyConnectListeners();\n\t\trequest.reject(new Error(reason ?? 'Connection rejected by user.'));\n\t}\n\n\t/** Subscribe to pending connect request changes. Returns an unsubscribe function. */\n\tonConnectChange(callback: (request: PendingConnectRequest | null) => void): () => void {\n\t\tthis.#connectListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#connectListeners.delete(callback);\n\t\t};\n\t}\n\n\t#notifyRequestListeners() {\n\t\tconst request = this.#pendingRequest;\n\t\tfor (const listener of this.#requestListeners) {\n\t\t\tlistener(request);\n\t\t}\n\t}\n\n\t#notifyConnectListeners() {\n\t\tconst request = this.#pendingConnect;\n\t\tfor (const listener of this.#connectListeners) {\n\t\t\tlistener(request);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to the \"a dApp invoked `standard:connect`\" edge. Returns an\n\t * unsubscribe function. Used by the page-register entry to gate its\n\t * narrow→widen account-selection on an OBSERVED connect rather than a fixed\n\t * timer (see `src/inject/index.ts`).\n\t */\n\tonDAppConnected(callback: () => void): () => void {\n\t\tthis.#connectedListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#connectedListeners.delete(callback);\n\t\t};\n\t}\n\n\t#notifyConnectedListeners() {\n\t\tfor (const listener of this.#connectedListeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[dev-wallet] onDAppConnected listener threw:', error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#aggregateAccounts(): ReadonlyWalletAccount[] {\n\t\treturn this.#adapters.flatMap((a) => a.getAccounts().map((acc) => acc.walletAccount));\n\t}\n\n\t#setupAdapterListeners() {\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tconst unsub = adapter.onAccountsChanged(() => {\n\t\t\t\tthis.#accounts = this.#aggregateAccounts();\n\t\t\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t\t\t});\n\t\t\tthis.#unsubscribeAdapters.push(unsub);\n\t\t}\n\t}\n\n\t#on: StandardEventsOnMethod = (event, listener) => {\n\t\tthis.#events.on(event, listener);\n\t\treturn () => this.#events.off(event, listener);\n\t};\n\n\t#connect: StandardConnectMethod = async () => {\n\t\t// Notify observers that a dApp has invoked `standard:connect`. This is\n\t\t// the wallet's authoritative \"a dApp reached us\" edge — fired before we\n\t\t// branch on auto/queued so it covers both the silent auto-connect path\n\t\t// and an interactive connect.\n\t\tthis.#notifyConnectedListeners();\n\n\t\t// Auto-connect: return all accounts immediately\n\t\tif (this.#autoConnect) {\n\t\t\treturn { accounts: this.accounts };\n\t\t}\n\n\t\t// Queue a connect request for user to select accounts\n\t\tif (this.#pendingConnect) {\n\t\t\treturn Promise.reject(new Error('A connect request is already pending.'));\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.#pendingConnect = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t};\n\t\t\tthis.#notifyConnectListeners();\n\t\t});\n\t};\n\n\t#disconnect: StandardDisconnectMethod = async () => {\n\t\t// Disconnecting ends the dApp session but does not affect wallet state.\n\t\t// The wallet retains its accounts and adapter subscriptions.\n\t};\n\n\t#getClientForChain(chain: string) {\n\t\tconst network = getNetworkFromChain(chain);\n\t\tif (!network) {\n\t\t\tthrow new Error(`Invalid chain identifier: ${chain}`);\n\t\t}\n\t\treturn this.#ensureClient(network);\n\t}\n\n\t#loadNetworkUrls(defaults: Record<string, string>): Record<string, string> {\n\t\tif (!this.#persistNetworks || typeof localStorage === 'undefined') {\n\t\t\treturn { ...defaults };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = localStorage.getItem(NETWORK_STORAGE_KEY);\n\t\t\tif (raw) {\n\t\t\t\tconst parsed = JSON.parse(raw);\n\t\t\t\tif (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n\t\t\t\t\treturn parsed as Record<string, string>;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Corrupted data — fall through to defaults\n\t\t}\n\t\treturn { ...defaults };\n\t}\n\n\t#saveNetworkUrls(): void {\n\t\tif (!this.#persistNetworks || typeof localStorage === 'undefined') return;\n\t\tlocalStorage.setItem(NETWORK_STORAGE_KEY, JSON.stringify(this.#networkUrls));\n\t}\n\n\t#ensureClient(network: string): ClientWithCoreApi {\n\t\tif (!this.#clients[network]) {\n\t\t\tconst url = this.#networkUrls[network];\n\t\t\tif (!url) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No client for network \"${network}\". Available: ${this.availableNetworks.join(', ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#clients[network] = this.#clientFactory(network, url);\n\t\t}\n\t\treturn this.#clients[network];\n\t}\n\n\t#getSigner(address: string) {\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tconst account = adapter.getAccount(address);\n\t\t\tif (account) return account.signer;\n\t\t}\n\t\tconst allAddresses = this.#adapters.flatMap((a) => a.getAccounts().map((acc) => acc.address));\n\t\tthrow new Error(\n\t\t\t`No account found for address \"${address}\". Available addresses: ${allAddresses.join(', ')}`,\n\t\t);\n\t}\n\n\t#shouldAutoApprove(\n\t\ttype: WalletRequest['type'],\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t): boolean {\n\t\t// Check adapter-level opt-out first — CLI adapters never auto-sign\n\t\tconst adapter = this.getAdapterForAccount(account.address);\n\t\tif (adapter?.allowAutoSign === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.#autoApprove === true) {\n\t\t\treturn true;\n\t\t}\n\t\tif (typeof this.#autoApprove === 'function') {\n\t\t\treturn this.#autoApprove({ type, account, chain, data });\n\t\t}\n\t\treturn false;\n\t}\n\n\tasync #executeSigning(\n\t\ttype: WalletRequest['type'],\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t) {\n\t\tconst signer = this.#getSigner(account.address);\n\t\tconst client = type !== 'sign-personal-message' ? this.#getClientForChain(chain) : undefined;\n\t\treturn executeSigning({ type, signer, data, client });\n\t}\n\n\t#enqueueRequest<T extends WalletRequest['type']>(\n\t\ttype: T,\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t): Promise<Extract<SigningResult, { type: T }>> {\n\t\ttype Result = Extract<SigningResult, { type: T }>;\n\t\tif (this.#destroyed) {\n\t\t\treturn Promise.reject(new Error('Wallet has been destroyed.'));\n\t\t}\n\t\tif (this.#shouldAutoApprove(type, account, chain, data)) {\n\t\t\treturn this.#executeSigning(type, account, chain, data) as Promise<Result>;\n\t\t}\n\n\t\t// Only one request at a time — subsequent requests are immediately rejected.\n\t\t// DApps that batch transactions should serialize their signing calls.\n\t\tif (this.#pendingRequest) {\n\t\t\treturn Promise.reject(new Error('A signing request is already pending.'));\n\t\t}\n\n\t\treturn new Promise<Result>((resolve, reject) => {\n\t\t\tthis.#pendingRequest = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\ttype,\n\t\t\t\taccount,\n\t\t\t\tchain,\n\t\t\t\tdata,\n\t\t\t\tresolve: resolve as (result: SigningResult) => void,\n\t\t\t\treject,\n\t\t\t};\n\t\t\tthis.#notifyRequestListeners();\n\t\t});\n\t}\n\n\t#signPersonalMessage: SuiSignPersonalMessageMethod = async (input) => {\n\t\treturn this.#enqueueRequest(\n\t\t\t'sign-personal-message',\n\t\t\tinput.account,\n\t\t\tinput.chain ?? 'sui:unknown',\n\t\t\tinput.message,\n\t\t);\n\t};\n\n\t#signTransaction: SuiSignTransactionMethod = async (input) => {\n\t\tconst txJson = await input.transaction.toJSON();\n\t\treturn this.#enqueueRequest('sign-transaction', input.account, input.chain, txJson);\n\t};\n\n\t#signAndExecuteTransaction: SuiSignAndExecuteTransactionMethod = async (input) => {\n\t\tconst txJson = await input.transaction.toJSON();\n\t\treturn this.#enqueueRequest('sign-and-execute-transaction', input.account, input.chain, txJson);\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dev-wallet.mjs","names":["#adapters","#name","#icon","#autoApprove","#autoConnect","#clientFactory","#persistNetworks","#networkUrls","#loadNetworkUrls","#faucetUrls","#clients","#activeNetwork","#accounts","#aggregateAccounts","#events","#unsubscribeAdapters","#pendingRequest","#requestListeners","#pendingConnect","#connectListeners","#connectedListeners","#setupAdapterListeners","#exposedAccounts","#selectedAddress","#connect","#on","#disconnect","#signPersonalMessage","#signTransaction","#signAndExecuteTransaction","#ensureClient","#saveNetworkUrls","#destroyed","#executeSigning","#notifyRequestListeners","#notifyConnectListeners","#notifyConnectedListeners","#getSigner","#getClientForChain","#shouldAutoApprove","#enqueueRequest"],"sources":["../../src/wallet/dev-wallet.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ClientWithCoreApi } from '@mysten/sui/client';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport { mitt, type Emitter } from '@mysten/utils';\nimport type {\n\tStandardConnectFeature,\n\tStandardConnectMethod,\n\tStandardDisconnectFeature,\n\tStandardDisconnectMethod,\n\tStandardEventsFeature,\n\tStandardEventsOnMethod,\n\tSuiFeatures,\n\tSuiSignAndExecuteTransactionMethod,\n\tSuiSignPersonalMessageMethod,\n\tSuiSignTransactionMethod,\n\tWallet,\n\tWalletAccount,\n\tWalletIcon,\n} from '@mysten/wallet-standard';\nimport { getWallets, ReadonlyWalletAccount } from '@mysten/wallet-standard';\n\nimport { chainsForNetworks } from '../adapters/build-managed-account.js';\nimport type { SignerAdapter } from '../types.js';\nimport { DEFAULT_WALLET_ICON, getNetworkFromChain, type WalletEventsMap } from './constants.js';\nimport { type SigningResult, executeSigning } from './signing.js';\n\nconst DEFAULT_WALLET_NAME = 'Dev Wallet';\nconst NETWORK_STORAGE_KEY = 'dev-wallet:networks';\n\n/**\n * Default gRPC endpoint URLs for standard Sui networks (devnet, testnet, localnet).\n */\nexport const DEFAULT_NETWORK_URLS = {\n\tdevnet: 'https://fullnode.devnet.sui.io:443',\n\ttestnet: 'https://fullnode.testnet.sui.io:443',\n\tlocalnet: 'http://127.0.0.1:9000',\n} as const;\n\n/** A pending signing request waiting for user approval via the wallet UI. */\nexport interface WalletRequest {\n\tid: string;\n\ttype: 'sign-transaction' | 'sign-and-execute-transaction' | 'sign-personal-message';\n\taccount: WalletAccount;\n\tchain: string;\n\t/** Transaction JSON string for sign/execute, Uint8Array for personal messages. */\n\tdata: string | Uint8Array;\n\tresolve: (result: SigningResult) => void;\n\treject: (error: Error) => void;\n}\n\n/** Public view of a pending signing request, omitting internal resolve/reject callbacks. */\nexport type PendingSigningRequest = Omit<WalletRequest, 'resolve' | 'reject'>;\n\nexport interface ConnectRequest {\n\tid: string;\n\tresolve: (result: { accounts: readonly WalletAccount[] }) => void;\n\treject: (error: Error) => void;\n}\n\n/** Public view of a pending connect request, omitting internal resolve/reject callbacks. */\nexport type PendingConnectRequest = Omit<ConnectRequest, 'resolve' | 'reject'>;\n\n/**\n * Auto-approval policy for signing requests.\n * `true` approves all requests; a function is called per-request and returns `true` to approve.\n */\nexport type AutoApprovePolicy =\n\t| boolean\n\t| ((request: {\n\t\t\ttype: WalletRequest['type'];\n\t\t\taccount: WalletAccount;\n\t\t\tchain: string;\n\t\t\tdata: string | Uint8Array;\n\t }) => boolean);\n\nexport interface DevWalletConfig {\n\tadapters: SignerAdapter[];\n\t/** Network URLs by name (network → gRPC endpoint). Defaults to devnet, testnet, and localnet. */\n\tnetworks?: Record<string, string>;\n\t/** Optional per-network faucet endpoints (network → faucet URL). The\n\t * active network's faucet is resolvable via {@link DevWallet.activeFaucet}\n\t * / {@link DevWallet.getFaucet}, so a fund flow funds the SELECTED\n\t * network's account. Networks without a faucet (live mainnet, fork\n\t * stacks) simply omit a key. */\n\tfaucets?: Record<string, string>;\n\t/** Display name for the wallet. Defaults to 'Dev Wallet'. */\n\tname?: string;\n\ticon?: WalletIcon;\n\t/** Initially active network. Defaults to the first key in `networks`. */\n\tactiveNetwork?: string;\n\t/**\n\t * When set, matching requests are signed immediately without queuing for user approval.\n\t * `true` approves everything; a function is called per-request for fine-grained control.\n\t */\n\tautoApprove?: AutoApprovePolicy;\n\t/** When true, connect requests resolve immediately with all accounts. */\n\tautoConnect?: boolean;\n\t/** Factory to create a client for a given network. Defaults to SuiGrpcClient. */\n\tclientFactory?: (network: string, url: string) => ClientWithCoreApi;\n\t/**\n\t * Persist network URLs to localStorage so custom configurations survive page reloads\n\t * and are shared with popup windows.\n\t */\n\tpersistNetworks?: boolean;\n}\n\n/**\n * wallet-standard Wallet implementation for development. All signing requests are queued\n * and require explicit approval — either via the UI or programmatically via `approveRequest()`.\n */\nexport class DevWallet implements Wallet {\n\treadonly #adapters: SignerAdapter[];\n\t#networkUrls: Record<string, string>;\n\t#faucetUrls: Record<string, string>;\n\t#clients: Record<string, ClientWithCoreApi>;\n\t#activeNetwork: string;\n\treadonly #name: string;\n\treadonly #icon: WalletIcon;\n\treadonly #autoApprove: AutoApprovePolicy;\n\treadonly #autoConnect: boolean;\n\treadonly #clientFactory: (network: string, url: string) => ClientWithCoreApi;\n\treadonly #persistNetworks: boolean;\n\t#accounts: ReadonlyWalletAccount[];\n\t#selectedAddress: string | null = null;\n\t#events: Emitter<WalletEventsMap>;\n\t#unsubscribeAdapters: (() => void)[];\n\t#pendingRequest: WalletRequest | null;\n\t#requestListeners: Set<(request: PendingSigningRequest | null) => void>;\n\t#pendingConnect: ConnectRequest | null;\n\t#connectListeners: Set<(request: PendingConnectRequest | null) => void>;\n\t/** Fired every time a dApp invokes `standard:connect` (the wallet's\n\t * authoritative \"a dApp is connecting\" signal). Distinct from\n\t * `#connectListeners`, which track the *pending-request* lifecycle for the\n\t * approval UI. Consumers (e.g. the inject's `selectAccount`) use this to\n\t * observe that the dApp has reached the wallet rather than racing a timer. */\n\t#connectedListeners: Set<() => void>;\n\t#destroyed = false;\n\n\tconstructor(config: DevWalletConfig) {\n\t\tif (config.adapters.length === 0) {\n\t\t\tconsole.warn('[dev-wallet] No adapters provided. The wallet will have no accounts.');\n\t\t}\n\t\tthis.#adapters = config.adapters;\n\t\tthis.#persistNetworks = config.persistNetworks ?? false;\n\t\tthis.#networkUrls = this.#loadNetworkUrls(config.networks ?? DEFAULT_NETWORK_URLS);\n\t\tthis.#faucetUrls = { ...config.faucets };\n\t\tthis.#clients = {};\n\t\tthis.#activeNetwork = config.activeNetwork ?? Object.keys(this.#networkUrls)[0] ?? '';\n\t\tthis.#name = config.name ?? DEFAULT_WALLET_NAME;\n\t\tthis.#icon = config.icon ?? DEFAULT_WALLET_ICON;\n\t\tthis.#autoApprove = config.autoApprove ?? false;\n\t\tthis.#autoConnect = config.autoConnect ?? false;\n\t\tthis.#clientFactory =\n\t\t\tconfig.clientFactory ?? ((network, url) => new SuiGrpcClient({ baseUrl: url, network }));\n\t\tthis.#accounts = this.#aggregateAccounts();\n\t\tthis.#events = mitt();\n\t\tthis.#unsubscribeAdapters = [];\n\t\tthis.#pendingRequest = null;\n\t\tthis.#requestListeners = new Set();\n\t\tthis.#pendingConnect = null;\n\t\tthis.#connectListeners = new Set();\n\t\tthis.#connectedListeners = new Set();\n\n\t\tthis.#setupAdapterListeners();\n\t}\n\n\tget version() {\n\t\treturn '1.0.0' as const;\n\t}\n\n\tget name() {\n\t\treturn this.#name;\n\t}\n\n\tget icon() {\n\t\treturn this.#icon;\n\t}\n\n\t/**\n\t * Wallet-standard chains this wallet advertises. Reflects the CONFIGURED\n\t * networks — each network name is advertised as `sui:<name>` so dApp Kit's\n\t * chain-gated paths work for fork networks (`testnet-fork`, …) and custom\n\t * network names, not just the fixed standard set. Unioned with the standard\n\t * Sui chains for safety, de-duplicated, with the standard chains first and\n\t * configured-only chains appended in network-map order.\n\t */\n\tget chains(): `sui:${string}`[] {\n\t\treturn chainsForNetworks(Object.keys(this.#networkUrls));\n\t}\n\n\tget accounts(): readonly ReadonlyWalletAccount[] {\n\t\treturn this.#exposedAccounts();\n\t}\n\n\t/**\n\t * Narrow the accounts this wallet exposes to dApps (via the\n\t * wallet-standard `accounts` array + `change` events) to a single\n\t * address, or pass `null` to expose all aggregated accounts again.\n\t *\n\t * Used by the devstack page-register entry to drive headless account\n\t * selection: dApp Kit reflects the `change` event by switching its\n\t * connected account to the now-single exposed account (or, if not yet\n\t * connected, auto-connects to it). No reference to the app's dApp Kit\n\t * instance is required — selection rides the wallet-standard protocol.\n\t */\n\tsetSelectedAccount(address: string | null): void {\n\t\tif (address !== null) {\n\t\t\tconst match = this.#accounts.find((a) => a.address.toLowerCase() === address.toLowerCase());\n\t\t\tif (match === undefined) {\n\t\t\t\tconst available = this.#accounts.map((a) => a.address).join(', ');\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No dev-wallet account for address \"${address}\". Available: ${available || '(none)'}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#selectedAddress = match.address;\n\t\t} else {\n\t\t\tthis.#selectedAddress = null;\n\t\t}\n\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t}\n\n\t/** Accounts exposed to dApps — narrowed to the selected address when set. */\n\t#exposedAccounts(): ReadonlyWalletAccount[] {\n\t\tif (this.#selectedAddress === null) return this.#accounts;\n\t\treturn this.#accounts.filter(\n\t\t\t(a) => a.address.toLowerCase() === this.#selectedAddress!.toLowerCase(),\n\t\t);\n\t}\n\n\tget features(): StandardConnectFeature &\n\t\tStandardEventsFeature &\n\t\tStandardDisconnectFeature &\n\t\tSuiFeatures {\n\t\treturn {\n\t\t\t'standard:connect': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\tconnect: this.#connect,\n\t\t\t},\n\t\t\t'standard:events': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\ton: this.#on,\n\t\t\t},\n\t\t\t'standard:disconnect': {\n\t\t\t\tversion: '1.0.0',\n\t\t\t\tdisconnect: this.#disconnect,\n\t\t\t},\n\t\t\t'sui:signPersonalMessage': {\n\t\t\t\tversion: '1.1.0',\n\t\t\t\tsignPersonalMessage: this.#signPersonalMessage,\n\t\t\t},\n\t\t\t'sui:signTransaction': {\n\t\t\t\tversion: '2.0.0',\n\t\t\t\tsignTransaction: this.#signTransaction,\n\t\t\t},\n\t\t\t'sui:signAndExecuteTransaction': {\n\t\t\t\tversion: '2.0.0',\n\t\t\t\tsignAndExecuteTransaction: this.#signAndExecuteTransaction,\n\t\t\t},\n\t\t};\n\t}\n\n\t/** The current pending signing request, or null if none. */\n\tget pendingRequest(): PendingSigningRequest | null {\n\t\treturn this.#pendingRequest;\n\t}\n\n\tget adapters(): readonly SignerAdapter[] {\n\t\treturn this.#adapters;\n\t}\n\n\tgetAdapterForAccount(address: string): SignerAdapter | undefined {\n\t\treturn this.#adapters.find((a) => a.getAccount(address) !== undefined);\n\t}\n\n\t/** Eagerly creates and returns clients for all configured networks. Prefer `getClient()` or `activeClient` for single-network access. */\n\tget clients(): Record<string, ClientWithCoreApi> {\n\t\tfor (const name of Object.keys(this.#networkUrls)) {\n\t\t\tthis.#ensureClient(name);\n\t\t}\n\t\treturn { ...this.#clients };\n\t}\n\n\tgetClient(network: string): ClientWithCoreApi {\n\t\treturn this.#ensureClient(network);\n\t}\n\n\tget networkUrls(): Record<string, string> {\n\t\treturn { ...this.#networkUrls };\n\t}\n\n\t/** Per-network faucet endpoints (network → faucet URL). Networks without\n\t * a faucet are absent from the map. */\n\tget faucetUrls(): Record<string, string> {\n\t\treturn { ...this.#faucetUrls };\n\t}\n\n\t/** Faucet endpoint for the named network, or `null` when that network has\n\t * no faucet configured. */\n\tgetFaucet(network: string): string | null {\n\t\treturn this.#faucetUrls[network] ?? null;\n\t}\n\n\t/** Faucet endpoint for the currently-active network, or `null` when the\n\t * active network has no faucet (live mainnet, fork stacks). Drives a\n\t * fund flow against the SELECTED network. */\n\tget activeFaucet(): string | null {\n\t\treturn this.getFaucet(this.#activeNetwork);\n\t}\n\n\tget activeNetwork(): string {\n\t\treturn this.#activeNetwork;\n\t}\n\n\tget activeClient(): ClientWithCoreApi | null {\n\t\tif (!this.#activeNetwork || !this.#networkUrls[this.#activeNetwork]) return null;\n\t\treturn this.#ensureClient(this.#activeNetwork);\n\t}\n\n\tget availableNetworks(): string[] {\n\t\treturn Object.keys(this.#networkUrls);\n\t}\n\n\t/** Switch the active network. Emits a `change` event. */\n\tsetActiveNetwork(network: string): void {\n\t\tif (!this.#networkUrls[network]) {\n\t\t\tthrow new Error(\n\t\t\t\t`No client for network \"${network}\". Available: ${this.availableNetworks.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tthis.#activeNetwork = network;\n\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t}\n\n\taddNetwork(name: string, url: string, faucet?: string | null): void {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(url);\n\t\t} catch {\n\t\t\tthrow new Error(`Invalid URL: ${url}`);\n\t\t}\n\t\tif (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n\t\t\tthrow new Error(`Invalid URL: must use http or https (got ${parsed.protocol})`);\n\t\t}\n\t\tthis.#networkUrls[name] = url;\n\t\tthis.#clients[name] = this.#clientFactory(name, url);\n\t\tif (faucet !== undefined && faucet !== null) {\n\t\t\tthis.#faucetUrls[name] = faucet;\n\t\t}\n\t\tthis.#saveNetworkUrls();\n\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t}\n\n\tremoveNetwork(name: string): void {\n\t\tdelete this.#networkUrls[name];\n\t\tdelete this.#faucetUrls[name];\n\t\tdelete this.#clients[name];\n\t\tif (this.#activeNetwork === name) {\n\t\t\tthis.#activeNetwork = Object.keys(this.#networkUrls)[0] ?? '';\n\t\t}\n\t\tthis.#saveNetworkUrls();\n\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t}\n\n\t/** Register this wallet with the wallet-standard registry. Returns an unregister function. */\n\tregister(): () => void {\n\t\tif (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {\n\t\t\tconsole.warn(\n\t\t\t\t'[@mysten-incubation/dev-wallet] DevWallet is intended for development only. Do not use in production.',\n\t\t\t);\n\t\t}\n\t\tconst walletsApi = getWallets();\n\t\treturn walletsApi.register(this);\n\t}\n\n\t/** Reject any pending requests, unsubscribe from adapters, destroy them, and clear all listeners. */\n\tdestroy(): void {\n\t\tthis.#destroyed = true;\n\t\tfor (const unsub of this.#unsubscribeAdapters) {\n\t\t\tunsub();\n\t\t}\n\t\tthis.#unsubscribeAdapters = [];\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tadapter.destroy();\n\t\t}\n\t\tif (this.#pendingRequest) {\n\t\t\tthis.#pendingRequest.reject(new Error('Wallet destroyed.'));\n\t\t\tthis.#pendingRequest = null;\n\t\t}\n\t\tif (this.#pendingConnect) {\n\t\t\tthis.#pendingConnect.reject(new Error('Wallet destroyed.'));\n\t\t\tthis.#pendingConnect = null;\n\t\t}\n\t\tthis.#requestListeners.clear();\n\t\tthis.#connectListeners.clear();\n\t\tthis.#connectedListeners.clear();\n\t\tthis.#events.all.clear();\n\t}\n\n\t/** Sign the pending request using the account's adapter signer and resolve the promise. */\n\tasync approveRequest(): Promise<void> {\n\t\tconst request = this.#pendingRequest;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending request to approve.');\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.#executeSigning(\n\t\t\t\trequest.type,\n\t\t\t\trequest.account,\n\t\t\t\trequest.chain,\n\t\t\t\trequest.data,\n\t\t\t);\n\t\t\tthis.#pendingRequest = null;\n\t\t\tthis.#notifyRequestListeners();\n\t\t\trequest.resolve(result);\n\t\t} catch (error) {\n\t\t\tthis.#pendingRequest = null;\n\t\t\tthis.#notifyRequestListeners();\n\t\t\trequest.reject(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n\n\trejectRequest(reason?: string): void {\n\t\tconst request = this.#pendingRequest;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending request to reject.');\n\t\t}\n\t\tthis.#pendingRequest = null;\n\t\tthis.#notifyRequestListeners();\n\t\trequest.reject(new Error(reason ?? 'Request rejected by user.'));\n\t}\n\n\t/** Subscribe to pending request changes. Returns an unsubscribe function. */\n\tonRequestChange(callback: (request: PendingSigningRequest | null) => void): () => void {\n\t\tthis.#requestListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#requestListeners.delete(callback);\n\t\t};\n\t}\n\n\t/** The current pending connect request, or null if none. */\n\tget pendingConnect(): PendingConnectRequest | null {\n\t\treturn this.#pendingConnect;\n\t}\n\n\t/** Expose the given addresses to the dApp. Pass an empty array to expose all accounts. */\n\tapproveConnect(selectedAddresses: string[]): void {\n\t\tconst request = this.#pendingConnect;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending connect request to approve.');\n\t\t}\n\n\t\tconst selected =\n\t\t\tselectedAddresses.length > 0\n\t\t\t\t? this.#accounts.filter((a) => selectedAddresses.includes(a.address))\n\t\t\t\t: [...this.#accounts];\n\n\t\tthis.#pendingConnect = null;\n\t\tthis.#notifyConnectListeners();\n\t\trequest.resolve({ accounts: selected });\n\t}\n\n\trejectConnect(reason?: string): void {\n\t\tconst request = this.#pendingConnect;\n\t\tif (!request) {\n\t\t\tthrow new Error('No pending connect request to reject.');\n\t\t}\n\t\tthis.#pendingConnect = null;\n\t\tthis.#notifyConnectListeners();\n\t\trequest.reject(new Error(reason ?? 'Connection rejected by user.'));\n\t}\n\n\t/** Subscribe to pending connect request changes. Returns an unsubscribe function. */\n\tonConnectChange(callback: (request: PendingConnectRequest | null) => void): () => void {\n\t\tthis.#connectListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#connectListeners.delete(callback);\n\t\t};\n\t}\n\n\t#notifyRequestListeners() {\n\t\tconst request = this.#pendingRequest;\n\t\tfor (const listener of this.#requestListeners) {\n\t\t\tlistener(request);\n\t\t}\n\t}\n\n\t#notifyConnectListeners() {\n\t\tconst request = this.#pendingConnect;\n\t\tfor (const listener of this.#connectListeners) {\n\t\t\tlistener(request);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to the \"a dApp invoked `standard:connect`\" edge. Returns an\n\t * unsubscribe function. Used by the page-register entry to gate its\n\t * narrow→widen account-selection on an OBSERVED connect rather than a fixed\n\t * timer (see `src/inject/index.ts`).\n\t */\n\tonDAppConnected(callback: () => void): () => void {\n\t\tthis.#connectedListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#connectedListeners.delete(callback);\n\t\t};\n\t}\n\n\t#notifyConnectedListeners() {\n\t\tfor (const listener of this.#connectedListeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[dev-wallet] onDAppConnected listener threw:', error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#aggregateAccounts(): ReadonlyWalletAccount[] {\n\t\treturn this.#adapters.flatMap((a) => a.getAccounts().map((acc) => acc.walletAccount));\n\t}\n\n\t#setupAdapterListeners() {\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tconst unsub = adapter.onAccountsChanged(() => {\n\t\t\t\tthis.#accounts = this.#aggregateAccounts();\n\t\t\t\tthis.#events.emit('change', { accounts: this.#exposedAccounts() });\n\t\t\t});\n\t\t\tthis.#unsubscribeAdapters.push(unsub);\n\t\t}\n\t}\n\n\t#on: StandardEventsOnMethod = (event, listener) => {\n\t\tthis.#events.on(event, listener);\n\t\treturn () => this.#events.off(event, listener);\n\t};\n\n\t#connect: StandardConnectMethod = async () => {\n\t\t// Notify observers that a dApp has invoked `standard:connect`. This is\n\t\t// the wallet's authoritative \"a dApp reached us\" edge — fired before we\n\t\t// branch on auto/queued so it covers both the silent auto-connect path\n\t\t// and an interactive connect.\n\t\tthis.#notifyConnectedListeners();\n\n\t\t// Auto-connect: return all accounts immediately\n\t\tif (this.#autoConnect) {\n\t\t\treturn { accounts: this.accounts };\n\t\t}\n\n\t\t// Queue a connect request for user to select accounts\n\t\tif (this.#pendingConnect) {\n\t\t\treturn Promise.reject(new Error('A connect request is already pending.'));\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.#pendingConnect = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t};\n\t\t\tthis.#notifyConnectListeners();\n\t\t});\n\t};\n\n\t#disconnect: StandardDisconnectMethod = async () => {\n\t\t// Disconnecting ends the dApp session but does not affect wallet state.\n\t\t// The wallet retains its accounts and adapter subscriptions.\n\t};\n\n\t#getClientForChain(chain: string) {\n\t\tconst network = getNetworkFromChain(chain);\n\t\tif (!network) {\n\t\t\tthrow new Error(`Invalid chain identifier: ${chain}`);\n\t\t}\n\t\treturn this.#ensureClient(network);\n\t}\n\n\t#loadNetworkUrls(defaults: Record<string, string>): Record<string, string> {\n\t\tif (!this.#persistNetworks || typeof localStorage === 'undefined') {\n\t\t\treturn { ...defaults };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = localStorage.getItem(NETWORK_STORAGE_KEY);\n\t\t\tif (raw) {\n\t\t\t\tconst parsed = JSON.parse(raw);\n\t\t\t\tif (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n\t\t\t\t\treturn parsed as Record<string, string>;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Corrupted data — fall through to defaults\n\t\t}\n\t\treturn { ...defaults };\n\t}\n\n\t#saveNetworkUrls(): void {\n\t\tif (!this.#persistNetworks || typeof localStorage === 'undefined') return;\n\t\tlocalStorage.setItem(NETWORK_STORAGE_KEY, JSON.stringify(this.#networkUrls));\n\t}\n\n\t#ensureClient(network: string): ClientWithCoreApi {\n\t\tif (!this.#clients[network]) {\n\t\t\tconst url = this.#networkUrls[network];\n\t\t\tif (!url) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No client for network \"${network}\". Available: ${this.availableNetworks.join(', ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#clients[network] = this.#clientFactory(network, url);\n\t\t}\n\t\treturn this.#clients[network];\n\t}\n\n\t#getSigner(address: string) {\n\t\tfor (const adapter of this.#adapters) {\n\t\t\tconst account = adapter.getAccount(address);\n\t\t\tif (account) return account.signer;\n\t\t}\n\t\tconst allAddresses = this.#adapters.flatMap((a) => a.getAccounts().map((acc) => acc.address));\n\t\tthrow new Error(\n\t\t\t`No account found for address \"${address}\". Available addresses: ${allAddresses.join(', ')}`,\n\t\t);\n\t}\n\n\t#shouldAutoApprove(\n\t\ttype: WalletRequest['type'],\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t): boolean {\n\t\t// Check adapter-level opt-out first — CLI adapters never auto-sign\n\t\tconst adapter = this.getAdapterForAccount(account.address);\n\t\tif (adapter?.allowAutoSign === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.#autoApprove === true) {\n\t\t\treturn true;\n\t\t}\n\t\tif (typeof this.#autoApprove === 'function') {\n\t\t\treturn this.#autoApprove({ type, account, chain, data });\n\t\t}\n\t\treturn false;\n\t}\n\n\tasync #executeSigning(\n\t\ttype: WalletRequest['type'],\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t) {\n\t\tconst signer = this.#getSigner(account.address);\n\t\tconst client = type !== 'sign-personal-message' ? this.#getClientForChain(chain) : undefined;\n\t\treturn executeSigning({ type, signer, data, client });\n\t}\n\n\t#enqueueRequest<T extends WalletRequest['type']>(\n\t\ttype: T,\n\t\taccount: WalletAccount,\n\t\tchain: string,\n\t\tdata: string | Uint8Array,\n\t): Promise<Extract<SigningResult, { type: T }>> {\n\t\ttype Result = Extract<SigningResult, { type: T }>;\n\t\tif (this.#destroyed) {\n\t\t\treturn Promise.reject(new Error('Wallet has been destroyed.'));\n\t\t}\n\t\tif (this.#shouldAutoApprove(type, account, chain, data)) {\n\t\t\treturn this.#executeSigning(type, account, chain, data) as Promise<Result>;\n\t\t}\n\n\t\t// Only one request at a time — subsequent requests are immediately rejected.\n\t\t// DApps that batch transactions should serialize their signing calls.\n\t\tif (this.#pendingRequest) {\n\t\t\treturn Promise.reject(new Error('A signing request is already pending.'));\n\t\t}\n\n\t\treturn new Promise<Result>((resolve, reject) => {\n\t\t\tthis.#pendingRequest = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\ttype,\n\t\t\t\taccount,\n\t\t\t\tchain,\n\t\t\t\tdata,\n\t\t\t\tresolve: resolve as (result: SigningResult) => void,\n\t\t\t\treject,\n\t\t\t};\n\t\t\tthis.#notifyRequestListeners();\n\t\t});\n\t}\n\n\t#signPersonalMessage: SuiSignPersonalMessageMethod = async (input) => {\n\t\treturn this.#enqueueRequest(\n\t\t\t'sign-personal-message',\n\t\t\tinput.account,\n\t\t\tinput.chain ?? 'sui:unknown',\n\t\t\tinput.message,\n\t\t);\n\t};\n\n\t#signTransaction: SuiSignTransactionMethod = async (input) => {\n\t\tconst txJson = await input.transaction.toJSON();\n\t\treturn this.#enqueueRequest('sign-transaction', input.account, input.chain, txJson);\n\t};\n\n\t#signAndExecuteTransaction: SuiSignAndExecuteTransactionMethod = async (input) => {\n\t\tconst txJson = await input.transaction.toJSON();\n\t\treturn this.#enqueueRequest('sign-and-execute-transaction', input.account, input.chain, txJson);\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -14,19 +14,45 @@ const DEFAULT_FEATURES: IdentifierString[] = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Resolve the wallet-standard chains to advertise for the given configured
|
|
18
|
+
* network names. Each network is advertised as `sui:<name>` so dApp Kit's
|
|
19
|
+
* chain-gated paths work for fork networks (`testnet-fork`, …) and custom
|
|
20
|
+
* names, unioned with the standard {@link SUI_CHAINS} for safety. Standard
|
|
21
|
+
* chains come first; configured-only chains are appended in the given order,
|
|
22
|
+
* de-duplicated.
|
|
23
|
+
*/
|
|
24
|
+
export function chainsForNetworks(networks?: readonly string[]): `sui:${string}`[] {
|
|
25
|
+
const chains: `sui:${string}`[] = [...SUI_CHAINS];
|
|
26
|
+
const seen = new Set<string>(SUI_CHAINS);
|
|
27
|
+
for (const network of networks ?? []) {
|
|
28
|
+
const chain = `sui:${network}` as const;
|
|
29
|
+
if (!seen.has(chain)) {
|
|
30
|
+
seen.add(chain);
|
|
31
|
+
chains.push(chain);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return chains;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a {@link ManagedAccount} from a signer, address, label, optional
|
|
39
|
+
* feature list, and optional advertised chains. When `chains` is omitted the
|
|
40
|
+
* account advertises the standard {@link SUI_CHAINS}; pass the configured
|
|
41
|
+
* network names (via {@link chainsForNetworks}) so per-account advertised
|
|
42
|
+
* chains include fork/custom networks too.
|
|
18
43
|
*/
|
|
19
44
|
export function buildManagedAccount(
|
|
20
45
|
signer: Signer,
|
|
21
46
|
address: string,
|
|
22
47
|
label: string,
|
|
23
48
|
features?: readonly IdentifierString[],
|
|
49
|
+
chains?: readonly `sui:${string}`[],
|
|
24
50
|
): ManagedAccount {
|
|
25
51
|
const walletAccount = new ReadonlyWalletAccount({
|
|
26
52
|
address,
|
|
27
53
|
label,
|
|
28
54
|
publicKey: signer.getPublicKey().toSuiBytes(),
|
|
29
|
-
chains: [...SUI_CHAINS],
|
|
55
|
+
chains: chains !== undefined ? [...chains] : [...SUI_CHAINS],
|
|
30
56
|
features: [...(features ?? DEFAULT_FEATURES)],
|
|
31
57
|
});
|
|
32
58
|
return { address, label, signer, walletAccount };
|
|
@@ -16,7 +16,7 @@ import { fromBase64, toBase64 } from '@mysten/sui/utils';
|
|
|
16
16
|
|
|
17
17
|
import type { ManagedAccount } from '../types.js';
|
|
18
18
|
import { BaseSignerAdapter } from './base-adapter.js';
|
|
19
|
-
import { buildManagedAccount } from './build-managed-account.js';
|
|
19
|
+
import { buildManagedAccount, chainsForNetworks } from './build-managed-account.js';
|
|
20
20
|
import { DEVSTACK_WALLET_HTTP_PATH } from './devstack-paths.js';
|
|
21
21
|
|
|
22
22
|
const DEVSTACK_WALLET_FEATURES = [
|
|
@@ -191,6 +191,11 @@ export interface DevstackSignerAdapterOptions {
|
|
|
191
191
|
token?: string | null;
|
|
192
192
|
/** Override the adapter's display name. Defaults to `'Devstack'`. */
|
|
193
193
|
name?: string;
|
|
194
|
+
/** The configured network names the wallet operates on. Each is advertised
|
|
195
|
+
* on every account as the wallet-standard chain `sui:<name>` (unioned with
|
|
196
|
+
* the standard Sui chains) so fork/custom networks are signable. Defaults
|
|
197
|
+
* to the standard chains only. */
|
|
198
|
+
networks?: readonly string[];
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
/**
|
|
@@ -199,10 +204,9 @@ export interface DevstackSignerAdapterOptions {
|
|
|
199
204
|
* shipping their private keys into the frontend bundle. All signing happens
|
|
200
205
|
* server-side via the `wallet()` plugin.
|
|
201
206
|
*
|
|
202
|
-
* The
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* adapter directly.
|
|
207
|
+
* The dev wallet that devstack injects at serve time registers this adapter
|
|
208
|
+
* automatically from the wallet connection devstack publishes — apps wire
|
|
209
|
+
* dapp-kit in their own `dapp-kit.ts` and never touch the adapter directly.
|
|
206
210
|
*
|
|
207
211
|
* To construct one manually (e.g. from a non-manifest source):
|
|
208
212
|
*
|
|
@@ -220,6 +224,7 @@ export class DevstackSignerAdapter extends BaseSignerAdapter {
|
|
|
220
224
|
|
|
221
225
|
#serverOrigin: string;
|
|
222
226
|
#authToken: string | null;
|
|
227
|
+
#chains: readonly `sui:${string}`[];
|
|
223
228
|
|
|
224
229
|
constructor(options: DevstackSignerAdapterOptions) {
|
|
225
230
|
super();
|
|
@@ -231,6 +236,7 @@ export class DevstackSignerAdapter extends BaseSignerAdapter {
|
|
|
231
236
|
this.#serverOrigin = options.serverOrigin;
|
|
232
237
|
this.#authToken = options.token ?? null;
|
|
233
238
|
this.name = options.name ?? 'Devstack';
|
|
239
|
+
this.#chains = chainsForNetworks(options.networks);
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
async initialize(): Promise<void> {
|
|
@@ -273,7 +279,13 @@ export class DevstackSignerAdapter extends BaseSignerAdapter {
|
|
|
273
279
|
serverOrigin: this.#serverOrigin,
|
|
274
280
|
authToken: this.#authToken,
|
|
275
281
|
});
|
|
276
|
-
return buildManagedAccount(
|
|
282
|
+
return buildManagedAccount(
|
|
283
|
+
signer,
|
|
284
|
+
info.address,
|
|
285
|
+
info.name,
|
|
286
|
+
DEVSTACK_WALLET_FEATURES,
|
|
287
|
+
this.#chains,
|
|
288
|
+
);
|
|
277
289
|
});
|
|
278
290
|
}
|
|
279
291
|
}
|
|
@@ -303,8 +315,8 @@ export function parseDevstackToken(pairedUrl: string | undefined): string | null
|
|
|
303
315
|
}
|
|
304
316
|
|
|
305
317
|
/** Narrow input — the only field the adapter consumes is
|
|
306
|
-
* `app.wallet.{url, pairUrl}`. Codegen emits exactly this shape so
|
|
307
|
-
* generated
|
|
318
|
+
* `app.wallet.{url, pairUrl}`. Codegen emits exactly this shape so the
|
|
319
|
+
* generated wiring doesn't have to fabricate placeholder
|
|
308
320
|
* manifest fields (`stack.app`, `coins`, etc.) just to satisfy the
|
|
309
321
|
* full `Manifest` type. Mirrors a slice of `AppManifest` from
|
|
310
322
|
* `@mysten-incubation/devstack` without importing it (dev-wallet
|
|
@@ -329,14 +341,22 @@ export interface DevstackAdapterManifest {
|
|
|
329
341
|
* {@link DevstackAdapterManifest}, so
|
|
330
342
|
* `createDevstackAdapterFromManifest(devstackManifest)` typechecks
|
|
331
343
|
* without a cast.
|
|
344
|
+
*
|
|
345
|
+
* The narrow manifest shape carries no network set, so pass
|
|
346
|
+
* `options.networks` (e.g. the generated `config.networkNames`) to advertise
|
|
347
|
+
* fork/custom networks as `sui:<name>` on every account — matching what the
|
|
348
|
+
* devstack-injected dev wallet does via the constructor. Omit it and accounts
|
|
349
|
+
* advertise the standard Sui chains only.
|
|
332
350
|
*/
|
|
333
351
|
export function createDevstackAdapterFromManifest(
|
|
334
352
|
manifest: DevstackAdapterManifest,
|
|
353
|
+
options?: { readonly networks?: readonly string[] },
|
|
335
354
|
): DevstackSignerAdapter | null {
|
|
336
355
|
const wallet = manifest.app?.wallet;
|
|
337
356
|
if (wallet === undefined) return null;
|
|
338
357
|
return new DevstackSignerAdapter({
|
|
339
358
|
serverOrigin: wallet.url,
|
|
340
359
|
token: parseDevstackToken(wallet.pairUrl),
|
|
360
|
+
networks: options?.networks,
|
|
341
361
|
});
|
|
342
362
|
}
|
package/src/inject/index.ts
CHANGED
|
@@ -20,35 +20,57 @@
|
|
|
20
20
|
// `registerDAppKitForTesting(dAppKit)` (DEV-only) — it needs the app's dApp
|
|
21
21
|
// Kit instance, which the wallet has no reference to.
|
|
22
22
|
|
|
23
|
+
import type { SignerAdapter } from '../types.js';
|
|
23
24
|
import type { AutoApprovePolicy, DevWallet } from '../wallet/dev-wallet.js';
|
|
24
25
|
import { mountAndRegisterDevWallet } from '../wallet/mount-and-register.js';
|
|
25
26
|
import { DevstackSignerAdapter } from '../adapters/devstack-adapter.js';
|
|
26
|
-
import { WebCryptoSignerAdapter } from '../adapters/webcrypto-adapter.js';
|
|
27
27
|
|
|
28
|
-
/** Shape of a single entry in the
|
|
29
|
-
* (`
|
|
28
|
+
/** Shape of a single entry in the dev `accounts` map (resolved off the
|
|
29
|
+
* injected deployment envelope via `resolveAccounts()`). Only `address` is
|
|
30
|
+
* consumed here. */
|
|
30
31
|
export interface DevstackAccountInfo {
|
|
31
32
|
readonly address: string;
|
|
32
33
|
readonly name?: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/** One network the dev wallet operates on — its routed RPC endpoint plus an
|
|
37
|
+
* optional faucet. The wallet advertises every key as the wallet-standard
|
|
38
|
+
* chain `sui:<name>` and routes signing/execution to whichever network dApp
|
|
39
|
+
* Kit has selected; the active network's `faucet` drives a fund flow. The
|
|
40
|
+
* `rpc` MUST be the SAME routed RPC the app's dApp Kit client uses for that
|
|
41
|
+
* network (a raw `127.0.0.1:9000` is CORS-blocked from the routed page
|
|
42
|
+
* origin). The wallet is agnostic to live-vs-local — local and live networks
|
|
43
|
+
* are passed through the same map. */
|
|
44
|
+
export interface DevstackNetworkInfo {
|
|
45
|
+
/** Routed RPC endpoint for this network. */
|
|
46
|
+
readonly rpc: string;
|
|
47
|
+
/** Optional faucet endpoint for this network (absent on live mainnet /
|
|
48
|
+
* fork stacks). */
|
|
49
|
+
readonly faucet?: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
export interface RegisterDevstackDevWalletConfig {
|
|
36
|
-
/** Wallet-app origin (`
|
|
53
|
+
/** Wallet-app origin (the dev-wallet connection's `walletUrl`). */
|
|
37
54
|
readonly serverOrigin: string;
|
|
38
|
-
/** Bearer token (
|
|
55
|
+
/** Bearer pairing token (read by the Vite dev server from the wallet's
|
|
56
|
+
* `0o600` side-channel token file), or null. */
|
|
39
57
|
readonly token?: string | null;
|
|
40
|
-
/**
|
|
58
|
+
/** Dev name→account map (resolved off the injected deployment envelope via
|
|
59
|
+
* `resolveAccounts()`). */
|
|
41
60
|
readonly accounts: Readonly<Record<string, DevstackAccountInfo>>;
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
/** The FULL network set the app supports — `{ <name>: { rpc, faucet? } }`,
|
|
62
|
+
* covering BOTH the live local network and any live (devnet/testnet/…)
|
|
63
|
+
* networks the deployment envelope carries. The wallet advertises each as
|
|
64
|
+
* the wallet-standard chain `sui:<name>` and operates on whichever one
|
|
65
|
+
* dApp Kit has selected, so it persists across a UI `switchNetwork` (only
|
|
66
|
+
* the active chain changes — the wallet is registered once). When omitted
|
|
67
|
+
* the wallet falls back to a single localnet entry (the non-routed default
|
|
68
|
+
* — only correct for a bare localnet setup). */
|
|
69
|
+
readonly networks?: Readonly<Record<string, DevstackNetworkInfo>>;
|
|
70
|
+
/** The network the wallet opens on (its initial active network). Must be a
|
|
71
|
+
* key of `networks`; defaults to the first `networks` key (`'localnet'`
|
|
72
|
+
* when `networks` is omitted). */
|
|
73
|
+
readonly defaultNetwork?: string;
|
|
52
74
|
/** Auto-approve all signing requests (headless e2e). Defaults to false. */
|
|
53
75
|
readonly autoApprove?: AutoApprovePolicy;
|
|
54
76
|
/** Mount the floating wallet drawer UI. Defaults to true. */
|
|
@@ -117,10 +139,20 @@ async function registerDevstackDevWalletImpl(
|
|
|
117
139
|
const { mountUI = true, autoApprove = false } = config;
|
|
118
140
|
globalThis.__DEV_WALLET_INJECTED__ = true;
|
|
119
141
|
|
|
142
|
+
// Resolve the FULL network set up front so it can be advertised both on
|
|
143
|
+
// the wallet (its `chains`) and on every devstack account (per-account
|
|
144
|
+
// `chains`) — fork/custom networks must be signable from both surfaces.
|
|
145
|
+
const networkInfos: Readonly<Record<string, DevstackNetworkInfo>> =
|
|
146
|
+
config.networks !== undefined && Object.keys(config.networks).length > 0
|
|
147
|
+
? config.networks
|
|
148
|
+
: { localnet: { rpc: 'http://127.0.0.1:9000' } };
|
|
149
|
+
const networkNames = Object.keys(networkInfos);
|
|
150
|
+
|
|
120
151
|
const adapter = new DevstackSignerAdapter({
|
|
121
152
|
serverOrigin: config.serverOrigin,
|
|
122
153
|
token: config.token ?? null,
|
|
123
154
|
name: config.name ?? 'Devstack',
|
|
155
|
+
networks: networkNames,
|
|
124
156
|
});
|
|
125
157
|
|
|
126
158
|
// The wallet EXECUTES (and simulates) `signAndExecuteTransaction` with
|
|
@@ -129,27 +161,59 @@ async function registerDevstackDevWalletImpl(
|
|
|
129
161
|
// hit the same routed RPC the app's dApp Kit uses (a raw 127.0.0.1 RPC
|
|
130
162
|
// is CORS-blocked from the routed page origin).
|
|
131
163
|
//
|
|
132
|
-
// Expose
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
// wallet-standard
|
|
136
|
-
// `sui:<network>` dApp Kit forwards for
|
|
137
|
-
// only
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
// Expose the FULL network set the app supports (local + any live networks
|
|
165
|
+
// from the deployment envelope) so the wallet operates on whichever
|
|
166
|
+
// network dApp Kit has selected. The wallet is registered ONCE via
|
|
167
|
+
// wallet-standard and advertises each key as the wallet-standard chain
|
|
168
|
+
// `sui:<network>` (matching the `sui:<network>` dApp Kit forwards for
|
|
169
|
+
// signing — the `sui:` prefix lives only at the wallet-standard boundary);
|
|
170
|
+
// a UI `switchNetwork` just changes the active chain, it never
|
|
171
|
+
// re-registers. The wallet does NOT know live-vs-local — both flow through
|
|
172
|
+
// the same map. Omitting `networks` falls back to a single localnet entry
|
|
173
|
+
// (the legacy non-routed default — resolved as `networkInfos` above).
|
|
174
|
+
const networks: Record<string, string> = {};
|
|
175
|
+
const faucets: Record<string, string> = {};
|
|
176
|
+
for (const [net, info] of Object.entries(networkInfos)) {
|
|
177
|
+
networks[net] = info.rpc;
|
|
178
|
+
if (info.faucet !== undefined && info.faucet !== null && info.faucet.length > 0) {
|
|
179
|
+
faucets[net] = info.faucet;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// The wallet opens on the requested default (the dApp-Kit default network),
|
|
183
|
+
// falling back to the first declared network.
|
|
184
|
+
const activeNetwork =
|
|
185
|
+
config.defaultNetwork !== undefined && networks[config.defaultNetwork] !== undefined
|
|
186
|
+
? config.defaultNetwork
|
|
187
|
+
: (Object.keys(networks)[0] ?? 'localnet');
|
|
141
188
|
|
|
142
189
|
// Delegate the construct → init-adapter → mount-UI → register → dispose
|
|
143
|
-
// sequence to the shared dev-wallet core helper.
|
|
144
|
-
//
|
|
145
|
-
//
|
|
190
|
+
// sequence to the shared dev-wallet core helper. The `DevstackSignerAdapter`
|
|
191
|
+
// brings the stack's server-resolved accounts (alice/bob/carol — headless
|
|
192
|
+
// HTTP signing) and is always present. The optional `WebCryptoSignerAdapter`
|
|
146
193
|
// 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
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
194
|
+
// IndexedDB across reloads (non-extractable WebCrypto keys, NOT in-memory) —
|
|
195
|
+
// but it depends on the OPTIONAL `@mysten/signers` peer. Load it dynamically
|
|
196
|
+
// and gate on its presence: an app that doesn't install `@mysten/signers`
|
|
197
|
+
// still gets a working dev wallet (just without create-your-own-account)
|
|
198
|
+
// instead of a hard inject crash on the unresolved peer. `createInitialAccount`
|
|
199
|
+
// stays off — the devstack adapter already supplies accounts, so we never
|
|
200
|
+
// fabricate a throwaway key; the WebCrypto adapter starts empty until the
|
|
201
|
+
// user adds one.
|
|
202
|
+
const adapters: SignerAdapter[] = [adapter];
|
|
203
|
+
try {
|
|
204
|
+
const { WebCryptoSignerAdapter } = await import('../adapters/webcrypto-adapter.js');
|
|
205
|
+
adapters.push(new WebCryptoSignerAdapter());
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.info(
|
|
208
|
+
'[dev-wallet] @mysten/signers is not installed — WebCrypto account creation is ' +
|
|
209
|
+
'disabled. Install @mysten/signers to enable creating your own accounts in the ' +
|
|
210
|
+
'dev wallet.',
|
|
211
|
+
error,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
151
215
|
const { wallet, dispose: disposeWallet } = await mountAndRegisterDevWallet({
|
|
152
|
-
adapters
|
|
216
|
+
adapters,
|
|
153
217
|
name: config.name ?? 'Devstack',
|
|
154
218
|
autoApprove,
|
|
155
219
|
// Auto-approve `standard:connect` whenever signing is auto-approved — both
|
|
@@ -159,6 +223,7 @@ async function registerDevstackDevWalletImpl(
|
|
|
159
223
|
// comes. In normal dev (no auto-approve) a human approves the connect.
|
|
160
224
|
autoConnect: Boolean(autoApprove),
|
|
161
225
|
networks,
|
|
226
|
+
faucets,
|
|
162
227
|
activeNetwork,
|
|
163
228
|
mountUI,
|
|
164
229
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import type { ClientWithCoreApi } from '@mysten/sui/client';
|
|
5
|
+
import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet';
|
|
5
6
|
import { css, html, LitElement, nothing } from 'lit';
|
|
6
7
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
8
|
|
|
@@ -162,6 +163,13 @@ export class DevWalletBalances extends LitElement {
|
|
|
162
163
|
min-width: 0;
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
.hero-actions {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
flex-shrink: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
165
173
|
.hero-balance {
|
|
166
174
|
display: flex;
|
|
167
175
|
align-items: baseline;
|
|
@@ -220,6 +228,13 @@ export class DevWalletBalances extends LitElement {
|
|
|
220
228
|
@property({ type: String })
|
|
221
229
|
network = '';
|
|
222
230
|
|
|
231
|
+
/** Faucet endpoint for the active network (the wallet's `activeFaucet`),
|
|
232
|
+
* or `null` when the active network has no faucet (live mainnet, fork
|
|
233
|
+
* stacks). When set, a "Request from faucet" button funds the active
|
|
234
|
+
* address against this endpoint and refreshes balances. */
|
|
235
|
+
@property({ attribute: false })
|
|
236
|
+
faucetHost: string | null = null;
|
|
237
|
+
|
|
223
238
|
/** Optional pre-seeded coin metadata — pass the generated `coins`
|
|
224
239
|
* constant from devstack codegen (`src/generated/coins.ts`) to skip
|
|
225
240
|
* per-coin `getCoinMetadata` RPC waterfalls on UI load. Unknown coin
|
|
@@ -236,6 +251,12 @@ export class DevWalletBalances extends LitElement {
|
|
|
236
251
|
@state()
|
|
237
252
|
private _error: string | null = null;
|
|
238
253
|
|
|
254
|
+
@state()
|
|
255
|
+
private _funding = false;
|
|
256
|
+
|
|
257
|
+
@state()
|
|
258
|
+
private _fundError: string | null = null;
|
|
259
|
+
|
|
239
260
|
#lastFetchedAddress = '';
|
|
240
261
|
#lastFetchedClient: ClientWithCoreApi | null = null;
|
|
241
262
|
#fetchGeneration = 0;
|
|
@@ -249,6 +270,24 @@ export class DevWalletBalances extends LitElement {
|
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
|
|
273
|
+
/** Request SUI from the active network's faucet for the active address,
|
|
274
|
+
* then refresh balances. No-op when no `faucetHost` is configured (live
|
|
275
|
+
* mainnet / fork stacks have none). */
|
|
276
|
+
async #requestFromFaucet() {
|
|
277
|
+
if (!this.faucetHost || !this.address || this._funding) return;
|
|
278
|
+
this._funding = true;
|
|
279
|
+
this._fundError = null;
|
|
280
|
+
try {
|
|
281
|
+
await requestSuiFromFaucetV2({ host: this.faucetHost, recipient: this.address });
|
|
282
|
+
this.refresh();
|
|
283
|
+
} catch (error) {
|
|
284
|
+
this._fundError =
|
|
285
|
+
error instanceof Error ? error.message : 'Faucet request failed. Try again.';
|
|
286
|
+
} finally {
|
|
287
|
+
this._funding = false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
252
291
|
override willUpdate(changedProperties: Map<string, unknown>) {
|
|
253
292
|
if (changedProperties.has('coins') && this.coins !== this.#lastCoinsRef) {
|
|
254
293
|
this.#coinIndex = indexCoinsByType(this.coins);
|
|
@@ -286,8 +325,25 @@ export class DevWalletBalances extends LitElement {
|
|
|
286
325
|
</div>
|
|
287
326
|
<div class="hero-note">${formatAddress(this.address)} · ${networkLabel}</div>
|
|
288
327
|
</div>
|
|
289
|
-
<
|
|
328
|
+
<div class="hero-actions">
|
|
329
|
+
${this.faucetHost
|
|
330
|
+
? html`<button
|
|
331
|
+
class="action-btn"
|
|
332
|
+
part="faucet-button"
|
|
333
|
+
?disabled=${this._funding}
|
|
334
|
+
@click=${this.#requestFromFaucet}
|
|
335
|
+
>
|
|
336
|
+
${this._funding ? 'Funding...' : 'Faucet'}
|
|
337
|
+
</button>`
|
|
338
|
+
: nothing}
|
|
339
|
+
<button class="action-btn" @click=${this.refresh}>Refresh</button>
|
|
340
|
+
</div>
|
|
290
341
|
</div>
|
|
342
|
+
${this._fundError
|
|
343
|
+
? html`<div class="error-state" part="faucet-error-message" aria-live="polite">
|
|
344
|
+
${this._fundError}
|
|
345
|
+
</div>`
|
|
346
|
+
: nothing}
|
|
291
347
|
<div class="coins-header">
|
|
292
348
|
<h3 class="section-header">Coins · ${this._balances.length}</h3>
|
|
293
349
|
<button class="manage-btn" disabled>Manage</button>
|
|
@@ -302,8 +358,11 @@ export class DevWalletBalances extends LitElement {
|
|
|
302
358
|
? html`<div class="empty-state empty-wallet" part="empty-state">
|
|
303
359
|
<div class="empty-title">No balances on ${networkLabel}</div>
|
|
304
360
|
<div class="empty-copy">
|
|
305
|
-
|
|
306
|
-
|
|
361
|
+
${this.faucetHost
|
|
362
|
+
? html`Use the Faucet button above to fund ${formatAddress(this.address)} on
|
|
363
|
+
${networkLabel}, then refresh.`
|
|
364
|
+
: html`Fund ${formatAddress(this.address)} from your local faucet or devstack
|
|
365
|
+
seed, then refresh balances here.`}
|
|
307
366
|
</div>
|
|
308
367
|
</div>`
|
|
309
368
|
: html`
|
|
@@ -225,10 +225,11 @@ export class WalletController implements ReactiveController {
|
|
|
225
225
|
? html`
|
|
226
226
|
<div class="section">
|
|
227
227
|
<dev-wallet-balances
|
|
228
|
-
exportparts="balance-list, loading: balances-loading, error-message: balances-error-message, empty-state: balances-empty-state"
|
|
228
|
+
exportparts="balance-list, loading: balances-loading, error-message: balances-error-message, empty-state: balances-empty-state, faucet-button: balances-faucet-button, faucet-error-message: balances-faucet-error-message"
|
|
229
229
|
.address=${this.activeAddress}
|
|
230
230
|
.client=${this.getActiveClient()}
|
|
231
231
|
.network=${this.#wallet.activeNetwork}
|
|
232
|
+
.faucetHost=${this.#wallet.activeFaucet}
|
|
232
233
|
.coins=${this.coins}
|
|
233
234
|
></dev-wallet-balances>
|
|
234
235
|
</div>
|