diff --git a/src/components/base/WalletConnection.tsx b/src/components/base/WalletConnection.tsx index c43fce6..f1372ea 100644 --- a/src/components/base/WalletConnection.tsx +++ b/src/components/base/WalletConnection.tsx @@ -40,7 +40,7 @@ const WalletConnection: React.FC<{ onClick={connectAeternityWallet} {...buttonProps} > - Connect SuperHero Wallet + Connect æternity Wallet , ); } diff --git a/src/components/navigation/AEWalletSelect.tsx b/src/components/navigation/AEWalletSelect.tsx new file mode 100644 index 0000000..1097def --- /dev/null +++ b/src/components/navigation/AEWalletSelect.tsx @@ -0,0 +1,87 @@ +import { Box, Button, Modal, Typography } from '@mui/material'; + +import * as Aeternity from 'src/services/aeternity'; +import useWalletContext from 'src/hooks/useWalletContext'; +import SuperHeroIcon from 'src/components/base/icons/superhero'; +import MetaMaskIcon from 'src/components/base/icons/metamask'; +import { SUPERHERO_WALLET_URL } from 'src/constants'; +import Logger from 'src/services/logger'; + +const style = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, +}; + +const AEWalletSelect = () => { + const { + showAeWalletSelect, + setShowAeWalletSelect, + aeternityWalletDetected, + ethereumWalletDetected, + setConnecting, + setAeternityAddress, + handleWalletConnectError, + } = useWalletContext(); + + const handleConnectButtonClick = async (wallet: 'metamask' | 'superhero') => { + try { + setConnecting(true); + const address = await Aeternity.connect(wallet, (accounts) => { + if (accounts.length > 0) { + setAeternityAddress(accounts[0].address); + } + }); + setAeternityAddress(address); + } catch (e) { + Logger.error(e); + handleWalletConnectError((e as Error).message); + } finally { + setConnecting(false); + setShowAeWalletSelect(false); + } + }; + + return ( + setShowAeWalletSelect(false)}> + + + Select a wallet + + Please select a wallet to connect to the application: + + {aeternityWalletDetected && ( + + )} + {ethereumWalletDetected && ( + + )} + {!aeternityWalletDetected && ( + + )} + + + + ); +}; + +export default AEWalletSelect; diff --git a/src/context/WalletContext.ts b/src/context/WalletContext.ts index 0ca0625..b76cbac 100644 --- a/src/context/WalletContext.ts +++ b/src/context/WalletContext.ts @@ -7,6 +7,13 @@ export interface IWalletContext { connectEthereumWallet: () => Promise; connectAeternityWallet: () => Promise; disconnectWallet: () => void; + showAeWalletSelect: boolean; + setShowAeWalletSelect: (show: boolean) => void; + aeternityWalletDetected: boolean; + ethereumWalletDetected: boolean; + setConnecting: (connecting: boolean) => void; + setAeternityAddress: (address: string) => void; + handleWalletConnectError: (message: string) => void; } const contextStub = { @@ -20,6 +27,19 @@ const contextStub = { disconnectWallet: async () => { // stub }, + showAeWalletSelect: false, + setShowAeWalletSelect: (show: boolean) => { + // stub + }, + aeternityWalletDetected: false, + ethereumWalletDetected: false, + setConnecting: (connecting: boolean) => { + // stub + }, + setAeternityAddress: (address: string) => {}, + handleWalletConnectError: (message: string) => { + // stub + }, }; const WalletContext = createContext(contextStub); diff --git a/src/context/WalletProvider.tsx b/src/context/WalletProvider.tsx index 11727ba..d47336c 100644 --- a/src/context/WalletProvider.tsx +++ b/src/context/WalletProvider.tsx @@ -13,6 +13,7 @@ const WalletProvider: React.FC<{ children: ReactNode }> = (props) => { const [connecting, setConnecting] = useState(false); const [ethereumAddress, setEthereumAddress] = useState(undefined); const [aeternityAddress, setAeternityAddress] = useState(undefined); + const [showAeWalletSelect, setShowAeWalletSelect] = useState(false); const isEthWalletDetectionEnded = useRef(false); const isAeWalletDetectionEnded = useRef(false); @@ -43,32 +44,12 @@ const WalletProvider: React.FC<{ children: ReactNode }> = (props) => { } }); } - - if (aeternityWalletDetected.current) { - Aeternity.Sdk.onAddressChange = ({ current }) => { - setAeternityAddress(Object.keys(current)[0]); - }; - } })(); }, []); const connectAeternityWallet = useCallback(async () => { if (isAeWalletDetectionEnded.current) { - if (!aeternityWalletDetected.current) { - handleWalletConnectError('æternity wallet extension not found'); - return; - } - - try { - setConnecting(true); - const address = await Aeternity.connect(); - setAeternityAddress(address); - } catch (e) { - Logger.error(e); - handleWalletConnectError((e as Error).message); - } finally { - setConnecting(false); - } + setShowAeWalletSelect(true); } else { setTimeout(connectAeternityWallet, 100); } @@ -149,6 +130,13 @@ const WalletProvider: React.FC<{ children: ReactNode }> = (props) => { connectAeternityWallet, connectEthereumWallet, disconnectWallet, + showAeWalletSelect, + setShowAeWalletSelect, + aeternityWalletDetected: aeternityWalletDetected.current, + ethereumWalletDetected: ethereumWalletDetected.current, + setConnecting, + setAeternityAddress, + handleWalletConnectError, }} > {props.children} diff --git a/src/index.tsx b/src/index.tsx index 64b0c3d..dce6593 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,6 +8,7 @@ import Router from './Routes'; import AppProvider from './context/AppProvider'; import WalletProvider from './context/WalletProvider'; import { SnackbarProvider } from 'notistack'; +import AEWalletSelect from './components/navigation/AEWalletSelect'; const container = document.getElementById('root'); if (!container) { @@ -24,6 +25,7 @@ const App = () => ( + diff --git a/src/services/aeternity.ts b/src/services/aeternity.ts index f5c54bb..2c56c8f 100644 --- a/src/services/aeternity.ts +++ b/src/services/aeternity.ts @@ -4,50 +4,56 @@ import { SUBSCRIPTION_TYPES, walletDetector, isAddressValid, - AeSdkAepp, + AeSdk, Contract, + AccountMetamaskFactory, + WalletConnectorFrame, + AccountBase, } from '@aeternity/aepp-sdk'; +type Wallet = Parameters[1]>[0]['newWallet']; import Constants from 'src/constants'; -export const Sdk = new AeSdkAepp({ +let connector: WalletConnectorFrame; + +export const Sdk = new AeSdk({ nodes: [{ name: Constants.isMainnet ? 'mainnet' : 'testnet', instance: new Node(Constants.aeternity.rpc) }], - name: 'Bridge Aepp', - onNetworkChange: async ({ networkId }) => { - const [{ name }] = (await Sdk.getNodesInPool()).filter((node) => node.nodeNetworkId === networkId); - Sdk.selectNode(name); - console.log('setNetworkId', networkId); - }, - onAddressChange: ({ current }) => console.log(Object.keys(current)[0]), - onDisconnect: () => console.log('Aepp is disconnected'), }); -export const connect = async (): Promise => { - return new Promise((resolve, reject) => { - const handleWallets = async ({ wallets, newWallet }: any) => { - try { - walletDetectionTimeout && clearTimeout(walletDetectionTimeout); - newWallet ||= Object.values(wallets)[0]; - if (newWallet) { - const walletInfo = await Sdk.connectToWallet(newWallet.getConnection()); - const { - address: { current }, - } = await Sdk.subscribeAddress(SUBSCRIPTION_TYPES.subscribe, 'connected'); - const address = Object.keys(current)[0]; - console.log(walletInfo, current); - resolve(address); - } +export const connect = async ( + wallet: 'metamask' | 'superhero', + onAccountChange?: (accounts: AccountBase[]) => void, +): Promise => { + if (wallet === 'metamask') { + const factory = new AccountMetamaskFactory(); + await factory.installSnap(); + + Sdk.addAccount(await factory.initialize(0), { select: true }); + console.log('Metamask account', Sdk.address); + return Sdk.address; + } else { + const wallet = await new Promise((resolveWallet) => { + const scannerConnection = new BrowserWindowMessageConnection(); + const stopScan = walletDetector(scannerConnection, ({ newWallet }) => { + resolveWallet(newWallet); stopScan(); - reject(); - } catch (e) { - reject(e); - } - }; + }); + }); + connector = await WalletConnectorFrame.connect('Bridge Aepp', wallet.getConnection()); - const scannerConnection = new BrowserWindowMessageConnection(); - const walletDetectionTimeout = setTimeout(() => reject(new Error('æternity wallet extension not found')), 5000); - const stopScan = walletDetector(scannerConnection, handleWallets); - }); + return new Promise(async (resolveConnect) => { + connector.addListener('accountsChange', async (accounts: AccountBase[]) => { + Sdk.addAccount(accounts[0], { select: true }); + onAccountChange && onAccountChange(accounts); + resolveConnect(Sdk.address); + }); + connector.addListener('networkIdChange', async (networkId: string) => { + Sdk.selectNode(networkId); + }); + connector.addListener('disconnect', () => alert('Aepp is disconnected')); + await connector.subscribeAccounts(SUBSCRIPTION_TYPES.subscribe, 'current'); + }); + } }; export const detectWallet = async (): Promise => { @@ -77,4 +83,4 @@ export const initializeContract = async (options: { }); }; -export { isAddressValid, Contract }; +export { isAddressValid, Contract, connector };