Created
September 8, 2023 08:57
-
-
Save Kais3rP/23d81bf762d707979ea913a55ec83e8c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useRef, useState } from 'react'; | |
import { useBlogStore, useBlogUIStore } from 'store'; | |
import { calculateRelativePosition, shortenAddress } from 'utils'; | |
import { | |
useAccount, | |
useEnsName, | |
useEnsAvatar, | |
useConnect, | |
useDisconnect, | |
useNetwork, | |
useSignMessage, | |
useSwitchNetwork, | |
} from 'wagmi'; | |
import { SiweMessage } from 'siwe'; | |
import jwtDecode from 'jwt-decode'; | |
import { getData, postJSONData } from 'libs/fetch'; | |
import { siweMessage } from 'data/web3'; | |
export const useWeb3Login = ({ elementContainer }) => { | |
const [isModalVisible, setIsModalVisible] = useState(false); | |
const modalWalletsRef = useRef(null); | |
/* const { setIsFirstConnectAuthentication, isFirstConnectAuthentication } = | |
useGlobalStore(); */ | |
const { updateWeb3User, removeWeb3User, updateModal } = useBlogStore(); | |
const { setCurrentConfetti } = useBlogUIStore(); | |
/* Get the current chain */ | |
const { chain } = useNetwork(); | |
/* Get the Wallet connector and current active address of the connected account */ | |
const { connector: activeConnector, address } = useAccount({ | |
async onConnect({ address, connector, isReconnected }) { | |
/* This is triggered everytime a route changes and the Layout is rerendered hence not very useless using NextJS */ | |
}, | |
}); | |
/* Handle chain switch */ | |
/* If the user is not on Ethereum network on connect it's prompted to change network */ | |
const { | |
chains, | |
// isLoading: isLoadingChains, | |
// pendingChainId, | |
switchNetwork, | |
} = useSwitchNetwork({ | |
throwForSwitchChainNotSupported: true, | |
chainId: 1, | |
onMutate() { | |
/* Sign message on chain change if it's Ethereum chain */ | |
console.log('Chain mutate', address, chain); | |
if (chain?.id === 1) { | |
// signMessage({ address, chainId: chain?.id }); | |
updateModal({ | |
title: 'Network', | |
body: `You are on Ethereum mainnet`, | |
type: 'ok', | |
}); | |
} else { | |
// switchNetwork?.(1); | |
updateModal({ | |
title: 'Network', | |
body: `You are not on Ethereum mainnet`, | |
type: 'warn', | |
}); | |
} | |
}, | |
onSuccess() { | |
console.log('Chain changed', address, chain); | |
if (chain?.id === 1) { | |
signMessage({ address, chainId: chain?.id }); | |
updateModal({ | |
title: 'Network', | |
body: `You are on Ethereum mainnet`, | |
type: 'ok', | |
}); | |
} | |
}, | |
onError(e) { | |
console.log('Chain error', e); | |
handleWeb3Logout(); | |
}, | |
}); | |
/* Connect / Disconnect */ | |
const { connectAsync, connectors, isLoading, pendingConnector } = | |
useConnect({ | |
onError() { | |
removeWeb3User(); | |
updateModal({ | |
title: 'Connect', | |
body: `Connection error, please refresh the page or try with a different wallet`, | |
type: 'error', | |
}); | |
}, | |
onSuccess(data) { | |
if (chain?.id === 1) | |
signMessage({ address, chainId: chain?.id }); | |
else switchNetwork?.(1); | |
updateWeb3User({ address: data.account }); | |
// This is the Global modal | |
updateModal({ | |
title: 'Connect', | |
body: `You connected your wallet ${shortenAddress( | |
data.account | |
)}`, | |
type: 'ok', | |
}); | |
/* setCurrentConfetti({ | |
id: 'web3-login-confetti', | |
position: calculateRelativePosition(elementContainer), | |
}); */ | |
}, | |
}); | |
const { disconnect } = useDisconnect({ | |
onError() { | |
updateModal({ | |
title: 'Disconnect', | |
body: `Disconnection error, please disconnect your wallet manually`, | |
type: 'error', | |
}); | |
}, | |
onSuccess() { | |
removeWeb3User(); | |
updateModal({ | |
title: 'Disconnect', | |
body: `You disconnected your wallet`, | |
type: 'ok', | |
}); | |
}, | |
}); | |
/* Sign a message */ | |
const { | |
data: signData, | |
isLoading: isSignLoading, | |
signMessageAsync, | |
} = useSignMessage({ | |
onError(e) { | |
updateWeb3User({ signed: false, signData: null }); | |
disconnect(); | |
updateModal({ | |
title: 'Sign with Ethereum', | |
body: `You need to sign in order to authenticate`, | |
type: 'error', | |
}); | |
}, | |
onSuccess(data) { | |
updateWeb3User({ signed: true, signData: data }); | |
// This is the Global modal | |
updateModal({ | |
title: 'Sign with Ethereum', | |
body: `Signature received correctly`, | |
type: 'ok', | |
}); | |
// This is the Web3 Wallets Connectors selection modal | |
hideModal(); | |
setCurrentConfetti({ | |
id: 'web3-login-confetti', | |
position: calculateRelativePosition(elementContainer), | |
}); | |
}, | |
}); | |
/* Get ENS name */ | |
const { data: ensName } = useEnsName({ | |
address, | |
onError(e) { | |
updateWeb3User({ ensName: null }); | |
// TO-DO fix the retrigger of this hook on each new render | |
/* updateModal({ | |
title: 'ENS', | |
body: `Couldn't retrieve an ENS name for this address, check that you are currently on the right network.`, | |
type: 'warn', | |
}); */ | |
}, | |
onSuccess(data) { | |
updateWeb3User({ ensName: data }); | |
// This is the Global modal | |
updateModal({ | |
title: 'ENS', | |
body: `ENS name retrieved ${data}`, | |
type: 'ok', | |
}); | |
// This is the Web3 Wallets Connectors selection modal | |
hideModal(); | |
setCurrentConfetti({ | |
id: 'web3-login-confetti', | |
position: calculateRelativePosition(elementContainer), | |
}); | |
}, | |
}); | |
/* Get ENS Avatar */ | |
const { data: ensAvatar } = useEnsAvatar({ | |
ensName, | |
onError(e) { | |
updateWeb3User({ ensAvatar: null }); | |
updateModal({ | |
title: 'ENS', | |
body: `Couldn't retrieve an ENS Avatar for this address, check that you are currently on the right network`, | |
type: 'warn', | |
}); | |
}, | |
onSuccess(data) { | |
updateWeb3User({ ensAvatar: data }); | |
// This is the Global modal | |
updateModal({ | |
title: 'ENS', | |
body: `ENS Avatar retrieved ${data}`, | |
type: 'ok', | |
}); | |
// This is the Web3 Wallets Connectors selection modal | |
hideModal(); | |
setCurrentConfetti({ | |
id: 'web3-login-confetti', | |
position: calculateRelativePosition(elementContainer), | |
}); | |
}, | |
}); | |
/* Handlers */ | |
const signMessage = useCallback( | |
async ({ address, chainId }) => { | |
const nonce = await getData('/api/v2/web3/generate-nonce'); | |
// console.log(address, chainId, nonce); | |
/* Now send the sign message */ | |
const message = new SiweMessage({ | |
domain: window.location.host, | |
address, | |
statement: siweMessage, | |
uri: window.location.origin, | |
version: '1', | |
chainId, | |
nonce, // Retrieved on first load | |
}); | |
console.log(message); | |
const signature = await signMessageAsync({ | |
message: message.prepareMessage(), | |
}); | |
// console.log('Signature:', signature); | |
// Verify signature | |
await postJSONData( | |
'/api/v2/web3/verify-signature', | |
JSON.stringify({ message, signature }) | |
); | |
// console.log(verifyRes); | |
updateModal({ | |
title: 'Sign', | |
body: 'The signature was verified correctly', | |
type: 'ok', | |
}); | |
}, | |
[signMessageAsync, updateModal] | |
); | |
const handleWeb3Login = useCallback( | |
async (connector) => { | |
try { | |
/* First step connect and retrieve address and chain */ | |
await connectAsync({ connector }); | |
/* Handle the rest of the procedure, the signature method, directly in the onConnect callback of useAccount */ | |
} catch (e) { | |
console.log(e.name); | |
updateModal({ | |
title: 'Error', | |
body: | |
e.name === 'ResourceNotFoundRpcError' | |
? 'You already have a pending connection request in your wallet' | |
: e.name, | |
type: 'error', | |
}); | |
// handleWeb3Logout(); | |
} | |
}, | |
[connectAsync, updateModal] | |
); | |
const handleWeb3Logout = useCallback(async () => { | |
try { | |
disconnect(); // Disconnects from the wallet | |
/* Destroy the SIWE session */ | |
await getData('/api/v2/session/logout'); | |
} catch (e) { | |
updateModal({ | |
title: 'Error', | |
body: e, | |
type: 'error', | |
}); | |
} | |
}, [disconnect, updateModal]); | |
/* UI Handlers */ | |
const showModal = () => setIsModalVisible(true); | |
const hideModal = () => setIsModalVisible(false); | |
return { | |
connectAsync, | |
disconnect, | |
handleWeb3Login, | |
handleWeb3Logout, | |
showModal, | |
hideModal, | |
connectors, | |
isLoading, | |
pendingConnector, | |
activeConnector, | |
isModalVisible, | |
address, | |
ensName, | |
ensAvatar, | |
chain, | |
chains, | |
signData, | |
isSignLoading, | |
modalWalletsRef, | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useEffect, useRef, useState } from 'react'; | |
import { useBlogUIStore, useBlogStore } from 'store'; | |
import GenericButton from 'components/shared/Buttons/GenericButton'; | |
import { useWeb3Login } from 'hooks/authentication'; | |
import { useOnClickOutside } from 'hooks'; | |
import metaMaskLogo from 'assets/metamask.svg'; | |
import coinbaseLogo from 'assets/coinbase.svg'; | |
import ledgerLogo from 'assets/ledger.svg'; | |
import safeWalletLogo from 'assets/safe-wallet.svg'; | |
import genericLogo from 'assets/generic-wallet.svg'; | |
import { shortenAddress } from 'utils'; | |
import { MdOutlineKeyboardArrowDown } from 'react-icons/md'; | |
import LoadingSquares from 'components/shared/Spinners/LoadingSquares'; | |
import Link from 'next/link'; | |
const Web3Login = () => { | |
const loginRef = useRef(null); | |
const buttonModalRef = useRef(null); | |
const [isButtonModalVisible, setisButtonModalVisible] = useState(false); | |
/* Stores */ | |
const { web3User, updateModal } = useBlogStore(); | |
const { isDarkMode } = useBlogUIStore(); | |
/* Web3 Login */ | |
const { | |
handleWeb3Login, | |
handleWeb3Logout, | |
showModal, | |
hideModal, | |
connectors, | |
isLoading, | |
pendingConnector, | |
activeConnector, | |
isModalVisible, | |
isSignLoading, | |
modalWalletsRef, | |
} = useWeb3Login({ elementContainer: loginRef.current }); // The element container is used to show the confetti animation | |
/* ButtonModal is the dropdown that contains info about the connected wallet. It's showed when clicking on the login button */ | |
const showButtonModal = () => setisButtonModalVisible(true); | |
const hideButtonModal = () => setisButtonModalVisible(false); | |
/* Close the wallets modal and diconnect on click outside during the login attempt */ | |
useOnClickOutside(modalWalletsRef, () => { | |
hideModal(); | |
if (activeConnector && isModalVisible) handleWeb3Logout(); | |
}); | |
/* Close the button modal on click outside */ | |
// useOnClickOutside(buttonModalRef, hideButtonModal); | |
/* Close the button modal if the user disconnects */ | |
useEffect(() => { | |
if (!web3User) hideButtonModal(); | |
}, [web3User]); | |
/* Handle the connect click */ | |
const handleConnectButtonClick = useCallback(() => { | |
// if (isSignLoading) return; | |
if (!activeConnector) showModal(); | |
else { | |
if (isButtonModalVisible) hideButtonModal(); | |
else showButtonModal(); | |
} | |
}, [activeConnector, isButtonModalVisible, showModal]); | |
return ( | |
<div className="max-w-[215px] relative z-10 flex flex-col"> | |
<GenericButton | |
ref={loginRef} | |
isDarkMode={isDarkMode} | |
theme={{ | |
primary: 'var(--bg-light)', | |
secondary: 'var(--blue1)', | |
}} | |
className={`relative text-xs flex justify-center items-center whitespace-nowrap`} | |
onPointerUp={handleConnectButtonClick} | |
> | |
{!activeConnector ? ( | |
'Connect with Ethereum' | |
) : isSignLoading || isLoading ? ( | |
'Check your wallet...' | |
) : ( | |
<div className="pr-3"> | |
<h5> | |
{web3User?.ensName || | |
shortenAddress(web3User?.address)} | |
</h5>{' '} | |
<div | |
style={{ | |
transform: isButtonModalVisible | |
? 'rotateZ(180deg) translateY(50%)' | |
: ' translateY(-50%)', | |
}} | |
className="absolute top-[50%] right-[10px] cursor-pointer transition-all text-xl" | |
> | |
<MdOutlineKeyboardArrowDown /> | |
</div> | |
</div> | |
)} | |
</GenericButton> | |
{/* Drop down */} | |
<div | |
style={{ | |
height: isButtonModalVisible ? '100%' : '0px', | |
}} | |
ref={buttonModalRef} | |
className={`w-[305px] overflow-hidden transition-all`} | |
> | |
<div | |
className={`relative min-h-[97px] rounded-[10px] transition-all p-2`} | |
> | |
<div | |
className={`${ | |
isDarkMode | |
? 'bg-bgLight text-colorLight' | |
: 'bg-bgDark text-colorDark' | |
} w-[40px] h-[40px] rounded-full flex justify-center items-center float-left text-[0.5rem] overflow-hidden`} | |
> | |
{web3User?.ensAvatar ? ( | |
<img | |
className="" | |
alt="avatar" | |
src={web3User?.ensAvatar} | |
/> | |
) : ( | |
<h5 className="w-[80%] text-ellipsis overflow-hidden text-center"> | |
{shortenAddress(web3User?.address, 'short')} | |
</h5> | |
)} | |
</div> | |
<h5 className="w-full flex pl-2 text-xs"> | |
Address: | |
<i className="ml-1"> | |
{shortenAddress(web3User?.address, 'long')} | |
</i> | |
</h5> | |
<h5 className="w-full flex pl-2 text-xs"> | |
ENS name:{' '} | |
{web3User?.ensName || ( | |
<a | |
className={`underline text-xs ml-1 ${ | |
isDarkMode | |
? 'hover:text-colorLight' | |
: 'hover:text-colorDark' | |
} transition-all whitespace-nowrap`} | |
target="_blank" | |
href="https://ens.domains/" | |
rel="noopener noreferrer" | |
> | |
Get one here | |
</a> | |
)} | |
</h5> | |
<button | |
onPointerUp={handleWeb3Logout} | |
className={`underline text-xs m-1 ${ | |
isDarkMode | |
? 'hover:text-colorLight' | |
: 'hover:text-colorDark' | |
} transition-all`} | |
> | |
Disconnect | |
</button> | |
</div> | |
</div> | |
{/* Wallets selector fixed position */} | |
<div | |
style={{ | |
opacity: isModalVisible ? 1 : 0, | |
pointerEvents: isModalVisible ? 'all' : 'none', | |
'-webkit-backdrop-filter': 'blur(12px)', | |
backdropFilter: 'blur(12px)', | |
}} | |
className="w-[100vw] h-[100vh] fixed top-0 left-0 z-[5000] flex justify-center items-center" | |
> | |
{/* Wallet selector */} | |
<div | |
ref={modalWalletsRef} | |
style={{ | |
backgroundColor: isDarkMode | |
? 'var(--bg-dark)' | |
: 'var(--bg-light)', | |
boxShadow: `0px 0px 5px 1px ${ | |
isDarkMode ? 'var(--bg-light)' : 'var(--bg-dark)' | |
} | |
`, | |
transform: isModalVisible | |
? 'translateY(0px)' | |
: 'translateY(200px)', | |
opacity: isModalVisible ? 1 : 0, | |
}} | |
className="relative rounded-lg flex flex-col max-w-[350px] p-5 overflow-hidden transition-transform" | |
> | |
{/* Sign in Loading overlay */} | |
{isSignLoading && ( | |
<div | |
style={{ | |
backgroundColor: isDarkMode | |
? 'var(--bg-dark)' | |
: 'var(--bg-light)', | |
}} | |
className="absolute z-[10] w-full h-full top-0 left-0 flex justify-center items-center" | |
> | |
<div className="flex flex-col justify-center items-center"> | |
<h5>Sign in your wallet</h5> | |
<div className="m-5"> | |
<LoadingSquares | |
size={30} | |
color={ | |
isDarkMode | |
? 'var(--bg-light)' | |
: 'var(--bg-dark)' | |
} | |
/> | |
</div> | |
</div> | |
</div> | |
)} | |
<div className="w-full text-center mb-2"> | |
Select your wallet | |
</div> | |
<div className="w-full text-center mb-2"> | |
{connectors.map((connector) => ( | |
<GenericButton | |
disabled={!isModalVisible} | |
isDarkMode={isDarkMode} | |
key={connector.id} | |
onPointerUp={ | |
!connector.ready | |
? () => | |
updateModal({ | |
title: 'Error', | |
body: 'The wallet connector is not ready, try to refresh the page', | |
type: 'warn', | |
}) | |
: () => handleWeb3Login(connector) | |
} | |
className="my-1 text-sm" | |
theme={{ | |
primary: 'var(--bg-light)', | |
secondary: 'var(--blue1)', | |
}} | |
> | |
<div className="flex items-center"> | |
<div className={`w-[30px] h-[30px]`}> | |
<img | |
src={ | |
connector?.id === 'metaMask' | |
? metaMaskLogo.src | |
: connector?.id === | |
'coinbaseWallet' | |
? coinbaseLogo.src | |
: connector?.id === 'ledger' | |
? ledgerLogo.src | |
: connector?.id === 'safe' | |
? safeWalletLogo.src | |
: genericLogo.src | |
} | |
alt="wallet-logo" | |
/> | |
</div> | |
<h5 className="font-semibold ml-2"> | |
{connector?.name === 'Injected' | |
? 'Default Wallet' | |
: connector?.name} | |
</h5> | |
<div className="ml-2"> | |
{(isLoading || pendingConnector) && | |
pendingConnector?.id === | |
connector.id && ( | |
<LoadingSquares | |
size={15} | |
color={ | |
isDarkMode | |
? 'var(--bg-light)' | |
: 'var(--bg-dark)' | |
} | |
/> | |
)} | |
</div> | |
</div> | |
</GenericButton> | |
))} | |
</div> | |
<div className="text-xs leading-4 text-justify"> | |
By connecting to the wallet, you automatically agree to | |
our{' '} | |
<Link | |
className="underline hover:opacity-[0.9] mb-1 whitespace-nowrap text-blue1" | |
href="/terms" | |
> | |
Terms of service | |
</Link>{' '} | |
and{' '} | |
<Link | |
className="underline hover:opacity-[0.9] whitespace-nowrap mb-1 text-blue1" | |
href="/privacypolicy" | |
> | |
Privacy Policy | |
</Link> | |
. | |
<h5 className="mt-3 text-[0.6rem]"> | |
{' '} | |
New to Ethereum?{' '} | |
<Link | |
className="underline hover:opacity-[0.9] whitespace-nowrap mb-1 text-blue1" | |
href="https://ethereum.org/en/wallets/" | |
target="_blank" | |
rel="noreferrer noopener" | |
> | |
learn more | |
</Link>{' '} | |
about wallets | |
</h5> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
export default Web3Login; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment