Add WalletConnect V2 support (#11)

This commit is contained in:
Apoorv Lathey
2023-05-15 03:17:07 +05:30
committed by GitHub
parent bf88e6db72
commit e66e746255
8 changed files with 1183 additions and 20046 deletions

View File

@@ -1,3 +1,4 @@
REACT_APP_INFURA_KEY=
REACT_APP_WC_PROJECT_ID=
REACT_APP_GITCOIN_GRANTS_ACTIVE=
REACT_APP_GITCOIN_GRANTS_LINK=

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.env
.vercel
# dependencies
/node_modules

2
.nvmrc
View File

@@ -1 +1 @@
v14.17.0
v16.15.0

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

19826
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,12 @@
"@types/node": "^17.0.10",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@walletconnect/client": "^1.6.2",
"@walletconnect/client": "^1.8.0",
"@walletconnect/core": "^2.7.3",
"@walletconnect/legacy-types": "^2.0.0",
"@walletconnect/types": "^2.7.3",
"@walletconnect/utils": "^2.7.3",
"@walletconnect/web3wallet": "^1.7.1",
"axios": "^0.24.0",
"chakra-react-select": "^4.4.3",
"ethereum-checksum-address": "^0.0.6",
@@ -45,19 +50,12 @@
]
},
"engines": {
"node": ">=14.0.0 <15.0.0"
"node": ">=16"
},
"engineStrict": true,
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import {
Container,
InputGroup,
@@ -55,8 +55,14 @@ import {
CloseIcon,
} from "@chakra-ui/icons";
import { Select as RSelect, SingleValue } from "chakra-react-select";
import WalletConnect from "@walletconnect/client";
import { IClientMeta } from "@walletconnect/types";
// WC v1
import LegacySignClient from "@walletconnect/client";
import { IClientMeta } from "@walletconnect/legacy-types";
// WC v2
import { Core } from "@walletconnect/core";
import { Web3Wallet, IWeb3Wallet } from "@walletconnect/web3wallet";
import { SessionTypes } from "@walletconnect/types";
import { getSdkError, parseUri } from "@walletconnect/utils";
import { ethers } from "ethers";
import axios from "axios";
import networksList from "evm-rpcs-list";
@@ -75,6 +81,17 @@ interface SelectedNetworkOption {
value: number;
}
const WCMetadata = {
name: "Impersonator",
description: "Login to dapps as any address",
url: "www.impersonator.xyz",
icons: ["https://www.impersonator.xyz/favicon.ico"],
};
const core = new Core({
projectId: process.env.REACT_APP_WC_PROJECT_ID,
});
const primaryNetworkIds = [
1, // ETH Mainnet
42161, // Arbitrum One
@@ -183,8 +200,13 @@ function Body() {
label: networksList[networkIdViaURL].name,
value: networkIdViaURL,
});
const [connector, setConnector] = useState<WalletConnect>();
const [peerMeta, setPeerMeta] = useState<IClientMeta>();
// WC v1
const [legacySignClient, setLegacySignClient] = useState<LegacySignClient>();
// WC v2
const [web3wallet, setWeb3Wallet] = useState<IWeb3Wallet>();
const [web3WalletSession, setWeb3WalletSession] =
useState<SessionTypes.Struct>();
const [legacyPeerMeta, setLegacyPeerMeta] = useState<IClientMeta>();
const [isConnected, setIsConnected] = useState(false);
const [loading, setLoading] = useState(false);
@@ -213,21 +235,24 @@ function Body() {
>([]);
useEffect(() => {
// WC V1
const { session, _showAddress } = getCachedSession();
if (session) {
let _connector = new WalletConnect({ session });
let _legacySignClient = new LegacySignClient({ session });
if (_connector.peerMeta) {
if (_legacySignClient.peerMeta) {
try {
setConnector(_connector);
setShowAddress(_showAddress ? _showAddress : _connector.accounts[0]);
setAddress(_connector.accounts[0]);
setUri(_connector.uri);
setPeerMeta(_connector.peerMeta);
setLegacySignClient(_legacySignClient);
setShowAddress(
_showAddress ? _showAddress : _legacySignClient.accounts[0]
);
setAddress(_legacySignClient.accounts[0]);
setUri(_legacySignClient.uri);
setLegacyPeerMeta(_legacySignClient.peerMeta);
setIsConnected(true);
const chainId =
(_connector.chainId as unknown as { chainID: number }).chainID ||
_connector.chainId;
(_legacySignClient.chainId as unknown as { chainID: number })
.chainID || _legacySignClient.chainId;
setNetworkId(chainId);
} catch {
@@ -236,6 +261,8 @@ function Body() {
}
}
}
// WC V2
initWeb3Wallet(true, _showAddress);
setProvider(
new ethers.providers.JsonRpcProvider(
@@ -249,20 +276,22 @@ function Body() {
useEffect(() => {
updateNetwork((selectedNetworkOption as SelectedNetworkOption).value);
// eslint-disable-next-line
}, [selectedNetworkOption]);
useEffect(() => {
if (provider && addressFromURL && urlFromURL) {
initIFrame();
}
// eslint-disable-next-line
}, [provider]);
useEffect(() => {
if (connector) {
if (legacySignClient || web3wallet) {
subscribeToEvents();
}
// eslint-disable-next-line
}, [connector]);
}, [legacySignClient, web3wallet]);
useEffect(() => {
localStorage.setItem("tenderlyForkId", tenderlyForkId);
@@ -274,11 +303,13 @@ function Body() {
useEffect(() => {
setIFrameAddress(address);
// eslint-disable-next-line
}, [address]);
useEffect(() => {
// TODO: use random rpc if this one is slow/down?
setRpcUrl(networksList[networkId].rpcs[0]);
// eslint-disable-next-line
}, [networkId]);
useEffect(() => {
@@ -317,6 +348,7 @@ function Body() {
.then((res) => console.log(res.data));
}
}
// eslint-disable-next-line
}, [latestTransaction, tenderlyForkId]);
useEffect(() => {
@@ -356,6 +388,44 @@ function Body() {
}
}, [safeDapps, networkId, searchSafeDapp]);
const initWeb3Wallet = async (
onlyIfActiveSessions?: boolean,
_showAddress?: string
) => {
const _web3wallet = await Web3Wallet.init({
core,
metadata: WCMetadata,
});
if (onlyIfActiveSessions) {
const sessions = _web3wallet.getActiveSessions();
const sessionsArray = Object.values(sessions);
if (sessionsArray.length > 0) {
const _address =
sessionsArray[0].namespaces["eip155"].accounts[0].split(":")[2];
console.log({ _showAddress, _address });
setWeb3WalletSession(sessionsArray[0]);
setShowAddress(
_showAddress && _showAddress.length > 0 ? _showAddress : _address
);
if (!(_showAddress && _showAddress.length > 0)) {
localStorage.setItem("showAddress", _address);
}
setAddress(_address);
setUri(
`wc:${sessionsArray[0].pairingTopic}@2?relay-protocol=irn&symKey=xxxxxx`
);
setWeb3Wallet(_web3wallet);
setIsConnected(true);
}
} else {
setWeb3Wallet(_web3wallet);
}
// for debugging
(window as any).w3 = _web3wallet;
};
const resolveAndValidateAddress = async () => {
let isValid;
let _address = address;
@@ -391,9 +461,7 @@ function Body() {
const getCachedSession = () => {
const local = localStorage ? localStorage.getItem("walletconnect") : null;
const _showAddress = localStorage
? localStorage.getItem("showAddress")
: null;
const _showAddress = localStorage.getItem("showAddress") ?? undefined;
let session = null;
if (local) {
@@ -411,15 +479,21 @@ function Body() {
const { isValid } = await resolveAndValidateAddress();
if (isValid) {
const { version } = parseUri(uri);
try {
let _connector = new WalletConnect({ uri });
if (version === 1) {
let _legacySignClient = new LegacySignClient({ uri });
if (!_connector.connected) {
await _connector.createSession();
if (!_legacySignClient.connected) {
await _legacySignClient.createSession();
}
setLegacySignClient(_legacySignClient);
setUri(_legacySignClient.uri);
} else {
await initWeb3Wallet();
}
setConnector(_connector);
setUri(_connector.uri);
} catch (err) {
console.error(err);
toast({
@@ -452,11 +526,11 @@ function Body() {
setAppUrl(_inputAppUrl);
};
const subscribeToEvents = () => {
const subscribeToEvents = async () => {
console.log("ACTION", "subscribeToEvents");
if (connector) {
connector.on("session_request", (error, payload) => {
if (legacySignClient) {
legacySignClient.on("session_request", (error, payload) => {
if (loading) {
setLoading(false);
}
@@ -467,10 +541,11 @@ function Body() {
}
console.log("SESSION_REQUEST", payload.params);
setPeerMeta(payload.params[0].peerMeta);
setLegacyPeerMeta(payload.params[0].peerMeta);
approveLegacySession();
});
connector.on("session_update", (error) => {
legacySignClient.on("session_update", (error) => {
console.log("EVENT", "session_update");
setLoading(false);
@@ -479,65 +554,15 @@ function Body() {
}
});
connector.on("call_request", async (error, payload) => {
legacySignClient.on("call_request", async (error, payload) => {
console.log({ payload });
if (payload.method === "eth_sendTransaction") {
setSendTxnData((data) => {
const newTxn = {
id: payload.id,
from: payload.params[0].from,
to: payload.params[0].to,
data: payload.params[0].data,
value: payload.params[0].value
? parseInt(payload.params[0].value, 16).toString()
: "0",
};
if (data.some((d) => d.id === newTxn.id)) {
return data;
} else {
return [newTxn, ...data];
}
});
if (tenderlyForkId.length > 0) {
const { data: res } = await axios.post(
"https://rpc.tenderly.co/fork/" + tenderlyForkId,
{
jsonrpc: "2.0",
id: payload.id,
method: payload.method,
params: payload.params,
}
);
console.log({ res });
// Approve Call Request
connector.approveRequest({
id: res.id,
result: res.result,
});
toast({
title: "Txn successful",
description: `Hash: ${res.result}`,
status: "success",
position: "bottom-right",
duration: null,
isClosable: true,
});
}
await handleSendTransaction(payload.id, payload.params);
}
// if (error) {
// throw error;
// }
// await getAppConfig().rpcEngine.router(payload, this.state, this.bindedSetState);
});
connector.on("connect", (error, payload) => {
legacySignClient.on("connect", (error, payload) => {
console.log("EVENT", "connect");
if (error) {
@@ -547,39 +572,200 @@ function Body() {
// this.setState({ connected: true });
});
connector.on("disconnect", (error, payload) => {
legacySignClient.on("disconnect", (error, payload) => {
console.log("EVENT", "disconnect");
if (error) {
throw error;
}
reset();
});
} else if (web3wallet) {
web3wallet.on("session_proposal", async (proposal) => {
if (loading) {
setLoading(false);
}
console.log("EVENT", "session_proposal", proposal);
const { requiredNamespaces, optionalNamespaces } = proposal.params;
const namespaceKey = "eip155";
const requiredNamespace = requiredNamespaces[namespaceKey];
const optionalNamespace = optionalNamespaces
? optionalNamespaces[namespaceKey]
: undefined;
let chains: string[] | undefined = requiredNamespace.chains;
if (optionalNamespace && optionalNamespace.chains) {
if (chains) {
// merge chains from requiredNamespace & optionalNamespace, while avoiding duplicates
chains = Array.from(
new Set(chains.concat(optionalNamespace.chains))
);
} else {
chains = optionalNamespace.chains;
}
}
const accounts: string[] = [];
chains?.map((chain) => {
accounts.push(`${chain}:${address}`);
return null;
});
const namespace: SessionTypes.Namespace = {
accounts,
chains: chains,
methods: requiredNamespace.methods,
events: requiredNamespace.events,
};
if (requiredNamespace.chains) {
const _chainId = parseInt(requiredNamespace.chains[0].split(":")[1]);
setSelectedNetworkOption({
label: networksList[_chainId].name,
value: _chainId,
});
}
const session = await web3wallet.approveSession({
id: proposal.id,
namespaces: {
[namespaceKey]: namespace,
},
});
setWeb3WalletSession(session);
setIsConnected(true);
});
try {
await web3wallet.core.pairing.pair({ uri });
} catch (e) {
console.error(e);
}
web3wallet.on("session_request", async (event) => {
const { topic, params, id } = event;
const { request } = params;
console.log("EVENT", "session_request", event);
if (request.method === "eth_sendTransaction") {
await handleSendTransaction(id, request.params, topic);
} else {
await web3wallet.respondSessionRequest({
topic,
response: {
jsonrpc: "2.0",
id: id,
error: {
code: 0,
message: "Method not supported by Impersonator",
},
},
});
}
});
web3wallet.on("session_delete", () => {
console.log("EVENT", "session_delete");
reset();
});
}
};
const approveSession = () => {
console.log("ACTION", "approveSession");
if (connector) {
const handleSendTransaction = async (
id: number,
params: any[],
topic?: string
) => {
setSendTxnData((data) => {
const newTxn = {
id: id,
from: params[0].from,
to: params[0].to,
data: params[0].data,
value: params[0].value ? parseInt(params[0].value, 16).toString() : "0",
};
if (data.some((d) => d.id === newTxn.id)) {
return data;
} else {
return [newTxn, ...data];
}
});
if (tenderlyForkId.length > 0) {
const { data: res } = await axios.post(
"https://rpc.tenderly.co/fork/" + tenderlyForkId,
{
jsonrpc: "2.0",
id: id,
method: "eth_sendTransaction",
params: params,
}
);
console.log({ res });
// Approve Call Request
if (legacySignClient) {
legacySignClient.approveRequest({
id: res.id,
result: res.result,
});
} else if (web3wallet && topic) {
await web3wallet.respondSessionRequest({
topic,
response: {
jsonrpc: "2.0",
id: res.id,
result: res.result,
},
});
}
toast({
title: "Txn successful",
description: `Hash: ${res.result}`,
status: "success",
position: "bottom-right",
duration: null,
isClosable: true,
});
} else {
if (web3wallet && topic) {
await web3wallet.respondSessionRequest({
topic,
response: {
jsonrpc: "2.0",
id: id,
error: { code: 0, message: "Method not supported by Impersonator" },
},
});
}
}
};
const approveLegacySession = () => {
console.log("ACTION", "approveLegacySession");
if (legacySignClient) {
let chainId = networkId;
if (!chainId) {
chainId = 1; // default to ETH Mainnet if no network selected
}
connector.approveSession({ chainId, accounts: [address] });
legacySignClient.approveSession({ chainId, accounts: [address] });
setIsConnected(true);
}
};
const rejectSession = () => {
console.log("ACTION", "rejectSession");
if (connector) {
connector.rejectSession();
setPeerMeta(undefined);
}
};
// const rejectLegacySession = () => {
// console.log("ACTION", "rejectSession");
// if (legacySignClient) {
// legacySignClient.rejectSession();
// setPeerMeta(undefined);
// }
// };
const updateSession = ({
const updateSession = async ({
newChainId,
newAddress,
}: {
@@ -589,11 +775,21 @@ function Body() {
let _chainId = newChainId || networkId;
let _address = newAddress || address;
if (connector && connector.connected) {
connector.updateSession({
if (legacySignClient && legacySignClient.connected) {
legacySignClient.updateSession({
chainId: _chainId,
accounts: [_address],
});
} else if (web3wallet && web3WalletSession) {
await web3wallet.emitSessionEvent({
topic: web3WalletSession.topic,
event: {
name: _chainId !== networkId ? "chainChanged" : "accountsChanged",
data: [_address],
},
chainId: `eip155:${_chainId}`,
});
setLoading(false);
} else {
setLoading(false);
}
@@ -632,20 +828,36 @@ function Body() {
}
};
const killSession = () => {
const killSession = async () => {
console.log("ACTION", "killSession");
if (connector) {
connector.killSession();
if (legacySignClient) {
legacySignClient.killSession();
setPeerMeta(undefined);
setLegacyPeerMeta(undefined);
setIsConnected(false);
} else if (web3wallet && web3WalletSession) {
try {
await web3wallet.disconnectSession({
topic: web3WalletSession.topic,
reason: getSdkError("USER_DISCONNECTED"),
});
} catch (e) {
console.error("killSession", e);
}
setWeb3WalletSession(undefined);
setUri("");
setIsConnected(false);
}
};
const reset = () => {
setPeerMeta(undefined);
const reset = (persistUri?: boolean) => {
setLegacyPeerMeta(undefined);
setWeb3WalletSession(undefined);
setIsConnected(false);
if (!persistUri) {
setUri("");
}
localStorage.removeItem("walletconnect");
};
@@ -844,7 +1056,7 @@ function Body() {
<Button
onClick={() => {
setLoading(false);
reset();
reset(true);
}}
>
Stop Loading
@@ -854,31 +1066,56 @@ function Body() {
</VStack>
</Center>
)}
{peerMeta && (
{legacyPeerMeta && isConnected && (
<>
<Box mt={4} fontSize={24} fontWeight="semibold">
{isConnected ? "✅ Connected To:" : "⚠ Allow to Connect"}
Connected To:
</Box>
<VStack>
<Avatar src={peerMeta.icons[0]} alt={peerMeta.name} />
<Text fontWeight="bold">{peerMeta.name}</Text>
<Text fontSize="sm">{peerMeta.description}</Text>
<Link href={peerMeta.url} textDecor="underline">
{peerMeta.url}
<Avatar
src={legacyPeerMeta.icons[0]}
alt={legacyPeerMeta.name}
/>
<Text fontWeight="bold">{legacyPeerMeta.name}</Text>
<Text fontSize="sm">{legacyPeerMeta.description}</Text>
<Link href={legacyPeerMeta.url} textDecor="underline">
{legacyPeerMeta.url}
</Link>
{!isConnected && (
<Box pt={6}>
<Button onClick={approveSession} mr={10}>
Connect
</Button>
<Button onClick={rejectSession}>Reject </Button>
</Box>
)}
{isConnected && (
<Box pt={6}>
<Button onClick={killSession}>Disconnect </Button>
</Box>
)}
<Box pt={6}>
<Button onClick={() => killSession()}>
Disconnect
</Button>
</Box>
</VStack>
</>
)}
{web3WalletSession && isConnected && (
<>
<Box mt={4} fontSize={24} fontWeight="semibold">
Connected To:
</Box>
<VStack>
<Avatar
src={web3WalletSession.peer?.metadata?.icons[0]}
alt={web3WalletSession.peer?.metadata?.name}
/>
<Text fontWeight="bold">
{web3WalletSession.peer?.metadata?.name}
</Text>
<Text fontSize="sm">
{web3WalletSession.peer?.metadata?.description}
</Text>
<Link
href={web3WalletSession.peer?.metadata?.url}
textDecor="underline"
>
{web3WalletSession.peer?.metadata?.url}
</Link>
<Box pt={6}>
<Button onClick={() => killSession()}>
Disconnect
</Button>
</Box>
</VStack>
</>
)}

883
yarn.lock

File diff suppressed because it is too large Load Diff