Skip to content

Commit

Permalink
feat: support switchChain (#96)
Browse files Browse the repository at this point in the history
* feat: support switch chain

* test: add chains test case for connector

* test: add switch test case for wagmi provider

* test: add assets props for custom chain

* fix: code format for ci issue

* fix: use Chain replace ChainIds for fix review issue

---------

Co-authored-by: yutingzhao1991 <[email protected]>
  • Loading branch information
yutingzhao1991 and yutingzhao1991 authored Nov 17, 2023
1 parent 0946ab0 commit 5a032e2
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 43 deletions.
8 changes: 5 additions & 3 deletions packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ export interface UniversalWeb3ProviderInterface {
chains?: Chain[];
currentChain?: Chain;

// connect and return conneted accounts
requestAccounts?: (wallet?: string) => Promise<Account[]>;
disconnect?: () => Promise<void>;
switchChain?: (chain: Chain) => Promise<void>;

getCurrentNetwork?: () => Promise<number>;
getNFTMetadata?: (params: { address: string; tokenId: bigint }) => Promise<NFTMetadata>;
}

Expand Down Expand Up @@ -141,8 +142,9 @@ export type Banlance = {
export interface ConnectorTriggerProps {
address?: string;
loading?: boolean;
onConnectClicked?: () => void;
onDisconnectClicked?: () => Promise<void>;
onConnectClick?: () => void;
onDisconnectClick?: () => Promise<void>;
onSwitchChain?: (chain: Chain) => Promise<void>;
domain?: string;
connected?: boolean;
chains?: Chain[];
Expand Down
6 changes: 3 additions & 3 deletions packages/ethereum/src/universal-provider.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
import type {
UniversalWeb3ProviderInterface,
NFTMetadata,
Account,
requestWeb3Asset,
Wallet,
} from '@ant-design/web3-common';
import { requestWeb3Asset } from '@ant-design/web3-common';
import { EventEmitter } from 'eventemitter3';
import { ethers } from 'ethers';
import { EthereumEIP1193LikeProvider } from './eip1193-provider';
import type { EthereumEIP1193LikeProvider } from './eip1193-provider';

export enum UniversalWeb3ProviderEventType {
AccountsChanged = 'accountsChanged',
Expand Down
58 changes: 43 additions & 15 deletions packages/wagmi/src/wagmi-provider/config-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@ import {
requestWeb3Asset,
fillAddressWith0x,
} from '@ant-design/web3-common';
import { useAccount, useConnect, useDisconnect, useNetwork } from 'wagmi';
import {
useAccount,
useConnect,
useDisconnect,
useNetwork,
useSwitchNetwork,
type Chain as WagmiChain,
} from 'wagmi';
import { readContract } from '@wagmi/core';
import type { WalletFactory } from '../interface';

export interface AntDesignWeb3ConfigProviderProps {
assets?: (WalletFactory | Chain)[];
children?: React.ReactNode;
chains: WagmiChain[];
}

export const AntDesignWeb3ConfigProvider: React.FC<AntDesignWeb3ConfigProviderProps> = (props) => {
const { children, assets } = props;
const { children, assets, chains } = props;
const { address, isDisconnected } = useAccount();
const { connectors, connectAsync } = useConnect();
const { chain, chains } = useNetwork();
const { switchNetwork } = useSwitchNetwork();
const { chain } = useNetwork();
const { disconnectAsync } = useDisconnect();
const [currentChain, setCurrentChain] = React.useState<Chain | undefined>(undefined);

const accounts: Account[] = React.useMemo(() => {
if (!address || isDisconnected) {
Expand All @@ -36,7 +46,9 @@ export const AntDesignWeb3ConfigProvider: React.FC<AntDesignWeb3ConfigProviderPr

const wallets: Wallet[] = React.useMemo(() => {
return connectors.map((connector) => {
const walletFactory = assets?.find((item) => item.name === connector.name) as WalletFactory;
const walletFactory = assets?.find(
(item) => (item as WalletFactory).name === connector.name,
) as WalletFactory;
if (!walletFactory?.create) {
throw new Error(`Can not find wallet factory for ${connector.name}`);
}
Expand All @@ -46,26 +58,38 @@ export const AntDesignWeb3ConfigProvider: React.FC<AntDesignWeb3ConfigProviderPr

const chainList: Chain[] = React.useMemo(() => {
return chains.map((item) => {
const c = assets?.find((asset) => asset.name === item.name) as Chain;
const c = assets?.find((asset) => {
return (asset as Chain).id === item.id;
}) as Chain;
if (!c?.id) {
throw new Error(`Can not find chain id for ${item.name}`);
return {
id: c.id,
name: c.name,
};
}
return c;
});
}, [chains, assets]);

console.log('chains', chains);

const currentChain = React.useMemo(() => {
if (!chain) {
return undefined;
React.useEffect(() => {
if (!chain && currentChain) {
// not connected any chain, keep current chain
return;
}
const c = assets?.find((item) => item.name === chain?.name) as Chain;
const currentWagmiChain = chain ?? chains[0];
if (!currentWagmiChain) {
return;
}
let c = assets?.find((item) => (item as Chain).id === currentWagmiChain?.id) as Chain;
if (!c?.id) {
throw new Error(`Can not find chain id for ${chain.name}`);
c = {
id: currentWagmiChain.id,
name: currentWagmiChain.name,
};
}
return c;
}, [chain, assets]);
setCurrentChain(c);
return;
}, [chain, assets, chains, currentChain]);

return (
<Web3ConfigProvider
Expand All @@ -77,6 +101,7 @@ export const AntDesignWeb3ConfigProvider: React.FC<AntDesignWeb3ConfigProviderPr
const connector = connectors.find((item) => item.name === wallet);
const { account } = await connectAsync({
connector,
chainId: currentChain?.id,
});
return [
{
Expand All @@ -87,6 +112,9 @@ export const AntDesignWeb3ConfigProvider: React.FC<AntDesignWeb3ConfigProviderPr
disconnect={async () => {
await disconnectAsync();
}}
switchChain={async (c: Chain) => {
switchNetwork?.(c.id);
}}
getNFTMetadata={async ({ address: contractAddress, tokenId }) => {
const tokenURI = await readContract({
address: fillAddressWith0x(contractAddress),
Expand Down
105 changes: 102 additions & 3 deletions packages/wagmi/src/wagmi-provider/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { createConfig, configureChains, mainnet } from 'wagmi';
import { describe, it, expect, vi } from 'vitest';
import { render, fireEvent } from '@testing-library/react';
import { createConfig, configureChains } from 'wagmi';
import { polygon, mainnet } from 'wagmi/chains';
import { publicProvider } from 'wagmi/providers/public';
import { Connector, type ConnectorTriggerProps } from '@ant-design/web3';
import { WagmiWeb3ConfigProvider } from '.';
import { Mainnet } from '@ant-design/web3-assets';

describe('WagmiWeb3ConfigProvider', () => {
it('mount correctly', () => {
Expand All @@ -21,4 +24,100 @@ describe('WagmiWeb3ConfigProvider', () => {
const { baseElement } = render(<App />);
expect(baseElement.querySelector('.content')?.textContent).toBe('test');
});

it('chains', () => {
const chains = [polygon, mainnet];
const { publicClient } = configureChains(chains, [publicProvider()]);
const config = createConfig({
autoConnect: true,
publicClient,
connectors: [],
});

const CustomButton: React.FC<React.PropsWithChildren<ConnectorTriggerProps>> = (props) => {
const { currentChain, onSwitchChain } = props;
return (
<div
onClick={() => {
onSwitchChain?.(Mainnet);
}}
className="content"
>
{currentChain?.name}
</div>
);
};

const switchChain = vi.fn();

const App = () => (
<WagmiWeb3ConfigProvider chains={chains} config={config}>
<Connector switchChain={switchChain}>
<CustomButton />
</Connector>
</WagmiWeb3ConfigProvider>
);
const { baseElement } = render(<App />);
expect(baseElement.querySelector('.content')?.textContent).toBe('Polygon');
fireEvent.click(baseElement.querySelector('.content')!);
expect(switchChain).toBeCalledWith(Mainnet);
});

it('custom assets', () => {
const customChainId = 2333;
const chains = [
{
...mainnet,
id: customChainId,
name: 'TEST Chain',
},
polygon,
];
const { publicClient } = configureChains(chains, [publicProvider()]);
const config = createConfig({
autoConnect: true,
publicClient,
connectors: [],
});

const CustomButton: React.FC<React.PropsWithChildren<ConnectorTriggerProps>> = (props) => {
const { currentChain, onSwitchChain } = props;
return (
<div
onClick={() => {
onSwitchChain?.(chains[0]);
}}
className="content"
>
{currentChain?.name}
</div>
);
};

const switchChain = vi.fn();
const assets = [
{
name: 'TEST Chain show text',
id: customChainId,
icon: <div>icon</div>,
nativeCurrency: {
name: 'Matic',
symbol: 'MATIC',
decimals: 18,
},
},
];

const App = () => (
<WagmiWeb3ConfigProvider assets={assets} chains={chains} config={config}>
<Connector switchChain={switchChain}>
<CustomButton />
</Connector>
</WagmiWeb3ConfigProvider>
);
const { baseElement } = render(<App />);
expect(baseElement.querySelector('.content')?.textContent).toBe('TEST Chain show text');
fireEvent.click(baseElement.querySelector('.content')!);
expect(switchChain).toBeCalledWith(chains[0]);
});
});
7 changes: 5 additions & 2 deletions packages/wagmi/src/wagmi-provider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WagmiConfig } from 'wagmi';
import { WagmiConfig, mainnet } from 'wagmi';

import type { PublicClient, WebSocketPublicClient, Config } from 'wagmi';
import type { PublicClient, WebSocketPublicClient, Config, Chain as WagmiChain } from 'wagmi';
import { AntDesignWeb3ConfigProvider } from './config-provider';
import type { Chain } from '@ant-design/web3-common';
import type { WalletFactory } from '../interface';
Expand All @@ -14,6 +14,7 @@ export type WagmiWeb3ConfigProviderProps<
TWebSocketPublicClient extends WebSocketPublicClient = WebSocketPublicClient,
> = {
config: Config<TPublicClient, TWebSocketPublicClient>;
chains?: WagmiChain[];
assets?: (Chain | WalletFactory)[];
};

Expand All @@ -23,6 +24,7 @@ export function WagmiWeb3ConfigProvider<
>({
children,
assets = [],
chains = [mainnet],
...restProps
}: React.PropsWithChildren<
WagmiWeb3ConfigProviderProps<TPublicClient, TWebSocketPublicClient>
Expand All @@ -31,6 +33,7 @@ export function WagmiWeb3ConfigProvider<
<WagmiConfig {...restProps}>
<AntDesignWeb3ConfigProvider
assets={[...assets, MetaMask, WallectConnect, Mainnet, Polygon, BSC, Goerli]}
chains={chains}
>
{children}
</AntDesignWeb3ConfigProvider>
Expand Down
19 changes: 16 additions & 3 deletions packages/web3/src/connect-button/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import { Address } from '../address';
import type { ConnectButtonProps } from './interface';

export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
const { address, connected, onConnectClicked, onDisconnectClicked, chains } = props;
const {
address,
connected,
onConnectClick,
onDisconnectClick,
chains,
currentChain,
onSwitchChain,
} = props;

const buttonProps = {
style: props.style,
Expand All @@ -14,9 +22,9 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
ghost: props.ghost,
onClick: () => {
if (connected) {
onDisconnectClicked?.();
onDisconnectClick?.();
} else {
onConnectClicked?.();
onConnectClick?.();
}
},
children: connected ? <Address ellipsis address={address} /> : 'Connect Wallet',
Expand All @@ -26,9 +34,14 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
if (chains && chains.length > 1) {
return (
<Dropdown.Button
icon={currentChain?.icon}
menu={{
items: chains.map((item) => {
return {
onClick: () => {
onSwitchChain?.(item);
},
icon: item.icon,
label: item.name,
key: item.id,
};
Expand Down
6 changes: 3 additions & 3 deletions packages/web3/src/connector/__tests__/basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ describe('Connector', () => {
const onConnectCallTest = vi.fn();
const onDisconnected = vi.fn();
const CustomButton: React.FC<React.PropsWithChildren<ConnectorTriggerProps>> = (props) => {
const { address, connected, onConnectClicked, onDisconnectClicked, children } = props;
const { address, connected, onConnectClick, onDisconnectClick, children } = props;
return (
<Button
onClick={() => {
if (connected) {
onDisconnectClicked?.();
onDisconnectClick?.();
} else {
onConnectClicked?.();
onConnectClick?.();
}
}}
>
Expand Down
Loading

0 comments on commit 5a032e2

Please sign in to comment.