feat: examples dapps update (#72)

* feat: updates example dapp with ethers to use `universal-provider`

* feat: updates example dapp with web3 to use `universal-provider`

* fix: sets `paramsString` and `address` to lower case when searching for match

* chore: yarn lock

* refactor: rm `outdated` warning

* refactor: standardize event logging

* refactor: rm logs

* refactor: adds `next` to `ethers` dapp

* chore: rm old files

* refactor: adds next to web3 example

* chore: test deploy vercel

* chore: remove console log

* chore: to trigger deployment

* Revert "chore: to trigger deployment"

This reverts commit bc7712780a101f0152c20ce4eca4d8d117d8eaa7.

* feat: updates readmes

Co-authored-by: crypblizz <45455218+crypblizz8@users.noreply.github.com>
This commit is contained in:
Gancho Radkov 2022-11-08 10:32:07 +02:00 committed by GitHub
parent 7721842601
commit 49920d0453
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 6970 additions and 6239 deletions

View File

@ -1,4 +1,3 @@
REACT_APP_PROJECT_ID=39bc93c... NEXT_PUBLIC_PROJECT_ID=39bc93c...
REACT_APP_INFURA_ID=5dc0df... NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.com
REACT_APP_RELAY_URL=wss://relay.walletconnect.com

View File

@ -1,9 +1,4 @@
# React dApp (with v2 EthereumProvider + Ethers.js) # React dApp (with v2 UniversalProvider + Ethers.js)
> **⚠️ This provider-based example is currently reliant on the deprecated `2.0.0-beta.26` SDK. ⚠️**
>
> This example will be updated to be compatible with the latest v2 SDK (`2.0.0-beta.100+`) in due time.
> In the meantime, please use the up-to-date [standalone client example](../react-dapp-v2/).
🔗 Live dapp demo - https://react-dapp-v2-with-ethers.vercel.app <br /> 🔗 Live dapp demo - https://react-dapp-v2-with-ethers.vercel.app <br />
🔗 Live wallet demo - https://react-wallet.walletconnect.com/ <br /> 🔗 Live wallet demo - https://react-wallet.walletconnect.com/ <br />
@ -11,7 +6,7 @@
## Overview ## Overview
This is an example implementation of a React dApp (generated via `create-react-app`) using the v2 [`EthereumProvider`](https://docs.walletconnect.com/2.0/quick-start/dapps/ethereum-provider) together with [`Ethers.js`](https://docs.ethers.io/v5/) to: This is an example implementation of a React dApp (generated via `create-react-app`) using the v2 [`UniversalProvider`](https://github.com/WalletConnect/walletconnect-monorepo/tree/v2.0/providers/universal-provider) together with [`Ethers.js`](https://docs.ethers.io/v5/) to:
- handle pairings - handle pairings
- manage sessions - manage sessions
@ -33,9 +28,8 @@ cp .env.local.example .env.local
Your `.env.local` now contains the following environment variables: Your `.env.local` now contains the following environment variables:
- `REACT_APP_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com - `NEXT_PUBLIC_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
- `REACT_APP_INFURA_ID` (placeholder) - You can generate your own Infura ID via https://infura.io/ - `NEXT_PUBLIC_RELAY_URL` (already set)
- `REACT_APP_RELAY_URL` (already set)
## Develop ## Develop

View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -0,0 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
distDir: "build",
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
};
return config;
},
};
module.exports = nextConfig;

View File

@ -11,10 +11,10 @@
"author": "WalletConnect, Inc. <walletconnect.com>", "author": "WalletConnect, Inc. <walletconnect.com>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "next dev",
"build": "react-scripts build", "build": "next build",
"test": "react-scripts test", "start": "next start",
"eject": "react-scripts eject", "lint": "next lint",
"prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'" "prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'"
}, },
"repository": { "repository": {
@ -29,11 +29,11 @@
}, },
"dependencies": { "dependencies": {
"@ethereumjs/tx": "^3.5.0", "@ethereumjs/tx": "^3.5.0",
"@walletconnect/client": "2.0.0-beta.26", "@walletconnect/encoding": "^1.0.1",
"@walletconnect/ethereum-provider": "2.0.0-beta.26",
"@walletconnect/qrcode-modal": "^1.7.1", "@walletconnect/qrcode-modal": "^1.7.1",
"@walletconnect/types": "2.0.0-beta.26", "@walletconnect/types": "^2.1.1",
"@walletconnect/utils": "2.0.0-beta.26", "@walletconnect/universal-provider": "^2.1.1",
"@walletconnect/utils": "^2.1.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"blockies-ts": "^1.0.0", "blockies-ts": "^1.0.0",
"caip-api": "^2.0.0-beta.1", "caip-api": "^2.0.0-beta.1",
@ -43,6 +43,7 @@
"ethers": "^5.3.0", "ethers": "^5.3.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"next": "12.2.4",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
@ -51,6 +52,7 @@
"web-vitals": "^0.2.4" "web-vitals": "^0.2.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.19.4",
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -61,8 +63,8 @@
"@types/pino": "^7.0.5", "@types/pino": "^7.0.5",
"@types/prop-types": "^15.7.4", "@types/prop-types": "^15.7.4",
"@types/qr-image": "^3.2.5", "@types/qr-image": "^3.2.5",
"@types/react": "^17.0.38", "@types/react": "18.0.15",
"@types/react-dom": "^17.0.11", "@types/react-dom": "18.0.6",
"@types/styled-components": "^5.1.21", "@types/styled-components": "^5.1.21",
"prettier": "^2.5.1" "prettier": "^2.5.1"
}, },
@ -72,16 +74,9 @@
"react-app/jest" "react-app/jest"
] ]
}, },
"browserslist": { "browserslist": [
"production": [ ">0.2%",
">0.2%", "not dead",
"not dead", "not op_mini all"
"not op_mini all" ]
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
} }

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import logo from "../assets/walletconnect.png";
const SBannerWrapper = styled.div` const SBannerWrapper = styled.div`
display: flex; display: flex;
@ -11,7 +10,7 @@ const SBannerWrapper = styled.div`
const SBanner = styled.div` const SBanner = styled.div`
width: 275px; width: 275px;
height: 45px; height: 45px;
background: url(${logo}) no-repeat; background: url(/assets/walletconnect.png) no-repeat;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
`; `;

View File

@ -50,7 +50,7 @@ const SActiveSession = styled(SActiveAccount as any)`
interface HeaderProps { interface HeaderProps {
ping: () => Promise<void>; ping: () => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
session: SessionTypes.Created | undefined; session: SessionTypes.Struct | undefined;
} }
const Header = (props: HeaderProps) => { const Header = (props: HeaderProps) => {

View File

@ -6,7 +6,7 @@ import { PairingTypes } from "@walletconnect/types";
import Peer from "./Peer"; import Peer from "./Peer";
interface PairingProps { interface PairingProps {
pairing: PairingTypes.Settled; pairing: PairingTypes.Struct;
onClick?: any; onClick?: any;
} }
@ -16,14 +16,12 @@ const SPairingContainer = styled.div`
`; `;
const Pairing = (props: PairingProps) => { const Pairing = (props: PairingProps) => {
const { const { peerMetadata } = props.pairing;
state: { metadata },
} = props.pairing;
return ( return (
<SPairingContainer onClick={props.onClick}> <SPairingContainer onClick={props.onClick}>
<div> <div>
{typeof metadata !== "undefined" ? ( {typeof peerMetadata !== "undefined" ? (
<Peer oneLiner metadata={metadata} /> <Peer oneLiner metadata={peerMetadata} />
) : ( ) : (
<div>{`Unknown`}</div> <div>{`Unknown`}</div>
)} )}

View File

@ -1,6 +1,6 @@
import { CoreTypes } from "@walletconnect/types";
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { AppMetadata } from "@walletconnect/types";
import { colors, fonts } from "../styles"; import { colors, fonts } from "../styles";
const SPeerOneLiner = styled.div` const SPeerOneLiner = styled.div`
@ -53,7 +53,7 @@ const SName = styled(SCenter as any)`
interface PeerProps { interface PeerProps {
oneLiner?: boolean; oneLiner?: boolean;
metadata: AppMetadata; metadata: CoreTypes.Metadata;
} }
const Peer = (props: PeerProps) => const Peer = (props: PeerProps) =>

View File

@ -19,14 +19,17 @@ export const DEFAULT_TEST_CHAINS = [
export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS]; export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID; export const DEFAULT_PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID;
export const DEFAULT_INFURA_ID = process.env.REACT_APP_INFURA_ID; export const DEFAULT_RELAY_URL = process.env.NEXT_PUBLIC_RELAY_URL;
export const DEFAULT_RELAY_URL = process.env.REACT_APP_RELAY_URL;
export const DEFAULT_EIP155_METHODS = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"]; export const DEFAULT_EIP155_METHODS = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"];
export enum DEFAULT_EIP_155_EVENTS {
ETH_CHAIN_CHANGED = "chainChanged",
ETH_ACCOUNTS_CHANGED = "accountsChanged",
}
export const DEFAULT_LOGGER = "debug"; export const DEFAULT_LOGGER = "debug";
export const DEFAULT_APP_METADATA = { export const DEFAULT_APP_METADATA = {

View File

@ -1,7 +1,3 @@
import Client, { CLIENT_EVENTS } from "@walletconnect/client";
import EthereumProvider from "@walletconnect/ethereum-provider";
import { PairingTypes, SessionTypes } from "@walletconnect/types";
import QRCodeModal from "@walletconnect/qrcode-modal";
import { import {
createContext, createContext,
ReactNode, ReactNode,
@ -11,31 +7,31 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import {
DEFAULT_INFURA_ID, import QRCodeModal from "@walletconnect/qrcode-modal";
DEFAULT_LOGGER, import { apiGetChainNamespace, ChainsMap } from "caip-api";
DEFAULT_PROJECT_ID, import UniversalProvider from "@walletconnect/universal-provider";
DEFAULT_RELAY_URL, import { PairingTypes, SessionTypes } from "@walletconnect/types";
} from "../constants"; import Client from "@walletconnect/sign-client";
import { DEFAULT_LOGGER, DEFAULT_PROJECT_ID, DEFAULT_RELAY_URL } from "../constants";
import { providers, utils } from "ethers"; import { providers, utils } from "ethers";
import { AccountBalances, ChainNamespaces, getAllChainNamespaces } from "../helpers"; import { AccountBalances, ChainNamespaces, getAllChainNamespaces } from "../helpers";
import { apiGetChainNamespace, ChainsMap } from "caip-api";
/** /**
* Types * Types
*/ */
interface IContext { interface IContext {
client: Client | undefined; client: Client | undefined;
session: SessionTypes.Created | undefined; session: SessionTypes.Struct | undefined;
connect: (caipChainId: string, pairing?: { topic: string }) => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
isInitializing: boolean; isInitializing: boolean;
chain: string; chain: string;
pairings: string[]; pairings: PairingTypes.Struct[];
accounts: string[]; accounts: string[];
balances: AccountBalances; balances: AccountBalances;
isFetchingBalances: boolean; isFetchingBalances: boolean;
chainData: ChainNamespaces; chainData: ChainNamespaces;
onEnable: (chainId: string) => Promise<void>;
web3Provider?: providers.Web3Provider; web3Provider?: providers.Web3Provider;
} }
@ -44,15 +40,18 @@ interface IContext {
*/ */
export const ClientContext = createContext<IContext>({} as IContext); export const ClientContext = createContext<IContext>({} as IContext);
/**
* Provider
*/
/** /**
* Provider * Provider
*/ */
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) { export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
const [client, setClient] = useState<Client>(); const [client, setClient] = useState<Client>();
const [pairings, setPairings] = useState<string[]>([]); const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
const [session, setSession] = useState<SessionTypes.Created>(); const [session, setSession] = useState<SessionTypes.Struct>();
const [ethereumProvider, setEthereumProvider] = useState<EthereumProvider>(); const [ethereumProvider, setEthereumProvider] = useState<UniversalProvider>();
const [web3Provider, setWeb3Provider] = useState<providers.Web3Provider>(); const [web3Provider, setWeb3Provider] = useState<providers.Web3Provider>();
const [isFetchingBalances, setIsFetchingBalances] = useState(false); const [isFetchingBalances, setIsFetchingBalances] = useState(false);
@ -96,33 +95,46 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
throw new Error("ethereumProvider is not initialized"); throw new Error("ethereumProvider is not initialized");
} }
await ethereumProvider.disconnect(); await ethereumProvider.disconnect();
resetApp();
}, [ethereumProvider]); }, [ethereumProvider]);
const _subscribeToClientEvents = useCallback(async (_client: Client) => { const _subscribeToProviderEvents = useCallback(async (_client: UniversalProvider) => {
if (typeof _client === "undefined") { if (typeof _client === "undefined") {
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
_client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => { _client.on("display_uri", async (uri: string) => {
const { uri } = proposal.signal.params;
console.log("EVENT", "QR Code Modal open"); console.log("EVENT", "QR Code Modal open");
QRCodeModal.open(uri, () => { QRCodeModal.open(uri, () => {
console.log("EVENT", "QR Code Modal closed"); console.log("EVENT", "QR Code Modal closed");
}); });
}); });
_client.on(CLIENT_EVENTS.pairing.created, async () => { // Subscribe to session ping
setPairings(_client.pairing.topics); _client.on("session_ping", ({ id, topic }: { id: number; topic: string }) => {
console.log("EVENT", "session_ping");
console.log(id, topic);
}); });
_client.on(CLIENT_EVENTS.session.updated, (updatedSession: SessionTypes.Settled) => { // Subscribe to session event
console.log("EVENT", "session_updated"); _client.on("session_event", ({ event, chainId }: { event: any; chainId: string }) => {
setAccounts(updatedSession.state.accounts); console.log("EVENT", "session_event");
setSession(updatedSession); console.log(event, chainId);
}); });
_client.on(CLIENT_EVENTS.session.deleted, () => { // Subscribe to session update
_client.on(
"session_update",
({ topic, session }: { topic: string; session: SessionTypes.Struct }) => {
console.log("EVENT", "session_updated");
setSession(session);
},
);
// Subscribe to session delete
_client.on("session_delete", ({ id, topic }: { id: number; topic: string }) => {
console.log("EVENT", "session_deleted"); console.log("EVENT", "session_deleted");
console.log(id, topic);
resetApp(); resetApp();
}); });
}, []); }, []);
@ -131,24 +143,30 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
try { try {
setIsInitializing(true); setIsInitializing(true);
const _client = await Client.init({ const provider = await UniversalProvider.init({
projectId: DEFAULT_PROJECT_ID,
logger: DEFAULT_LOGGER, logger: DEFAULT_LOGGER,
relayUrl: DEFAULT_RELAY_URL, relayUrl: DEFAULT_RELAY_URL,
projectId: DEFAULT_PROJECT_ID,
}); });
setClient(_client); setEthereumProvider(provider);
await _subscribeToClientEvents(_client); setClient(provider.client);
await _subscribeToProviderEvents(provider);
} catch (err) { } catch (err) {
throw err; throw err;
} finally { } finally {
setIsInitializing(false); setIsInitializing(false);
} }
}, [_subscribeToClientEvents]); }, [_subscribeToProviderEvents]);
const onEnable = useCallback( const createWeb3Provider = useCallback((ethereumProvider: UniversalProvider) => {
async (caipChainId: string) => { const web3Provider = new providers.Web3Provider(ethereumProvider);
if (!client) { setWeb3Provider(web3Provider);
}, []);
const connect = useCallback(
async (caipChainId: string, pairing?: { topic: string }) => {
if (!ethereumProvider) {
throw new ReferenceError("WalletConnect Client is not initialized."); throw new ReferenceError("WalletConnect Client is not initialized.");
} }
@ -164,33 +182,97 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
{}, {},
); );
// Create WalletConnect Provider const session = await ethereumProvider.connect({
const ethereumProvider = new EthereumProvider({ namespaces: {
chainId: Number(chainId), eip155: {
rpc: { methods: [
infuraId: DEFAULT_INFURA_ID, "eth_sendTransaction",
custom: customRpcs, "eth_signTransaction",
"eth_sign",
"personal_sign",
"eth_signTypedData",
],
chains: [`eip155:${chainId}`],
events: ["chainChanged", "accountsChanged"],
rpcMap: customRpcs,
},
}, },
client, pairingTopic: pairing?.topic,
}); });
const web3Provider = new providers.Web3Provider(ethereumProvider);
console.log(ethereumProvider);
setEthereumProvider(ethereumProvider);
setWeb3Provider(web3Provider);
createWeb3Provider(ethereumProvider);
const _accounts = await ethereumProvider.enable(); const _accounts = await ethereumProvider.enable();
const _session = await client.session.get(client.session.topics[0]); console.log("_accounts", _accounts);
setAccounts(_accounts); setAccounts(_accounts);
setSession(_session); setSession(session);
setChain(caipChainId); setChain(caipChainId);
QRCodeModal.close();
},
[ethereumProvider, chainData.eip155, createWeb3Provider],
);
const onSessionConnected = useCallback(
async (_session: SessionTypes.Struct) => {
if (!ethereumProvider) {
throw new ReferenceError("EthereumProvider is not initialized.");
}
const allNamespaceAccounts = Object.values(_session.namespaces)
.map(namespace => namespace.accounts)
.flat();
const allNamespaceChains = Object.keys(_session.namespaces);
const chainData = allNamespaceAccounts[0].split(":");
const caipChainId = `${chainData[0]}:${chainData[1]}`;
console.log("restored caipChainId", caipChainId);
setChain(caipChainId);
setSession(_session);
setAccounts(allNamespaceAccounts.map(account => account.split(":")[2]));
console.log("RESTORED", allNamespaceChains, allNamespaceAccounts);
createWeb3Provider(ethereumProvider);
},
[ethereumProvider, createWeb3Provider],
);
const _checkForPersistedSession = useCallback(
async (provider: UniversalProvider) => {
if (typeof provider === "undefined") {
throw new Error("WalletConnect is not initialized");
}
const pairings = provider.client.pairing.getAll({ active: true });
// populates existing pairings to state
setPairings(pairings);
console.log("RESTORED PAIRINGS: ", pairings);
if (typeof session !== "undefined") return;
// populates (the last) existing session to state
if (ethereumProvider?.session) {
const _session = ethereumProvider?.session;
console.log("RESTORED SESSION:", _session);
await onSessionConnected(_session);
return _session;
}
},
[session, ethereumProvider, onSessionConnected],
);
useEffect(() => {
loadChainData();
}, []);
useEffect(() => {
if (!client) {
createClient();
}
}, [client, createClient]);
useEffect(() => {
const fetchBalances = async () => {
if (!web3Provider || !accounts) return;
try { try {
setIsFetchingBalances(true); setIsFetchingBalances(true);
const _balances = await Promise.all( const _balances = await Promise.all(
_accounts.map(async account => { accounts.map(async account => {
const balance = await web3Provider.getBalance(account); const balance = await web3Provider.getBalance(account);
return { return {
account, account,
@ -212,51 +294,22 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
} finally { } finally {
setIsFetchingBalances(false); setIsFetchingBalances(false);
} }
};
QRCodeModal.close(); fetchBalances();
}, }, [web3Provider, accounts]);
[client, chainData.eip155],
);
const _checkForPersistedSession = useCallback(
async (_client: Client) => {
if (typeof _client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
// populates existing pairings to state
setPairings(_client.pairing.topics);
if (typeof session !== "undefined") return;
// populates existing session to state (assume only the top one)
if (_client.session.topics.length) {
const _session = await _client.session.get(_client.session.topics[0]);
const [namespace, chainId] = _session.state.accounts[0].split(":");
const caipChainId = `${namespace}:${chainId}`;
onEnable(caipChainId);
}
},
[session, onEnable],
);
useEffect(() => {
loadChainData();
}, []);
useEffect(() => {
if (!client) {
createClient();
}
}, [client, createClient]);
useEffect(() => { useEffect(() => {
const getPersistedSession = async () => { const getPersistedSession = async () => {
if (client && !hasCheckedPersistedSession) { if (!ethereumProvider) return;
await _checkForPersistedSession(client); await _checkForPersistedSession(ethereumProvider);
setHasCheckedPersistedSession(true); setHasCheckedPersistedSession(true);
}
}; };
getPersistedSession(); if (ethereumProvider && chainData && !hasCheckedPersistedSession) {
}, [client, _checkForPersistedSession, hasCheckedPersistedSession]); getPersistedSession();
}
}, [ethereumProvider, chainData, _checkForPersistedSession, hasCheckedPersistedSession]);
const value = useMemo( const value = useMemo(
() => ({ () => ({
@ -269,8 +322,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
client, client,
session, session,
disconnect, disconnect,
connect,
chainData, chainData,
onEnable,
web3Provider, web3Provider,
}), }),
[ [
@ -283,8 +336,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
client, client,
session, session,
disconnect, disconnect,
connect,
chainData, chainData,
onEnable,
web3Provider, web3Provider,
], ],
); );

View File

@ -190,6 +190,7 @@ export function setLocaleStorageTestnetFlag(value: boolean): void {
} }
export function getLocalStorageTestnetFlag(): boolean { export function getLocalStorageTestnetFlag(): boolean {
if (typeof window === "undefined") return false;
let value = INITIAL_STATE_TESTNET_DEFAULT; let value = INITIAL_STATE_TESTNET_DEFAULT;
const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET); const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET);
if (!persisted) { if (!persisted) {

View File

@ -1,27 +0,0 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { createGlobalStyle } from "styled-components";
import { ClientContextProvider } from "./contexts/ClientContext";
import App from "./App";
import { globalStyle } from "./styles";
const GlobalStyle = createGlobalStyle`
${globalStyle}
`;
declare global {
// tslint:disable-next-line
interface Window {
blockies: any;
}
}
ReactDOM.render(
<>
<GlobalStyle />
<ClientContextProvider>
<App />
</ClientContextProvider>
</>,
document.getElementById("root"),
);

View File

@ -9,7 +9,7 @@ import { STable } from "../components/shared";
import { SModalContainer, SModalTitle } from "./shared"; import { SModalContainer, SModalTitle } from "./shared";
interface PairingModalProps { interface PairingModalProps {
pairings: PairingTypes.Settled[]; pairings: PairingTypes.Struct[];
connect: (pairing?: { topic: string }) => Promise<void>; connect: (pairing?: { topic: string }) => Promise<void>;
} }

View File

@ -0,0 +1,3 @@
export default function FourOhFour() {
return <h1>404 Page Not Found</h1>;
}

View File

@ -0,0 +1,22 @@
import type { AppProps } from "next/app";
import { createGlobalStyle } from "styled-components";
import { ClientContextProvider } from "../contexts/ClientContext";
import { globalStyle } from "../styles";
const GlobalStyle = createGlobalStyle`
${globalStyle}
`;
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<GlobalStyle />
<ClientContextProvider>
<Component {...pageProps} />
</ClientContextProvider>
</>
);
}
export default MyApp;

View File

@ -0,0 +1,3 @@
export default function Error() {
return <div>An error as occured</div>;
}

View File

@ -1,26 +1,27 @@
import type { NextPage } from "next";
import React, { useState } from "react"; import React, { useState } from "react";
import { version } from "@walletconnect/client/package.json"; import { version } from "@walletconnect/universal-provider/package.json";
import * as encoding from "@walletconnect/encoding"; import * as encoding from "@walletconnect/encoding";
import { BigNumber, utils } from "ethers"; import { BigNumber, utils } from "ethers";
import { TypedDataField } from "@ethersproject/abstract-signer"; import { TypedDataField } from "@ethersproject/abstract-signer";
import { Transaction } from "@ethereumjs/tx"; import { Transaction } from "@ethereumjs/tx";
import Banner from "./components/Banner"; import Banner from "./../components/Banner";
import Blockchain from "./components/Blockchain"; import Blockchain from "./../components/Blockchain";
import Column from "./components/Column"; import Column from "./../components/Column";
import Header from "./components/Header"; import Header from "./../components/Header";
import Modal from "./components/Modal"; import Modal from "./../components/Modal";
import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants"; import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./../constants";
import { import {
AccountAction, AccountAction,
eip712, eip712,
formatTestTransaction, formatTestTransaction,
getLocalStorageTestnetFlag, getLocalStorageTestnetFlag,
setLocaleStorageTestnetFlag, setLocaleStorageTestnetFlag,
} from "./helpers"; } from "./../helpers";
import Toggle from "./components/Toggle"; import Toggle from "./../components/Toggle";
import RequestModal from "./modals/RequestModal"; import RequestModal from "./../modals/RequestModal";
import PingModal from "./modals/PingModal"; import PingModal from "./../modals/PingModal";
import { import {
SAccounts, SAccounts,
SAccountsContainer, SAccountsContainer,
@ -29,8 +30,8 @@ import {
SLanding, SLanding,
SLayout, SLayout,
SToggleContainer, SToggleContainer,
} from "./components/app"; } from "./../components/app";
import { useWalletConnectClient } from "./contexts/ClientContext"; import { useWalletConnectClient } from "./../contexts/ClientContext";
interface IFormattedRpcResponse { interface IFormattedRpcResponse {
method: string; method: string;
@ -39,7 +40,7 @@ interface IFormattedRpcResponse {
result: string; result: string;
} }
export default function App() { const Home: NextPage = () => {
const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag()); const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag());
const [isRpcRequestPending, setIsRpcRequestPending] = useState(false); const [isRpcRequestPending, setIsRpcRequestPending] = useState(false);
const [rpcResult, setRpcResult] = useState<IFormattedRpcResponse | null>(); const [rpcResult, setRpcResult] = useState<IFormattedRpcResponse | null>();
@ -61,7 +62,7 @@ export default function App() {
chainData, chainData,
isFetchingBalances, isFetchingBalances,
isInitializing, isInitializing,
onEnable, connect,
web3Provider, web3Provider,
} = useWalletConnectClient(); } = useWalletConnectClient();
@ -73,10 +74,13 @@ export default function App() {
throw new Error("WalletConnect Client is not initialized"); throw new Error("WalletConnect Client is not initialized");
} }
if (typeof session === "undefined") {
throw new Error("Session is not connected");
}
try { try {
setIsRpcRequestPending(true); setIsRpcRequestPending(true);
const _session = await client.session.get(client.session.topics[0]); await client.ping({ topic: session.topic });
await client.session.ping(_session.topic);
setRpcResult({ setRpcResult({
address: "", address: "",
method: "ping", method: "ping",
@ -274,16 +278,6 @@ export default function App() {
<Banner /> <Banner />
<h6> <h6>
<span>{`Using v${version || "2.0.0-beta"}`}</span> <span>{`Using v${version || "2.0.0-beta"}`}</span>
<sup>
(
<a
style={{ textDecoration: "underline" }}
href="https://github.com/WalletConnect/web-examples/tree/main/dapps/react-dapp-v2-with-ethers"
>
outdated
</a>{" "}
)
</sup>
</h6> </h6>
<SButtonContainer> <SButtonContainer>
<h6>Select an Ethereum chain:</h6> <h6>Select an Ethereum chain:</h6>
@ -292,7 +286,7 @@ export default function App() {
<Toggle active={isTestnet} onClick={toggleTestnets} /> <Toggle active={isTestnet} onClick={toggleTestnets} />
</SToggleContainer> </SToggleContainer>
{chainOptions.map(chainId => ( {chainOptions.map(chainId => (
<Blockchain key={chainId} chainId={chainId} chainData={chainData} onClick={onEnable} /> <Blockchain key={chainId} chainId={chainId} chainData={chainData} onClick={connect} />
))} ))}
</SButtonContainer> </SButtonContainer>
</SLanding> </SLanding>
@ -330,4 +324,6 @@ export default function App() {
</Modal> </Modal>
</SLayout> </SLayout>
); );
} };
export default Home;

View File

@ -1,26 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noEmit": true,
"esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "jsx": "preserve",
"jsx": "react-jsx" "incremental": true
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"src" "exclude": ["node_modules"]
]
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
REACT_APP_PROJECT_ID=39bc93... NEXT_PUBLIC_PROJECT_ID=39bc93c...
REACT_APP_INFURA_ID=5dc0df... NEXT_PUBLIC_RELAY_URL=wss://relay.walletconnect.com
REACT_APP_RELAY_URL=wss://relay.dev.walletconnect.com

View File

@ -1,9 +1,4 @@
# React dApp (with v2 EthereumProvider + web3.js) # React dApp (with v2 UniversalProvider + web3.js)
> **⚠️ This provider-based example is currently reliant on the deprecated `2.0.0-beta.26` SDK. ⚠️**
>
> This example will be updated to be compatible with the latest v2 SDK (`2.0.0-beta.100+`) in due time.
> In the meantime, please use the up-to-date [standalone client example](../react-dapp-v2/).
🔗 Live dapp demo - https://react-dapp-v2-with-web3js.vercel.app <br /> 🔗 Live dapp demo - https://react-dapp-v2-with-web3js.vercel.app <br />
🔗 Live wallet demo - https://react-wallet.walletconnect.com/ <br /> 🔗 Live wallet demo - https://react-wallet.walletconnect.com/ <br />
@ -11,7 +6,7 @@
## Overview ## Overview
This is an example implementation of a React dApp (generated via `create-react-app`) using the v2 [`EthereumProvider`](https://docs.walletconnect.com/2.0/quick-start/dapps/ethereum-provider) together with [`web3.js`](https://web3js.readthedocs.io/) to: This is an example implementation of a React dApp (generated via `create-react-app`) using the v2 [`UniversalProvider`](https://github.com/WalletConnect/walletconnect-monorepo/tree/v2.0/providers/universal-provider) together with [`web3.js`](https://web3js.readthedocs.io/) to:
- handle pairings - handle pairings
- manage sessions - manage sessions
@ -33,9 +28,8 @@ cp .env.local.example .env.local
Your `.env.local` now contains the following environment variables: Your `.env.local` now contains the following environment variables:
- `REACT_APP_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com - `NEXT_PUBLIC_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
- `REACT_APP_INFURA_ID` (placeholder) - You can generate your own Infura ID via https://infura.io/ - `NEXT_PUBLIC_RELAY_URL` (already set)
- `REACT_APP_RELAY_URL` (already set)
## Develop ## Develop

View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -0,0 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
distDir: "build",
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
};
return config;
},
};
module.exports = nextConfig;

View File

@ -11,10 +11,10 @@
"author": "WalletConnect, Inc. <walletconnect.com>", "author": "WalletConnect, Inc. <walletconnect.com>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "next dev",
"build": "react-scripts build", "build": "next build",
"test": "react-scripts test", "start": "next start",
"eject": "react-scripts eject", "lint": "next lint",
"prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'" "prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'"
}, },
"repository": { "repository": {
@ -29,11 +29,11 @@
}, },
"dependencies": { "dependencies": {
"@ethereumjs/tx": "^3.5.0", "@ethereumjs/tx": "^3.5.0",
"@walletconnect/client": "2.0.0-beta.26", "@walletconnect/encoding": "^1.0.1",
"@walletconnect/ethereum-provider": "2.0.0-beta.26", "@walletconnect/universal-provider": "2.1.1",
"@walletconnect/qrcode-modal": "^1.7.1", "@walletconnect/qrcode-modal": "^1.7.1",
"@walletconnect/types": "2.0.0-beta.26", "@walletconnect/types": "2.1.1",
"@walletconnect/utils": "2.0.0-beta.26", "@walletconnect/utils": "2.1.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"blockies-ts": "^1.0.0", "blockies-ts": "^1.0.0",
"caip-api": "^2.0.0-beta.1", "caip-api": "^2.0.0-beta.1",
@ -43,6 +43,7 @@
"ethers": "^5.3.0", "ethers": "^5.3.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"next": "12.2.4",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
@ -52,6 +53,7 @@
"web3": "^1.7.0" "web3": "^1.7.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.19.4",
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -62,8 +64,8 @@
"@types/pino": "^7.0.5", "@types/pino": "^7.0.5",
"@types/prop-types": "^15.7.4", "@types/prop-types": "^15.7.4",
"@types/qr-image": "^3.2.5", "@types/qr-image": "^3.2.5",
"@types/react": "^17.0.38", "@types/react": "18.0.15",
"@types/react-dom": "^17.0.11", "@types/react-dom": "18.0.6",
"@types/styled-components": "^5.1.21", "@types/styled-components": "^5.1.21",
"prettier": "^2.5.1" "prettier": "^2.5.1"
}, },
@ -73,16 +75,9 @@
"react-app/jest" "react-app/jest"
] ]
}, },
"browserslist": { "browserslist": [
"production": [ ">0.2%",
">0.2%", "not dead",
"not dead", "not op_mini all"
"not op_mini all" ]
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import logo from "../assets/walletconnect.png";
const SBannerWrapper = styled.div` const SBannerWrapper = styled.div`
display: flex; display: flex;
@ -11,7 +10,7 @@ const SBannerWrapper = styled.div`
const SBanner = styled.div` const SBanner = styled.div`
width: 275px; width: 275px;
height: 45px; height: 45px;
background: url(${logo}) no-repeat; background: url(/assets/walletconnect.png) no-repeat;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
`; `;

View File

@ -50,7 +50,7 @@ const SActiveSession = styled(SActiveAccount as any)`
interface HeaderProps { interface HeaderProps {
ping: () => Promise<void>; ping: () => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
session: SessionTypes.Created | undefined; session: SessionTypes.Struct | undefined;
} }
const Header = (props: HeaderProps) => { const Header = (props: HeaderProps) => {

View File

@ -6,7 +6,7 @@ import { PairingTypes } from "@walletconnect/types";
import Peer from "./Peer"; import Peer from "./Peer";
interface PairingProps { interface PairingProps {
pairing: PairingTypes.Settled; pairing: PairingTypes.Struct;
onClick?: any; onClick?: any;
} }
@ -16,14 +16,12 @@ const SPairingContainer = styled.div`
`; `;
const Pairing = (props: PairingProps) => { const Pairing = (props: PairingProps) => {
const { const { peerMetadata } = props.pairing;
state: { metadata },
} = props.pairing;
return ( return (
<SPairingContainer onClick={props.onClick}> <SPairingContainer onClick={props.onClick}>
<div> <div>
{typeof metadata !== "undefined" ? ( {typeof peerMetadata !== "undefined" ? (
<Peer oneLiner metadata={metadata} /> <Peer oneLiner metadata={peerMetadata} />
) : ( ) : (
<div>{`Unknown`}</div> <div>{`Unknown`}</div>
)} )}

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { AppMetadata } from "@walletconnect/types"; import { CoreTypes } from "@walletconnect/types";
import { colors, fonts } from "../styles"; import { colors, fonts } from "../styles";
const SPeerOneLiner = styled.div` const SPeerOneLiner = styled.div`
@ -53,7 +53,7 @@ const SName = styled(SCenter as any)`
interface PeerProps { interface PeerProps {
oneLiner?: boolean; oneLiner?: boolean;
metadata: AppMetadata; metadata: CoreTypes.Metadata;
} }
const Peer = (props: PeerProps) => const Peer = (props: PeerProps) =>

View File

@ -19,11 +19,9 @@ export const DEFAULT_TEST_CHAINS = [
export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS]; export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID; export const DEFAULT_PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID;
export const DEFAULT_INFURA_ID = process.env.REACT_APP_INFURA_ID; export const DEFAULT_RELAY_URL = process.env.NEXT_PUBLIC_RELAY_URL;
export const DEFAULT_RELAY_URL = process.env.REACT_APP_RELAY_URL;
export const DEFAULT_EIP155_METHODS = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"]; export const DEFAULT_EIP155_METHODS = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"];

View File

@ -1,7 +1,3 @@
import Client, { CLIENT_EVENTS } from "@walletconnect/client";
import EthereumProvider from "@walletconnect/ethereum-provider";
import { PairingTypes, SessionTypes } from "@walletconnect/types";
import QRCodeModal from "@walletconnect/qrcode-modal";
import { import {
createContext, createContext,
ReactNode, ReactNode,
@ -11,33 +7,31 @@ import {
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import Web3 from "web3";
import QRCodeModal from "@walletconnect/qrcode-modal";
import { apiGetChainNamespace, ChainsMap } from "caip-api"; import { apiGetChainNamespace, ChainsMap } from "caip-api";
import UniversalProvider from "@walletconnect/universal-provider";
import { import Client from "@walletconnect/sign-client";
DEFAULT_INFURA_ID, import Web3 from "web3";
DEFAULT_LOGGER, import { DEFAULT_LOGGER, DEFAULT_PROJECT_ID, DEFAULT_RELAY_URL } from "../constants";
DEFAULT_PROJECT_ID,
DEFAULT_RELAY_URL,
} from "../constants";
import { AccountBalances, ChainNamespaces, getAllChainNamespaces } from "../helpers";
import { utils } from "ethers"; import { utils } from "ethers";
import { AccountBalances, ChainNamespaces, getAllChainNamespaces } from "../helpers";
import { PairingTypes, SessionTypes } from "@walletconnect/types";
/** /**
* Types * Types
*/ */
interface IContext { interface IContext {
client: Client | undefined; client: Client | undefined;
session: SessionTypes.Created | undefined; session: SessionTypes.Struct | undefined;
connect: (caipChainId: string, pairing?: { topic: string }) => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
isInitializing: boolean; isInitializing: boolean;
chain: string; chain: string;
pairings: string[]; pairings: PairingTypes.Struct[];
accounts: string[]; accounts: string[];
balances: AccountBalances; balances: AccountBalances;
isFetchingBalances: boolean; isFetchingBalances: boolean;
chainData: ChainNamespaces; chainData: ChainNamespaces;
onEnable: (chainId: string) => Promise<void>;
web3Provider?: Web3; web3Provider?: Web3;
} }
@ -51,10 +45,10 @@ export const ClientContext = createContext<IContext>({} as IContext);
*/ */
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) { export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
const [client, setClient] = useState<Client>(); const [client, setClient] = useState<Client>();
const [pairings, setPairings] = useState<string[]>([]); const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
const [session, setSession] = useState<SessionTypes.Created>(); const [session, setSession] = useState<SessionTypes.Struct>();
const [ethereumProvider, setEthereumProvider] = useState<EthereumProvider>(); const [ethereumProvider, setEthereumProvider] = useState<UniversalProvider>();
const [web3Provider, setWeb3Provider] = useState<Web3>(); const [web3Provider, setWeb3Provider] = useState<Web3>();
const [isFetchingBalances, setIsFetchingBalances] = useState(false); const [isFetchingBalances, setIsFetchingBalances] = useState(false);
@ -98,33 +92,46 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
throw new Error("ethereumProvider is not initialized"); throw new Error("ethereumProvider is not initialized");
} }
await ethereumProvider.disconnect(); await ethereumProvider.disconnect();
resetApp();
}, [ethereumProvider]); }, [ethereumProvider]);
const _subscribeToClientEvents = useCallback(async (_client: Client) => { const _subscribeToProviderEvents = useCallback(async (_client: UniversalProvider) => {
if (typeof _client === "undefined") { if (typeof _client === "undefined") {
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
_client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => { _client.on("display_uri", async (uri: string) => {
const { uri } = proposal.signal.params;
console.log("EVENT", "QR Code Modal open"); console.log("EVENT", "QR Code Modal open");
QRCodeModal.open(uri, () => { QRCodeModal.open(uri, () => {
console.log("EVENT", "QR Code Modal closed"); console.log("EVENT", "QR Code Modal closed");
}); });
}); });
_client.on(CLIENT_EVENTS.pairing.created, async () => { // Subscribe to session ping
setPairings(_client.pairing.topics); _client.on("session_ping", ({ id, topic }: { id: number; topic: string }) => {
console.log("EVENT", "session_ping");
console.log(id, topic);
}); });
_client.on(CLIENT_EVENTS.session.updated, (updatedSession: SessionTypes.Settled) => { // Subscribe to session event
console.log("EVENT", "session_updated"); _client.on("session_event", ({ event, chainId }: { event: any; chainId: string }) => {
setAccounts(updatedSession.state.accounts); console.log("EVENT", "session_event");
setSession(updatedSession); console.log(event, chainId);
}); });
_client.on(CLIENT_EVENTS.session.deleted, () => { // Subscribe to session update
_client.on(
"session_update",
({ topic, session }: { topic: string; session: SessionTypes.Struct }) => {
console.log("EVENT", "session_updated");
setSession(session);
},
);
// Subscribe to session delete
_client.on("session_delete", ({ id, topic }: { id: number; topic: string }) => {
console.log("EVENT", "session_deleted"); console.log("EVENT", "session_deleted");
console.log(id, topic);
resetApp(); resetApp();
}); });
}, []); }, []);
@ -133,24 +140,30 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
try { try {
setIsInitializing(true); setIsInitializing(true);
const _client = await Client.init({ const provider = await UniversalProvider.init({
projectId: DEFAULT_PROJECT_ID,
logger: DEFAULT_LOGGER, logger: DEFAULT_LOGGER,
relayUrl: DEFAULT_RELAY_URL, relayUrl: DEFAULT_RELAY_URL,
projectId: DEFAULT_PROJECT_ID,
}); });
setClient(_client); setEthereumProvider(provider);
await _subscribeToClientEvents(_client); setClient(provider.client);
await _subscribeToProviderEvents(provider);
} catch (err) { } catch (err) {
throw err; throw err;
} finally { } finally {
setIsInitializing(false); setIsInitializing(false);
} }
}, [_subscribeToClientEvents]); }, [_subscribeToProviderEvents]);
const onEnable = useCallback( const createWeb3Provider = useCallback((ethereumProvider: UniversalProvider) => {
async (caipChainId: string) => { const web3Provider = new Web3(ethereumProvider);
if (!client) { setWeb3Provider(web3Provider);
}, []);
const connect = useCallback(
async (caipChainId: string, pairing?: { topic: string }) => {
if (!ethereumProvider) {
throw new ReferenceError("WalletConnect Client is not initialized."); throw new ReferenceError("WalletConnect Client is not initialized.");
} }
@ -166,35 +179,97 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
{}, {},
); );
// Create WalletConnect Provider const session = await ethereumProvider.connect({
const ethereumProvider = new EthereumProvider({ namespaces: {
chainId: Number(chainId), eip155: {
rpc: { methods: [
infuraId: DEFAULT_INFURA_ID, "eth_sendTransaction",
custom: customRpcs, "eth_signTransaction",
"eth_sign",
"personal_sign",
"eth_signTypedData",
],
chains: [`eip155:${chainId}`],
events: ["chainChanged", "accountsChanged"],
rpcMap: customRpcs,
},
}, },
client, pairingTopic: pairing?.topic,
}); });
const web3Provider = new Web3(ethereumProvider); createWeb3Provider(ethereumProvider);
console.log(ethereumProvider);
console.log(web3Provider);
setEthereumProvider(ethereumProvider);
setWeb3Provider(web3Provider);
const _accounts = await ethereumProvider.enable(); const _accounts = await ethereumProvider.enable();
const _session = await client.session.get(client.session.topics[0]); console.log("_accounts", _accounts);
setAccounts(_accounts); setAccounts(_accounts);
setSession(_session); setSession(session);
setChain(caipChainId); setChain(caipChainId);
QRCodeModal.close();
},
[ethereumProvider, chainData.eip155, createWeb3Provider],
);
const onSessionConnected = useCallback(
async (_session: SessionTypes.Struct) => {
if (!ethereumProvider) {
throw new ReferenceError("EthereumProvider is not initialized.");
}
const allNamespaceAccounts = Object.values(_session.namespaces)
.map(namespace => namespace.accounts)
.flat();
const allNamespaceChains = Object.keys(_session.namespaces);
const chainData = allNamespaceAccounts[0].split(":");
const caipChainId = `${chainData[0]}:${chainData[1]}`;
console.log("restored caipChainId", caipChainId);
setChain(caipChainId);
setSession(_session);
setAccounts(allNamespaceAccounts.map(account => account.split(":")[2]));
console.log("RESTORED", allNamespaceChains, allNamespaceAccounts);
createWeb3Provider(ethereumProvider);
},
[ethereumProvider, createWeb3Provider],
);
const _checkForPersistedSession = useCallback(
async (provider: UniversalProvider) => {
if (typeof provider === "undefined") {
throw new Error("WalletConnect is not initialized");
}
const pairings = provider.client.pairing.getAll({ active: true });
// populates existing pairings to state
setPairings(pairings);
console.log("RESTORED PAIRINGS: ", pairings);
if (typeof session !== "undefined") return;
// populates (the last) existing session to state
if (ethereumProvider?.session) {
const _session = ethereumProvider?.session;
console.log("RESTORED SESSION:", _session);
await onSessionConnected(_session);
return _session;
}
},
[session, ethereumProvider, onSessionConnected],
);
useEffect(() => {
loadChainData();
}, []);
useEffect(() => {
if (!client) {
createClient();
}
}, [client, createClient]);
useEffect(() => {
const fetchBalances = async () => {
if (!web3Provider || !accounts) return;
try { try {
setIsFetchingBalances(true); setIsFetchingBalances(true);
const _balances = await Promise.all( const _balances = await Promise.all(
_accounts.map(async account => { accounts.map(async account => {
const balance = await web3Provider.eth.getBalance(account); const balance = await web3Provider.eth.getBalance(account);
return { return {
account, account,
@ -216,51 +291,22 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
} finally { } finally {
setIsFetchingBalances(false); setIsFetchingBalances(false);
} }
};
QRCodeModal.close(); fetchBalances();
}, }, [web3Provider, accounts]);
[client, chainData.eip155],
);
const _checkForPersistedSession = useCallback(
async (_client: Client) => {
if (typeof _client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
// populates existing pairings to state
setPairings(_client.pairing.topics);
if (typeof session !== "undefined") return;
// populates existing session to state (assume only the top one)
if (_client.session.topics.length) {
const _session = await _client.session.get(_client.session.topics[0]);
const [namespace, chainId] = _session.state.accounts[0].split(":");
const caipChainId = `${namespace}:${chainId}`;
onEnable(caipChainId);
}
},
[session, onEnable],
);
useEffect(() => {
loadChainData();
}, []);
useEffect(() => {
if (!client) {
createClient();
}
}, [client, createClient]);
useEffect(() => { useEffect(() => {
const getPersistedSession = async () => { const getPersistedSession = async () => {
if (client && !hasCheckedPersistedSession) { if (!ethereumProvider) return;
await _checkForPersistedSession(client); await _checkForPersistedSession(ethereumProvider);
setHasCheckedPersistedSession(true); setHasCheckedPersistedSession(true);
}
}; };
getPersistedSession(); if (ethereumProvider && chainData && !hasCheckedPersistedSession) {
}, [client, _checkForPersistedSession, hasCheckedPersistedSession]); getPersistedSession();
}
}, [ethereumProvider, chainData, _checkForPersistedSession, hasCheckedPersistedSession]);
const value = useMemo( const value = useMemo(
() => ({ () => ({
@ -273,8 +319,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
client, client,
session, session,
disconnect, disconnect,
connect,
chainData, chainData,
onEnable,
web3Provider, web3Provider,
}), }),
[ [
@ -287,8 +333,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
client, client,
session, session,
disconnect, disconnect,
connect,
chainData, chainData,
onEnable,
web3Provider, web3Provider,
], ],
); );

View File

@ -190,6 +190,7 @@ export function setLocaleStorageTestnetFlag(value: boolean): void {
} }
export function getLocalStorageTestnetFlag(): boolean { export function getLocalStorageTestnetFlag(): boolean {
if (typeof window === "undefined") return false;
let value = INITIAL_STATE_TESTNET_DEFAULT; let value = INITIAL_STATE_TESTNET_DEFAULT;
const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET); const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET);
if (!persisted) { if (!persisted) {

View File

@ -1,27 +0,0 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { createGlobalStyle } from "styled-components";
import { ClientContextProvider } from "./contexts/ClientContext";
import App from "./App";
import { globalStyle } from "./styles";
const GlobalStyle = createGlobalStyle`
${globalStyle}
`;
declare global {
// tslint:disable-next-line
interface Window {
blockies: any;
}
}
ReactDOM.render(
<>
<GlobalStyle />
<ClientContextProvider>
<App />
</ClientContextProvider>
</>,
document.getElementById("root"),
);

View File

@ -9,7 +9,7 @@ import { STable } from "../components/shared";
import { SModalContainer, SModalTitle } from "./shared"; import { SModalContainer, SModalTitle } from "./shared";
interface PairingModalProps { interface PairingModalProps {
pairings: PairingTypes.Settled[]; pairings: PairingTypes.Struct[];
connect: (pairing?: { topic: string }) => Promise<void>; connect: (pairing?: { topic: string }) => Promise<void>;
} }

View File

@ -0,0 +1,3 @@
export default function FourOhFour() {
return <h1>404 Page Not Found</h1>;
}

View File

@ -0,0 +1,22 @@
import type { AppProps } from "next/app";
import { createGlobalStyle } from "styled-components";
import { ClientContextProvider } from "../contexts/ClientContext";
import { globalStyle } from "../styles";
const GlobalStyle = createGlobalStyle`
${globalStyle}
`;
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<GlobalStyle />
<ClientContextProvider>
<Component {...pageProps} />
</ClientContextProvider>
</>
);
}
export default MyApp;

View File

@ -0,0 +1,3 @@
export default function Error() {
return <div>An error as occured</div>;
}

View File

@ -1,25 +1,25 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { version } from "@walletconnect/client/package.json"; import { version } from "@walletconnect/universal-provider/package.json";
import * as encoding from "@walletconnect/encoding"; import * as encoding from "@walletconnect/encoding";
import { utils } from "ethers"; import { utils } from "ethers";
import { TypedDataField } from "@ethersproject/abstract-signer"; import { TypedDataField } from "@ethersproject/abstract-signer";
import { Transaction } from "@ethereumjs/tx"; import { Transaction } from "@ethereumjs/tx";
import Banner from "./components/Banner"; import Banner from "./../components/Banner";
import Blockchain from "./components/Blockchain"; import Blockchain from "./../components/Blockchain";
import Column from "./components/Column"; import Column from "./../components/Column";
import Header from "./components/Header"; import Header from "./../components/Header";
import Modal from "./components/Modal"; import Modal from "./../components/Modal";
import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants"; import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./../constants";
import { import {
AccountAction, AccountAction,
eip712, eip712,
getLocalStorageTestnetFlag, getLocalStorageTestnetFlag,
setLocaleStorageTestnetFlag, setLocaleStorageTestnetFlag,
} from "./helpers"; } from "./../helpers";
import Toggle from "./components/Toggle"; import Toggle from "./../components/Toggle";
import RequestModal from "./modals/RequestModal"; import RequestModal from "./../modals/RequestModal";
import PingModal from "./modals/PingModal"; import PingModal from "./../modals/PingModal";
import { import {
SAccounts, SAccounts,
SAccountsContainer, SAccountsContainer,
@ -28,8 +28,8 @@ import {
SLanding, SLanding,
SLayout, SLayout,
SToggleContainer, SToggleContainer,
} from "./components/app"; } from "./../components/app";
import { useWalletConnectClient } from "./contexts/ClientContext"; import { useWalletConnectClient } from "./../contexts/ClientContext";
interface IFormattedRpcResponse { interface IFormattedRpcResponse {
method: string; method: string;
@ -60,7 +60,7 @@ export default function App() {
chainData, chainData,
isFetchingBalances, isFetchingBalances,
isInitializing, isInitializing,
onEnable, connect,
web3Provider, web3Provider,
} = useWalletConnectClient(); } = useWalletConnectClient();
@ -72,10 +72,13 @@ export default function App() {
throw new Error("WalletConnect Client is not initialized"); throw new Error("WalletConnect Client is not initialized");
} }
if (typeof session === "undefined") {
throw new Error("Session is not connected");
}
try { try {
setIsRpcRequestPending(true); setIsRpcRequestPending(true);
const _session = await client.session.get(client.session.topics[0]); await client.ping({ topic: session.topic });
await client.session.ping(_session.topic);
setRpcResult({ setRpcResult({
address: "", address: "",
method: "ping", method: "ping",
@ -124,7 +127,6 @@ export default function App() {
} }
const [address] = await web3Provider.eth.getAccounts(); const [address] = await web3Provider.eth.getAccounts();
const tx = { const tx = {
from: address, from: address,
to: address, to: address,
@ -283,16 +285,6 @@ export default function App() {
<Banner /> <Banner />
<h6> <h6>
<span>{`Using v${version || "2.0.0-beta"}`}</span> <span>{`Using v${version || "2.0.0-beta"}`}</span>
<sup>
(
<a
style={{ textDecoration: "underline" }}
href="https://github.com/WalletConnect/web-examples/tree/main/dapps/react-dapp-v2-with-web3js"
>
outdated
</a>{" "}
)
</sup>
</h6> </h6>
<SButtonContainer> <SButtonContainer>
<h6>Select an Ethereum chain:</h6> <h6>Select an Ethereum chain:</h6>
@ -301,7 +293,7 @@ export default function App() {
<Toggle active={isTestnet} onClick={toggleTestnets} /> <Toggle active={isTestnet} onClick={toggleTestnets} />
</SToggleContainer> </SToggleContainer>
{chainOptions.map(chainId => ( {chainOptions.map(chainId => (
<Blockchain key={chainId} chainId={chainId} chainData={chainData} onClick={onEnable} /> <Blockchain key={chainId} chainId={chainId} chainData={chainData} onClick={connect} />
))} ))}
</SButtonContainer> </SButtonContainer>
</SLanding> </SLanding>

View File

@ -1,26 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noEmit": true,
"esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "jsx": "preserve",
"jsx": "react-jsx" "incremental": true
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"src" "exclude": ["node_modules"]
]
} }

File diff suppressed because it is too large Load Diff