Skip to content

Commit

Permalink
dapp: refactor web3 provider with new design
Browse files Browse the repository at this point in the history
This commit does not add any new feature to the dApp, but refactors the
design how the dApp gets an Ethereum connection and account to
instantiate the SDK. This new approach has been developed to prepare the

The new design introduces an abstract class which represents an Ethereum
connection, holding the actual network/RPC provider and the account to
use for signing. This abstract class gets them implemented by all the
various connection methods the dApp provides. The intention is to have a
nice and clean type system where the logic to instantiate the SDK and
connect the dApp does not care about where the connection comes from,
but just takes everything that implements this interface. Thereby the
logic handling the connection process can be easily decoupled and stays
static, no matter how many new connection methods get added or removed.
Furthermore is must not care (not yet ready) about how the connections
get configured. This allows the user to take interactively control over
the configuration instead of a static file served by the web-server.

After all, for the moment, the Home view component that handles the
connection just gets a connection instance and passes its properties to
the SDK. The selection and instantiation of the connection however is
now done in the former components was well. This is the part that
becomes replaced with the connection manager at a later point.

Furthermore the does this approach improves the testability of the
touched components. The connection process module is static and
independently testable from the connection method. And each connection
method can be tested individually. Mocking becomes (theoretically) easy.
All by the bounding interface that tights everything together.
  • Loading branch information
weilbith committed May 6, 2021
1 parent c8f3a23 commit 589ba42
Show file tree
Hide file tree
Showing 16 changed files with 288 additions and 149 deletions.
4 changes: 4 additions & 0 deletions raiden-dapp/__mocks__/@walletconnect/web3-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default class WalletConnectProvider {
enable = jest.fn().mockResolvedValue(true);
on = jest.fn();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class DirectRpcProvider {
public readonly account = 0;

public static connect() {
return new this();
}

get provider() {
return {
getNetwork: jest.fn().mockResolvedValue({ chainId: 5 }),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { providers } from 'ethers';

import { EthereumConnection } from './types';

export class DirectRpcProvider extends EthereumConnection {
public static readonly connection_name = 'direct_rpc_provider';
public readonly provider: providers.JsonRpcProvider;
public readonly account: string;

private constructor(rpcUrl: string, privateKey: string) {
super();
this.provider = new providers.JsonRpcProvider(rpcUrl);
this.account = privateKey;
}

public static async connect(options: {
rpcUrl: string;
privateKey: string;
}): Promise<DirectRpcProvider> {
return new this(options.rpcUrl, options.privateKey);
}
}
4 changes: 4 additions & 0 deletions raiden-dapp/src/services/ethereum-connection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './direct-rpc-provider';
export * from './injected-provider';
export * from './types';
export * from './wallet-connect';
39 changes: 39 additions & 0 deletions raiden-dapp/src/services/ethereum-connection/injected-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { providers } from 'ethers';

import { EthereumConnection } from './types';

export class InjectedProvider extends EthereumConnection {
public static readonly connection_name = 'injected_provider';
public readonly provider: providers.JsonRpcProvider;
public readonly account = 0; // Refers to the currently selected account in the wallet.

private constructor(injetedProvider: providers.ExternalProvider) {
super();
this.provider = new providers.Web3Provider(injetedProvider);
}

public static async connect(): Promise<InjectedProvider> {
if (!window.ethereum && !window.web3) {
throw new Error('No injected provider is available.');
}

let injectedProvider;

if (window.ethereum) {
window.ethereum.autoRefreshOnNetworkChange = false;
await window.ethereum.request({ method: 'eth_requestAccounts' });
injectedProvider = window.ethereum;
} else {
injectedProvider = window.web3.currentProvider;
}

injectedProvider.on('chainChanged', resetHandler);
injectedProvider.on('disconnect', resetHandler);

return new this(injectedProvider);
}
}

function resetHandler() {
window.location.replace(window.location.origin);
}
8 changes: 8 additions & 0 deletions raiden-dapp/src/services/ethereum-connection/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { providers } from 'ethers';

export abstract class EthereumConnection {
static connection_name: string;
static connect: (options?: any) => Promise<EthereumConnection>; // eslint-disable-line @typescript-eslint/no-explicit-any
abstract provider: providers.JsonRpcProvider;
abstract account: string | number;
}
35 changes: 35 additions & 0 deletions raiden-dapp/src/services/ethereum-connection/wallet-connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import WalletConnectProvider from '@walletconnect/web3-provider';
import { providers } from 'ethers';

import { EthereumConnection } from './types';

export class WalletConnect extends EthereumConnection {
public static readonly connection_name = 'wallet_connect';
public readonly provider: providers.JsonRpcProvider;
public readonly account = 0; // Refers to the currently selected account in the wallet.

private constructor(walletConnectProvider: providers.ExternalProvider) {
super();
this.provider = new providers.Web3Provider(walletConnectProvider);
}

public static async connect(options: { rpcUrl: string }): Promise<WalletConnect> {
const temporaryJsonRpcProvider = new providers.JsonRpcProvider(options.rpcUrl);
const networkOfProvider = await temporaryJsonRpcProvider.getNetwork();
const walletConnectProvider = new WalletConnectProvider({
rpc: {
[networkOfProvider.chainId]: options.rpcUrl,
},
});

await walletConnectProvider.enable();
walletConnectProvider.on('chainChanged', resetHandler);
walletConnectProvider.on('disconnect', resetHandler);

return new this(walletConnectProvider);
}
}

function resetHandler() {
window.location.replace(window.location.origin);
}
4 changes: 2 additions & 2 deletions raiden-dapp/src/services/raiden-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class RaidenService {
public usingSubkey: boolean | undefined;

private static async createRaiden(
provider: providers.JsonRpcProvider | providers.ExternalProvider | string,
provider: providers.JsonRpcProvider | string,
privateKeyOrProviderAccountIndex: string | number = 0,
stateBackup?: string,
subkey?: true,
Expand Down Expand Up @@ -130,7 +130,7 @@ export default class RaidenService {
}

async connect(
ethereumProvider: providers.JsonRpcProvider,
ethereumProvider: providers.JsonRpcProvider | string,
privateKeyOrProviderAccountIndex?: string | number,
stateBackup?: string,
presetTokens: Configuration['per_network'] = {},
Expand Down
81 changes: 0 additions & 81 deletions raiden-dapp/src/services/web3-provider.ts

This file was deleted.

40 changes: 33 additions & 7 deletions raiden-dapp/src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ import ConnectionPendingDialog from '@/components/dialogs/ConnectionPendingDialo
import type { TokenModel } from '@/model/types';
import { ErrorCode } from '@/model/types';
import { RouteNames } from '@/router/route-names';
import type { Configuration } from '@/services/config-provider';
import { ConfigProvider } from '@/services/config-provider';
import { Web3Provider } from '@/services/web3-provider';
import type { EthereumConnection } from '@/services/ethereum-connection';
import {
DirectRpcProvider,
InjectedProvider,
WalletConnect,
} from '@/services/ethereum-connection';
import type { Settings } from '@/types';
function mapRaidenServiceErrorToErrorCode(error: Error): ErrorCode {
Expand Down Expand Up @@ -126,16 +132,16 @@ export default class Home extends Vue {
const stateBackup = this.stateBackup;
const configuration = await ConfigProvider.configuration();
const useRaidenAccount = this.settings.useRaidenAccount ? true : undefined;
const ethereumProvider = await Web3Provider.provider(configuration);
const ethereumConnection = await this.getEthereumConnection(configuration);
// TODO: This will become removed when we have the connection manager.
if (!ethereumProvider) {
if (ethereumConnection === undefined) {
this.connectionError = ErrorCode.NO_ETHEREUM_PROVIDER;
this.connecting = false;
return;
}
const ethereumNetwork = await ethereumProvider.getNetwork();
const ethereumNetwork = await ethereumConnection.provider.getNetwork();
if (ethereumNetwork.chainId === 1 && process.env.VUE_APP_ALLOW_MAINNET !== 'true') {
this.connectionError = ErrorCode.UNSUPPORTED_NETWORK;
Expand All @@ -145,16 +151,16 @@ export default class Home extends Vue {
try {
await this.$raiden.connect(
ethereumProvider,
configuration.private_key,
ethereumConnection.provider,
ethereumConnection.account,
stateBackup,
configuration.per_network,
useRaidenAccount,
);
} catch (error) {
this.connectionError = mapRaidenServiceErrorToErrorCode(error);
this.connecting = false;
return
return;
}
this.$store.commit('setConnected');
Expand All @@ -167,6 +173,26 @@ export default class Home extends Vue {
// There is no clean way to cancel the asynchronous connection function, therefore reload page.
window.location.replace(window.location.origin);
}
async getEthereumConnection(
configuration: Configuration,
): Promise<EthereumConnection | undefined> {
const {
rpc_endpoint: rpcUrl,
private_key: privateKey,
rpc_endpoint_wallet_connect: rpcUrlWalletConnect,
} = configuration;
if (rpcUrl && privateKey) {
return DirectRpcProvider.connect({ rpcUrl, privateKey });
} else if (!!window.ethereum || !!window.web3) {
return InjectedProvider.connect();
} else if (rpcUrlWalletConnect) {
return WalletConnect.connect({ rpcUrl: rpcUrlWalletConnect });
} else {
return undefined;
}
}
}
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DirectRpcProvider } from '@/services/ethereum-connection/direct-rpc-provider';

describe('DirectRpcProvider', () => {
test('it can connect', async () => {
const options = { rpcUrl: 'https://some.rpc.provider', privateKey: 'privateKey' };
const connection = await DirectRpcProvider.connect(options);

expect(connection.provider.connection.url).toBe('https://some.rpc.provider');
expect(connection.account).toBe('privateKey');
});
});
Loading

0 comments on commit 589ba42

Please sign in to comment.