diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index c79eadb..af42cc8 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -1,6 +1,8 @@ "use client"; import { RandomLoadingBackground } from "./RandomLoadingBackground"; +import { WalletConnectProposalDrawer } from "./burnerwallet/WalletConnectProposalDrawer"; +import { WalletConnectTransactionDrawer } from "./burnerwallet/WalletConnectTransactionDrawer"; import Jazzicon, { jsNumberForAddress } from "react-jazzicon"; import { useLocalStorage } from "usehooks-ts"; import { useAccount, useNetwork, useSwitchNetwork } from "wagmi"; @@ -84,6 +86,8 @@ export const Header = ({ isGenerateWalletLoading, showIntro, updateHistory }: He + + diff --git a/packages/nextjs/components/burnerwallet/WalletConnectDrawer.tsx b/packages/nextjs/components/burnerwallet/WalletConnectDrawer.tsx index ddcddc5..284dbd2 100644 --- a/packages/nextjs/components/burnerwallet/WalletConnectDrawer.tsx +++ b/packages/nextjs/components/burnerwallet/WalletConnectDrawer.tsx @@ -1,546 +1,64 @@ "use client"; -import React, { ReactNode, useEffect, useState } from "react"; -import { parseUri } from "@walletconnect/utils"; -import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils"; -import { Web3WalletTypes } from "@walletconnect/web3wallet"; -import { useLocalStorage } from "usehooks-ts"; -import { Hex, PrivateKeyAccount, createWalletClient, hexToBigInt, hexToString, http, isAddress, isHex } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; -import { useSwitchNetwork } from "wagmi"; +import React from "react"; import { Drawer, DrawerContent, DrawerHeader, DrawerLine, DrawerTitle } from "~~/components/Drawer"; -import { EIP155_SIGNING_METHODS } from "~~/data/EIP155Data"; -import { SCAFFOLD_CHAIN_ID_STORAGE_KEY, burnerStorageKey } from "~~/hooks/scaffold-eth"; -import { useGlobalState } from "~~/services/store/store"; -import { errorResponse } from "~~/utils/RpcErrors"; -import { web3wallet } from "~~/utils/WalletConnectUtil"; -import { notification } from "~~/utils/scaffold-eth"; -import { getTargetNetworks } from "~~/utils/scaffold-eth"; - -const networks = getTargetNetworks(); +import { useWalletConnectManager } from "~~/hooks/useWalletConnectManager"; export const WalletConnectDrawer = () => { - const walletConnectUid = useGlobalState(state => state.walletConnectUid); - const setWalletConnectUid = useGlobalState(state => state.setWalletConnectUid); - - const isWalletConnectOpen = useGlobalState(state => state.isWalletConnectOpen); - const setIsWalletConnectOpen = useGlobalState(state => state.setIsWalletConnectOpen); - - const walletConnectSession = useGlobalState(state => state.walletConnectSession); - const setWalletConnectSession = useGlobalState(state => state.setWalletConnectSession); - - const isWalletConnectInitialized = useGlobalState(state => state.isWalletConnectInitialized); - - const [initialized, setInitialized] = useState(false); - - const [sessionProposalData, setSessionProposalData] = useState(); - const [isSessionProposalOpen, setIsSessionProposalOpen] = useState(false); - - const [loading, setLoading] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); - const [confirmationData, setConfirmationData] = useState({}); - const [confirmationTitle, setConfirmationTitle] = useState(""); - const [confirmationInfo, setConfirmationInfo] = useState(); - - const setChainId = useLocalStorage(SCAFFOLD_CHAIN_ID_STORAGE_KEY, networks[0].id)[1]; - - const { chains, switchNetwork } = useSwitchNetwork({ - onSuccess(data) { - setChainId(data.id); - }, - }); - - function getAccount() { - let currentSk: Hex = "0x"; - if (typeof window != "undefined" && window != null) { - currentSk = (window?.localStorage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? "0x") as Hex; - } - const account = privateKeyToAccount(currentSk); - return account; - } - - async function onSessionProposalAccept({ id, params }: Web3WalletTypes.SessionProposal) { - try { - const account: PrivateKeyAccount = getAccount(); - - const eip155Chains = chains.map(chain => `eip155:${chain.id}`); - const accounts = eip155Chains.map(chain => `${chain}:${account.address}`); - - const approvedNamespaces = buildApprovedNamespaces({ - // @ts-ignore: TODO: fix types after update WalletConnect to 1.12.1 - proposal: params, - supportedNamespaces: { - eip155: { - chains: eip155Chains, - methods: [ - EIP155_SIGNING_METHODS.PERSONAL_SIGN, - EIP155_SIGNING_METHODS.ETH_SIGN, - EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA, - EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3, - EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4, - EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION, - EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION, - ], - // TODO: support these events - events: ["accountsChanged", "chainChanged"], - accounts: accounts, - }, - }, - }); - - const newSession = await web3wallet.approveSession({ - id, - namespaces: approvedNamespaces, - }); - setWalletConnectSession(newSession); - setWalletConnectUid(""); - notification.success("Connected to WalletConnect"); - } catch (error) { - notification.error((error as Error).message); - await web3wallet.rejectSession({ - id: id, - reason: getSdkError("USER_REJECTED"), - }); - } - } - - async function onSessionProposalReject({ id }: Web3WalletTypes.SessionProposal) { - await web3wallet.rejectSession({ - id: id, - reason: getSdkError("USER_REJECTED"), - }); - } - - async function onConnect(uri: string) { - const { topic: pairingTopic } = parseUri(uri); - - const pairingExpiredListener = ({ topic }: { topic: string }) => { - if (pairingTopic === topic) { - notification.error("Pairing expired. Please try again with new Connection URI"); - web3wallet.core.pairing.events.removeListener("pairing_expire", pairingExpiredListener); - } - }; - - async function onSessionProposal({ id, params }: Web3WalletTypes.SessionProposal) { - setSessionProposalData({ id, params }); - setIsSessionProposalOpen(true); - } - - async function onSessionRequest(event: Web3WalletTypes.SessionRequest) { - const { topic, params, id } = event; - const { chainId, request } = params; - const requestParamsMessage = request.params[0]; - - const currentChainId = - window?.localStorage?.getItem?.(SCAFFOLD_CHAIN_ID_STORAGE_KEY) ?? networks[0].id.toString(); - - if (!chainId || !currentChainId) { - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, "Invalid chain"), - }); - } - - const chainIdNumber = parseInt(chainId.split(":")[1]); - const chainFromRequest = chains.find(c => c.id === chainIdNumber); - - if (!chainFromRequest) { - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, "Invalid chain from request"), - }); - } - - if (chainIdNumber !== parseInt(currentChainId)) { - if (!switchNetwork) { - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, "Can not switch network"), - }); - } - - try { - if (confirm(`Do you want to switch to ${chainFromRequest.name}?`)) { - switchNetwork(chainIdNumber); - } else { - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, "User rejected network switch"), - }); - } - } catch (error: any) { - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, error.message), - }); - } - } - - switch (request.method) { - case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - case EIP155_SIGNING_METHODS.ETH_SIGN: - const messageToSign = isHex(requestParamsMessage) ? hexToString(requestParamsMessage) : requestParamsMessage; - setConfirmationData({ - id, - topic, - chain: chainFromRequest, - method: request.method, - data: messageToSign, - }); - setIsModalOpen(true); - return; - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: - let data = request.params.filter((p: any) => !isAddress(p))[0]; - - if (typeof data === "string") { - data = JSON.parse(data); - } - setConfirmationData({ - id, - topic, - chain: chainFromRequest, - method: request.method, - data: data, - }); - setIsModalOpen(true); - return; - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: - const fieldsToCheck = ["value", "gas"]; - for (const field of fieldsToCheck) { - if (typeof requestParamsMessage[field] === "string") { - if (requestParamsMessage[field].endsWith("n")) { - requestParamsMessage[field] = BigInt(requestParamsMessage[field].slice(0, -1)); - } else { - requestParamsMessage[field] = hexToBigInt(requestParamsMessage[field]); - } - } - } - setConfirmationData({ - id, - topic, - chain: chainFromRequest, - method: request.method, - data: requestParamsMessage, - }); - setIsModalOpen(true); - return; - default: - console.error("Invalid Method"); - return await web3wallet.respondSessionRequest({ - topic, - response: errorResponse(id, "Invalid Method"), - }); - } - } - - try { - setLoading(true); - - web3wallet.core.pairing.events.on("pairing_expire", pairingExpiredListener); - web3wallet.once("session_proposal", () => { - web3wallet.core.pairing.events.removeListener("pairing_expire", pairingExpiredListener); - }); - - if (!initialized) { - web3wallet.on("session_proposal", onSessionProposal); - web3wallet.on("session_request", onSessionRequest); - - web3wallet.on("session_delete", async data => { - console.log("session_delete event received", data); - setWalletConnectSession(null); - notification.success("Disconnected from WalletConnect"); - }); - setInitialized(true); - } - - await web3wallet.pair({ uri }); - } catch (error) { - notification.error((error as Error).message); - } finally { - setLoading(false); - } - } - - async function disconnect() { - setLoading(true); - if (walletConnectSession) { - await web3wallet.disconnectSession({ - topic: walletConnectSession.topic, - reason: getSdkError("USER_DISCONNECTED"), - }); - setWalletConnectSession(null); - notification.success("Disconnected from WalletConnect"); - setIsWalletConnectOpen(false); - } - setLoading(false); - } - - async function onConfirmTransaction() { - try { - setLoading(true); - const account: PrivateKeyAccount = getAccount(); - const wallet = createWalletClient({ - chain: confirmationData.chain, - account: account, - transport: http(), - }); - switch (confirmationData.method) { - case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - case EIP155_SIGNING_METHODS.ETH_SIGN: - const signedMessage = await wallet.signMessage({ message: confirmationData.data }); - const responseSign = { id: confirmationData.id, result: signedMessage, jsonrpc: "2.0" }; - - await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSign }); - break; - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: - const { domain, types, message, primaryType } = confirmationData.data; - - const signedData = await wallet.signTypedData({ domain, types, message, primaryType }); - const responseSignData = { id: confirmationData.id, result: signedData, jsonrpc: "2.0" }; - - await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSignData }); - break; - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - const hash = await wallet.sendTransaction(confirmationData.data); - const responseSendTransaction = { id: confirmationData.id, result: hash, jsonrpc: "2.0" }; - - await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSendTransaction }); - break; - case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: - const signature = await wallet.signTransaction(confirmationData.data); - const responseSignTransaction = { id: confirmationData.id, result: signature, jsonrpc: "2.0" }; - - await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSignTransaction }); - break; - } - } catch (error: any) { - notification.error((error as Error).message); - console.error(error); - setIsModalOpen(false); - return await web3wallet.respondSessionRequest({ - topic: confirmationData.topic, - response: errorResponse(confirmationData.id, error.message), - }); - } finally { - setLoading(false); - setIsModalOpen(false); - } - } - - async function onRejectTransaction() { - setLoading(true); - let message = "User rejected "; - if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION) { - message += "send transaction"; - } - if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION) { - message += "sign transaction"; - } - await web3wallet.respondSessionRequest({ - topic: confirmationData.topic, - response: errorResponse(confirmationData.id, message), - }); - setLoading(false); - setIsModalOpen(false); - } - - useEffect(() => { - if (!confirmationData || !confirmationData.method) { - setConfirmationTitle(""); - } else { - switch (confirmationData.method) { - case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - case EIP155_SIGNING_METHODS.ETH_SIGN: - setConfirmationTitle("Do you want to sign this message?"); - setConfirmationInfo( -
-
Message:
-
{confirmationData.data}
-
, - ); - break; - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: - setConfirmationTitle("Do you want to sign this message?"); - setConfirmationInfo( -
-
Message:
-
{JSON.stringify(confirmationData.data.message)}
-
, - ); - break; - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: - let action = "send"; - if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION) { - action = "sign"; - } - setConfirmationTitle(`Do you want to ${action} this transaction?`); - setConfirmationInfo( -
-

-

From:
-
{confirmationData.data.from}
-

-

-

To:
-
{confirmationData.data.to}
-

-

-

Value:
-
{confirmationData.data.value?.toString()}
-

-

-

Data:
-
{confirmationData.data.data}
-

-
, - ); - break; - default: - setConfirmationTitle(""); - break; - } - } - }, [confirmationData]); - - useEffect(() => { - if (walletConnectUid) { - if (walletConnectSession) { - // if there is a current session, show the option to disconnect and connect to new dapp - setIsWalletConnectOpen(true); - } else { - if (isWalletConnectInitialized) { - // if there is no session and WC is initialized, show the confirmation data to connect to dapp - onConnect(walletConnectUid); - } else { - // if there is no session and WC is not initialized, show a loading message - setIsSessionProposalOpen(true); - } - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletConnectUid, walletConnectSession, isWalletConnectInitialized]); + const { + onConnect, + disconnect, + isWalletConnectOpen, + setIsWalletConnectOpen, + isWalletConnectInitialized, + loading, + walletConnectSession, + walletConnectUid, + } = useWalletConnectManager(); return ( - <> - - - - - WalletConnect - -
-
- {walletConnectSession ? ( - walletConnectUid ? ( - - ) : ( -
-
Connected to {walletConnectSession.peer.metadata.name}
- -
- ) - ) : isWalletConnectInitialized ? ( + + + + + WalletConnect + +
+
+ {walletConnectSession ? ( + walletConnectUid ? ( - ) : ( -
Loading WalletConnect...
- )} -
-
-
-
- - - - - WalletConnect - - {isWalletConnectInitialized ? ( -
-
- {sessionProposalData?.params?.proposer?.metadata?.icons[0] && ( - // eslint-disable-next-line @next/next/no-img-element - Dapp Icon - )} -

{sessionProposalData?.params?.proposer?.metadata?.name}

-

{sessionProposalData?.params?.proposer?.metadata?.url}

-

Wants to connect to your wallet

-

Requested permissions

-
    -
  • View your balance and activity
  • -
  • Send approval requests
  • -
-
-
- - -
-
- ) : ( -
Loading WalletConnect...
- )} -
-
- -
-

{confirmationTitle}

-

{confirmationInfo}

-
- - + ) : ( +
+
Connected to {walletConnectSession.peer.metadata.name}
+ +
+ ) + ) : isWalletConnectInitialized ? ( + + ) : ( +
Loading WalletConnect...
+ )}
-
- + + ); }; diff --git a/packages/nextjs/components/burnerwallet/WalletConnectProposalDrawer.tsx b/packages/nextjs/components/burnerwallet/WalletConnectProposalDrawer.tsx new file mode 100644 index 0000000..4af2777 --- /dev/null +++ b/packages/nextjs/components/burnerwallet/WalletConnectProposalDrawer.tsx @@ -0,0 +1,73 @@ +"use client"; + +import React from "react"; +import { Drawer, DrawerContent, DrawerHeader, DrawerLine, DrawerTitle } from "~~/components/Drawer"; +import { useWalletConnectManager } from "~~/hooks/useWalletConnectManager"; + +export const WalletConnectProposalDrawer = () => { + const { + isWalletConnectInitialized, + isSessionProposalOpen, + setIsSessionProposalOpen, + sessionProposalData, + onSessionProposalAccept, + onSessionProposalReject, + } = useWalletConnectManager(); + + return ( + + + + + WalletConnect + + {isWalletConnectInitialized ? ( +
+
+ {sessionProposalData?.params?.proposer?.metadata?.icons[0] && ( + // eslint-disable-next-line @next/next/no-img-element + Dapp Icon + )} +

{sessionProposalData?.params?.proposer?.metadata?.name}

+

{sessionProposalData?.params?.proposer?.metadata?.url}

+

Wants to connect to your wallet

+

Requested permissions

+
    +
  • View your balance and activity
  • +
  • Send approval requests
  • +
+
+
+ + +
+
+ ) : ( +
Loading WalletConnect...
+ )} +
+
+ ); +}; diff --git a/packages/nextjs/components/burnerwallet/WalletConnectTransactionDrawer.tsx b/packages/nextjs/components/burnerwallet/WalletConnectTransactionDrawer.tsx new file mode 100644 index 0000000..045c65c --- /dev/null +++ b/packages/nextjs/components/burnerwallet/WalletConnectTransactionDrawer.tsx @@ -0,0 +1,39 @@ +"use client"; + +import React from "react"; +import { Drawer, DrawerContent, DrawerHeader, DrawerLine, DrawerTitle } from "~~/components/Drawer"; +import { useWalletConnectManager } from "~~/hooks/useWalletConnectManager"; + +export const WalletConnectTransactionDrawer = () => { + const { + loading, + isTransactionConfirmOpen, + setIsTransactionConfirmOpen, + confirmationTitle, + confirmationInfo, + onConfirmTransaction, + onRejectTransaction, + } = useWalletConnectManager(); + + return ( + + + + + {confirmationTitle} + +
+
{confirmationInfo}
+
+ + +
+
+
+
+ ); +}; diff --git a/packages/nextjs/hooks/useWalletConnectManager.tsx b/packages/nextjs/hooks/useWalletConnectManager.tsx new file mode 100644 index 0000000..2cbdf65 --- /dev/null +++ b/packages/nextjs/hooks/useWalletConnectManager.tsx @@ -0,0 +1,448 @@ +"use client"; + +import React, { ReactNode, useEffect, useState } from "react"; +import { parseUri } from "@walletconnect/utils"; +import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils"; +import { Web3WalletTypes } from "@walletconnect/web3wallet"; +import { useLocalStorage } from "usehooks-ts"; +import { Hex, PrivateKeyAccount, createWalletClient, hexToBigInt, hexToString, http, isAddress, isHex } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { useSwitchNetwork } from "wagmi"; +import { EIP155_SIGNING_METHODS } from "~~/data/EIP155Data"; +import { SCAFFOLD_CHAIN_ID_STORAGE_KEY, burnerStorageKey } from "~~/hooks/scaffold-eth"; +import { useGlobalState } from "~~/services/store/store"; +import { errorResponse } from "~~/utils/RpcErrors"; +import { web3wallet } from "~~/utils/WalletConnectUtil"; +import { notification } from "~~/utils/scaffold-eth"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; + +const networks = getTargetNetworks(); + +export const useWalletConnectManager = () => { + const isWalletConnectInitialized = useGlobalState(state => state.isWalletConnectInitialized); + const [initialized, setInitialized] = useState(false); + const [loading, setLoading] = useState(false); + + const walletConnectUid = useGlobalState(state => state.walletConnectUid); + const setWalletConnectUid = useGlobalState(state => state.setWalletConnectUid); + const walletConnectSession = useGlobalState(state => state.walletConnectSession); + const setWalletConnectSession = useGlobalState(state => state.setWalletConnectSession); + + const isWalletConnectOpen = useGlobalState(state => state.isWalletConnectOpen); + const setIsWalletConnectOpen = useGlobalState(state => state.setIsWalletConnectOpen); + const [isSessionProposalOpen, setIsSessionProposalOpen] = useState(false); + const [isTransactionConfirmOpen, setIsTransactionConfirmOpen] = useState(false); + + const [sessionProposalData, setSessionProposalData] = useState(); + const [confirmationData, setConfirmationData] = useState({}); + const [confirmationTitle, setConfirmationTitle] = useState(""); + const [confirmationInfo, setConfirmationInfo] = useState(); + + const setChainId = useLocalStorage(SCAFFOLD_CHAIN_ID_STORAGE_KEY, networks[0].id)[1]; + + const { chains, switchNetwork } = useSwitchNetwork({ + onSuccess(data) { + setChainId(data.id); + }, + }); + + function getAccount() { + let currentSk: Hex = "0x"; + if (typeof window != "undefined" && window != null) { + currentSk = (window?.localStorage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? "0x") as Hex; + } + const account = privateKeyToAccount(currentSk); + return account; + } + + async function onSessionProposalAccept({ id, params }: Web3WalletTypes.SessionProposal) { + try { + const account: PrivateKeyAccount = getAccount(); + + const eip155Chains = chains.map(chain => `eip155:${chain.id}`); + const accounts = eip155Chains.map(chain => `${chain}:${account.address}`); + + const approvedNamespaces = buildApprovedNamespaces({ + // @ts-ignore: TODO: fix types after update WalletConnect to 1.12.1 + proposal: params, + supportedNamespaces: { + eip155: { + chains: eip155Chains, + methods: [ + EIP155_SIGNING_METHODS.PERSONAL_SIGN, + EIP155_SIGNING_METHODS.ETH_SIGN, + EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA, + EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3, + EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4, + EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION, + EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION, + ], + // TODO: support these events + events: ["accountsChanged", "chainChanged"], + accounts: accounts, + }, + }, + }); + + const newSession = await web3wallet.approveSession({ + id, + namespaces: approvedNamespaces, + }); + setWalletConnectSession(newSession); + setWalletConnectUid(""); + notification.success("Connected to WalletConnect"); + } catch (error) { + notification.error((error as Error).message); + await web3wallet.rejectSession({ + id: id, + reason: getSdkError("USER_REJECTED"), + }); + } + } + + async function onSessionProposalReject({ id }: Web3WalletTypes.SessionProposal) { + await web3wallet.rejectSession({ + id: id, + reason: getSdkError("USER_REJECTED"), + }); + } + + async function onConnect(uri: string) { + const { topic: pairingTopic } = parseUri(uri); + + const pairingExpiredListener = ({ topic }: { topic: string }) => { + if (pairingTopic === topic) { + notification.error("Pairing expired. Please try again with new Connection URI"); + web3wallet.core.pairing.events.removeListener("pairing_expire", pairingExpiredListener); + } + }; + + async function onSessionProposal({ id, params }: Web3WalletTypes.SessionProposal) { + setSessionProposalData({ id, params }); + setIsSessionProposalOpen(true); + } + + async function onSessionRequest(event: Web3WalletTypes.SessionRequest) { + const { topic, params, id } = event; + const { chainId, request } = params; + const requestParamsMessage = request.params[0]; + + const currentChainId = + window?.localStorage?.getItem?.(SCAFFOLD_CHAIN_ID_STORAGE_KEY) ?? networks[0].id.toString(); + + if (!chainId || !currentChainId) { + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, "Invalid chain"), + }); + } + + const chainIdNumber = parseInt(chainId.split(":")[1]); + const chainFromRequest = chains.find(c => c.id === chainIdNumber); + + if (!chainFromRequest) { + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, "Invalid chain from request"), + }); + } + + if (chainIdNumber !== parseInt(currentChainId)) { + if (!switchNetwork) { + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, "Can not switch network"), + }); + } + + try { + if (confirm(`Do you want to switch to ${chainFromRequest.name}?`)) { + switchNetwork(chainIdNumber); + } else { + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, "User rejected network switch"), + }); + } + } catch (error: any) { + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, error.message), + }); + } + } + + switch (request.method) { + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + case EIP155_SIGNING_METHODS.ETH_SIGN: + const messageToSign = isHex(requestParamsMessage) ? hexToString(requestParamsMessage) : requestParamsMessage; + setConfirmationData({ + id, + topic, + chain: chainFromRequest, + method: request.method, + data: messageToSign, + }); + setIsTransactionConfirmOpen(true); + return; + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: + let data = request.params.filter((p: any) => !isAddress(p))[0]; + + if (typeof data === "string") { + data = JSON.parse(data); + } + setConfirmationData({ + id, + topic, + chain: chainFromRequest, + method: request.method, + data: data, + }); + setIsTransactionConfirmOpen(true); + return; + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: + const fieldsToCheck = ["value", "gas"]; + for (const field of fieldsToCheck) { + if (typeof requestParamsMessage[field] === "string") { + if (requestParamsMessage[field].endsWith("n")) { + requestParamsMessage[field] = BigInt(requestParamsMessage[field].slice(0, -1)); + } else { + requestParamsMessage[field] = hexToBigInt(requestParamsMessage[field]); + } + } + } + setConfirmationData({ + id, + topic, + chain: chainFromRequest, + method: request.method, + data: requestParamsMessage, + }); + setIsTransactionConfirmOpen(true); + return; + default: + console.error("Invalid Method"); + return await web3wallet.respondSessionRequest({ + topic, + response: errorResponse(id, "Invalid Method"), + }); + } + } + + try { + setLoading(true); + + web3wallet.core.pairing.events.on("pairing_expire", pairingExpiredListener); + web3wallet.once("session_proposal", () => { + web3wallet.core.pairing.events.removeListener("pairing_expire", pairingExpiredListener); + }); + + if (!initialized) { + web3wallet.on("session_proposal", onSessionProposal); + web3wallet.on("session_request", onSessionRequest); + + web3wallet.on("session_delete", async data => { + console.log("session_delete event received", data); + setWalletConnectSession(null); + notification.success("Disconnected from WalletConnect"); + }); + setInitialized(true); + } + + await web3wallet.pair({ uri }); + } catch (error) { + notification.error((error as Error).message); + } finally { + setLoading(false); + } + } + + async function disconnect() { + setLoading(true); + if (walletConnectSession) { + await web3wallet.disconnectSession({ + topic: walletConnectSession.topic, + reason: getSdkError("USER_DISCONNECTED"), + }); + setWalletConnectSession(null); + notification.success("Disconnected from WalletConnect"); + setIsWalletConnectOpen(false); + } + setLoading(false); + } + + async function onConfirmTransaction() { + try { + setLoading(true); + const account: PrivateKeyAccount = getAccount(); + const wallet = createWalletClient({ + chain: confirmationData.chain, + account: account, + transport: http(), + }); + switch (confirmationData.method) { + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + case EIP155_SIGNING_METHODS.ETH_SIGN: + const signedMessage = await wallet.signMessage({ message: confirmationData.data }); + const responseSign = { id: confirmationData.id, result: signedMessage, jsonrpc: "2.0" }; + + await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSign }); + break; + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: + const { domain, types, message, primaryType } = confirmationData.data; + + const signedData = await wallet.signTypedData({ domain, types, message, primaryType }); + const responseSignData = { id: confirmationData.id, result: signedData, jsonrpc: "2.0" }; + + await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSignData }); + break; + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + const hash = await wallet.sendTransaction(confirmationData.data); + const responseSendTransaction = { id: confirmationData.id, result: hash, jsonrpc: "2.0" }; + + await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSendTransaction }); + break; + case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: + const signature = await wallet.signTransaction(confirmationData.data); + const responseSignTransaction = { id: confirmationData.id, result: signature, jsonrpc: "2.0" }; + + await web3wallet.respondSessionRequest({ topic: confirmationData.topic, response: responseSignTransaction }); + break; + } + } catch (error: any) { + notification.error((error as Error).message); + console.error(error); + setIsTransactionConfirmOpen(false); + return await web3wallet.respondSessionRequest({ + topic: confirmationData.topic, + response: errorResponse(confirmationData.id, error.message), + }); + } finally { + setLoading(false); + setIsTransactionConfirmOpen(false); + } + } + + async function onRejectTransaction() { + setLoading(true); + let message = "User rejected "; + if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION) { + message += "send transaction"; + } + if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION) { + message += "sign transaction"; + } + await web3wallet.respondSessionRequest({ + topic: confirmationData.topic, + response: errorResponse(confirmationData.id, message), + }); + setLoading(false); + setIsTransactionConfirmOpen(false); + } + + useEffect(() => { + if (!confirmationData || !confirmationData.method) { + setConfirmationTitle(""); + } else { + switch (confirmationData.method) { + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + case EIP155_SIGNING_METHODS.ETH_SIGN: + setConfirmationTitle("Do you want to sign this message?"); + setConfirmationInfo( +
+
Message:
+
{confirmationData.data}
+
, + ); + break; + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: + setConfirmationTitle("Do you want to sign this message?"); + setConfirmationInfo( +
+
Message:
+
{JSON.stringify(confirmationData.data.message)}
+
, + ); + break; + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: + let action = "send"; + if (confirmationData.method === EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION) { + action = "sign"; + } + setConfirmationTitle(`Do you want to ${action} this transaction?`); + setConfirmationInfo( +
+

+

From:
+
{confirmationData.data.from}
+

+

+

To:
+
{confirmationData.data.to}
+

+

+

Value:
+
{confirmationData.data.value?.toString()}
+

+

+

Data:
+
{confirmationData.data.data}
+

+
, + ); + break; + default: + setConfirmationTitle(""); + break; + } + } + }, [confirmationData]); + + useEffect(() => { + if (walletConnectUid) { + if (walletConnectSession) { + // if there is a current session, show the option to disconnect and connect to new dapp + setIsWalletConnectOpen(true); + } else { + if (isWalletConnectInitialized) { + // if there is no session and WC is initialized, show the confirmation data to connect to dapp + onConnect(walletConnectUid); + } else { + // if there is no session and WC is not initialized, show a loading message + setIsSessionProposalOpen(true); + } + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [walletConnectUid, walletConnectSession, isWalletConnectInitialized]); + + return { + onConnect, + disconnect, + isWalletConnectOpen, + setIsWalletConnectOpen, + isWalletConnectInitialized, + isSessionProposalOpen, + setIsSessionProposalOpen, + sessionProposalData, + loading, + isTransactionConfirmOpen, + setIsTransactionConfirmOpen, + confirmationTitle, + confirmationInfo, + onConfirmTransaction, + onRejectTransaction, + walletConnectSession, + walletConnectUid, + onSessionProposalAccept, + onSessionProposalReject, + }; +};