-
+
+ {{ errorMessage }}
+
+
+
+
{{ note }}
Create New Wallet
@@ -20,9 +40,17 @@
@click="step = 'restore'"
>
- Use Existing Recovery Key
+ Restore Recovery Phrase
+
+ A legacy local wallet was found. It is not read or migrated
+ automatically. Restore it with the recovery phrase you saved when it was
+ created.
+
-
-
-
- Write down these 12 words in order. Think of them as your secure
- password so keep them safe - you will need them to restore your account.
- They will not be shown again.
-
+
+
Create an encrypted wallet
+
+ This separate vault passphrase encrypts your recovery phrase before it
+ is synchronized. Networked cannot recover it for you.
+
+
+
+ Vault passphrase
+
+
+
+ Confirm passphrase
+
+
+
+ Create Wallet
+
+
+
+ Back
+
+
+
+
+
Save these 12 words
+
+ This recovery phrase is your independent exit if this app or your
+ passkey provider becomes unavailable. It will only be shown again
+ after unlocking the vault.
+
+
- {{ i + 1 }}
+ {{ index + 1 }}
{{ word }}
-
-
-
- I've saved my seed phrase
-
-
-
+
+ I saved my recovery phrase somewhere safe
+
Continue
-
- Back
+ Add a passkey first (optional)
-
-
-
- Enter your 12-word seed phrase to restore your wallet.
-
-
+
+
Restore wallet
+
+ Enter the original 12-word phrase and choose a new vault passphrase
+ for synchronized unlock.
+
+
-
+
+ New vault passphrase
+
+
+
+ Confirm passphrase
+
+
Restore Wallet
Back
-
+
+
+
-
-
+
+ Encrypted wallet
+ {{ snapshot.address ? shortAddress(snapshot.address) : '' }} is unlocked
+ for this tab.
+
+
+ Continue with In App
+
+
+ Add another passkey
+
+
+
+ Back
+
diff --git a/packages/components.evm/src/composables/inAppWallet.ts b/packages/components.evm/src/composables/inAppWallet.ts
new file mode 100644
index 00000000..0dd3ed66
--- /dev/null
+++ b/packages/components.evm/src/composables/inAppWallet.ts
@@ -0,0 +1,131 @@
+import {
+ computed,
+ inject,
+ readonly,
+ shallowRef,
+ type ComputedRef,
+ type InjectionKey,
+ type Ref,
+} from 'vue'
+import {
+ EncryptedWalletKeyring,
+ WalletLockedError,
+ type PasskeyRegistrationOptions,
+ type UnlockReason,
+ type WalletKeyringSnapshot,
+ type WalletVaultStore,
+} from '../connectors/inAppWallet'
+
+export type EvmInAppWalletHost = {
+ store: WalletVaultStore
+ scope: string
+ rpName: string
+ rpId?: string
+ userName?: string
+}
+
+let configuredHost: EvmInAppWalletHost | null = null
+
+export function configureEvmInAppWalletHost(
+ host: EvmInAppWalletHost | null,
+): void {
+ configuredHost = host
+}
+
+export function getConfiguredEvmInAppWalletHost(): EvmInAppWalletHost | null {
+ return configuredHost
+}
+
+export class EvmInAppWalletController {
+ readonly keyring: EncryptedWalletKeyring
+ readonly host: EvmInAppWalletHost
+ readonly snapshot: Ref
+ readonly unlockReason: Readonly[>
+ readonly unlockRequested: ComputedRef]
+
+ readonly #pendingReason = shallowRef(null)
+ #resolveUnlock: (() => void) | null = null
+ #rejectUnlock: ((error: unknown) => void) | null = null
+ readonly #unsubscribe: () => void
+
+ constructor(keyring: EncryptedWalletKeyring, host: EvmInAppWalletHost) {
+ this.keyring = keyring
+ this.host = host
+ this.snapshot = shallowRef(keyring.snapshot())
+ this.unlockReason = readonly(this.#pendingReason)
+ this.unlockRequested = computed(() => this.#pendingReason.value !== null)
+ this.#unsubscribe = keyring.subscribe((snapshot) => {
+ this.snapshot.value = snapshot
+ })
+ }
+
+ requestUnlock = (reason: UnlockReason): Promise => {
+ if (this.keyring.isUnlocked) return Promise.resolve()
+ this.cancelUnlock()
+ this.#pendingReason.value = reason
+ return new Promise((resolve, reject) => {
+ this.#resolveUnlock = resolve
+ this.#rejectUnlock = reject
+ })
+ }
+
+ async unlockWithPassphrase(passphrase: string): Promise {
+ await this.keyring.unlockWithPassphrase(passphrase)
+ this.completeUnlock()
+ }
+
+ async unlockWithPasskey(slotId?: string): Promise {
+ await this.keyring.unlockWithPasskey(slotId)
+ this.completeUnlock()
+ }
+
+ async addPasskey(label?: string): Promise {
+ const options: PasskeyRegistrationOptions = {
+ rpName: this.host.rpName,
+ rpId: this.host.rpId,
+ userName: this.host.userName,
+ label,
+ }
+ await this.keyring.addPasskey(options)
+ }
+
+ completeUnlock(): void {
+ if (!this.keyring.isUnlocked) throw new WalletLockedError()
+ this.#resolveUnlock?.()
+ this.#clearUnlock()
+ }
+
+ cancelUnlock(): void {
+ this.#rejectUnlock?.(new WalletLockedError('Wallet unlock cancelled'))
+ this.#clearUnlock()
+ }
+
+ destroy(): void {
+ this.cancelUnlock()
+ this.#unsubscribe()
+ this.keyring.destroy()
+ }
+
+ #clearUnlock(): void {
+ this.#pendingReason.value = null
+ this.#resolveUnlock = null
+ this.#rejectUnlock = null
+ }
+}
+
+export const EvmInAppWalletControllerKey: InjectionKey =
+ Symbol('EvmInAppWalletController')
+
+export function useEvmInAppWallet(): EvmInAppWalletController {
+ const controller = inject(EvmInAppWalletControllerKey)
+ if (!controller) {
+ throw new Error(
+ 'The in-app wallet is not enabled or its controller was not provided',
+ )
+ }
+ return controller
+}
+
+export function useOptionalEvmInAppWallet(): EvmInAppWalletController | null {
+ return inject(EvmInAppWalletControllerKey, null)
+}
diff --git a/packages/components.evm/src/connectors/inAppWallet.ts b/packages/components.evm/src/connectors/inAppWallet.ts
index 1741596c..4ea3690e 100644
--- a/packages/components.evm/src/connectors/inAppWallet.ts
+++ b/packages/components.evm/src/connectors/inAppWallet.ts
@@ -1,5 +1,17 @@
export {
+ EncryptedWalletKeyring,
+ PasskeyPrfUnavailableError,
+ VaultConflictError,
+ WalletLockedError,
+ assertVaultPassphrase,
+ createLocalEncryptedVaultStore,
inAppWallet,
- prepareInAppWallet,
type InAppWalletParameters,
+ type EncryptedWalletVaultV1,
+ type PasskeyRegistrationOptions,
+ type RequestUnlock,
+ type SyncedWalletVault,
+ type UnlockReason,
+ type WalletKeyringSnapshot,
+ type WalletVaultStore,
} from '@1001-digital/wagmi-in-app-wallet'
diff --git a/packages/components.evm/src/index.ts b/packages/components.evm/src/index.ts
index 9d519c37..5fdca555 100644
--- a/packages/components.evm/src/index.ts
+++ b/packages/components.evm/src/index.ts
@@ -131,9 +131,18 @@ export type {
SiweSignInOptions,
SiweSignInResult,
} from './composables/siwe'
+export {
+ configureEvmInAppWalletHost,
+ EvmInAppWalletController,
+ EvmInAppWalletControllerKey,
+ getConfiguredEvmInAppWalletHost,
+ useEvmInAppWallet,
+ useOptionalEvmInAppWallet,
+} from './composables/inAppWallet'
+export type { EvmInAppWalletHost } from './composables/inAppWallet'
// Connectors
-export { inAppWallet, prepareInAppWallet } from './connectors/inAppWallet'
+export * from './connectors/inAppWallet'
export { clientOnlyComponents } from './client-only'
@@ -161,6 +170,8 @@ export { default as EvmMultiTransactionFlow } from './components/EvmMultiTransac
export { default as EvmMultiTransactionFlowDialog } from './components/EvmMultiTransactionFlowDialog.vue'
export { default as EvmSeedPhraseInput } from './components/EvmSeedPhraseInput.vue'
export { default as EvmInAppWalletSetup } from './components/EvmInAppWalletSetup.vue'
+export { default as EvmInAppWalletManager } from './components/EvmInAppWalletManager.vue'
+export { default as EvmInAppWalletUnlockDialog } from './components/EvmInAppWalletUnlockDialog.vue'
export { default as EvmSiwe } from './components/EvmSiwe.vue'
export { default as EvmSiweDialog } from './components/EvmSiweDialog.vue'
export { default as EvmConnectAuth } from './components/EvmConnectAuth.vue'
diff --git a/packages/components.evm/src/types.ts b/packages/components.evm/src/types.ts
index 22049168..01049e7b 100644
--- a/packages/components.evm/src/types.ts
+++ b/packages/components.evm/src/types.ts
@@ -267,6 +267,11 @@ export interface EvmInAppWalletSetupEmits {
back: []
}
+export interface EvmInAppWalletUnlockDialogEmits {
+ unlocked: []
+ cancelled: []
+}
+
// EvmSiwe
export interface EvmSiweProps {
getNonce: () => Promise
diff --git a/packages/layers.evm/app/composables/inAppWallet.ts b/packages/layers.evm/app/composables/inAppWallet.ts
new file mode 100644
index 00000000..85db1fa3
--- /dev/null
+++ b/packages/layers.evm/app/composables/inAppWallet.ts
@@ -0,0 +1,5 @@
+// Re-exported to enable Nuxt auto-imports for layer consumers
+export {
+ useEvmInAppWallet,
+ useOptionalEvmInAppWallet,
+} from '@1001-digital/components.evm'
diff --git a/packages/layers.evm/app/plugins/components.ts b/packages/layers.evm/app/plugins/components.ts
index eaa38806..5aea3e7f 100644
--- a/packages/layers.evm/app/plugins/components.ts
+++ b/packages/layers.evm/app/plugins/components.ts
@@ -13,6 +13,8 @@ import {
EvmConnectorQR,
EvmEthInput,
EvmInAppWalletSetup,
+ EvmInAppWalletManager,
+ EvmInAppWalletUnlockDialog,
EvmMetaMaskQR,
EvmMultiTransactionFlow,
EvmMultiTransactionFlowDialog,
@@ -50,6 +52,8 @@ const shadowed = {
EvmConnectorQR,
EvmEthInput,
EvmInAppWalletSetup,
+ EvmInAppWalletManager,
+ EvmInAppWalletUnlockDialog,
EvmMetaMaskQR,
EvmMultiTransactionFlow,
EvmMultiTransactionFlowDialog,
diff --git a/packages/layers.evm/app/plugins/wagmi.ts b/packages/layers.evm/app/plugins/wagmi.ts
index 084cdd84..670c32b8 100644
--- a/packages/layers.evm/app/plugins/wagmi.ts
+++ b/packages/layers.evm/app/plugins/wagmi.ts
@@ -1,11 +1,19 @@
import { VueQueryPlugin } from '@tanstack/vue-query'
import { WagmiPlugin } from '@wagmi/vue'
-import { EvmConfigKey } from '@1001-digital/components.evm'
+import {
+ createLocalEncryptedVaultStore,
+ EncryptedWalletKeyring,
+ EvmConfigKey,
+ EvmInAppWalletController,
+ EvmInAppWalletControllerKey,
+ getConfiguredEvmInAppWalletHost,
+ type EvmInAppWalletHost,
+} from '@1001-digital/components.evm'
import { createWagmiConfig } from '../wagmi'
export default defineNuxtPlugin({
name: 'wagmi',
- setup(nuxtApp) {
+ async setup(nuxtApp) {
const appConfig = useAppConfig()
const runtimeConfig = nuxtApp.$config.public.evm as {
walletConnectProjectId: string
@@ -16,6 +24,19 @@ export default defineNuxtPlugin({
const indexers =
runtimeConfig.ens?.indexers?.split(/\s+/).filter(Boolean) || []
+ let walletController: EvmInAppWalletController | null = null
+ if (import.meta.client && appConfig.evm?.inAppWallet?.enabled) {
+ const configuredHost = getConfiguredEvmInAppWalletHost()
+ const host: EvmInAppWalletHost = configuredHost ?? {
+ store: createLocalEncryptedVaultStore(),
+ scope: window.location.host,
+ rpName: appConfig.evm?.title || 'EVM Layer',
+ }
+ const keyring = new EncryptedWalletKeyring({ store: host.store })
+ walletController = new EvmInAppWalletController(keyring, host)
+ await keyring.load()
+ }
+
const { wagmiConfig, evmConfig } = createWagmiConfig({
title: appConfig.evm?.title || 'EVM Layer',
appLogoUrl: appConfig.evm?.appLogoUrl,
@@ -28,7 +49,12 @@ export default defineNuxtPlugin({
ipfsGateway: appConfig.evm?.ipfsGateway,
arweaveGateway: appConfig.evm?.arweaveGateway,
baseURL: nuxtApp.$config.app.baseURL,
- inAppWalletEnabled: appConfig.evm?.inAppWallet?.enabled,
+ inAppWallet: walletController
+ ? {
+ keyring: walletController.keyring,
+ requestUnlock: walletController.requestUnlock,
+ }
+ : undefined,
isClient: import.meta.client,
})
@@ -37,9 +63,14 @@ export default defineNuxtPlugin({
.use(VueQueryPlugin, {})
.provide(EvmConfigKey, evmConfig)
+ if (walletController) {
+ nuxtApp.vueApp.provide(EvmInAppWalletControllerKey, walletController)
+ }
+
return {
provide: {
wagmi: wagmiConfig,
+ evmInAppWallet: walletController,
},
}
},
diff --git a/packages/layers.evm/app/shims/components.ts b/packages/layers.evm/app/shims/components.ts
index 0958d7fd..45cf9166 100644
--- a/packages/layers.evm/app/shims/components.ts
+++ b/packages/layers.evm/app/shims/components.ts
@@ -58,6 +58,14 @@ export const EvmInAppWalletSetup = proxy(
'EvmInAppWalletSetup',
Originals.EvmInAppWalletSetup,
)
+export const EvmInAppWalletManager = proxy(
+ 'EvmInAppWalletManager',
+ Originals.EvmInAppWalletManager,
+)
+export const EvmInAppWalletUnlockDialog = proxy(
+ 'EvmInAppWalletUnlockDialog',
+ Originals.EvmInAppWalletUnlockDialog,
+)
export const EvmMetaMaskQR = proxy('EvmMetaMaskQR', Originals.EvmMetaMaskQR)
export const EvmMultiTransactionFlow = proxy(
'EvmMultiTransactionFlow',
diff --git a/packages/layers.evm/app/types/in-app-wallet.d.ts b/packages/layers.evm/app/types/in-app-wallet.d.ts
new file mode 100644
index 00000000..590be1cd
--- /dev/null
+++ b/packages/layers.evm/app/types/in-app-wallet.d.ts
@@ -0,0 +1,15 @@
+import type { EvmInAppWalletController } from '@1001-digital/components.evm'
+
+declare module '#app' {
+ interface NuxtApp {
+ $evmInAppWallet: EvmInAppWalletController | null
+ }
+}
+
+declare module 'vue' {
+ interface ComponentCustomProperties {
+ $evmInAppWallet: EvmInAppWalletController | null
+ }
+}
+
+export {}
diff --git a/packages/layers.evm/app/utils/connectors.ts b/packages/layers.evm/app/utils/connectors.ts
index 4b722e28..d8be9329 100644
--- a/packages/layers.evm/app/utils/connectors.ts
+++ b/packages/layers.evm/app/utils/connectors.ts
@@ -1,2 +1,6 @@
// Re-exported to enable Nuxt auto-imports for layer consumers
-export { inAppWallet, prepareInAppWallet } from '@1001-digital/components.evm'
+export {
+ configureEvmInAppWalletHost,
+ createLocalEncryptedVaultStore,
+ inAppWallet,
+} from '@1001-digital/components.evm'
diff --git a/packages/layers.evm/app/wagmi.ts b/packages/layers.evm/app/wagmi.ts
index af27680e..a13bbaf9 100644
--- a/packages/layers.evm/app/wagmi.ts
+++ b/packages/layers.evm/app/wagmi.ts
@@ -21,6 +21,7 @@ import {
resolveChain,
inAppWallet,
type EvmConfig,
+ type InAppWalletParameters,
} from '@1001-digital/components.evm'
interface ChainEntry {
@@ -40,7 +41,7 @@ interface CreateOptions {
ipfsGateway?: string
arweaveGateway?: string
baseURL?: string
- inAppWalletEnabled?: boolean
+ inAppWallet?: InAppWalletParameters
isClient?: boolean
}
@@ -60,7 +61,7 @@ export function createWagmiConfig(options: CreateOptions): {
ipfsGateway,
arweaveGateway,
baseURL,
- inAppWalletEnabled,
+ inAppWallet: inAppWalletOptions,
isClient,
} = options
@@ -121,7 +122,9 @@ export function createWagmiConfig(options: CreateOptions): {
}),
)
- if (inAppWalletEnabled) connectors.push(inAppWallet())
+ if (inAppWalletOptions) {
+ connectors.push(inAppWallet(inAppWalletOptions) as CreateConnectorFn)
+ }
const wagmiConfig: Config = createConfig({
chains: chains as [Chain, ...Chain[]],