- {walletConnectSession ? (
- walletConnectUid ? (
-
{
- await disconnect();
- await onConnect(walletConnectUid);
- }}
- >
- {loading ? "Loading..." : "Disconnect and Connect to new Dapp"}
-
- ) : (
-
-
Connected to {walletConnectSession.peer.metadata.name}
-
- Disconnect
-
-
- )
- ) : isWalletConnectInitialized ? (
+
+
+
+
+ WalletConnect
+
+
+
+ {walletConnectSession ? (
+ walletConnectUid ? (
onConnect(walletConnectUid)}
- >
- {loading ? "Loading..." : "Connect to Dapp"}
-
- ) : (
-
Loading WalletConnect...
- )}
-
-
-
-
-
-
-
-
- WalletConnect
-
- {isWalletConnectInitialized ? (
-
-
- {sessionProposalData?.params?.proposer?.metadata?.icons[0] && (
- // eslint-disable-next-line @next/next/no-img-element
-
- )}
-
{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
-
-
-
- {
- onSessionProposalReject(sessionProposalData);
- setIsSessionProposalOpen(false);
+ onClick={async () => {
+ await disconnect();
+ await onConnect(walletConnectUid);
}}
>
- Reject
+ {loading ? "Loading..." : "Disconnect and Connect to new Dapp"}
- {
- onSessionProposalAccept(sessionProposalData);
- setIsSessionProposalOpen(false);
- }}
- >
- Connect
-
-
-
- ) : (
- Loading WalletConnect...
- )}
-
-
-
-
-
{confirmationTitle}
-
{confirmationInfo}
-
-
- Reject
-
-
- Confirm
-
+ ) : (
+
+
Connected to {walletConnectSession.peer.metadata.name}
+
+ Disconnect
+
+
+ )
+ ) : isWalletConnectInitialized ? (
+
onConnect(walletConnectUid)}
+ >
+ {loading ? "Loading..." : "Connect to Dapp"}
+
+ ) : (
+
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
+
+ )}
+
{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
+
+
+
+ {
+ onSessionProposalReject(sessionProposalData);
+ setIsSessionProposalOpen(false);
+ }}
+ >
+ Reject
+
+ {
+ onSessionProposalAccept(sessionProposalData);
+ setIsSessionProposalOpen(false);
+ }}
+ >
+ Connect
+
+
+
+ ) : (
+ 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}
+
+
+ Reject
+
+
+ Accept
+
+
+
+
+
+ );
+};
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,
+ };
+};