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: update behavior to switch chains #3922

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/clean-ladybugs-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@venusprotocol/evm": minor
---

update behavior to switch chains
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useGetVaiControllerContractAddress,
useGetVenusLensContractAddress,
} from 'libs/contracts';
import { useChainId, usePublicClient } from 'libs/wallet';
import { usePublicClient } from 'libs/wallet';
import { renderHook } from 'testUtils/render';
import { restService } from 'utilities/restService';
import { useGetPools } from '..';
Expand Down Expand Up @@ -87,11 +87,9 @@ describe('useGetPools', () => {
});

it('returns pools with time based reward rates in the correct format', async () => {
(useChainId as Mock).mockImplementation(() => ({
const { result } = renderHook(() => useGetPools(), {
chainId: ChainId.ARBITRUM_SEPOLIA,
}));

const { result } = renderHook(() => useGetPools());
});

await waitFor(() => expect(result.current.data).toBeDefined());
expect(result.current.data).toMatchSnapshot();
Expand Down
39 changes: 3 additions & 36 deletions apps/evm/src/containers/ConnectWallet/__tests__/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import type { Mock } from 'vitest';

import fakeAccountAddress from '__mocks__/models/address';
import { en } from 'libs/translations';
import { useAuthModal, useSwitchChain } from 'libs/wallet';
import { useAuthModal } from 'libs/wallet';
import { renderComponent } from 'testUtils/render';
import { ChainId } from 'types';
import { ConnectWallet } from '..';

describe('ConnectWallet', () => {
beforeEach(() => {
(useAuthModal as Mock).mockReturnValue({ openAuthModal: vi.fn() });
(useSwitchChain as Mock).mockReturnValue({ switchChain: vi.fn() });
});

it('renders without crashing', () => {
Expand All @@ -38,40 +36,9 @@ describe('ConnectWallet', () => {
});
});

it('displays chain switching button when current chain is different from chainId parameter', async () => {
const { getByText } = renderComponent(<ConnectWallet chainId={ChainId.OPBNB_TESTNET} />, {
accountAddress: fakeAccountAddress,
});

const switchChainButton = getByText(
en.connectWallet.switchChain.replace('{{chainName}}', 'opBNB testnet'),
);
expect(switchChainButton).toBeInTheDocument();
});

it('calls switchChain when switch chain button is clicked', async () => {
const mockSwitchChain = vi.fn();
(useSwitchChain as Mock).mockReturnValue({ switchChain: mockSwitchChain });

const { getByText } = renderComponent(<ConnectWallet chainId={ChainId.OPBNB_TESTNET} />, {
accountAddress: fakeAccountAddress,
});

const switchChainButton = getByText(
en.connectWallet.switchChain.replace('{{chainName}}', 'opBNB testnet'),
);

fireEvent.click(switchChainButton);

await waitFor(() => {
expect(mockSwitchChain).toHaveBeenCalledTimes(1);
expect(mockSwitchChain).toHaveBeenCalledWith({ chainId: ChainId.OPBNB_TESTNET });
});
});

it('renders children when user is connected and on the correct chain', () => {
it('renders children when user is connected', () => {
const { getByText } = renderComponent(
<ConnectWallet chainId={ChainId.BSC_TESTNET}>
<ConnectWallet>
<div>Child Component</div>
</ConnectWallet>,
{
Expand Down
67 changes: 19 additions & 48 deletions apps/evm/src/containers/ConnectWallet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,40 @@
import { chainMetadata } from '@venusprotocol/chains';
import { useMemo } from 'react';

import { Button, type Variant } from 'components/Button';
import { Button } from 'components/Button';
import { NoticeInfo } from 'components/Notice';
import { useTranslation } from 'libs/translations';
import { useAccountAddress, useAuthModal, useChainId, useSwitchChain } from 'libs/wallet';
import type { ChainId } from 'types';
import { Container } from './Container';
import { useAccountAddress, useAuthModal } from 'libs/wallet';

export interface ConnectWalletProps extends React.HTMLAttributes<HTMLDivElement> {
chainId?: ChainId;
buttonVariant?: Variant;
export interface ConnectWalletProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'className'> {
message?: string;
className?: string;
children?: React.ReactNode;
}

export const ConnectWallet: React.FC<ConnectWalletProps> = ({
children,
chainId,
message,
buttonVariant,
...otherProps
}) => {
const { accountAddress } = useAccountAddress();
const isUserConnected = !!accountAddress;

const { chainId: currentChainId } = useChainId();

const isOnWrongChain = useMemo(
() => chainId && currentChainId !== chainId,
[currentChainId, chainId],
);

const chain = chainId && chainMetadata[chainId];

const { openAuthModal } = useAuthModal();
const { switchChain } = useSwitchChain();
const handleSwitchChain = () => chainId && switchChain({ chainId });

const { t } = useTranslation();

if (!isUserConnected) {
return (
<Container {...otherProps}>
{!!message && <NoticeInfo className="mb-8" description={message} />}

<Button variant={buttonVariant} className="w-full" onClick={openAuthModal}>
{t('connectWallet.connectButton')}
</Button>
</Container>
);
}

if (isOnWrongChain) {
return (
<Container {...otherProps}>
<Button variant={buttonVariant} className="w-full" onClick={handleSwitchChain}>
{t('connectWallet.switchChain', {
chainName: chain?.name,
})}
</Button>
</Container>
);
}

return <Container {...otherProps}>{children}</Container>;
return (
<div {...otherProps}>
{isUserConnected ? (
children
) : (
<>
{!!message && <NoticeInfo className="mb-8" description={message} />}

<Button className="w-full" onClick={openAuthModal}>
{t('connectWallet.connectButton')}
</Button>
</>
)}
</div>
);
};
26 changes: 22 additions & 4 deletions apps/evm/src/containers/Form/RhfSubmitButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import { type ButtonProps, PrimaryButton } from 'components';
import { ConnectWallet } from 'containers/ConnectWallet';
import { cn } from 'utilities';

import type { ChainId } from '@venusprotocol/chains';
import { SwitchChain } from 'containers/SwitchChain';
import { ApproveTokenSteps, type ApproveTokenStepsProps } from './ApproveTokenSteps';

export interface RhfSubmitButtonProps extends ButtonProps {
control: Control<any>;
enabledLabel: string;
disabledLabel: string;
isDangerousSubmission?: boolean;
requiresConnectedWallet?: boolean;
requiresConnectedWallet?:
| boolean
| {
chainId: ChainId;
};
spendingApproval?: Omit<ApproveTokenStepsProps, 'children' | 'secondStepButtonLabel'>;
}

Expand Down Expand Up @@ -41,16 +47,28 @@ export const RhfSubmitButton: React.FC<RhfSubmitButtonProps> = ({
</PrimaryButton>
);

if (spendingApproval) {
if (formState.isValid && spendingApproval) {
dom = (
<ApproveTokenSteps secondStepButtonLabel={enabledLabel} {...spendingApproval}>
{dom}
</ApproveTokenSteps>
);
}

if (requiresConnectedWallet || spendingApproval) {
dom = <ConnectWallet buttonVariant="primary">{dom}</ConnectWallet>;
if (formState.isValid && (requiresConnectedWallet || spendingApproval)) {
dom = (
<ConnectWallet>
<SwitchChain
chainId={
typeof requiresConnectedWallet !== 'boolean'
? requiresConnectedWallet.chainId
: undefined
}
>
{dom}
</SwitchChain>
</ConnectWallet>
);
}

return <div className={className}>{dom}</div>;
Expand Down
28 changes: 17 additions & 11 deletions apps/evm/src/containers/Layout/ClaimRewardButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useTranslation } from 'libs/translations';
import { useAccountAddress } from 'libs/wallet';
import { cn, formatCentsToReadableValue } from 'utilities';

import { ConnectWallet } from 'containers/ConnectWallet';
import { SwitchChain } from 'containers/SwitchChain';
import TEST_IDS from '../testIds';
import { RewardGroup } from './RewardGroup';
import type { Group } from './types';
Expand Down Expand Up @@ -127,17 +129,21 @@ export const ClaimRewardButtonUi: React.FC<ClaimRewardButtonUiProps> = ({
))}
</div>

<PrimaryButton
onClick={handleClaimReward}
className="w-full"
disabled={isSubmitDisabled}
data-testid={TEST_IDS.claimRewardSubmitButton}
loading={isClaimingRewards}
>
{isSubmitDisabled
? t('claimReward.modal.claimButton.disabledLabel')
: t('claimReward.modal.claimButton.enabledLabel')}
</PrimaryButton>
<ConnectWallet>
<SwitchChain>
<PrimaryButton
onClick={handleClaimReward}
className="w-full"
disabled={isSubmitDisabled}
data-testid={TEST_IDS.claimRewardSubmitButton}
loading={isClaimingRewards}
>
{isSubmitDisabled
? t('claimReward.modal.claimButton.disabledLabel')
: t('claimReward.modal.claimButton.enabledLabel')}
</PrimaryButton>
</SwitchChain>
</ConnectWallet>
</>
</Modal>
</>
Expand Down
70 changes: 70 additions & 0 deletions apps/evm/src/containers/SwitchChain/__tests__/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import type { Mock } from 'vitest';

import { ChainId } from '@venusprotocol/chains';
import { chainMetadata } from '@venusprotocol/chains';
import fakeAddress from '__mocks__/models/address';
import { en } from 'libs/translations';
import { useSwitchChain } from 'libs/wallet';
import { renderComponent } from 'testUtils/render';
import { SwitchChain } from '..';

const fakeContent = 'Fake content';

describe('SwitchChain', () => {
beforeEach(() => {
(useSwitchChain as Mock).mockReturnValue({ switchChain: vi.fn() });
});

it('renders without SwitchChain', () => {
renderComponent(<SwitchChain />);
});

it('displays children when user is not connected', async () => {
renderComponent(<SwitchChain>{fakeContent}</SwitchChain>);

expect(screen.queryByText(fakeContent)).toBeInTheDocument();
});

it('displays switch button when user is connected to the wrong chain', async () => {
renderComponent(<SwitchChain chainId={ChainId.BSC_TESTNET}>{fakeContent}</SwitchChain>, {
accountAddress: fakeAddress,
chainId: ChainId.ETHEREUM,
});

expect(screen.queryByText(fakeContent)).not.toBeInTheDocument();

expect(
screen.queryByText(
en.switchChain.switchButton.replace(
'{{chainName}}',
chainMetadata[ChainId.BSC_TESTNET].name,
),
),
).toBeInTheDocument();
});

it('calls switchChain when switch button is clicked', async () => {
const mockSwitchChain = vi.fn();
(useSwitchChain as Mock).mockReturnValue({ switchChain: mockSwitchChain });

renderComponent(<SwitchChain chainId={ChainId.BSC_TESTNET}>{fakeContent}</SwitchChain>, {
accountAddress: fakeAddress,
chainId: ChainId.ETHEREUM,
});

fireEvent.click(
screen.getByText(
en.switchChain.switchButton.replace(
'{{chainName}}',
chainMetadata[ChainId.BSC_TESTNET].name,
),
),
);

await waitFor(() => expect(mockSwitchChain).toHaveBeenCalledTimes(1));
expect(mockSwitchChain).toHaveBeenCalledWith({
chainId: ChainId.BSC_TESTNET,
});
});
});
41 changes: 41 additions & 0 deletions apps/evm/src/containers/SwitchChain/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { chainMetadata } from '@venusprotocol/chains';

import { Button } from 'components/Button';
import { useTranslation } from 'libs/translations';
import { useAccountAddress, useAccountChainId, useChainId, useSwitchChain } from 'libs/wallet';
import type { ChainId } from 'types';

export interface SwitchChainProps extends React.HTMLAttributes<HTMLDivElement> {
chainId?: ChainId;
}

export const SwitchChain: React.FC<SwitchChainProps> = ({ children, chainId, ...otherProps }) => {
const { accountAddress } = useAccountAddress();
const isUserConnected = !!accountAddress;

const { chainId: currentChainId } = useChainId();
const targetChainId = chainId || currentChainId;

const { chainId: accountChainId } = useAccountChainId();
const isOnWrongChain = accountChainId !== targetChainId;
const targetChain = chainMetadata[targetChainId];

const { switchChain } = useSwitchChain();
const { t } = useTranslation();

const handleSwitchChain = () => switchChain({ chainId: targetChainId });

return (
<div {...otherProps}>
{isUserConnected && isOnWrongChain ? (
<Button className="w-full" onClick={handleSwitchChain}>
{t('switchChain.switchButton', {
chainName: targetChain.name,
})}
</Button>
) : (
children
)}
</div>
);
};
Loading