Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wallet manager package #12

Merged
merged 52 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2eac503
chore: setup pipelines, details on package.json file, linter and pret…
wainola Sep 12, 2023
c41a2bc
chore: folder structure for the packages
wainola Sep 14, 2023
3ffdae4
chore: setup for modern yarn and pipelines to check building phase
wainola Sep 15, 2023
4fa5870
chore: remove build step
wainola Sep 15, 2023
0788c62
chore: pipeline for the react component
wainola Sep 15, 2023
c60f73e
chore: scripts and adding some stuff to gitignores
wainola Sep 15, 2023
428f718
chore: eslint
wainola Sep 15, 2023
fe0dc5d
feat: basic implementation for wallet classes
wainola Sep 15, 2023
74c9649
feat: EmvWallet class base implementation
wainola Sep 28, 2023
a8861d0
chore: fix conflicts
wainola Sep 28, 2023
94fd2b0
chore: update package and lock file with vitest
wainola Sep 28, 2023
e6caa8d
feat: substrate wallet class base implementation and some fixes for E…
wainola Sep 28, 2023
43666c3
chore: update tests
wainola Oct 2, 2023
08e7d70
feat: base implementation for reactive controller of wallet manager
wainola Oct 4, 2023
5149995
chore: update Evm base class
wainola Oct 4, 2023
8697e81
chore: basic tests for the reactive controller
wainola Oct 5, 2023
8c9b14b
feat: base implementation for context provider
wainola Oct 11, 2023
e41ebd4
feat: init of providers
wainola Oct 11, 2023
1c3cd31
update package lock
FSM1 Oct 11, 2023
c3e76e3
fix lint and spelling
FSM1 Oct 11, 2023
fa18c10
chore: update on tsconfig and lock file
wainola Oct 11, 2023
c90ab38
feat: public methods to react to change of account and network
wainola Oct 12, 2023
ad4b54a
feat: update yarn lock
wainola Oct 12, 2023
e6c6c0e
update lock
FSM1 Oct 12, 2023
d295070
chore: handling error when connection to provider is rejected and som…
wainola Oct 13, 2023
1386eb8
chore: moving synthetic event creator as util function and removing i…
wainola Oct 18, 2023
b099ce9
chore: addressing comments on pr review
wainola Oct 19, 2023
556c825
chore: removing static methods to instantiate evm wallet class
wainola Oct 19, 2023
797c073
chore: ethers and lit context as peer dependencies
wainola Oct 19, 2023
95b496d
chore: lit context back to dependency definition
wainola Oct 19, 2023
a4cfef5
chore: pr review
wainola Oct 30, 2023
98d0120
chore: ethers as peer dependency and update on lock file
wainola Oct 30, 2023
2c5ad0a
chore: fixating dependencies
wainola Oct 30, 2023
6319a44
chore: versiong yarn
wainola Oct 30, 2023
3f74c0c
chore: going to stable version of yarn
wainola Oct 30, 2023
2a80e1e
chore: update lock file
wainola Oct 30, 2023
eb39261
chore: update yarnrc file
wainola Oct 30, 2023
94d5694
update lock file
FSM1 Oct 30, 2023
2684963
chore: removing checksum behaviour flag
wainola Oct 30, 2023
40592ce
chore: removing js docs comments, adding yarn folder to the gitignore
wainola Nov 2, 2023
fba19a5
chore: changing to removeAllListeners
wainola Nov 2, 2023
f04f2b0
chore: change on gitignore
wainola Nov 2, 2023
909bace
chore: remove reference to yarn release file
wainola Nov 2, 2023
d002ac2
chore: change method names
wainola Nov 2, 2023
244bc78
chore: PR review, changes on constructor implementation
wainola Nov 6, 2023
68ba84d
chore: update gitignore
wainola Nov 7, 2023
ab3e13f
chore: fixing and adding some more basic tests
wainola Nov 7, 2023
c3ab7da
chore: change name of the context provider
wainola Nov 7, 2023
47302b8
chore: init wallet manager on connectedCallback and not the constructor
wainola Nov 23, 2023
7b90cae
chore: default parameter for network value
wainola Nov 23, 2023
3fdf418
chore: pr review last comments
wainola Nov 28, 2023
9405bd5
chore: rename networks property to network
wainola Nov 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/wallet-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
"dev": "tsc --build --clean && tsc --build ./tsconfig.json --watch",
"clean": "rm -rf ./build",
"lint": "eslint 'src/**/*.ts'",
"lint:fix": "yarn run lint --fix"
"lint:fix": "yarn run lint --fix",
"test": "vitest --config ./vite.config.ts"
},
"author": "Sygmaprotocol Product Team",
"devDependencies": {
"@polkadot/types": "10.7.2",
"typescript": "^5.2.2"
"jsdom": "^22.1.0",
"typescript": "^5.2.2",
"vitest": "^0.34.5"
},
"dependencies": {
"@polkadot/api": "10.7.2",
Expand Down
1 change: 1 addition & 0 deletions packages/wallet-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EvmWallet, SubstrateWallet } from './wallets';
27 changes: 27 additions & 0 deletions packages/wallet-manager/src/test/EvmWallet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ethers } from 'ethers';
import { ExternalProvider } from '@ethersproject/providers';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { EvmWallet } from '../';

describe('EvmWallet', () => {
describe('EvmWallet + provider on window', () => {
beforeEach(() => {
window.ethereum = {
request: () => Promise.resolve(true),
on: vi.fn()
} as ExternalProvider & { on: () => void };
});
it('should be able to create an instance of EvmWallet passing a web3Provider', () => {
const w3Provider = new ethers.providers.Web3Provider(window.ethereum);
const evmWallet = EvmWallet.initFromWeb3Provider(w3Provider);

expect(evmWallet).toBeInstanceOf(EvmWallet);
});

it('should be able to create an instance of EvmWallet without passing a web3Provider', () => {
const evmWallet = EvmWallet.initFromWindow();

expect(evmWallet).toBeInstanceOf(EvmWallet);
});
});
});
37 changes: 37 additions & 0 deletions packages/wallet-manager/src/test/Substrate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ApiPromise, WsProvider } from '@polkadot/api';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { SubstrateWallet } from '../wallets';

vi.mock('@polkadot/api', async () => {
const mod =
await vi.importActual<typeof import('@polkadot/api')>('@polkadot/api');
const WsProviderMock = vi.fn();
const ApiPromiseMock = {
create: async () => {}
};
return {
...mod,
WsProvider: WsProviderMock,
ApiPromise: ApiPromiseMock
};
});

describe('SubstrateWallet', () => {
afterEach(() => {
vi.clearAllMocks();
});

it('should be able to create an instance of SubstrateWallet passing an ApiPromise', async () => {
const wsProvider = new WsProvider('wss:someurl');
const apiPromise = await ApiPromise.create({ provider: wsProvider });

const substrateWallet = SubstrateWallet.connectFromApiPromise(apiPromise);
expect(substrateWallet).toBeInstanceOf(SubstrateWallet);
});

it('should be able to create an instance of SubstrateWallet passing a wssProvider', async () => {
const substrateWallet =
await SubstrateWallet.connectFromWssProvider('wss:someurl');
expect(substrateWallet).toBeInstanceOf(SubstrateWallet);
});
});
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 { ExternalProvider } from '@ethersproject/providers';

declare global {
interface Window {
ethereum: ExternalProvider;
}
}
167 changes: 167 additions & 0 deletions packages/wallet-manager/src/wallets/Evm/Evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { ethers } from 'ethers';
import events from 'events';
import type {
Web3Provider,
ExternalProvider,
Provider
} from '@ethersproject/providers';
import { IEvmWallet } from '../interfaces';

class EvmWallet extends events.EventEmitter implements IEvmWallet {
public account: string | undefined;
public web3Provider!: Web3Provider;
public windowConnector: Provider;

constructor(provider?: Web3Provider) {
super();

if (!window.ethereum) {
throw new Error('window.ethereum is not defined.');
} else {
this.windowConnector = window.ethereum as Provider;
wainola marked this conversation as resolved.
Show resolved Hide resolved
}
if (!provider) {
this.web3Provider = new ethers.providers.Web3Provider(
this.windowConnector as ExternalProvider
);
} else {
this.web3Provider = provider;
}
this.appendProviderEvents();
}

static initFromWeb3Provider(web3Provider: Web3Provider) {
return new EvmWallet(web3Provider);
}

static initFromWindow() {
return new EvmWallet();
}

private async calculateAccountData(accounts?: string[]) {
if (accounts?.length) {
this.account = accounts[0];
}

const _accounts = await this.web3Provider.listAccounts();

if (!_accounts.length) {
return;
}

this.account = _accounts![0];
}

private reConnectToProvider() {
this.web3Provider = new ethers.providers.Web3Provider(
this.windowConnector as ExternalProvider
);
}

private appendProviderEvents(): void {
try {
this.checkWindow();
} catch (e) {
throw e;
}
wainola marked this conversation as resolved.
Show resolved Hide resolved
this.windowConnector.on('connect', async () => {
this.reConnectToProvider();
await this.calculateAccountData();
});

this.windowConnector.on(
'disconnect',
async (error: Error & { code: number; data?: unknown }) => {
wainola marked this conversation as resolved.
Show resolved Hide resolved
console.log(error);

Check warning on line 75 in packages/wallet-manager/src/wallets/Evm/Evm.ts

View workflow job for this annotation

GitHub Actions / check (ubuntu-latest, 18)

Unexpected console statement

Check warning on line 75 in packages/wallet-manager/src/wallets/Evm/Evm.ts

View workflow job for this annotation

GitHub Actions / check (ubuntu-latest, 18)

Unexpected console statement
this.reConnectToProvider();
await this.calculateAccountData();
}
);

this.windowConnector.on('chainChanged', async () => {
this.reConnectToProvider();
await this.calculateAccountData();

this.emit('walletChainChanged', this.web3Provider);
wainola marked this conversation as resolved.
Show resolved Hide resolved
});

this.windowConnector.on('accountsChanged', async (accounts: string[]) => {
this.reConnectToProvider();
await this.calculateAccountData(accounts);

this.emit('walletAccountChanged', this.account);
});
}

// eslint-disable-next-line class-methods-use-this
private checkWindow() {
if (window === undefined) {
throw new Error('window object is not defined.');
}
}
wainola marked this conversation as resolved.
Show resolved Hide resolved

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;
}
wainola marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
sztok7 marked this conversation as resolved.
Show resolved Hide resolved
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) {
sztok7 marked this conversation as resolved.
Show resolved Hide resolved
throw addError;
}
}
}
}
}
}

export { EvmWallet };
1 change: 1 addition & 0 deletions packages/wallet-manager/src/wallets/Evm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EvmWallet } from './Evm';
44 changes: 44 additions & 0 deletions packages/wallet-manager/src/wallets/Substrate/Substrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ApiPromise, WsProvider } from '@polkadot/api';
import { web3Accounts, web3Enable } from '@polkadot/extension-dapp';

class SubstrateWallet {
substrateAccount?: string;
apiPromise?: ApiPromise;
wssProvider?: WsProvider;

constructor(apiPromise?: ApiPromise) {
this.apiPromise = apiPromise;
}

static async connectFromWssProvider(wssProvider: string) {
const wsProvider = await SubstrateWallet.conntectToApi(wssProvider);
const apiPromise = await ApiPromise.create({ provider: wsProvider });
return new SubstrateWallet(apiPromise);
}

static connectFromApiPromise(apiPromise: ApiPromise) {
return new SubstrateWallet(apiPromise);
}

static async conntectToApi(wssProvider: string): Promise<WsProvider> {
wainola marked this conversation as resolved.
Show resolved Hide resolved
const wsProvider = new WsProvider(wssProvider);
return wsProvider;
}

public async connect() {
const injectors = await web3Enable('Polkadot Wallet');
// using polkadot-js extension
const polkadotInjector = injectors.find(
(injector) => injector.name === 'polkadot-js'
);

if (polkadotInjector) {
// eslint-disable-next-line no-console
console.log('polkadot-js extension found');
const allAccounts = await web3Accounts();
this.substrateAccount = allAccounts[0].address;
}
}
}

export { SubstrateWallet };
1 change: 1 addition & 0 deletions packages/wallet-manager/src/wallets/Substrate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SubstrateWallet } from './Substrate';
2 changes: 2 additions & 0 deletions packages/wallet-manager/src/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { EvmWallet } from './Evm';
export { SubstrateWallet } from './Substrate';
36 changes: 36 additions & 0 deletions packages/wallet-manager/src/wallets/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Provider, Web3Provider } from '@ethersproject/providers';
import { ApiPromise, WsProvider } from '@polkadot/api';

export interface SupportedWallet {
id: string;
name: string;
icon: string;
providerName: string;
}

export interface IEvmWallet {
web3Provider: Web3Provider;
windowConnector: Provider;
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 {
wssProvider?: WsProvider;
apiPromise?: ApiPromise;
substrateAccount?: string;
}
8 changes: 8 additions & 0 deletions packages/wallet-manager/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
environment: 'jsdom',
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**']
}
});
Loading
Loading