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 20 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
12 changes: 9 additions & 3 deletions packages/wallet-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@
"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": {
"@ethersproject/abstract-signer": "^5.7.0",
"@lit/context": "^1.0.0",
"@polkadot/api": "10.7.2",
"@polkadot/extension-dapp": "^0.46.5",
"ethers": "5.7.2"
"ethers": "5.7.2",
"lit": "^2.8.0"
}
}
70 changes: 70 additions & 0 deletions packages/wallet-manager/src/WalletManagerContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { provide, createContext } from '@lit/context';
import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import {
type WalletManagerController as TWalletManagerController,
WalletManagerController
} from './WalletManagerController';
import { ethers } from 'ethers';
import { ApiPromise } from '@polkadot/api';
import { SyntheticEventCreator } from './types';

export const WalletManagerContext = createContext<
TWalletManagerController | undefined
>('wallet-context');

export const SyntheticEventCreatorContext =
createContext<SyntheticEventCreator>('synthetic-event');

@customElement('wallet-manager-context')
export class WalletManagerContextProvider extends LitElement {
@provide({ context: WalletManagerContext })
@state()
walletManagerController?: WalletManagerController;
wainola marked this conversation as resolved.
Show resolved Hide resolved

@provide({ context: SyntheticEventCreatorContext })
@state()
syntheticEventCreator: SyntheticEventCreator = (
eventName: string,
dataToPass: unknown
wainola marked this conversation as resolved.
Show resolved Hide resolved
) => {
const event = new CustomEvent(eventName, {
bubbles: true,
composed: true,
detail: dataToPass
});

this.dispatchEvent(event);
};
wainola marked this conversation as resolved.
Show resolved Hide resolved

@property({ type: Object })
web3Provider?: ethers.providers.Web3Provider;

@property({ type: Object })
apiPromise?: ApiPromise;

@property({ type: String })
wssProvider?: string;

constructor() {
super();
this.walletManagerController = new WalletManagerController(this);
}

connectedCallback(): void {
super.connectedCallback();
if (this.web3Provider) {
this.walletManagerController?.initFromWeb3Provider(this.web3Provider);
} else if (this.apiPromise) {
this.walletManagerController?.connectFromApiPromise(this.apiPromise!);
wainola marked this conversation as resolved.
Show resolved Hide resolved
} else if (this.wssProvider) {
this.walletManagerController?.connectFromWssProvider(this.wssProvider!);
} else {
this.walletManagerController?.initFromWindow();
}
}

render() {
return html` <slot></slot>`;
}
}
137 changes: 137 additions & 0 deletions packages/wallet-manager/src/WalletManagerController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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 './interfaces';
import { Signer } from '@ethersproject/abstract-signer';

export class WalletManagerController implements IWalletManagerController {
host: ReactiveControllerHost;
wainola marked this conversation as resolved.
Show resolved Hide resolved
evmWallet?: EvmWallet;
substrateWallet?: SubstrateWallet;
account?: string;
substrateAccount?: string;

constructor(host: ReactiveControllerHost) {
(this.host = host).addController(this as ReactiveController);
wainola marked this conversation as resolved.
Show resolved Hide resolved
}

private appendProviderEvents(evmWallet: EvmWallet): void {
evmWallet.addListener('walletAccountChanged', (account) => {
this.account = account;
this.host.requestUpdate();
wainola marked this conversation as resolved.
Show resolved Hide resolved
});
}

/**
* @name initFromWeb3Provider
* @param web3Provider Web3Provider
* @description Initializes the EvmWallet from a Web3Provider
*/
wainola marked this conversation as resolved.
Show resolved Hide resolved
public initFromWeb3Provider(web3Provider: Web3Provider): void {
this.evmWallet = EvmWallet.initFromWeb3Provider(web3Provider);
wainola marked this conversation as resolved.
Show resolved Hide resolved
this.appendProviderEvents(this.evmWallet);
}

/**
* @name initFromWindow
* @description Initializes the EvmWallet from a valid EIP-1193 provider
*/
public initFromWindow(): void {
wainola marked this conversation as resolved.
Show resolved Hide resolved
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<void> {
this.substrateWallet =
await SubstrateWallet.connectFromWssProvider(wssProvider);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as for the EVM wallet init

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem here is that intantiation passing the wssProvider requires awaiting the result of ApiPromise.create, in contrast to the evm side, where either you pass the provider or not

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it matter?


/**
* @name addChain
* @param { AddChain }
* @description Adds a chain to the EvmWallet
* @returns void
*/
public async addChain({
chainId,
chainName,
rpcUrl,
nativeCurrency
}: AddChain): Promise<void> {
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<void> {
wainola marked this conversation as resolved.
Show resolved Hide resolved
await this.substrateWallet?.connect();
this.substrateAccount = this.substrateWallet?.substrateAccount;
this.host.requestUpdate();
wainola marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @name connectEvmWallet
* @returns void
* @description Connects the EvmWallet
*/
public async connectEvmWallet(): Promise<void> {
await this.evmWallet?.connect();
this.account = this.evmWallet?.address;
this.host.requestUpdate();
}

public getSigner(): Signer {
if (this.evmWallet) {
return this.evmWallet?.signer as Signer;
} else {
throw new Error('EvmWallet not initialized');
}
}
wainola marked this conversation as resolved.
Show resolved Hide resolved

get accountData(): string | undefined {
return this.account;
}

get substrateAccountAddress(): string | undefined {
return this.substrateAccount;
}

get provider(): Web3Provider | undefined {
return this.evmWallet?.web3Provider;
}

get apiPromise(): ApiPromise | undefined {
if (this.substrateWallet) {
return this.substrateWallet?.apiPromise;
wainola marked this conversation as resolved.
Show resolved Hide resolved
} else {
throw new Error('SubstrateWallet not initialized');
}
}
}
9 changes: 9 additions & 0 deletions packages/wallet-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { EvmWallet, SubstrateWallet } from './wallets';
export { WalletManagerController } from './WalletManagerController';
export {
WalletManagerContextProvider,
WalletManagerContext,
SyntheticEventCreatorContext
} from './WalletManagerContext';

export { type SyntheticEventCreator } from './types';
58 changes: 58 additions & 0 deletions packages/wallet-manager/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Web3Provider, ExternalProvider } from '@ethersproject/providers';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { ReactiveController } from 'lit';
import { AddChain } from '../types';
import { Signer } from '@ethersproject/abstract-signer';

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

export interface IEvmWallet {
web3Provider: Web3Provider;
windowConnector: ExternalProvider;
address?: string;
signer?: Signer;
connect(): Promise<void>;
addChain({
chainId,
rpcUrl,
chainName
}: {
chainId: number;
rpcUrl: string;
chainName: string;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
wainola marked this conversation as resolved.
Show resolved Hide resolved
}): Promise<void>;
}

export interface ISusbtrateWallet {
wainola marked this conversation as resolved.
Show resolved Hide resolved
wssProvider?: WsProvider;
apiPromise?: ApiPromise;
wainola marked this conversation as resolved.
Show resolved Hide resolved
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<void>;
connectFromApiPromise(apiPromise: ApiPromise): void;
addChain(addChainParameters: AddChain): Promise<void>;
connectoToSubstrate(): Promise<void>;
connectEvmWallet(): Promise<void>;
}
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);
});
});
Loading
Loading