Skip to content

Commit

Permalink
feat: support aeternity MM snap #73
Browse files Browse the repository at this point in the history
  • Loading branch information
yusufseyrek committed Nov 30, 2024
1 parent c1ccd7d commit d9ccf50
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/components/base/WalletConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const WalletConnection: React.FC<{
onClick={connectAeternityWallet}
{...buttonProps}
>
Connect SuperHero Wallet
Connect æternity Wallet
</Button>,
);
}
Expand Down
87 changes: 87 additions & 0 deletions src/components/navigation/AEWalletSelect.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Modal open={showAeWalletSelect} onClose={() => setShowAeWalletSelect(false)}>
<Box sx={style}>
<Typography variant="h6" component="h2">
Select a wallet
</Typography>
<Typography sx={{ my: 2 }}>Please select a wallet to connect to the application:</Typography>
<Box display={'flex'} flexDirection={'column'} gap={1}>
{aeternityWalletDetected && (
<Button variant="text" fullWidth={true} onClick={() => handleConnectButtonClick('superhero')}>
<SuperHeroIcon width={32} height={32} style={{ marginRight: 5 }} />
Superhero Wallet
</Button>
)}
{ethereumWalletDetected && (
<Button variant="text" fullWidth={true} onClick={() => handleConnectButtonClick('metamask')}>
<MetaMaskIcon width={32} height={32} style={{ marginRight: 5 }} />
Metamask Wallet
</Button>
)}
{!aeternityWalletDetected && (
<Button
variant="text"
fullWidth={true}
onClick={() => (window as any).open(SUPERHERO_WALLET_URL, '_blank').focus()}
>
<SuperHeroIcon width={32} height={32} style={{ marginRight: 5 }} />
Install Superhero Wallet
</Button>
)}
</Box>
</Box>
</Modal>
);
};

export default AEWalletSelect;
20 changes: 20 additions & 0 deletions src/context/WalletContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export interface IWalletContext {
connectEthereumWallet: () => Promise<void>;
connectAeternityWallet: () => Promise<void>;
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 = {
Expand All @@ -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<IWalletContext>(contextStub);
Expand Down
30 changes: 9 additions & 21 deletions src/context/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const WalletProvider: React.FC<{ children: ReactNode }> = (props) => {
const [connecting, setConnecting] = useState(false);
const [ethereumAddress, setEthereumAddress] = useState<string | undefined>(undefined);
const [aeternityAddress, setAeternityAddress] = useState<string | undefined>(undefined);
const [showAeWalletSelect, setShowAeWalletSelect] = useState(false);

const isEthWalletDetectionEnded = useRef<boolean>(false);
const isAeWalletDetectionEnded = useRef<boolean>(false);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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}
Expand Down
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -24,6 +25,7 @@ const App = () => (
<SnackbarProvider classes={{ containerRoot: 'snackRoot' }}>
<WalletProvider>
<AppProvider>
<AEWalletSelect />
<Router />
</AppProvider>
</WalletProvider>
Expand Down
76 changes: 41 additions & 35 deletions src/services/aeternity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,56 @@ import {
SUBSCRIPTION_TYPES,
walletDetector,
isAddressValid,
AeSdkAepp,
AeSdk,
Contract,
AccountMetamaskFactory,
WalletConnectorFrame,
AccountBase,
} from '@aeternity/aepp-sdk';
type Wallet = Parameters<Parameters<typeof walletDetector>[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<string> => {
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<string> => {
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<Wallet>((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<boolean> => {
Expand Down Expand Up @@ -77,4 +83,4 @@ export const initializeContract = async (options: {
});
};

export { isAddressValid, Contract };
export { isAddressValid, Contract, connector };

0 comments on commit d9ccf50

Please sign in to comment.