diff --git a/packages/base/adapters/evm/ethers/index.ts b/packages/base/adapters/evm/ethers/index.ts index 93fbb3d639..4a329b15b0 100644 --- a/packages/base/adapters/evm/ethers/index.ts +++ b/packages/base/adapters/evm/ethers/index.ts @@ -1,6 +1,7 @@ import '@web3modal/polyfills' export { EVMEthersClient } from './client.js' +export * from '@web3modal/scaffold-utils/ethers' // -- Types export type { AdapterOptions } from './client.js' diff --git a/packages/base/adapters/evm/ethers5/client.ts b/packages/base/adapters/evm/ethers5/client.ts index e2cf775d8c..9b9474ef9c 100644 --- a/packages/base/adapters/evm/ethers5/client.ts +++ b/packages/base/adapters/evm/ethers5/client.ts @@ -28,6 +28,9 @@ import { EthersHelpersUtil, EthersStoreUtil } from '@web3modal/scaffold-utils/ethers' +import { W3mFrameProvider, W3mFrameHelpers, W3mFrameRpcConstants } from '@web3modal/wallet' +import type { W3mFrameTypes } from '@web3modal/wallet' +import type { CombinedProvider } from '@web3modal/scaffold-utils/ethers' import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider' import { NetworkUtil } from '@web3modal/common' import type { Chain as AvailableChain } from '@web3modal/common' @@ -104,6 +107,8 @@ export class EVMEthers5Client { private metadata?: Metadata + private authProvider?: W3mFrameProvider + public networkControllerClient: NetworkControllerClient public connectionControllerClient: ConnectionControllerClient @@ -438,6 +443,10 @@ export class EVMEthers5Client { this.checkActiveInjectedProvider(this.ethersConfig) } + if (this.ethersConfig.auth) { + this.syncAuthConnector(this.options.projectId, this.ethersConfig.auth) + } + if (this.ethersConfig.coinbase) { this.checkActiveCoinbaseProvider(this.ethersConfig) } @@ -582,7 +591,8 @@ export class EVMEthers5Client { id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] + imageUrl: chainImages?.[chain.chainId], + chain: CommonConstantsUtil.CHAIN.EVM }) as CaipNetwork ) this.appKit?.setRequestedCaipNetworks(requestedCaipNetworks ?? [], this.chain) @@ -655,6 +665,10 @@ export class EVMEthers5Client { EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider) EthersStoreUtil.setStatus('connected') EthersStoreUtil.setIsConnected(true) + this.appKit?.setAllAccounts( + WalletConnectProvider.accounts.map(address => ({ address, type: 'eoa' })), + this.chain + ) this.setAddress(WalletConnectProvider.accounts?.[0]) this.watchWalletConnect() } @@ -671,6 +685,10 @@ export class EVMEthers5Client { EthersStoreUtil.setProvider(provider) EthersStoreUtil.setStatus('connected') EthersStoreUtil.setIsConnected(true) + this.appKit?.setAllAccounts( + addresses.map(address => ({ address, type: 'eoa' })), + this.chain + ) this.setAddress(addresses[0]) this.watchEIP6963(provider) } @@ -689,6 +707,10 @@ export class EVMEthers5Client { EthersStoreUtil.setProvider(config.injected) EthersStoreUtil.setStatus('connected') EthersStoreUtil.setIsConnected(true) + this.appKit?.setAllAccounts( + addresses.map(address => ({ address, type: 'eoa' })), + this.chain + ) this.setAddress(addresses[0]) this.watchCoinbase(config) } @@ -706,12 +728,56 @@ export class EVMEthers5Client { EthersStoreUtil.setProvider(config.coinbase) EthersStoreUtil.setStatus('connected') EthersStoreUtil.setIsConnected(true) + this.appKit?.setAllAccounts( + addresses.map(address => ({ address, type: 'eoa' })), + this.chain + ) this.setAddress(addresses[0]) this.watchCoinbase(config) } } } + private async setAuthProvider() { + SafeLocalStorage.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.AUTH_CONNECTOR_ID) + + if (this.authProvider) { + this.appKit?.setLoading(true) + const { + address, + chainId, + smartAccountDeployed, + preferredAccountType, + accounts = [] + } = await this.authProvider.connect({ chainId: this.getChainId() }) + + const { smartAccountEnabledNetworks } = + await this.authProvider.getSmartAccountEnabledNetworks() + + this.appKit?.setSmartAccountEnabledNetworks(smartAccountEnabledNetworks, this.chain) + if (address && chainId) { + this.appKit?.setAllAccounts( + accounts.length > 0 + ? accounts + : [{ address, type: preferredAccountType as 'eoa' | 'smartAccount' }], + this.chain + ) + + EthersStoreUtil.setChainId(NetworkUtil.parseEvmChainId(chainId)) + EthersStoreUtil.setProviderType(ConstantsUtil.AUTH_CONNECTOR_ID as 'w3mAuth') + EthersStoreUtil.setProvider(this.authProvider as unknown as CombinedProvider) + EthersStoreUtil.setStatus('connected') + EthersStoreUtil.setIsConnected(true) + EthersStoreUtil.setAddress(address as Address) + EthersStoreUtil.setPreferredAccountType(preferredAccountType as W3mFrameTypes.AccountType) + this.appKit?.setSmartAccountDeployed(Boolean(smartAccountDeployed), this.chain) + this.watchAuth() + this.watchModal() + } + this.appKit?.setLoading(false) + } + } + private async watchWalletConnect() { const WalletConnectProvider = await this.getWalletConnectProvider() @@ -784,6 +850,9 @@ export class EVMEthers5Client { } private watchEIP6963(provider: Provider) { + const appKit = this.appKit + const namespace = this.chain + function disconnectHandler() { SafeLocalStorage.removeItem(EthersConstantsUtil.WALLET_ID) EthersStoreUtil.reset() @@ -797,7 +866,12 @@ export class EVMEthers5Client { const currentAccount = accounts?.[0] if (currentAccount) { EthersStoreUtil.setAddress(utils.getAddress(currentAccount) as Address) + appKit?.setAllAccounts( + accounts.map(address => ({ address, type: 'eoa' })), + namespace + ) } else { + appKit?.setAllAccounts([], namespace) SafeLocalStorage.removeItem(EthersConstantsUtil.WALLET_ID) EthersStoreUtil.reset() } @@ -854,6 +928,93 @@ export class EVMEthers5Client { } } + private watchAuth() { + if (this.authProvider) { + this.authProvider.onRpcRequest(request => { + if (W3mFrameHelpers.checkIfRequestExists(request)) { + if (!W3mFrameHelpers.checkIfRequestIsAllowed(request)) { + if (this.appKit?.isOpen()) { + if (this.appKit?.isTransactionStackEmpty()) { + return + } + if (this.appKit?.isTransactionShouldReplaceView()) { + this.appKit?.replace('ApproveTransaction') + } else { + this.appKit?.redirect('ApproveTransaction') + } + } else { + this.appKit?.open({ view: 'ApproveTransaction' }) + } + } + } else { + this.appKit?.open() + // eslint-disable-next-line no-console + console.error(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_MESSAGE, { + method: request.method + }) + setTimeout(() => { + this.appKit?.showErrorMessage(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_UI_MESSAGE) + }, 300) + } + }) + + this.authProvider.onRpcError(() => { + const isModalOpen = this.appKit?.isOpen() + + if (isModalOpen) { + if (this.appKit?.isTransactionStackEmpty()) { + this.appKit?.close() + } else { + this.appKit?.popTransactionStack(true) + } + } + }) + + this.authProvider.onRpcSuccess(() => { + if (this.appKit?.isTransactionStackEmpty()) { + this.appKit?.close() + } else { + this.appKit?.popTransactionStack() + } + }) + + this.authProvider.onNotConnected(() => { + this.appKit?.setIsConnected(false, this.chain) + this.appKit?.setLoading(false) + }) + + this.authProvider.onIsConnected(({ preferredAccountType }) => { + this.appKit?.setIsConnected(true, this.chain) + this.appKit?.setLoading(false) + EthersStoreUtil.setPreferredAccountType(preferredAccountType as W3mFrameTypes.AccountType) + }) + + this.authProvider.onSetPreferredAccount(({ address, type }) => { + if (!address) { + return + } + this.appKit?.setLoading(true) + const chainId = NetworkUtil.caipNetworkIdToNumber(this.appKit?.getCaipNetwork()?.id) + EthersStoreUtil.setAddress(address as Address) + EthersStoreUtil.setChainId(chainId) + EthersStoreUtil.setStatus('connected') + EthersStoreUtil.setIsConnected(true) + EthersStoreUtil.setPreferredAccountType(type as W3mFrameTypes.AccountType) + this.syncAccount().then(() => this.appKit?.setLoading(false)) + }) + } + } + + private watchModal() { + if (this.authProvider) { + this.subscribeState(val => { + if (!val.open) { + this.authProvider?.rejectRpcRequests() + } + }) + } + } + private async syncAccount() { const address = EthersStoreUtil.state.address const chainId = EthersStoreUtil.state.chainId @@ -879,6 +1040,7 @@ export class EVMEthers5Client { } else if (!isConnected && this.hasSyncedConnectedAccount) { this.appKit?.resetWcConnection() this.appKit?.resetNetwork() + this.appKit?.setAllAccounts([], this.chain) } } @@ -1037,7 +1199,6 @@ export class EVMEthers5Client { method: 'wallet_switchEthereumChain', params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] }) - EthersStoreUtil.setChainId(chainId) // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (switchError: any) { @@ -1051,6 +1212,29 @@ export class EVMEthers5Client { ) } } + } else if (providerType === ConstantsUtil.INJECTED_CONNECTOR_ID && chain) { + const InjectedProvider = provider + if (InjectedProvider) { + try { + await InjectedProvider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] + }) + EthersStoreUtil.setChainId(chain.chainId) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (switchError: any) { + if ( + switchError.code === EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID || + switchError.code === EthersConstantsUtil.ERROR_CODE_DEFAULT || + switchError?.data?.originalError?.code === + EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID + ) { + await EthersHelpersUtil.addEthereumChain(InjectedProvider, chain) + } else { + throw new Error('Chain is not supported') + } + } + } } else if (providerType === ConstantsUtil.EIP6963_CONNECTOR_ID && chain) { const EIP6963Provider = provider @@ -1093,9 +1277,33 @@ export class EVMEthers5Client { EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID ) { await EthersHelpersUtil.addEthereumChain(CoinbaseProvider, chain) + } else { + throw new Error('Error switching network') } } } + } else if (providerType === ConstantsUtil.AUTH_CONNECTOR_ID) { + if (this.authProvider && chain?.chainId) { + try { + this.appKit?.setLoading(true) + await this.authProvider.switchNetwork(chain?.chainId) + EthersStoreUtil.setChainId(chain.chainId) + + const { address, preferredAccountType } = await this.authProvider.connect({ + chainId: chain?.chainId + }) + + EthersStoreUtil.setAddress(address as Address) + EthersStoreUtil.setPreferredAccountType( + preferredAccountType as W3mFrameTypes.AccountType + ) + await this.syncAccount() + } catch { + throw new Error('Switching chain failed') + } finally { + this.appKit?.setLoading(false) + } + } } } } @@ -1147,6 +1355,34 @@ export class EVMEthers5Client { this.appKit?.setConnectors(w3mConnectors) } + private async syncAuthConnector(projectId: string, auth: ProviderType['auth']) { + if (typeof window !== 'undefined') { + this.authProvider = new W3mFrameProvider(projectId) + + this.appKit?.addConnector({ + id: ConstantsUtil.AUTH_CONNECTOR_ID, + type: 'AUTH', + name: 'Auth', + provider: this.authProvider, + email: auth?.email, + socials: auth?.socials, + showWallets: auth?.showWallets === undefined ? true : auth.showWallets, + chain: this.chain, + walletFeatures: auth?.walletFeatures + }) + + this.appKit?.setLoading(true) + const isLoginEmailUsed = this.authProvider.getLoginEmailUsed() + this.appKit?.setLoading(isLoginEmailUsed) + const { isConnected } = await this.authProvider.isConnected() + if (isConnected) { + await this.setAuthProvider() + } else { + this.appKit?.setLoading(false) + } + } + } + private eip6963EventHandler(event: CustomEventInit) { if (event.detail) { const { info, provider } = event.detail diff --git a/packages/base/adapters/evm/ethers5/index.ts b/packages/base/adapters/evm/ethers5/index.ts index 7ce51af5c1..25b94a5c44 100644 --- a/packages/base/adapters/evm/ethers5/index.ts +++ b/packages/base/adapters/evm/ethers5/index.ts @@ -1,6 +1,7 @@ import '@web3modal/polyfills' export { EVMEthers5Client } from './client.js' +export * from '@web3modal/scaffold-utils/ethers' // -- Types export type { AdapterOptions } from './client.js' diff --git a/packages/ethers5/exports/react.tsx b/packages/ethers5/exports/react.tsx index 736603c3a8..572b173bff 100644 --- a/packages/ethers5/exports/react.tsx +++ b/packages/ethers5/exports/react.tsx @@ -2,7 +2,7 @@ import { AppKit } from '@web3modal/base' import type { AppKitOptions } from '@web3modal/base' -import { EVMEthersClient, type AdapterOptions } from '@web3modal/base/adapters/evm/ethers' +import { EVMEthers5Client, type AdapterOptions } from '@web3modal/base/adapters/evm/ethers5' import { ConstantsUtil } from '@web3modal/scaffold-utils' import { EthersStoreUtil } from '@web3modal/scaffold-utils/ethers' import { getWeb3Modal } from '@web3modal/base/utils/library/react' @@ -15,7 +15,7 @@ export { defaultConfig } from '@web3modal/base/adapters/evm/ethers' // -- Setup ------------------------------------------------------------------- let appkit: AppKit | undefined = undefined -let ethersAdapter: EVMEthersClient | undefined = undefined +let ethersAdapter: EVMEthers5Client | undefined = undefined export type Ethers5AppKitOptions = Omit< AppKitOptions, @@ -24,7 +24,7 @@ export type Ethers5AppKitOptions = Omit< AdapterOptions export function createWeb3Modal(options: Ethers5AppKitOptions) { - ethersAdapter = new EVMEthersClient({ + ethersAdapter = new EVMEthers5Client({ ethersConfig: options.ethersConfig, siweConfig: options.siweConfig, chains: options.chains, diff --git a/packages/ethers5/exports/vue.ts b/packages/ethers5/exports/vue.ts index c8b4d140f5..27f9cab668 100644 --- a/packages/ethers5/exports/vue.ts +++ b/packages/ethers5/exports/vue.ts @@ -1,6 +1,6 @@ import { AppKit } from '@web3modal/base' import type { AppKitOptions } from '@web3modal/base' -import { EVMEthersClient, type AdapterOptions } from '@web3modal/base/adapters/evm/ethers' +import { EVMEthers5Client, type AdapterOptions } from '@web3modal/base/adapters/evm/ethers5' import { ConstantsUtil } from '@web3modal/scaffold-utils' import { getWeb3Modal } from '@web3modal/base/utils/library/vue' import { onUnmounted, ref } from 'vue' @@ -12,13 +12,13 @@ export { defaultConfig } from '@web3modal/base/adapters/evm/ethers' // -- Setup ------------------------------------------------------------------- let appkit: AppKit | undefined = undefined -let ethersAdapter: EVMEthersClient | undefined = undefined +let ethersAdapter: EVMEthers5Client | undefined = undefined type EthersAppKitOptions = Omit, 'adapters' | 'sdkType' | 'sdkVersion'> & AdapterOptions export function createWeb3Modal(options: EthersAppKitOptions) { - ethersAdapter = new EVMEthersClient({ + ethersAdapter = new EVMEthers5Client({ ethersConfig: options.ethersConfig, siweConfig: options.siweConfig, chains: options.chains,