Skip to content

Commit

Permalink
feat: added impersonated connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Argeare5 committed Oct 23, 2023
1 parent e5d3c1c commit cda69f3
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 12 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"engines": {
"node": ">=18"
},
"type": "module",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"module": "dist/index.js",
Expand Down
172 changes: 172 additions & 0 deletions src/web3/connectors/ImpersonatedConnector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { MockProvider, MockProviderOptions } from '@wagmi/connectors/mock';
import { Connector, ConnectorData, WalletClient } from '@wagmi/core';
import { createWalletClient, getAddress, Hex, http } from 'viem';
import type { Chain } from 'viem/chains';
import { mainnet } from 'viem/chains';

export function normalizeChainId(chainId: string | number | bigint) {
if (typeof chainId === 'string')
return Number.parseInt(
chainId,
chainId.trim().substring(0, 2) === '0x' ? 16 : 10,
);
if (typeof chainId === 'bigint') return Number(chainId);
return chainId;
}

type MockConnectorOptions = Omit<
MockProviderOptions,
'chainId' | 'walletClient'
> & {
chainId?: number;
};

export class ImpersonatedConnector extends Connector<
MockProvider,
MockConnectorOptions
> {
readonly id = 'impersonated';
readonly name = 'Impersonated';
readonly ready = true;

#provider?: MockProvider;

constructor({
chains,
options,
}: {
chains?: Chain[];
options: MockConnectorOptions;
}) {
super({
chains,
options: {
...options,
chainId: options.chainId ?? chains?.[0]?.id,
},
});
}

async connect({
address,
chainId,
}: { address?: Hex; chainId?: number } = {}) {
const provider = await this.getProvider({ address, chainId });
provider.on('accountsChanged', this.onAccountsChanged);
provider.on('chainChanged', this.onChainChanged);
provider.on('disconnect', this.onDisconnect);

this.emit('message', { type: 'connecting' });

const accounts = await provider.enable();
const account = getAddress(accounts[0] as string);
const id = normalizeChainId(provider.chainId);
const unsupported = this.isChainUnsupported(id);
const data = { account, chain: { id, unsupported }, provider };

if (!this.options.flags?.noSwitchChain)
this.switchChain = this.#switchChain;

return new Promise<Required<ConnectorData>>((res) =>
setTimeout(() => res(data), 100),
);
}

async disconnect() {
const provider = await this.getProvider();
await provider.disconnect();

provider.removeListener('accountsChanged', this.onAccountsChanged);
provider.removeListener('chainChanged', this.onChainChanged);
provider.removeListener('disconnect', this.onDisconnect);
}

async getAccount() {
const provider = await this.getProvider();
const accounts = await provider.getAccounts();
const account = accounts[0];
if (!account) throw new Error('Failed to get account');
// return checksum address
return getAddress(account);
}

async getChainId() {
const provider = await this.getProvider();
return normalizeChainId(provider.chainId);
}

async getProvider({
address,
chainId,
}: { address?: Hex; chainId?: number } = {}) {
if (!this.#provider || chainId)
this.#provider = new MockProvider({
...this.options,
chainId: chainId ?? this.options.chainId ?? this.chains[0]!.id,
walletClient: createWalletClient({
account: address || '0x0',
chain: this.chains.find((chain) => chain.id === chainId) || mainnet,
transport: http(),
}),
});
return this.#provider;
}

async getWalletClient(): Promise<WalletClient> {
const provider = await this.getProvider();
return provider.getWalletClient();
}

async isAuthorized() {
try {
const provider = await this.getProvider();
const account = await provider.getAccounts();
return this.options.flags?.isAuthorized ?? !!account;
} catch {
return false;
}
}

async #switchChain(chainId: number) {
const provider = await this.getProvider();
await provider.switchChain(chainId);
return (
this.chains.find((x) => x.id === chainId) ?? {
id: chainId,
name: `Chain ${chainId}`,
network: `${chainId}`,
nativeCurrency: { name: 'Ether', decimals: 18, symbol: 'ETH' },
rpcUrls: { default: { http: [''] }, public: { http: [''] } },
}
);
}

async watchAsset(asset: {
address: string;
decimals?: number;
image?: string;
symbol: string;
}) {
const provider = await this.getProvider();
return provider.watchAsset(asset);
}

protected onAccountsChanged = (accounts: string[]) => {
if (accounts.length === 0) this.emit('disconnect');
else this.emit('change', { account: getAddress(accounts[0] as string) });
};

protected onChainChanged = (chainId: number | string) => {
const id = normalizeChainId(chainId);
const unsupported = this.isChainUnsupported(id);
this.emit('change', { chain: { id, unsupported } });
};

protected onDisconnect = () => {
this.emit('disconnect');
};

toJSON() {
return '<MockConnector>';
}
}
15 changes: 9 additions & 6 deletions src/web3/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// TODO: need add mock connector

import { Chain } from 'viem';
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { SafeConnector } from 'wagmi/connectors/safe';
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';

import { ImpersonatedConnector } from './ImpersonatedConnector';

export type ConnectorType =
| InjectedConnector
| WalletConnectConnector
| CoinbaseWalletConnector
| SafeConnector;
| SafeConnector
| ImpersonatedConnector;

export type AllConnectorsInitProps = {
appName: string;
Expand Down Expand Up @@ -79,17 +80,19 @@ export const initAllConnectors = (props: AllConnectorsInitProps) => {
};

export type WalletType =
| 'Metamask'
| 'Injected'
| 'WalletConnect'
| 'Coinbase'
| 'GnosisSafe';
| 'GnosisSafe'
| 'Impersonated';

export function getConnectorName(
connector: ConnectorType,
): WalletType | undefined {
if (connector instanceof InjectedConnector) return 'Metamask'; // TODO: change to injected
if (connector instanceof InjectedConnector) return 'Injected';
if (connector instanceof WalletConnectConnector) return 'WalletConnect';
if (connector instanceof CoinbaseWalletConnector) return 'Coinbase';
if (connector instanceof SafeConnector) return 'GnosisSafe';
if (connector instanceof ImpersonatedConnector) return 'Impersonated';
return;
}
14 changes: 11 additions & 3 deletions src/web3/store/walletSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export type IWalletSlice = {
checkAndSwitchNetwork: (chainId?: number) => Promise<void>;
connectors: ConnectorType[];
setConnectors: (connectors: ConnectorType[]) => void;
_impersonatedAddress?: string;
setImpersonatedAddress: (address: string) => void;
_impersonatedAddress?: Hex;
setImpersonatedAddress: (address: Hex) => void;
checkIsContractWallet: (
wallet: Omit<Wallet, 'walletClient'>,
) => Promise<boolean>;
Expand Down Expand Up @@ -135,7 +135,15 @@ export function createWalletSlice({

try {
if (connector) {
await connect({ connector });
if (walletType === 'Impersonated') {
// @ts-ignore
await connector.connect({
address: get()._impersonatedAddress,
});
} else {
await connect({ connector });
}

setLocalStorageWallet(walletType);
get().updateEthAdapter(walletType === 'GnosisSafe');

Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2842,7 +2842,7 @@ bindings@^1.3.0:

bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0:
version "5.2.1"
resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==

borsh@^0.7.0:
Expand Down Expand Up @@ -4100,7 +4100,7 @@ has@^1.0.3:

hash.js@^1.1.7:
version "1.1.7"
resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
Expand Down

0 comments on commit cda69f3

Please sign in to comment.