Skip to content

Commit

Permalink
feat: EmvWallet class base implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
wainola committed Sep 28, 2023
1 parent fe0dc5d commit 74c9649
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 37 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Sygma Widget UI
Empty file added packages/react/src/index.ts
Empty file.
Empty file.
25 changes: 25 additions & 0 deletions packages/wallet-manager/src/test/EvmWallet.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
7 changes: 7 additions & 0 deletions packages/wallet-manager/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Provider } from '@ethersproject/providers';

declare global {
interface Window {
ethereum: Provider;
}
}
189 changes: 152 additions & 37 deletions packages/wallet-manager/src/wallets/Evm/Evm.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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;
}
}
}
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions packages/wallet-manager/src/wallets/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -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<void>;
addChain({
chainId,
rpcUrl,
chainName
}: {
chainId: number;
rpcUrl: string;
chainName: string;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
}): Promise<void>;
}

export interface SusbtrateWallet {}
7 changes: 7 additions & 0 deletions packages/wallet-manager/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
environment: 'jsdom'
}
});
Empty file added packages/widget/src/index.ts
Empty file.

0 comments on commit 74c9649

Please sign in to comment.