diff --git a/package.json b/package.json index 161ea1a1..92bc0063 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "@emotion/react": "^11.7.1", "@emotion/server": "^11.4.0", "@emotion/styled": "^11.6.0", + "@mimirdev/apps-inject": "^0.3.0", + "@mimirdev/apps-sdk": "^0.2.0", "@mui/icons-material": "^5.2.5", "@mui/lab": "5.0.0-alpha.134", "@mui/material": "^5.15.14", @@ -80,4 +82,4 @@ "typescript": "^4.7.4", "webpack": "^5.81.0" } -} \ No newline at end of file +} diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index f4c1e2e2..f40ea4d4 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -9,6 +9,7 @@ import { Address, NetworkSelector, TxHistoryModal } from '@/components'; import { useAccounts } from '@/contexts/account'; import styles from './index.module.scss'; +import { WalletModal } from '@/components/Wallet'; export const Header = () => { const theme = useTheme(); @@ -21,6 +22,8 @@ export const Header = () => { const [txHistoryModalOpen, openTxHistoryModal] = useState(false); + const [walletModalOpen, openWalletModal] = useState(false); + const onDisconnect = () => { disconnectWallet(); }; @@ -87,7 +90,7 @@ export const Header = () => { ) : ( - )} @@ -102,6 +105,12 @@ export const Header = () => { ) : ( <> )} + {walletModalOpen && + openWalletModal(false)} + /> + } ); }; diff --git a/src/components/Mimir/hook.tsx b/src/components/Mimir/hook.tsx new file mode 100644 index 00000000..2201c733 --- /dev/null +++ b/src/components/Mimir/hook.tsx @@ -0,0 +1,8 @@ +import { useEffect } from "react"; +import { tryInitMimir } from "."; + +export default function useInitMimir() { + useEffect(() => { + tryInitMimir(); + }, []); +} \ No newline at end of file diff --git a/src/components/Mimir/index.tsx b/src/components/Mimir/index.tsx new file mode 100644 index 00000000..46678c0d --- /dev/null +++ b/src/components/Mimir/index.tsx @@ -0,0 +1,30 @@ + +import { inject, isMimirReady, MIMIR_REGEXP } from "@mimirdev/apps-inject"; + +export async function tryInitMimir() { + if (typeof window === "undefined") { + return; + } + + const openInIframe = window !== window.parent; + console.log(openInIframe); + + if (!openInIframe) { + return; + } + + const origin = await isMimirReady(); + + if (!origin) { + return; + } + + // check is mimir url + if (!MIMIR_REGEXP.test(origin)) { + return; + } + + + // inject to window.injectedWeb3.mimir + inject(); +} diff --git a/src/components/Wallet/index.module.scss b/src/components/Wallet/index.module.scss new file mode 100644 index 00000000..d7925074 --- /dev/null +++ b/src/components/Wallet/index.module.scss @@ -0,0 +1,11 @@ +.logo { + margin: 0.5em; +} + +.container { + display: flex; + flex-direction: column; + gap: 1.55rem; + padding: 1.25rem; + width: 20rem; +} \ No newline at end of file diff --git a/src/components/Wallet/index.tsx b/src/components/Wallet/index.tsx new file mode 100644 index 00000000..450c2004 --- /dev/null +++ b/src/components/Wallet/index.tsx @@ -0,0 +1,74 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + List, + ListItemButton, + Typography, +} from '@mui/material'; +import Image from 'next/image'; + +import styles from './index.module.scss'; +import { SubstrateWallet, enableWallet, isWalletInstalled, polkadotjs, subwallet, talisman } from './wallets'; +import theme from '@/utils/muiTheme'; +import { OpenInNew } from '@mui/icons-material'; +import { useAccounts } from '@/contexts/account'; +import { APP_NAME } from '@/consts'; + +interface WalletModalProps { + open: boolean; + onClose: () => void; + +} + +export const allSubstrateWallets: SubstrateWallet[] = [ + subwallet, + talisman, + polkadotjs, +]; + +export const WalletModal = (props: WalletModalProps) => { + + const { connectWallet } = useAccounts(); + const onConnect = async (wallet: SubstrateWallet) => { + const injectedExstension = await enableWallet(wallet, APP_NAME); + if (!injectedExstension) { + console.log("Failed to enable wallet"); + return; + } + connectWallet(injectedExstension); + props.onClose(); + }; + + return ( + + + + Choose Wallet + + + {allSubstrateWallets.map((wallet, index) => ( + onConnect(wallet)} + disabled={!isWalletInstalled(wallet)} + > + logo + {wallet.name} + + ))} + + + + + + + ); +}; diff --git a/src/components/Wallet/wallets.ts b/src/components/Wallet/wallets.ts new file mode 100644 index 00000000..db991630 --- /dev/null +++ b/src/components/Wallet/wallets.ts @@ -0,0 +1,95 @@ +import { InjectedExtension, InjectedWindow } from "@polkadot/extension-inject/types"; + +export type SubstrateWallet = { + id: string; + name: string; + urls: { + website: string; + chromeExtension: string; + firefoxExtension: string; + }, + logoUrls: Array; +} + +export const polkadotjs: SubstrateWallet = { + id: 'polkadot-js', + name: 'Polkadot{.js}', + urls: { + website: 'https://polkadot.js.org/extension/', + chromeExtension: + 'https://chrome.google.com/webstore/detail/polkadot%7Bjs%7D-extension/mopnmbcafieddcagagdcbnhejhlodfdd', + firefoxExtension: 'https://addons.mozilla.org/en-US/firefox/addon/polkadot-js-extension/', + }, + logoUrls: [ + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/polkadot@128w.png', + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/polkadot@512w.png', + ], +} + +export const subwallet: SubstrateWallet = { + id: 'subwallet-js', + name: 'SubWallet', + urls: { + website: 'https://subwallet.app/', + chromeExtension: + 'https://chrome.google.com/webstore/detail/subwallet-polkadot-extens/onhogfjeacnfoofkfgppdlbmlmnplgbn', + firefoxExtension: 'https://addons.mozilla.org/en-US/firefox/addon/subwallet/', + }, + logoUrls: [ + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/subwallet@128w.png', + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/subwallet@512w.png', + ], +} + +export const talisman: SubstrateWallet = { + id: 'talisman', + name: 'Talisman', + urls: { + website: 'https://www.talisman.xyz/', + chromeExtension: + 'https://chrome.google.com/webstore/detail/talisman-polkadot-wallet/fijngjgcjhjmmpcmkeiomlglpeiijkld', + firefoxExtension: 'https://addons.mozilla.org/en-US/firefox/addon/talisman-wallet-extension/', + }, + logoUrls: [ + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/talisman@128w.png', + 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/talisman@512w.png', + ], +} + +export const isWalletInstalled = (wallet: SubstrateWallet) => { + try { + if (typeof window === 'undefined') return undefined + const injectedWindow = window as Window & InjectedWindow + const injectedExtension = injectedWindow?.injectedWeb3?.[wallet.id] + + return !!injectedExtension + } catch (e) { + return undefined + } +} + +export const enableWallet = async (wallet: SubstrateWallet, appName: string) => { + if (!isWalletInstalled(wallet)) return undefined + + try { + if (typeof window === 'undefined') return undefined + // @ts-ignore + const injectedWindow = window as InjectedWindow; + + const injectedWindowProvider = + injectedWindow?.injectedWeb3?.[wallet.id] + if (!injectedWindowProvider?.enable) + throw new Error('No according `InjectedWindowProvider` found.') + + const injected = await injectedWindowProvider.enable(appName); + const injectedExtension: InjectedExtension = { + ...injected, + name: wallet.id, + version: injectedWindowProvider.version || '', + } + return injectedExtension; + } catch (e) { + console.error('Error while enabling wallet', e) + return undefined + } +} diff --git a/src/contexts/account/index.tsx b/src/contexts/account/index.tsx index 68797d90..542c72df 100644 --- a/src/contexts/account/index.tsx +++ b/src/contexts/account/index.tsx @@ -1,14 +1,22 @@ import type { Signer } from '@polkadot/api/types'; -import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types'; +import { InjectedAccount, InjectedAccountWithMeta, InjectedAccounts, InjectedExtension } from '@polkadot/extension-inject/types'; import React, { createContext, useContext, useEffect, useReducer } from 'react'; import { isValidAddress } from '@/utils/functions'; import { APP_NAME } from '@/consts'; +import useInitMimir from '@/components/Mimir/hook'; const LOCAL_STORAGE_ACCOUNTS = 'accounts'; const LOCAL_STORAGE_ACTIVE_ACCOUNT = 'active-account'; +export enum WalletTypes { + TALISMAN = 'talisman', + POLKADOTJS = 'polkadot-js', + SUBWALLET = 'subwallet', + MIMIR = 'mimir', +} + export enum KeyringState { // eslint-disable-next-line no-unused-vars DISCONNECTED = 'disconnected', @@ -67,7 +75,7 @@ const reducer = (state: State, action: any) => { interface AccountData { state: State; setActiveAccount: (_acct: InjectedAccountWithMeta | null) => void; - connectWallet: () => void; + connectWallet: (injectedExtension: InjectedExtension) => void; disconnectWallet: () => void; } @@ -88,19 +96,19 @@ const AccountDataContext = createContext(defaultAccountData); const AccountProvider = ({ children }: Props) => { const [state, dispatch] = useReducer(reducer, initialState); + useInitMimir(); const setActiveAccount = (acct: InjectedAccountWithMeta | null) => { localStorage.setItem(LOCAL_STORAGE_ACTIVE_ACCOUNT, acct?.address || ''); dispatch({ type: 'SET_ACTIVE_ACCOUNT', payload: acct }); }; - const connectWallet = () => { + const connectWallet = (injectedExtension: InjectedExtension) => { const asyncLoadAccounts = async () => { try { dispatch({ type: 'LOAD_KEYRING' }); - const extensionDapp = await import('@polkadot/extension-dapp'); - const { web3Accounts } = extensionDapp; - const accounts: InjectedAccountWithMeta[] = await web3Accounts(); + const accounts: InjectedAccounts = injectedExtension.accounts; + console.log(injectedExtension); dispatch({ type: 'KEYRING_READY' }); dispatch({ type: 'SET_ACCOUNTS', payload: accounts }); } catch (e) { @@ -138,6 +146,7 @@ const AccountProvider = ({ children }: Props) => { try { const { web3FromSource } = await import('@polkadot/extension-dapp'); const injector = await web3FromSource(activeAccount.meta.source); + console.log(activeAccount.meta.source); dispatch({ type: 'SET_ACTIVE_SIGNER', payload: injector.signer }); } catch (e) { /** */