Skip to content

Commit

Permalink
feat: use ConnectModal in ConnectButton for support multiple wallets (#…
Browse files Browse the repository at this point in the history
…36)

* refact: improve provider

* fix: wallect connect request bug

* feat: use ConnectModal in ConnectButton for support multiple wallets

---------

Co-authored-by: yutingzhao1991 <[email protected]>
  • Loading branch information
yutingzhao1991 and yutingzhao1991 authored Oct 12, 2023
1 parent 0f2c9e0 commit bb9bb83
Show file tree
Hide file tree
Showing 21 changed files with 9,823 additions and 3,314 deletions.
94 changes: 87 additions & 7 deletions packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ export interface Chain {
};
}

export enum Wallets {
MetaMask = 'MetaMask',
WalletConnect = 'WalletConnect',
}

export interface NFTMetadata {
name?: string;
description?: string;
Expand All @@ -46,9 +41,10 @@ export enum UniversalWeb3ProviderEventType {
export interface UniversalWeb3ProviderInterface {
getAccounts: () => Promise<Account[]>;
getCurrentAccount: () => Promise<Account | undefined>;
requestAccounts: (wallet?: Wallets) => Promise<Account[]>;
requestAccounts: (wallet?: string) => Promise<Account[]>;
getQrCodeLink: () => Promise<string>;
getNFTMetadata: (address: string, id: number) => Promise<NFTMetadata>;
getAvaliableWallets: () => Promise<WalletMetadata[]>;
disconnect: () => Promise<void>;
on: (type: UniversalWeb3ProviderEventType, handler: (params?: any) => void) => void;
off: (type: UniversalWeb3ProviderEventType, handler: (params?: any) => void) => void;
Expand All @@ -74,8 +70,92 @@ export interface EIP1193LikeProviderFactory {
create: (options?: WalletProviderOptions) => Promise<EIP1193LikeProvider>;
}

export interface WalletProvider extends EIP1193LikeProviderFactory {}
export interface WalletProvider extends EIP1193LikeProviderFactory {
metadata: WalletMetadata;
}

export interface JsonRpcProvider extends EIP1193LikeProviderFactory {
getRpcUrl: (chain: Chains) => string;
}

/**
* @desc 浏览器扩展程序信息
*/
export type WalletExtensionItem = {
/**
* @desc 支持的浏览器的 key
* @descEn The key of the supported browser
*/
key: 'Chrome' | 'Firefox' | 'Edge' | 'Safari' | (string & {});
/**
* @desc 浏览器扩展程序的链接
* @descEn Link to browser extension
*/
link: string;
/**
* @desc 浏览器扩展程序的图标
* @descEn Icon of browser extension
*/
browserIcon: string;
/**
* @desc 浏览器扩展程序的名称
* @descEn Name of browser extension
*/
browserName: string;
/**
* @desc 浏览器扩展程序的描述
* @descEn Description of browser extension
*/
description: string;
};

/**
* @desc 钱包
* @descEn Wallet
*/
export type WalletMetadata = {
/**
* @desc 钱包名称
* @descEn Wallet name
*/
name: string;
/**
* @desc 钱包简介
* @descEn Wallet introduction
*/
remark: string;
/**
* @desc 钱包的 key
* @descEn The key of Wallet
*/
key?: React.Key;
/**
* @desc 钱包图标
* @descEn Wallet icon
*/
icon?: string | React.ReactNode;
/**
* @desc 该钱包支持的浏览器扩展程序列表
* @descEn The list of browser extensions supported by the wallet
*/
extensions?: false | WalletExtensionItem[];
/**
* @desc 该钱包是否支持 APP 调用
* @descEn Whether the wallet supports APP calls
*/
app?:
| false
| {
link: string;
};
/**
* @desc 钱包所属分组名称
* @descEn The name of the group to which the wallet belongs
*/
group?: string;
/**
* @desc 钱包是否已安装
* @descEn Whether the wallet is installed
*/
installed?: boolean;
};
23 changes: 22 additions & 1 deletion packages/common/src/wallets/meta-mask.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import { EIP1193LikeProvider, WalletProvider } from '../types';
import { EIP1193LikeProvider, WalletMetadata, WalletProvider } from '../types';

export class MetaMaskProvider implements WalletProvider {
metadata: WalletMetadata = {
icon: 'https://metamask.io/images/metamask-logo.png',
name: 'MetaMask',
remark: 'MetaMask Wallet',
app: {
link: 'https://metamask.io/',
},
extensions: [
{
key: 'Chrome',
browserIcon:
'https://github.com/ant-design/ant-design/assets/10286961/0d4e4ac7-8f89-4147-a06a-de72c02e85cb',
browserName: 'Chrome',
link: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
description: 'Access your wallet right from your favorite web browser.',
},
],
// @ts-ignore
installed: window.ethereum?.isMetaMask ?? false,
};

create = async (): Promise<EIP1193LikeProvider> => {
// @ts-ignore
if (!window.ethereum) {
Expand Down
18 changes: 17 additions & 1 deletion packages/common/src/wallets/wallet-connect.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
// Only for WallectConnect v2, v1 is not supported
import { EIP1193LikeProvider, WalletProvider, WalletProviderOptions } from '../types';
import {
EIP1193LikeProvider,
WalletMetadata,
WalletProvider,
WalletProviderOptions,
} from '../types';
import { EthereumProvider } from '@walletconnect/ethereum-provider';

export class WalletConnectProvider implements WalletProvider {
metadata: WalletMetadata = {
icon: 'https://docs.walletconnect.com/img/walletconnect-logo-black.svg#light-mode-only',
name: 'WalletConnect',
remark: 'Connect with mobile APP',
app: {
link: 'https://walletconnect.com/',
},
group: 'Popular',
installed: true,
};

constructor(private options?: WalletProviderOptions) {}
create = async (): Promise<EIP1193LikeProvider> => {
if (!this.options?.projectId) {
Expand Down
51 changes: 38 additions & 13 deletions packages/ethereum/src/eip1193-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
JsonRpcProvider,
WalletProvider,
Chain,
WalletMetadata,
} from '@ant-design/web3-common';
import { createDebug } from './utils';

Expand All @@ -16,52 +17,72 @@ const debug = createDebug('eip1193-provider');

const wallectsMethods = ['eth_requestAccounts', 'eth_accounts', 'eth_chainId'];

export function createProvider(options: CreateProviderOptions): EIP1193LikeProvider {
export interface EthereumProvider extends EIP1193LikeProvider {
wallets: WalletMetadata[];
updateUseWallet: (wallet?: string) => void;
}

export function createProvider(options: CreateProviderOptions): EthereumProvider {
let rpcProvders: EIP1193LikeProvider[];
let walletProviders: EIP1193LikeProvider[];
let useWallet: string | undefined;

const getWalletProvider = async (): Promise<EIP1193LikeProvider | undefined> => {
const { wallets } = options;
if (!walletProviders) {
walletProviders = await Promise.all((wallets || []).map((wallet) => wallet.create()));
}
// TODO support multiple provider
if (!walletProviders || walletProviders.length === 0) {
return undefined;
}
if (useWallet) {
const useProviderIndex = wallets?.findIndex((item) => item.metadata.name === useWallet);
if (useProviderIndex !== undefined && useProviderIndex >= 0) {
return walletProviders[useProviderIndex];
}
}
return walletProviders[0];
};

const getRpcProvider = async (): Promise<EIP1193LikeProvider | undefined> => {
const getRpcProvider = async (index = 0): Promise<EIP1193LikeProvider | undefined> => {
const { rpcs } = options;
if (!rpcProvders) {
rpcProvders = await Promise.all((rpcs || []).map((rpc) => rpc.create()));
}
// TODO support multiple provider
if (!rpcProvders || rpcProvders.length === 0) {
if (!rpcProvders || rpcProvders.length <= index) {
return undefined;
}
return rpcProvders[0];
return rpcProvders[index];
};

const request = async (requestParams: { method: string; params?: any }) => {
const request = async (
requestParams: { method: string; params?: any },
retryTimes = 0,
): Promise<any> => {
debug('request', requestParams);
const { method } = requestParams;
const rpcProvider = await getRpcProvider();
const walletProvider = await getWalletProvider();
if (!rpcProvider && !walletProvider) {
throw new Error('No provider found');
}

if (wallectsMethods.includes(method)) {
const walletProvider = await getWalletProvider();
if (!walletProvider) {
throw new Error('No wallet provider found');
}
return walletProvider.request(requestParams);
} else {
const rpcProvider = await getRpcProvider(retryTimes);
if (!rpcProvider) {
throw new Error('No rpc provider found');
}
return rpcProvider.request(requestParams);
try {
const result = rpcProvider.request(requestParams);
return result;
} catch (err) {
if (retryTimes >= rpcProvders.length) {
// when retryTimes is 1, rpcProvders length must large then 1
throw err;
}
return request(requestParams, retryTimes + 1);
}
}
};

Expand All @@ -80,5 +101,9 @@ export function createProvider(options: CreateProviderOptions): EIP1193LikeProvi
await walletProvider?.disconnect?.();
}
},
wallets: options.wallets?.map((wallet) => wallet.metadata) || [],
updateUseWallet: (wallet?: string) => {
useWallet = wallet;
},
};
}
34 changes: 22 additions & 12 deletions packages/ethereum/src/universal-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@ import {
UniversalWeb3ProviderInterface,
NFTMetadata,
Account,
Wallets,
requestWeb3Asset,
EIP1193LikeProvider,
UniversalWeb3ProviderEventType,
WalletMetadata,
} from '@ant-design/web3-common';
import { EventEmitter } from 'eventemitter3';
import { ethers } from 'ethers';
import { EthereumProvider } from './eip1193-provider';

const USE_WALLET_LOCAL_STORAGE_KEY = 'antd-web3-use-wallet';

export class UniversalProvider extends EventEmitter implements UniversalWeb3ProviderInterface {
private useWallet?: Wallets;
private useWallet?: string;

constructor(private eip1193Provider: EIP1193LikeProvider) {
constructor(private eip1193Provider: EthereumProvider) {
super();
const wallet = localStorage.getItem(USE_WALLET_LOCAL_STORAGE_KEY);
if (Object.values(Wallets).includes(wallet as Wallets)) {
this.useWallet = wallet as Wallets;
const wallet: string = localStorage.getItem(USE_WALLET_LOCAL_STORAGE_KEY) || '';
if (wallet) {
this.updateUseWallet(wallet);
}
}

private updateUseWallet(wallet: string) {
this.useWallet = wallet;
localStorage.setItem(USE_WALLET_LOCAL_STORAGE_KEY, this.useWallet);
this.eip1193Provider.updateUseWallet(this.useWallet);
}

async getAccounts(): Promise<Account[]> {
if (!this.useWallet) {
return [];
Expand All @@ -41,9 +47,10 @@ export class UniversalProvider extends EventEmitter implements UniversalWeb3Prov
return accounts[0];
}

async requestAccounts(wallet?: Wallets): Promise<Account[]> {
this.useWallet = wallet || Wallets.MetaMask;
localStorage.setItem(USE_WALLET_LOCAL_STORAGE_KEY, this.useWallet);
async requestAccounts(wallet?: string): Promise<Account[]> {
if (wallet) {
this.updateUseWallet(wallet);
}
await this.eip1193Provider?.connect?.();
const provider = new ethers.BrowserProvider(this.eip1193Provider);
await provider.getSigner();
Expand All @@ -57,11 +64,14 @@ export class UniversalProvider extends EventEmitter implements UniversalWeb3Prov
return;
}
await this.eip1193Provider?.disconnect?.();
this.useWallet = undefined;
localStorage.removeItem(USE_WALLET_LOCAL_STORAGE_KEY);
this.updateUseWallet('');
this.emit(UniversalWeb3ProviderEventType.AccountsChanged, []);
}

async getAvaliableWallets(): Promise<WalletMetadata[]> {
return this.eip1193Provider.wallets;
}

getQrCodeLink(): Promise<string> {
throw new Error('Method not implemented.');
}
Expand Down
5 changes: 3 additions & 2 deletions packages/ethereum/src/web3-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { Web3ConfigProvider, EIP1193LikeProvider } from '@ant-design/web3-common';
import { Web3ConfigProvider } from '@ant-design/web3-common';
import { EthereumProvider } from './eip1193-provider';
import { UniversalProvider } from './universal-provider';

export interface Web3ProviderProps {
provider: EIP1193LikeProvider;
provider: EthereumProvider;
children?: React.ReactNode;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/web3/src/ConnectModal/__tests__/mock/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DefaultGuide, Wallet } from '@ant-design/web3';
import type { DefaultGuide } from '@ant-design/web3';

export const walletList: Wallet[] = [
export const walletList = [
{
icon: 'https://xsgames.co/randomusers/avatar.php?g=pixel&key=0',
name: '测试钱包',
Expand Down
4 changes: 2 additions & 2 deletions packages/web3/src/ConnectModal/components/QrCode.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useContext, useEffect, useState } from 'react';
import type { Wallet } from '../interface';
import type { WalletMetadata } from '../interface';
import MainPanelHeader from './MainPanelHeader';
import { connectModalContext } from '../context';
import { Button, QRCode } from 'antd';
import useProvider from '../../hooks/useProvider';

export type QrCodeProps = {
wallet: Wallet;
wallet: WalletMetadata;
isSimple?: boolean;
};

Expand Down
Loading

0 comments on commit bb9bb83

Please sign in to comment.