diff --git a/extension/src/panel/providers/index.ts b/extension/src/panel/providers/index.ts index 86548447..7547d2f8 100644 --- a/extension/src/panel/providers/index.ts +++ b/extension/src/panel/providers/index.ts @@ -1,4 +1,11 @@ export { default as ForkProvider } from './ForkProvider' export { getReadOnlyProvider } from './readOnlyProvider' -export { ProvideInjectedWallet, useInjectedWallet } from './useInjectedWallet' -export { default as useWalletConnect } from './useWalletConnect' +export { + ProvideInjectedWallet, + useInjectedWallet, + type InjectedWalletContextT, +} from './useInjectedWallet' +export { + default as useWalletConnect, + type WalletConnectResult, +} from './useWalletConnect' diff --git a/extension/src/panel/routes/ConnectButton/index.tsx b/extension/src/panel/routes/ConnectButton/index.tsx deleted file mode 100644 index 67c9e6bc..00000000 --- a/extension/src/panel/routes/ConnectButton/index.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { Alert, Button } from '@/components' -import { invariant } from '@epic-web/invariant' -import { ZeroAddress } from 'ethers' -import React from 'react' -import { ChainId, parsePrefixedAddress } from 'ser-kit' -import { CHAIN_NAME } from '../../../chains' -import { ProviderType, Route } from '../../../types' -import { useInjectedWallet, useWalletConnect } from '../../providers' -import { isConnectedTo } from '../routeHooks' -import { Account } from './Account' -import { InjectedWallet } from './InjectedWallet' -import { WalletConnect } from './WalletConnect' - -interface Props { - route: Route - onConnect(args: { - providerType: ProviderType - chainId: ChainId - account: string - }): void - onDisconnect(): void -} - -const ConnectButton: React.FC = ({ route, onConnect, onDisconnect }) => { - const pilotAddress = getPilotAddress(route) - - const injectedWallet = useInjectedWallet() - const walletConnect = useWalletConnect(route.id) - - // not connected - if (pilotAddress == null) { - return ( -
- - onConnect({ - providerType: ProviderType.WalletConnect, - chainId, - account, - }) - } - /> - - - onConnect({ - providerType: ProviderType.InjectedWallet, - chainId, - account, - }) - } - /> -
- ) - } - - const isWalletConnectWallet = - route.providerType === ProviderType.WalletConnect - const walletConnectProviderAvailable = walletConnect != null - - const isInjectedWallet = route.providerType === ProviderType.InjectedWallet - - // atm, we don't yet support cross-chain routes, so can derive a general chainId from the avatar - const [chainId] = parsePrefixedAddress(route.avatar) - - invariant(chainId != null, 'chainId is empty') - - const connected = - route.initiator && - isConnectedTo( - isInjectedWallet ? injectedWallet : walletConnect, - route.initiator, - chainId - ) - - // good to go - if (connected) { - return ( -
- {pilotAddress} - - -
- ) - } - - // WalletConnect: wrong chain - if ( - isWalletConnectWallet && - walletConnect && - walletConnect.accounts.some((acc) => acc.toLowerCase() === pilotAddress) && - walletConnect.chainId !== chainId - ) { - const chainName = CHAIN_NAME[chainId] || `#${chainId}` - - return ( -
- {pilotAddress} - - - The connected wallet belongs to a different chain. Connect a wallet on{' '} - {chainName} to use Pilot. - - - -
- ) - } - - // Injected wallet - if (isInjectedWallet && injectedWallet.provider) { - const accountInWallet = injectedWallet.accounts.some( - (acc) => acc.toLowerCase() === pilotAddress - ) - - // Wallet disconnected - if (injectedWallet.accounts.length === 0) { - return ( -
- - Your wallet is disconnected from Pilot. Reconnect it to use the - selected account with Pilot. - - {pilotAddress} - -
- - -
-
- ) - } - - // Injected wallet: right account, wrong chain - if (accountInWallet && injectedWallet.chainId !== chainId) { - const chainName = CHAIN_NAME[chainId] || `#${chainId}` - - return ( -
- - The connected wallet belongs to a different chain. To use it you - need to switch back to {chainName} - - - {pilotAddress} - - -
- ) - } - - // Wrong account - if (!accountInWallet) { - return ( -
- - Switch your wallet to this account in order to use Pilot. - - - {pilotAddress} - - -
- ) - } - } -} - -export default ConnectButton - -const getPilotAddress = (route: Route) => { - if (route.initiator == null) { - return null - } - - const address = parsePrefixedAddress(route.initiator)[1].toLowerCase() - - if (address === ZeroAddress) { - return null - } - - return address -} diff --git a/extension/src/panel/routes/Edit/index.tsx b/extension/src/panel/routes/Edit/index.tsx index 49b472e4..a876e23c 100644 --- a/extension/src/panel/routes/Edit/index.tsx +++ b/extension/src/panel/routes/Edit/index.tsx @@ -26,7 +26,6 @@ import { useClearTransactions } from '../../state/transactionHooks' import { decodeRoleKey, encodeRoleKey } from '../../utils' import AvatarInput from '../AvatarInput' import ChainSelect from '../ChainSelect' -import ConnectButton from '../ConnectButton' import { asLegacyConnection, fromLegacyConnection, @@ -35,6 +34,7 @@ import { ModSelect, NO_MODULE_OPTION } from '../ModSelect' import { useRoute, useRoutes, useSelectedRouteId } from '../routeHooks' import useConnectionDryRun from '../useConnectionDryRun' import classes from './style.module.css' +import { ConnectWallet } from './wallet' type ConnectionPatch = { label?: string @@ -223,7 +223,7 @@ const EditConnection: React.FC = () => { /> - { updateConnection({ diff --git a/extension/src/panel/routes/ConnectButton/Account.tsx b/extension/src/panel/routes/Edit/wallet/Account.tsx similarity index 83% rename from extension/src/panel/routes/ConnectButton/Account.tsx rename to extension/src/panel/routes/Edit/wallet/Account.tsx index 089dee54..9cf1c6a3 100644 --- a/extension/src/panel/routes/ConnectButton/Account.tsx +++ b/extension/src/panel/routes/Edit/wallet/Account.tsx @@ -1,7 +1,7 @@ import { Circle, RawAddress } from '@/components' +import { ProviderType } from '@/types' import { validateAddress } from '@/utils' -import { ProviderType } from '../../../types' -import { ProviderLogo } from './ProviderLogo' +import { ProviderLogo } from './providerLogo' type AccountProps = { providerType: ProviderType diff --git a/extension/src/panel/routes/Edit/wallet/ConnectWallet.tsx b/extension/src/panel/routes/Edit/wallet/ConnectWallet.tsx new file mode 100644 index 00000000..da439c6b --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/ConnectWallet.tsx @@ -0,0 +1,91 @@ +import { WalletConnectResult } from '@/providers' +import { ProviderType, Route } from '@/types' +import { ZeroAddress } from 'ethers' +import { ChainId, parsePrefixedAddress } from 'ser-kit' +import { InjectedWalletContextT } from '../../../providers/useInjectedWallet' +import { getChainId, isConnectedTo } from '../../routeHooks' +import { InjectedWallet, InjectedWalletConnect } from './injectedWallet' +import { WalletConnect, WalletConnectConnect } from './walletConnect' + +interface Props { + route: Route + onConnect(args: { + providerType: ProviderType + chainId: ChainId + account: string + }): void + onDisconnect(): void +} + +export const ConnectWallet = ({ route, onConnect, onDisconnect }: Props) => { + const pilotAddress = getPilotAddress(route) + const chainId = getChainId(route.avatar) + + const isConnected = ( + provider: InjectedWalletContextT | WalletConnectResult + ) => + route.initiator != null && isConnectedTo(provider, route.initiator, chainId) + + // not connected + if (pilotAddress == null) { + return ( +
+ + onConnect({ + providerType: ProviderType.WalletConnect, + chainId, + account, + }) + } + /> + + + onConnect({ + providerType: ProviderType.InjectedWallet, + chainId, + account, + }) + } + /> +
+ ) + } + + switch (route.providerType) { + case ProviderType.InjectedWallet: + return ( + + ) + case ProviderType.WalletConnect: + return ( + + ) + } +} + +const getPilotAddress = (route: Route) => { + if (route.initiator == null) { + return null + } + + const address = parsePrefixedAddress(route.initiator)[1].toLowerCase() + + if (address === ZeroAddress) { + return null + } + + return address +} diff --git a/extension/src/panel/routes/Edit/wallet/Connected.tsx b/extension/src/panel/routes/Edit/wallet/Connected.tsx new file mode 100644 index 00000000..1a844028 --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/Connected.tsx @@ -0,0 +1,15 @@ +import { Button } from '@/components' +import { PropsWithChildren } from 'react' +import { Section } from './Section' + +type ConnectedProps = PropsWithChildren<{ + onDisconnect: () => void +}> + +export const Connected = ({ children, onDisconnect }: ConnectedProps) => ( +
+ {children} + + +
+) diff --git a/extension/src/panel/routes/Edit/wallet/Section.tsx b/extension/src/panel/routes/Edit/wallet/Section.tsx new file mode 100644 index 00000000..0a93211f --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/Section.tsx @@ -0,0 +1,11 @@ +import { PropsWithChildren } from 'react' + +export const Section = ({ children }: PropsWithChildren) => ( +
{children}
+) + +const Actions = ({ children }: PropsWithChildren) => ( +
{children}
+) + +Section.Actions = Actions diff --git a/extension/src/panel/routes/Edit/wallet/SwitchChain.tsx b/extension/src/panel/routes/Edit/wallet/SwitchChain.tsx new file mode 100644 index 00000000..80aa8ed1 --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/SwitchChain.tsx @@ -0,0 +1,40 @@ +import { CHAIN_NAME } from '@/chains' +import { Alert, Button } from '@/components' +import { PropsWithChildren } from 'react' +import { ChainId } from 'ser-kit' +import { Section } from './Section' + +type SwitchChainProps = PropsWithChildren<{ + chainId: ChainId + + onSwitch?: () => void + onDisconnect?: () => void +}> + +export const SwitchChain = ({ + chainId, + children, + onSwitch, + onDisconnect, +}: SwitchChainProps) => { + const chainName = CHAIN_NAME[chainId] || `#${chainId}` + + return ( +
+ + The connected wallet belongs to a different chain. To use it you need to + switch back to {chainName} + + + {children} + + + {onSwitch && ( + + )} + + {onDisconnect && } + +
+ ) +} diff --git a/extension/src/panel/routes/Edit/wallet/WalletDisconnected.tsx b/extension/src/panel/routes/Edit/wallet/WalletDisconnected.tsx new file mode 100644 index 00000000..c7cd5cba --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/WalletDisconnected.tsx @@ -0,0 +1,28 @@ +import { Alert, Button } from '@/components' +import { PropsWithChildren } from 'react' +import { Section } from './Section' + +type WalletDisconnectedProps = PropsWithChildren<{ + onReconnect: () => void + onDisconnect: () => void +}> + +export const WalletDisconnected = ({ + children, + onReconnect, + onDisconnect, +}: WalletDisconnectedProps) => ( +
+ + Your wallet is disconnected from Pilot. Reconnect it to use the selected + account with Pilot. + + + {children} + + + + + +
+) diff --git a/extension/src/panel/routes/Edit/wallet/WrongAccount.tsx b/extension/src/panel/routes/Edit/wallet/WrongAccount.tsx new file mode 100644 index 00000000..62284643 --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/WrongAccount.tsx @@ -0,0 +1,19 @@ +import { Alert, Button } from '@/components' +import { PropsWithChildren } from 'react' +import { Section } from './Section' + +type WrongAccountProps = PropsWithChildren<{ + onDisconnect: () => void +}> + +export const WrongAccount = ({ children, onDisconnect }: WrongAccountProps) => ( +
+ + Switch your wallet to this account in order to use Pilot. + + + {children} + + +
+) diff --git a/extension/src/panel/routes/Edit/wallet/index.ts b/extension/src/panel/routes/Edit/wallet/index.ts new file mode 100644 index 00000000..8524fdbe --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/index.ts @@ -0,0 +1 @@ +export { ConnectWallet } from './ConnectWallet' diff --git a/extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWallet.tsx b/extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWallet.tsx new file mode 100644 index 00000000..f040c1fe --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWallet.tsx @@ -0,0 +1,78 @@ +import { InjectedWalletContextT, useInjectedWallet } from '@/providers' +import { ProviderType } from '@/types' +import { ChainId } from 'ser-kit' +import { Account } from '../Account' +import { Connected } from '../Connected' +import { SwitchChain } from '../SwitchChain' +import { WalletDisconnected } from '../WalletDisconnected' +import { WrongAccount } from '../WrongAccount' + +type InjectedWalletProps = { + pilotAddress: string + chainId: ChainId + + onDisconnect: () => void + isConnected: (provider: InjectedWalletContextT) => boolean +} + +export const InjectedWallet = ({ + pilotAddress, + chainId, + onDisconnect, + isConnected, +}: InjectedWalletProps) => { + const injectedWallet = useInjectedWallet() + + if (isConnected(injectedWallet)) { + return ( + + + {pilotAddress} + + + ) + } + + // Wallet disconnected + if (injectedWallet.accounts.length === 0) { + return ( + injectedWallet.connect()} + > + + {pilotAddress} + + + ) + } + + // Injected wallet: right account, wrong chain + if (injectedWallet.chainId !== chainId) { + return ( + injectedWallet.switchChain(chainId)} + > + + {pilotAddress} + + + ) + } + + const accountInWallet = injectedWallet.accounts.some( + (acc) => acc.toLowerCase() === pilotAddress + ) + + // Wrong account + if (!accountInWallet) { + return ( + + + {pilotAddress} + + + ) + } +} diff --git a/extension/src/panel/routes/ConnectButton/InjectedWallet.tsx b/extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWalletConnect.tsx similarity index 74% rename from extension/src/panel/routes/ConnectButton/InjectedWallet.tsx rename to extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWalletConnect.tsx index 3aa47f09..5530922c 100644 --- a/extension/src/panel/routes/ConnectButton/InjectedWallet.tsx +++ b/extension/src/panel/routes/Edit/wallet/injectedWallet/InjectedWalletConnect.tsx @@ -1,14 +1,14 @@ import { Button } from '@/components' +import { useInjectedWallet } from '@/providers' +import { ProviderType } from '@/types' import { ChainId } from 'ser-kit' -import { ProviderType } from '../../../types' -import { useInjectedWallet } from '../../providers' -import { ProviderLogo } from './ProviderLogo' +import { ProviderLogo } from '../providerLogo' type InjectedWalletProps = { onConnect: (chainId: ChainId, account: string) => void } -export const InjectedWallet = ({ onConnect }: InjectedWalletProps) => { +export const InjectedWalletConnect = ({ onConnect }: InjectedWalletProps) => { const injectedWallet = useInjectedWallet() if (injectedWallet.provider == null) { diff --git a/extension/src/panel/routes/Edit/wallet/injectedWallet/index.ts b/extension/src/panel/routes/Edit/wallet/injectedWallet/index.ts new file mode 100644 index 00000000..b43499ff --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/injectedWallet/index.ts @@ -0,0 +1,2 @@ +export { InjectedWallet } from './InjectedWallet' +export { InjectedWalletConnect } from './InjectedWalletConnect' diff --git a/extension/src/panel/routes/ConnectButton/ProviderLogo.tsx b/extension/src/panel/routes/Edit/wallet/providerLogo/ProviderLogo.tsx similarity index 93% rename from extension/src/panel/routes/ConnectButton/ProviderLogo.tsx rename to extension/src/panel/routes/Edit/wallet/providerLogo/ProviderLogo.tsx index eb4a4a2f..1c302a3e 100644 --- a/extension/src/panel/routes/ConnectButton/ProviderLogo.tsx +++ b/extension/src/panel/routes/Edit/wallet/providerLogo/ProviderLogo.tsx @@ -1,4 +1,4 @@ -import { ProviderType } from '../../../types' +import { ProviderType } from '@/types' import metamaskLogoUrl from './metamask-logo.svg' import walletConnectLogoUrl from './wallet-connect-logo.png' diff --git a/extension/src/panel/routes/Edit/wallet/providerLogo/index.ts b/extension/src/panel/routes/Edit/wallet/providerLogo/index.ts new file mode 100644 index 00000000..8e3cb38e --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/providerLogo/index.ts @@ -0,0 +1 @@ +export { ProviderLogo } from './ProviderLogo' diff --git a/extension/src/panel/routes/ConnectButton/metamask-logo.svg b/extension/src/panel/routes/Edit/wallet/providerLogo/metamask-logo.svg similarity index 100% rename from extension/src/panel/routes/ConnectButton/metamask-logo.svg rename to extension/src/panel/routes/Edit/wallet/providerLogo/metamask-logo.svg diff --git a/extension/src/panel/routes/ConnectButton/wallet-connect-logo.png b/extension/src/panel/routes/Edit/wallet/providerLogo/wallet-connect-logo.png similarity index 100% rename from extension/src/panel/routes/ConnectButton/wallet-connect-logo.png rename to extension/src/panel/routes/Edit/wallet/providerLogo/wallet-connect-logo.png diff --git a/extension/src/panel/routes/ConnectButton/style.module.css b/extension/src/panel/routes/Edit/wallet/style.module.css similarity index 100% rename from extension/src/panel/routes/ConnectButton/style.module.css rename to extension/src/panel/routes/Edit/wallet/style.module.css diff --git a/extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnect.tsx b/extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnect.tsx new file mode 100644 index 00000000..3845d65e --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnect.tsx @@ -0,0 +1,55 @@ +import { useWalletConnect, WalletConnectResult } from '@/providers' +import { ProviderType } from '@/types' +import { invariant } from '@epic-web/invariant' +import { ChainId } from 'ser-kit' +import { Account } from '../Account' +import { Connected } from '../Connected' +import { SwitchChain } from '../SwitchChain' + +type WalletConnectProps = { + routeId: string + pilotAddress: string + chainId: ChainId + + isConnected: (provider: WalletConnectResult) => boolean +} + +export const WalletConnect = ({ + routeId, + pilotAddress, + chainId, + isConnected, +}: WalletConnectProps) => { + const walletConnect = useWalletConnect(routeId) + + invariant( + walletConnect != null, + 'Wallet connect chosen as provider but not available' + ) + + if (isConnected(walletConnect)) { + return ( + walletConnect.disconnect()}> + + {pilotAddress} + + + ) + } + + if ( + walletConnect.accounts.some((acc) => acc.toLowerCase() === pilotAddress) && + walletConnect.chainId !== chainId + ) { + return ( + walletConnect.disconnect()} + > + + {pilotAddress} + + + ) + } +} diff --git a/extension/src/panel/routes/ConnectButton/WalletConnect.tsx b/extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnectConnect.tsx similarity index 76% rename from extension/src/panel/routes/ConnectButton/WalletConnect.tsx rename to extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnectConnect.tsx index 54a81ceb..d7c645de 100644 --- a/extension/src/panel/routes/ConnectButton/WalletConnect.tsx +++ b/extension/src/panel/routes/Edit/wallet/walletConnect/WalletConnectConnect.tsx @@ -1,16 +1,19 @@ import { Button } from '@/components' +import { useWalletConnect } from '@/providers' +import { ProviderType } from '@/types' import { invariant } from '@epic-web/invariant' import { ChainId } from 'ser-kit' -import { ProviderType } from '../../../types' -import { useWalletConnect } from '../../providers' -import { ProviderLogo } from './ProviderLogo' +import { ProviderLogo } from '../providerLogo' type WalletConnectProps = { routeId: string onConnect: (chainId: ChainId, account: string) => void } -export const WalletConnect = ({ routeId, onConnect }: WalletConnectProps) => { +export const WalletConnectConnect = ({ + routeId, + onConnect, +}: WalletConnectProps) => { const walletConnect = useWalletConnect(routeId) return ( diff --git a/extension/src/panel/routes/Edit/wallet/walletConnect/index.ts b/extension/src/panel/routes/Edit/wallet/walletConnect/index.ts new file mode 100644 index 00000000..981ad23d --- /dev/null +++ b/extension/src/panel/routes/Edit/wallet/walletConnect/index.ts @@ -0,0 +1,2 @@ +export { WalletConnect } from './WalletConnect' +export { WalletConnectConnect } from './WalletConnectConnect' diff --git a/extension/src/panel/routes/routeHooks.tsx b/extension/src/panel/routes/routeHooks.tsx index 008623ee..de8a18ae 100644 --- a/extension/src/panel/routes/routeHooks.tsx +++ b/extension/src/panel/routes/routeHooks.tsx @@ -1,3 +1,4 @@ +import { invariant } from '@epic-web/invariant' import { ZeroAddress } from 'ethers' import { nanoid } from 'nanoid' import React, { @@ -110,6 +111,15 @@ export const useSelectedRouteId = () => { return result } +export const getChainId = (address: PrefixedAddress) => { + // atm, we don't yet support cross-chain routes, so can derive a general chainId from the avatar + const [chainId] = parsePrefixedAddress(address) + + invariant(chainId != null, 'chainId is empty') + + return chainId +} + export const useRoute = (id?: string) => { const [routes] = useRoutes() const [selectedRouteId] = useSelectedRouteId() @@ -119,11 +129,7 @@ export const useRoute = (id?: string) => { routes[0] || INITIAL_DEFAULT_ROUTE - // atm, we don't yet support cross-chain routes, so can derive a general chainId from the avatar - const [chainId] = parsePrefixedAddress(route.avatar) - if (!chainId) { - throw new Error('chainId is empty') - } + const chainId = getChainId(route.avatar) const injectedWallet = useInjectedWallet() const walletConnect = useWalletConnect(route.id) @@ -135,7 +141,7 @@ export const useRoute = (id?: string) => { : walletConnect?.provider) || defaultProvider const connected = - route.initiator && + route.initiator != null && isConnectedTo( route.providerType === ProviderType.InjectedWallet ? injectedWallet diff --git a/extension/tsconfig.json b/extension/tsconfig.json index 18c09642..57c81616 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -20,6 +20,9 @@ "paths": { "@/components": ["./src/components/index.ts"], "@/utils": ["./src/utils/index.ts"], + "@/types": ["./src/types.ts"], + "@/chains": ["./src/chains.ts"], + "@/providers": ["./src/panel/providers/index.ts"], "@/e2e-utils": ["./e2e/utils/index.ts"] } },