@mysten-incubation/dev-wallet 0.4.1 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/adapters/build-managed-account.d.mts +15 -2
  3. package/dist/adapters/build-managed-account.d.mts.map +1 -1
  4. package/dist/adapters/build-managed-account.mjs +28 -4
  5. package/dist/adapters/build-managed-account.mjs.map +1 -1
  6. package/dist/adapters/devstack-adapter.d.mts +19 -7
  7. package/dist/adapters/devstack-adapter.d.mts.map +1 -1
  8. package/dist/adapters/devstack-adapter.mjs +16 -8
  9. package/dist/adapters/devstack-adapter.mjs.map +1 -1
  10. package/dist/inject/index.d.mts +37 -16
  11. package/dist/inject/index.d.mts.map +1 -1
  12. package/dist/inject/index.mjs +12 -4
  13. package/dist/inject/index.mjs.map +1 -1
  14. package/dist/standalone/assets/{base-adapter-DET6CP7z.js → base-adapter-C5w5q7XZ.js} +1 -1
  15. package/dist/standalone/assets/{in-memory-adapter-CX7xym4e.js → in-memory-adapter-Ban2kRYS.js} +1 -1
  16. package/dist/standalone/assets/{main-CuOnBmaa.js → main-xfzarPod.js} +3 -3
  17. package/dist/standalone/assets/{webcrypto-adapter-CUQmbHDy.js → webcrypto-adapter-Ddqn-RAu.js} +1 -1
  18. package/dist/standalone/index.html +2 -2
  19. package/dist/ui/dev-wallet-balances.d.mts +7 -0
  20. package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
  21. package/dist/ui/dev-wallet-balances.mjs +50 -3
  22. package/dist/ui/dev-wallet-balances.mjs.map +1 -1
  23. package/dist/ui/wallet-controller.mjs +2 -1
  24. package/dist/ui/wallet-controller.mjs.map +1 -1
  25. package/dist/wallet/dev-wallet.d.mts +26 -2
  26. package/dist/wallet/dev-wallet.d.mts.map +1 -1
  27. package/dist/wallet/dev-wallet.mjs +35 -6
  28. package/dist/wallet/dev-wallet.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/adapters/build-managed-account.ts +28 -2
  31. package/src/adapters/devstack-adapter.ts +28 -8
  32. package/src/inject/index.ts +72 -24
  33. package/src/ui/dev-wallet-balances.ts +62 -3
  34. package/src/ui/wallet-controller.ts +2 -1
  35. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mysten-incubation/dev-wallet",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A modular dev wallet for Sui",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Mysten Labs <build@mystenlabs.com>",
@@ -14,19 +14,45 @@ const DEFAULT_FEATURES: IdentifierString[] = [
14
14
  ];
15
15
 
16
16
  /**
17
- * Build a {@link ManagedAccount} from a signer, address, label, and optional feature list.
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 simplest construction uses devstack's generated `dapp-kit-config.ts`,
203
- * which bakes the wallet adapter wiring at codegen time — apps spread
204
- * `devstackDappKitConfig` into `createDAppKit(...)` and never touch the
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(signer, info.address, info.name, DEVSTACK_WALLET_FEATURES);
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 `dapp-kit-config.ts` doesn't have to fabricate placeholder
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
  }
@@ -25,30 +25,52 @@ import type { AutoApprovePolicy, DevWallet } from '../wallet/dev-wallet.js';
25
25
  import { mountAndRegisterDevWallet } from '../wallet/mount-and-register.js';
26
26
  import { DevstackSignerAdapter } from '../adapters/devstack-adapter.js';
27
27
 
28
- /** Shape of a single entry in the generated `accounts` map
29
- * (`generated-extras/accounts.ts`). Only `address` is consumed here. */
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 (`devWallet.walletUrl`). */
53
+ /** Wallet-app origin (the dev-wallet connection's `walletUrl`). */
37
54
  readonly serverOrigin: string;
38
- /** Bearer token (`parseDevstackToken(devWallet.pairUrl)`), or null. */
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
- /** Generated name→account map (`accounts` from `generated-extras/accounts.ts`). */
58
+ /** Dev name→account map (resolved off the injected deployment envelope via
59
+ * `resolveAccounts()`). */
41
60
  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;
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,15 +161,30 @@ 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 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 };
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
190
  // sequence to the shared dev-wallet core helper. The `DevstackSignerAdapter`
@@ -176,6 +223,7 @@ async function registerDevstackDevWalletImpl(
176
223
  // comes. In normal dev (no auto-approve) a human approves the connect.
177
224
  autoConnect: Boolean(autoApprove),
178
225
  networks,
226
+ faucets,
179
227
  activeNetwork,
180
228
  mountUI,
181
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
- <button class="action-btn" @click=${this.refresh}>Refresh</button>
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
- Fund ${formatAddress(this.address)} from your local faucet or devstack seed, then
306
- refresh balances here.
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>