diff --git a/README.md b/README.md index e69de29b..e438815b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +# Sygma Widget UI \ No newline at end of file diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/sdk-manager/src/index.ts b/packages/sdk-manager/src/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/wallet-manager/src/test/EvmWallet.test.ts b/packages/wallet-manager/src/test/EvmWallet.test.ts new file mode 100644 index 00000000..accc3933 --- /dev/null +++ b/packages/wallet-manager/src/test/EvmWallet.test.ts @@ -0,0 +1,25 @@ +import { ExternalProvider } from '@ethersproject/providers'; +import { describe, it, expect, beforeEach } from 'vitest'; +import { EvmWallet } from '../'; + +declare global { + interface Window { + ethereum: ExternalProvider; + } +} + +describe('EvmWallet', () => { + describe('EvmWallet + provider on window', () => { + beforeEach(() => { + window.ethereum = { + request: () => Promise.resolve(true) + }; + }); + it('should be able to create an instance of EvmWallet', () => { + const evmWallet = new EvmWallet('metamask', 'icon', window.ethereum); + + expect(evmWallet).toBeInstanceOf(EvmWallet); + expect(evmWallet.name).toBe('metamask'); + }); + }); +}); diff --git a/packages/wallet-manager/src/types.ts b/packages/wallet-manager/src/types.ts new file mode 100644 index 00000000..97b18c1c --- /dev/null +++ b/packages/wallet-manager/src/types.ts @@ -0,0 +1,7 @@ +import { Provider } from '@ethersproject/providers'; + +declare global { + interface Window { + ethereum: Provider; + } +} diff --git a/packages/wallet-manager/src/wallets/Evm/Evm.ts b/packages/wallet-manager/src/wallets/Evm/Evm.ts index 42f98917..7325bf5c 100644 --- a/packages/wallet-manager/src/wallets/Evm/Evm.ts +++ b/packages/wallet-manager/src/wallets/Evm/Evm.ts @@ -1,59 +1,174 @@ -import { UnsignedTransaction, ethers, providers } from 'ethers'; +import { ethers } from 'ethers'; +import events from 'events'; +import type { + Web3Provider, + ExternalProvider, + Provider +} from '@ethersproject/providers'; +import { IEvmWallet } from '../interfaces'; -class EvmWallet { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - metamask: any | undefined; - account: string | undefined; - provider: ethers.providers.Web3Provider | undefined; - balance: string | undefined; +class EvmWallet extends events.EventEmitter implements IEvmWallet { + public account: string | undefined; + public icon: string; + public name: string; + public web3Provider!: Web3Provider; + public windowConnector: Provider; - public connect() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof (window as any).ethereum !== 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.metamask = (window as any).ethereum; - this.provider = new ethers.providers.Web3Provider(this.metamask); + constructor(name: string, icon: string, provider?: Web3Provider) { + super(); + this.name = name; + this.icon = icon; + + if (!window.ethereum) { + throw new Error('window.ethereum is not defined.'); + } else { + this.windowConnector = window.ethereum as Provider; + } + if (!provider) { + this.web3Provider = new ethers.providers.Web3Provider( + this.windowConnector as ExternalProvider + ); + } else { + this.web3Provider = provider; } + this.appendProviderEvents(); } - public async getAccount() { - if (this.metamask) { - const accounts = await this.metamask.request({ - method: 'eth_requestAccounts' - }); + static initFromWeb3Provider( + name: string, + icon: string, + web3Provider: Web3Provider + ) { + return new EvmWallet(name, icon, web3Provider); + } + + static initFromWindow(name: string, icon: string) { + return new EvmWallet(name, icon); + } + + private async calculateAccountData(accounts?: string[]) { + if (accounts?.length) { this.account = accounts[0]; } - } - public async getBalance() { - if (this.account && this.provider) { - const signer = this.getSigner(); - const balance = await signer?.getBalance(); - this.balance = ethers.utils.formatEther(balance as ethers.BigNumber); + const _accounts = await this.web3Provider.listAccounts(); + + if (!_accounts.length) { + return; } + + this.account = _accounts![0]; } - public getSigner() { - if (this.provider) { - return this.provider.getSigner(); - } + private reConnectToProvider() { + this.web3Provider = new ethers.providers.Web3Provider( + this.windowConnector as ExternalProvider + ); } - public sendTransaction(approval: UnsignedTransaction) { - const signer = this.getSigner(); - return signer?.sendTransaction(approval as providers.TransactionRequest); + private appendProviderEvents(): void { + try { + this.checkWindow(); + } catch (e) { + throw e; + } + this.windowConnector.on('connect', async () => { + this.reConnectToProvider(); + await this.calculateAccountData(); + }); + + this.windowConnector.on( + 'disconnect', + async (error: Error & { code: number; data?: unknown }) => { + console.log(error); + this.reConnectToProvider(); + await this.calculateAccountData(); + } + ); + + this.windowConnector.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.emit('walletAccountChanged', this.account); + }); } - get currentAccount() { - return this.account; + // eslint-disable-next-line class-methods-use-this + private checkWindow() { + if (window === undefined) { + throw new Error('window object is not defined.'); + } } - get currentProvider() { - return this.provider; + public async connect() { + try { + this.checkWindow(); + } catch (e) { + throw e; + } + + try { + const accounts = await (this.windowConnector as ExternalProvider) + .request!({ + method: 'eth_requestAccounts' + }); + this.account = accounts[0]; + } catch (e) { + throw e; + } } - get currentBalance() { - return this.balance; + public async addChain({ + chainId, + chainName, + rpcUrl, + nativeCurrency + }: { + chainId: number; + rpcUrl: string; + chainName: string; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; + }): Promise { + this.checkWindow(); + + try { + await (this.windowConnector as ExternalProvider).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!({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: `0x${chainId.toString(16)}`, + rpcUrls: [rpcUrl], + chainName, + nativeCurrency + } + ] + }); + } catch (addError: unknown) { + if ((addError as { code: number }).code !== 4001) { + throw addError; + } + } + } + } } } diff --git a/packages/wallet-manager/src/wallets/interfaces/index.ts b/packages/wallet-manager/src/wallets/interfaces/index.ts new file mode 100644 index 00000000..7d41e246 --- /dev/null +++ b/packages/wallet-manager/src/wallets/interfaces/index.ts @@ -0,0 +1,33 @@ +import { Provider, Web3Provider } from '@ethersproject/providers'; + +export interface SupportedWallet { + id: string; + name: string; + icon: string; + providerName: string; +} + +export interface IEvmWallet { + web3Provider: Web3Provider; + windowConnector: Provider; + name: string; + icon: string; + account?: string; + connect(): Promise; + addChain({ + chainId, + rpcUrl, + chainName + }: { + chainId: number; + rpcUrl: string; + chainName: string; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; + }): Promise; +} + +export interface SusbtrateWallet {} diff --git a/packages/wallet-manager/vite.config.ts b/packages/wallet-manager/vite.config.ts new file mode 100644 index 00000000..0a5d6230 --- /dev/null +++ b/packages/wallet-manager/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom' + } +}); diff --git a/packages/widget/src/index.ts b/packages/widget/src/index.ts new file mode 100644 index 00000000..e69de29b