From 2f6368d0356c2efe9fea50b0572ee6fd18540f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= Date: Thu, 14 Nov 2024 17:57:34 +0000 Subject: [PATCH 01/16] feat(evm): add dynamic --- apps/evm/package.json | 7 + .../components/BridgeForm/BobBridgeForm.tsx | 2 +- .../components/BridgeForm/BtcBridgeForm.tsx | 2 +- .../BridgeStatus/BridgeStatus.style.tsx | 2 +- .../components/StakeForm/BtcStakeForm.tsx | 2 +- apps/evm/src/app/[lang]/nested-providers.tsx | 23 +- apps/evm/src/app/[lang]/providers.tsx | 55 +- apps/evm/src/app/[lang]/sign-up/SignUp.tsx | 50 +- .../AuthButton/AuthButton.stories.tsx | 110 +++ .../src/components/AuthButton/AuthButton.tsx | 111 +++ .../AuthButton/__tests__/AuthButton.test.tsx | 0 .../AuthButton/index.tsx | 0 apps/evm/src/components/Layout/Header.tsx | 6 +- .../components/LoginButton/LoginButton.tsx | 52 +- .../components/SignUpButton/SignUpButton.tsx | 50 +- .../components/WithdrawModal/WithdrawForm.tsx | 2 +- apps/evm/src/components/index.ts | 1 + .../component/ConnectWallet/ConnectWallet.tsx | 48 +- packages/wagmi/src/index.ts | 14 + pnpm-lock.yaml | 723 ++++++++++++++++-- 20 files changed, 1057 insertions(+), 203 deletions(-) create mode 100644 apps/evm/src/components/AuthButton/AuthButton.stories.tsx create mode 100644 apps/evm/src/components/AuthButton/AuthButton.tsx rename apps/evm/src/{connect-ui/component => components}/AuthButton/__tests__/AuthButton.test.tsx (100%) rename apps/evm/src/{connect-ui/component => components}/AuthButton/index.tsx (100%) create mode 100644 packages/wagmi/src/index.ts diff --git a/apps/evm/package.json b/apps/evm/package.json index 3e6b06403..146eccd2c 100644 --- a/apps/evm/package.json +++ b/apps/evm/package.json @@ -18,6 +18,13 @@ "dependencies": { "@binance/w3w-wagmi-connector-v2": "1.2.4-alpha.0", "@gobob/bob-sdk": "^3.1.0", + "@dynamic-labs/bitcoin": "^3.6.2", + "@dynamic-labs/ethereum": "^v3.0.0-alpha.29", + "@dynamic-labs/iconic": "^3.6.2", + "@dynamic-labs/sdk-react-core": "^v3.0.0-alpha.29", + "@dynamic-labs/wagmi-connector": "^v3.0.0-alpha.29", + "@eth-optimism/sdk": "^3.1.6", + "@ethersproject/providers": "^5.7.2", "@gobob/chains": "workspace:^", "@gobob/currency": "workspace:^", "@gobob/hooks": "workspace:^", diff --git a/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx b/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx index c3fb3bedd..d7aeb092e 100644 --- a/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx +++ b/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BobBridgeForm.tsx @@ -19,7 +19,7 @@ import { BridgeAlert } from './BridgeAlert'; import { l1StandardBridgeAbi } from '@/abis/L1StandardBridge.abi'; import { l2StandardBridgeAbi } from '@/abis/L2StandardBridge.abi'; -import { AuthButton } from '@/connect-ui'; +import { AuthButton } from '@/components'; import { L1_CHAIN, L2_CHAIN } from '@/constants'; import { bridgeContracts } from '@/constants/bridge'; import { diff --git a/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BtcBridgeForm.tsx b/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BtcBridgeForm.tsx index acc14cdf3..c3fd2b8ef 100644 --- a/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BtcBridgeForm.tsx +++ b/apps/evm/src/app/[lang]/(bridge)/bridge/components/BridgeForm/BtcBridgeForm.tsx @@ -13,7 +13,7 @@ import { useAccount } from 'wagmi'; import { BtcTokenInput, GatewayGasSwitch, GatewayTransactionDetails } from '../../../components'; import { useGateway, useGatewayForm } from '../../../hooks'; -import { AuthButton } from '@/connect-ui'; +import { AuthButton } from '@/components'; import { isProd } from '@/constants'; import { TokenData } from '@/hooks'; import { BRIDGE_RECIPIENT, BridgeFormValues } from '@/lib/form/bridge'; diff --git a/apps/evm/src/app/[lang]/(bridge)/components/BridgeStatus/BridgeStatus.style.tsx b/apps/evm/src/app/[lang]/(bridge)/components/BridgeStatus/BridgeStatus.style.tsx index a557467ac..3e0907038 100644 --- a/apps/evm/src/app/[lang]/(bridge)/components/BridgeStatus/BridgeStatus.style.tsx +++ b/apps/evm/src/app/[lang]/(bridge)/components/BridgeStatus/BridgeStatus.style.tsx @@ -1,7 +1,7 @@ import { P } from '@gobob/ui'; import styled from 'styled-components'; -import { AuthButton } from '@/connect-ui'; +import { AuthButton } from '@/components'; const StyledTimePill = styled(P)` padding: ${({ theme }) => `${theme.spacing('xs')} ${theme.spacing('lg')}`}; diff --git a/apps/evm/src/app/[lang]/(bridge)/stake/components/StakeForm/BtcStakeForm.tsx b/apps/evm/src/app/[lang]/(bridge)/stake/components/StakeForm/BtcStakeForm.tsx index 8333fe91b..0b49cf6cb 100644 --- a/apps/evm/src/app/[lang]/(bridge)/stake/components/StakeForm/BtcStakeForm.tsx +++ b/apps/evm/src/app/[lang]/(bridge)/stake/components/StakeForm/BtcStakeForm.tsx @@ -12,7 +12,7 @@ import { useGateway, useGatewayForm } from '../../../hooks'; import { StrategyData } from './StakeForm'; -import { AuthButton } from '@/connect-ui'; +import { AuthButton } from '@/components'; import { BRIDGE_RECIPIENT, BridgeFormValues } from '@/lib/form/bridge'; import { GatewayTransactionType, InitGatewayTransaction } from '@/types'; diff --git a/apps/evm/src/app/[lang]/nested-providers.tsx b/apps/evm/src/app/[lang]/nested-providers.tsx index 0e198d844..5091cc330 100644 --- a/apps/evm/src/app/[lang]/nested-providers.tsx +++ b/apps/evm/src/app/[lang]/nested-providers.tsx @@ -11,7 +11,6 @@ import { isAddressEqual } from 'viem'; import { useAccount, useAccountEffect, useChainId, useConfig, useSwitchChain } from 'wagmi'; import { Header, Layout, Sidebar } from '@/components'; -import { ConnectProvider } from '@/connect-ui'; import { isClient, L2_CHAIN, LocalStorageKey } from '@/constants'; import { useBalances, useGetUser, useLogout, useTokens } from '@/hooks'; import { StyledComponentsRegistry } from '@/lib/styled-components'; @@ -136,18 +135,16 @@ export function NestedProviders({ children }: PropsWithChildren) { return ( - - - - - - - - -
- {children} - - + + + + + + + +
+ {children} + ); diff --git a/apps/evm/src/app/[lang]/providers.tsx b/apps/evm/src/app/[lang]/providers.tsx index fe4d008c4..5477b72b8 100644 --- a/apps/evm/src/app/[lang]/providers.tsx +++ b/apps/evm/src/app/[lang]/providers.tsx @@ -1,13 +1,17 @@ 'use client'; -import { SatsWagmiConfig } from '@gobob/sats-wagmi'; +import { BitcoinWalletConnectors } from '@dynamic-labs/bitcoin'; +import { EthereumWalletConnectors } from '@dynamic-labs/ethereum'; +import { BitcoinIcon, EthereumIcon } from '@dynamic-labs/iconic'; +import { DynamicContextProvider, FilterChain } from '@dynamic-labs/sdk-react-core'; +import { DynamicWagmiConnector } from '@dynamic-labs/wagmi-connector'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { PropsWithChildren, useState } from 'react'; import { State, WagmiProvider } from 'wagmi'; import { NestedProviders } from './nested-providers'; -import { bitcoinNetwork, INTERVAL, isProd } from '@/constants'; +import { INTERVAL, isProd } from '@/constants'; import { getConfig } from '@/lib/wagmi'; import { FetchError } from '@/types/fetch'; @@ -38,15 +42,46 @@ export function Providers({ initialState, children }: PropsWithChildren<{ initia } }) ); - const [config] = useState(() => getConfig({ isProd })); + const [config] = useState(() => getConfig({ isProd, multiInjectedProviderDiscovery: false })); return ( - - - - {children} - - - + }, + walletsFilter: FilterChain('EVM') + }, + { + label: { icon: }, + walletsFilter: FilterChain('BTC') + } + ] + } + } + ] + } + }} + > + + + {/* */} + + {children} + + + + ); } diff --git a/apps/evm/src/app/[lang]/sign-up/SignUp.tsx b/apps/evm/src/app/[lang]/sign-up/SignUp.tsx index 01918b13c..29dca95f3 100644 --- a/apps/evm/src/app/[lang]/sign-up/SignUp.tsx +++ b/apps/evm/src/app/[lang]/sign-up/SignUp.tsx @@ -1,28 +1,28 @@ 'use client'; -import { ChainId } from '@gobob/chains'; -import { Button, Divider, Flex, P, toast } from '@gobob/ui'; +import { useDynamicContext } from '@dynamic-labs/sdk-react-core'; +import { Button, Divider, Flex, P } from '@gobob/ui'; import { Trans, t } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { useMutation } from '@tanstack/react-query'; import { useParams, useRouter } from 'next/navigation'; import { FormEventHandler, Suspense, useEffect, useState } from 'react'; -import { useAccount, useSwitchChain } from 'wagmi'; +import { useAccount, useAccountEffect } from 'wagmi'; import { Auditors, HighlightText, ReferralInput } from './components'; import { StyledAuthCard, StyledH1 } from './SignUp.style'; import { Geoblock, LoginSection, Main } from '@/components'; -import { useConnectModal } from '@/connect-ui'; -import { L1_CHAIN, L2_CHAIN, RoutesPath, isValidChain } from '@/constants'; +import { RoutesPath } from '@/constants'; import { useGetUser, useSignUp } from '@/hooks'; import { fusionKeys } from '@/lib/react-query'; import { apiClient } from '@/utils'; const SignUp = (): JSX.Element | null => { - const { address, chain } = useAccount(); - const { switchChainAsync } = useSwitchChain(); - const { open } = useConnectModal(); + const { address } = useAccount(); + + const { setShowAuthFlow, setSelectedTabIndex } = useDynamicContext(); + const { i18n } = useLingui(); const router = useRouter(); @@ -31,6 +31,8 @@ const SignUp = (): JSX.Element | null => { const [referalCode, setReferalCode] = useState(''); + const [isConnecting, setConnecting] = useState(false); + const { mutate: signUp, isPending: isLoadingSignUp } = useSignUp(); const { @@ -54,6 +56,16 @@ const SignUp = (): JSX.Element | null => { setReferalCode(code); }; + useAccountEffect({ + onConnect: () => { + if (isConnecting) { + signUp({}); + + setConnecting(false); + } + } + }); + const handleSubmit: FormEventHandler = async (e) => { e.preventDefault(); @@ -62,24 +74,12 @@ const SignUp = (): JSX.Element | null => { } if (!address) { - return open({ - onConnectEvm: async ({ address, connector }) => { - if (!address) return; - if (!isValidChain((await connector?.getChainId()) as ChainId)) { - const chain = await connector?.switchChain?.({ chainId: L2_CHAIN }); - - if (!chain) { - return toast.error(Something went wrong. Please try connecting your wallet again.); - } - } - - return signUp(address); - } - }); - } + setSelectedTabIndex(1); + setShowAuthFlow(true); + + setConnecting(true); - if (!chain || (chain && !isValidChain(chain?.id))) { - await switchChainAsync({ chainId: L1_CHAIN }); + return; } return signUp(address); diff --git a/apps/evm/src/components/AuthButton/AuthButton.stories.tsx b/apps/evm/src/components/AuthButton/AuthButton.stories.tsx new file mode 100644 index 000000000..d68784ac1 --- /dev/null +++ b/apps/evm/src/components/AuthButton/AuthButton.stories.tsx @@ -0,0 +1,110 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { useDisconnect, useAccount } from '@gobob/wagmi'; +import { useDisconnect as useStasDisconnect, useAccount as useSatsAccount } from '@gobob/sats-wagmi'; +import { Flex } from '@gobob/ui'; + +import { ConnectProvider } from '../..'; + +import { AuthButton as Component, AuthButtonProps } from '.'; + +export default { + title: 'Connect/AuthButton', + component: Component, + parameters: { + layout: 'centered' + }, + decorators: [ + (Story) => ( + + + + ) + ] +} as Meta; + +const RenderDefault = (args: AuthButtonProps) => { + const { address } = useAccount(); + const { disconnect } = useDisconnect(); + + const handleDisconnect = () => { + if (address) { + disconnect(); + } + }; + + return ( + + {address} + {address && Disconnect} + + ); +}; + +export const Default: StoryObj = { + args: {}, + render: RenderDefault +}; + +const RenderOnlyBtc = (args: AuthButtonProps) => { + const { address } = useSatsAccount(); + const { disconnect } = useStasDisconnect(); + + const handleDisconnect = () => { + if (address) { + disconnect(); + } + }; + + return ( + + {address} + {address && Disconnect} + + ); +}; + +export const OnlyBTC: StoryObj = { + args: { isBtcAuthRequired: true }, + decorators: [ + (Story) => ( + + + + ) + ], + render: RenderOnlyBtc +}; + +const RenderBtcEvm = (args: AuthButtonProps) => { + const { address } = useAccount(); + const { disconnect } = useDisconnect(); + const { address: btcAddress } = useSatsAccount(); + const { disconnect: btcDisconnect } = useStasDisconnect(); + + const handleDisconnect = () => { + if (address) { + disconnect(); + } + + if (btcAddress) { + btcDisconnect(); + } + }; + + return ( + + + {address} +
+ {btcAddress} +
+ {address && Disconnect} +
+ ); +}; + +export const EVM_and_BTC: StoryObj = { + args: { isBtcAuthRequired: true }, + + render: RenderBtcEvm +}; diff --git a/apps/evm/src/components/AuthButton/AuthButton.tsx b/apps/evm/src/components/AuthButton/AuthButton.tsx new file mode 100644 index 000000000..5a09c9da1 --- /dev/null +++ b/apps/evm/src/components/AuthButton/AuthButton.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useDynamicContext, useUserWallets } from '@dynamic-labs/sdk-react-core'; +import { ChainId, getChainName } from '@gobob/chains'; +import { Button, ButtonProps } from '@gobob/ui'; +import { useAccount, useSwitchChain } from '@gobob/wagmi'; +import { Trans } from '@lingui/macro'; +import { PressEvent } from '@react-aria/interactions'; +import { useIsClient } from 'usehooks-ts'; + +type Props = { + chain?: ChainId; + isBtcAuthRequired?: boolean; + isEvmAuthRequired?: boolean; + isSilentSwitch?: boolean; + shouldPressAfterSwitch?: boolean; +}; + +type InheritAttrs = Omit; + +type AuthButtonProps = Props & InheritAttrs; + +const AuthButton = ({ + onPress, + onClick, + disabled, + children, + type, + chain: chainProp, + isEvmAuthRequired = true, + isBtcAuthRequired, + isSilentSwitch, + shouldPressAfterSwitch, + ...props +}: AuthButtonProps) => { + const isClient = useIsClient(); + + const { chain } = useAccount(); + const { switchChainAsync, isPending: isSwitchNetworkLoading } = useSwitchChain(); + + const { setShowAuthFlow, setSelectedTabIndex } = useDynamicContext(); + const userWallets = useUserWallets(); + + if (!isClient) { + return ( + + ); + } + + const evmWallet = userWallets.find((wallet) => wallet.chain === 'EVM'); + + // Comes first because if the connection includes evm, the priority is always evm + if (isEvmAuthRequired && !evmWallet) { + const handlePress = (): void => { + setSelectedTabIndex(1); + setShowAuthFlow(true); + }; + + return ( + + ); + } + + if (isEvmAuthRequired && chainProp && chain?.id !== chainProp) { + const name = getChainName(chainProp); + const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1); + + const handlePress = async (e: PressEvent) => { + await switchChainAsync?.({ chainId: chainProp }); + + if (shouldPressAfterSwitch) { + onPress?.(e); + } + }; + + return ( + + ); + } + + const btcWallet = userWallets.find((wallet) => wallet.chain === 'BTC'); + + if (isBtcAuthRequired && !btcWallet) { + const handlePress = (): void => { + console.log('here'); + setSelectedTabIndex(2); + setShowAuthFlow(true); + }; + + return ( + + ); + } + + return ( + + ); +}; + +export { AuthButton }; +export type { AuthButtonProps }; diff --git a/apps/evm/src/connect-ui/component/AuthButton/__tests__/AuthButton.test.tsx b/apps/evm/src/components/AuthButton/__tests__/AuthButton.test.tsx similarity index 100% rename from apps/evm/src/connect-ui/component/AuthButton/__tests__/AuthButton.test.tsx rename to apps/evm/src/components/AuthButton/__tests__/AuthButton.test.tsx diff --git a/apps/evm/src/connect-ui/component/AuthButton/index.tsx b/apps/evm/src/components/AuthButton/index.tsx similarity index 100% rename from apps/evm/src/connect-ui/component/AuthButton/index.tsx rename to apps/evm/src/components/AuthButton/index.tsx diff --git a/apps/evm/src/components/Layout/Header.tsx b/apps/evm/src/components/Layout/Header.tsx index eaa5352fd..bd05b287b 100644 --- a/apps/evm/src/components/Layout/Header.tsx +++ b/apps/evm/src/components/Layout/Header.tsx @@ -1,5 +1,6 @@ 'use client'; +import { DynamicWidget } from '@dynamic-labs/sdk-react-core'; import { Bars3, Button, @@ -13,9 +14,9 @@ import { useMediaQuery } from '@gobob/ui'; import { t, Trans } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { useState } from 'react'; import { useTheme } from 'styled-components'; -import { useLingui } from '@lingui/react'; import { Logo } from '../Logo'; import { SocialsGroup } from '../SocialsGroup'; @@ -26,7 +27,6 @@ import { useLayoutContext } from './LayoutContext'; import { Nav } from './Nav'; import { NavItem } from './NavItem'; -import { ConnectWallet } from '@/connect-ui'; import { DocsLinks, RoutesPath } from '@/constants'; import { useUserAgent } from '@/user-agent'; @@ -113,7 +113,7 @@ const Header = ({ isTestnet, isFusion, ...props }: HeaderProps): JSX.Element =>