From 08e7d70d01b775599aa8d6020c250063d108bf9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 3 Oct 2023 23:31:03 -0300 Subject: [PATCH] feat: base implementation for reactive controller of wallet manager --- packages/wallet-manager/package.json | 3 +- .../src/WalletManagerController.ts | 116 ++++++++++++++++++ packages/wallet-manager/src/index.ts | 1 + packages/wallet-manager/src/types.ts | 11 ++ .../wallet-manager/src/wallets/Evm/Evm.ts | 72 +++++++---- .../src/wallets/Substrate/Substrate.ts | 3 +- .../src/wallets/interfaces/index.ts | 26 +++- 7 files changed, 201 insertions(+), 31 deletions(-) create mode 100644 packages/wallet-manager/src/WalletManagerController.ts diff --git a/packages/wallet-manager/package.json b/packages/wallet-manager/package.json index 940b3fa1..6483c65e 100644 --- a/packages/wallet-manager/package.json +++ b/packages/wallet-manager/package.json @@ -24,6 +24,7 @@ "dependencies": { "@polkadot/api": "10.7.2", "@polkadot/extension-dapp": "^0.46.5", - "ethers": "5.7.2" + "ethers": "5.7.2", + "lit": "^2.8.0" } } diff --git a/packages/wallet-manager/src/WalletManagerController.ts b/packages/wallet-manager/src/WalletManagerController.ts new file mode 100644 index 00000000..ed2ca6c9 --- /dev/null +++ b/packages/wallet-manager/src/WalletManagerController.ts @@ -0,0 +1,116 @@ +import { ReactiveController, ReactiveControllerHost } from 'lit'; +import { EvmWallet, SubstrateWallet } from '.'; +import { Web3Provider } from '@ethersproject/providers'; +import { ApiPromise } from '@polkadot/api'; +import { AddChain } from './types'; +import { IWalletManagerController } from './wallets/interfaces'; + +export class WalletManagerController implements IWalletManagerController { + host: ReactiveControllerHost; + evmWallet?: EvmWallet; + substrateWallet?: SubstrateWallet; + account?: string; + substrateAccount?: string; + + constructor(host: ReactiveControllerHost) { + (this.host = host).addController(this as ReactiveController); + } + + /** + * @name initFromWeb3Provider + * @param web3Provider Web3Provider + * @description Initializes the EvmWallet from a Web3Provider + */ + public initFromWeb3Provider(web3Provider: Web3Provider): void { + this.evmWallet = EvmWallet.initFromWeb3Provider(web3Provider); + this.appendProviderEvents(this.evmWallet); + } + + /** + * @name initFromWindow + * @description Initializes the EvmWallet from a valid EIP-1193 provider + */ + public initFromWindow(): void { + this.evmWallet = EvmWallet.initFromWindow(); + } + + /** + * @name connectFromApiPromise + * @param apiPromise + * @description Initializes the SubstrateWallet from an ApiPromise + */ + public connectFromApiPromise(apiPromise: ApiPromise): void { + this.substrateWallet = SubstrateWallet.connectFromApiPromise(apiPromise); + } + + /** + * @name connectFromWssProvider + * @param wssProvider + * @description Initializes the SubstrateWallet from a wssProvider + */ + public async connectFromWssProvider(wssProvider: string): Promise { + this.substrateWallet = + await SubstrateWallet.connectFromWssProvider(wssProvider); + } + + /** + * @name addChain + * @param { AddChain } + * @description Adds a chain to the EvmWallet + * @returns void + */ + public async addChain({ + chainId, + chainName, + rpcUrl, + nativeCurrency + }: AddChain): Promise { + try { + await this.evmWallet?.addChain({ + chainId, + chainName, + rpcUrl, + nativeCurrency + }); + } catch (error) { + throw error; + } + } + + /** + * @name connect + * @returns void + * @description Connects the Substrate extension key manager + */ + public async connectoToSubstrate(): Promise { + await this.substrateWallet?.connect(); + this.substrateAccount = this.substrateWallet?.substrateAccount; + this.host.requestUpdate(); + } + + /** + * @name connectEvmWallet + * @returns void + * @description Connects the EvmWallet + */ + public async connectEvmWallet(): Promise { + await this.evmWallet?.connect(); + this.account = this.evmWallet?.account; + this.host.requestUpdate(); + } + + get accountData(): string | undefined { + return this.account; + } + + get substrateAccountAddress(): string | undefined { + return this.substrateAccount; + } + + private appendProviderEvents(evmWallet: EvmWallet): void { + evmWallet.addListener('walletAccountChanged', (account) => { + this.account = account; + this.host.requestUpdate(); + }); + } +} diff --git a/packages/wallet-manager/src/index.ts b/packages/wallet-manager/src/index.ts index 71077b51..b17aaa6e 100644 --- a/packages/wallet-manager/src/index.ts +++ b/packages/wallet-manager/src/index.ts @@ -1 +1,2 @@ export { EvmWallet, SubstrateWallet } from './wallets'; +export { WalletManagerController } from './WalletManagerController'; diff --git a/packages/wallet-manager/src/types.ts b/packages/wallet-manager/src/types.ts index 0e70975b..f6b75f8e 100644 --- a/packages/wallet-manager/src/types.ts +++ b/packages/wallet-manager/src/types.ts @@ -5,3 +5,14 @@ declare global { ethereum: ExternalProvider; } } + +export type AddChain = { + chainId: number; + rpcUrl: string; + chainName: string; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; +}; diff --git a/packages/wallet-manager/src/wallets/Evm/Evm.ts b/packages/wallet-manager/src/wallets/Evm/Evm.ts index b48c19d1..640b63ad 100644 --- a/packages/wallet-manager/src/wallets/Evm/Evm.ts +++ b/packages/wallet-manager/src/wallets/Evm/Evm.ts @@ -6,11 +6,12 @@ import type { Provider } from '@ethersproject/providers'; import { IEvmWallet } from '../interfaces'; +import { AddChain } from '../../types'; class EvmWallet extends events.EventEmitter implements IEvmWallet { public account: string | undefined; public web3Provider!: Web3Provider; - public windowConnector: Provider; + public windowConnector: ExternalProvider; constructor(provider?: Web3Provider) { super(); @@ -18,7 +19,7 @@ class EvmWallet extends events.EventEmitter implements IEvmWallet { if (!window.ethereum) { throw new Error('window.ethereum is not defined.'); } else { - this.windowConnector = window.ethereum as Provider; + this.windowConnector = window.ethereum as ExternalProvider; } if (!provider) { this.web3Provider = new ethers.providers.Web3Provider( @@ -30,15 +31,31 @@ class EvmWallet extends events.EventEmitter implements IEvmWallet { this.appendProviderEvents(); } - static initFromWeb3Provider(web3Provider: Web3Provider) { + /** + * @name initFromWeb3Provider + * @param web3Provider + * @returns EvmWallet + * @description Initializes the EvmWallet from a Web3Provider + */ + static initFromWeb3Provider(web3Provider: Web3Provider): EvmWallet { return new EvmWallet(web3Provider); } - static initFromWindow() { + /** + * @name initFromWindow + * @returns EvmWallet + * @description Initializes the EvmWallet from a valid EIP-1193 provider + */ + static initFromWindow(): EvmWallet { return new EvmWallet(); } - private async calculateAccountData(accounts?: string[]) { + /** + * @name calculateAccountData + * @param accounts + * @returns void + */ + private async calculateAccountData(accounts?: string[]): Promise { if (accounts?.length) { this.account = accounts[0]; } @@ -52,24 +69,33 @@ class EvmWallet extends events.EventEmitter implements IEvmWallet { this.account = _accounts![0]; } - private reConnectToProvider() { + /** + * @name reConnectToProvider + * @returns void + */ + private reConnectToProvider(): void { this.web3Provider = new ethers.providers.Web3Provider( this.windowConnector as ExternalProvider ); } + /** + * @name appendProviderEvents + * @returns void + * @description Appends the provider events to the windowConnector + */ private appendProviderEvents(): void { try { this.checkWindow(); } catch (e) { throw e; } - this.windowConnector.on('connect', async () => { + (this.windowConnector as Provider).on('connect', async () => { this.reConnectToProvider(); await this.calculateAccountData(); }); - this.windowConnector.on( + (this.windowConnector as Provider).on( 'disconnect', async (error: Error & { code: number; data?: unknown }) => { console.log(error); @@ -78,19 +104,22 @@ class EvmWallet extends events.EventEmitter implements IEvmWallet { } ); - this.windowConnector.on('chainChanged', async () => { + (this.windowConnector as Provider).on('chainChanged', async () => { this.reConnectToProvider(); await this.calculateAccountData(); this.emit('walletChainChanged', this.web3Provider); }); - this.windowConnector.on('accountsChanged', async (accounts: string[]) => { - this.reConnectToProvider(); - await this.calculateAccountData(accounts); + (this.windowConnector as Provider).on( + 'accountsChanged', + async (accounts: string[]) => { + this.reConnectToProvider(); + await this.calculateAccountData(accounts); - this.emit('walletAccountChanged', this.account); - }); + this.emit('walletAccountChanged', this.account); + } + ); } // eslint-disable-next-line class-methods-use-this @@ -123,27 +152,18 @@ class EvmWallet extends events.EventEmitter implements IEvmWallet { chainName, rpcUrl, nativeCurrency - }: { - chainId: number; - rpcUrl: string; - chainName: string; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; - }): Promise { + }: AddChain): Promise { this.checkWindow(); try { - await (this.windowConnector as ExternalProvider).request!({ + await this.windowConnector.request!({ method: 'wallet_switchEthereumChain', params: [{ chainId: `0x${chainId.toString(16)}` }] }); } catch (switchError: unknown) { if ((switchError as { code: number }).code === 4902) { try { - await (this.windowConnector as ExternalProvider).request!({ + await this.windowConnector.request!({ method: 'wallet_addEthereumChain', params: [ { diff --git a/packages/wallet-manager/src/wallets/Substrate/Substrate.ts b/packages/wallet-manager/src/wallets/Substrate/Substrate.ts index f5b3a92f..f635079e 100644 --- a/packages/wallet-manager/src/wallets/Substrate/Substrate.ts +++ b/packages/wallet-manager/src/wallets/Substrate/Substrate.ts @@ -1,7 +1,8 @@ import { ApiPromise, WsProvider } from '@polkadot/api'; import { web3Accounts, web3Enable } from '@polkadot/extension-dapp'; +import { ISusbtrateWallet } from '../interfaces'; -class SubstrateWallet { +class SubstrateWallet implements ISusbtrateWallet { substrateAccount?: string; apiPromise?: ApiPromise; wssProvider?: WsProvider; diff --git a/packages/wallet-manager/src/wallets/interfaces/index.ts b/packages/wallet-manager/src/wallets/interfaces/index.ts index 43b86036..075eb170 100644 --- a/packages/wallet-manager/src/wallets/interfaces/index.ts +++ b/packages/wallet-manager/src/wallets/interfaces/index.ts @@ -1,5 +1,7 @@ -import { Provider, Web3Provider } from '@ethersproject/providers'; +import { Web3Provider, ExternalProvider } from '@ethersproject/providers'; import { ApiPromise, WsProvider } from '@polkadot/api'; +import { ReactiveController } from 'lit'; +import { AddChain } from '../../types'; export interface SupportedWallet { id: string; @@ -10,7 +12,7 @@ export interface SupportedWallet { export interface IEvmWallet { web3Provider: Web3Provider; - windowConnector: Provider; + windowConnector: ExternalProvider; account?: string; connect(): Promise; addChain({ @@ -29,8 +31,26 @@ export interface IEvmWallet { }): Promise; } -export interface SusbtrateWallet { +export interface ISusbtrateWallet { wssProvider?: WsProvider; apiPromise?: ApiPromise; substrateAccount?: string; } + +export interface IWalletManagerController extends ReactiveController { + web3Provider?: Web3Provider; + apiPromise?: ApiPromise; + wsProviderUrl?: string; + evmWallet?: IEvmWallet; + substrateWallet?: ISusbtrateWallet; + account?: string; + substrateAccount?: string; + + initFromWeb3Provider(web3Provider: Web3Provider): void; + initFromWindow(): void; + connectFromWssProvider(wssProvider: string): Promise; + connectFromApiPromise(apiPromise: ApiPromise): void; + addChain(addChainParameters: AddChain): Promise; + connectoToSubstrate(): Promise; + connectEvmWallet(): Promise; +} \ No newline at end of file