Merge branch 'feat/dapp-ethereum-provider-ethers' into main
This commit is contained in:
commit
ea083b2216
4
dapps/react-dapp-v2-with-ethers/.env.local.example
Normal file
4
dapps/react-dapp-v2-with-ethers/.env.local.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
REACT_APP_PROJECT_ID=39bc93c4affb2e20cb6f8d36ca107dd9
|
||||||
|
REACT_APP_INFURA_ID=5dc0df...
|
||||||
|
REACT_APP_RELAY_URL=wss://relay.dev.walletconnect.com
|
||||||
|
|
25
dapps/react-dapp-v2-with-ethers/.gitignore
vendored
Normal file
25
dapps/react-dapp-v2-with-ethers/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
.eslintcache
|
7
dapps/react-dapp-v2-with-ethers/.prettierrc
Normal file
7
dapps/react-dapp-v2-with-ethers/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 100,
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
21
dapps/react-dapp-v2-with-ethers/LICENSE
Normal file
21
dapps/react-dapp-v2-with-ethers/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 WalletConnect, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
33
dapps/react-dapp-v2-with-ethers/README.md
Normal file
33
dapps/react-dapp-v2-with-ethers/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# WalletConnect React App
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Install the app's dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
Set up your local environment variables by copying the example into your own `.env.local` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.local.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
7
dapps/react-dapp-v2-with-ethers/images.d.ts
vendored
Normal file
7
dapps/react-dapp-v2-with-ethers/images.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare module "*.svg";
|
||||||
|
declare module "*.png";
|
||||||
|
declare module "*.jpg";
|
||||||
|
declare module "*.jpeg";
|
||||||
|
declare module "*.gif";
|
||||||
|
declare module "*.bmp";
|
||||||
|
declare module "*.tiff";
|
86
dapps/react-dapp-v2-with-ethers/package.json
Normal file
86
dapps/react-dapp-v2-with-ethers/package.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "react-dapp-v2-with-ethers",
|
||||||
|
"version": "2.0.0-beta.23",
|
||||||
|
"private": true,
|
||||||
|
"keywords": [
|
||||||
|
"walletconnect",
|
||||||
|
"ethereum",
|
||||||
|
"web3",
|
||||||
|
"crypto"
|
||||||
|
],
|
||||||
|
"author": "WalletConnect, Inc. <walletconnect.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/walletconnect/walletconnect-monorepo.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/walletconnect/walletconnect-monorepo/issues"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"react-error-overlay": "6.0.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@walletconnect/client": "2.0.0-beta.23",
|
||||||
|
"@walletconnect/ethereum-provider": "2.0.0-beta.23",
|
||||||
|
"@walletconnect/qrcode-modal": "^1.7.1",
|
||||||
|
"@walletconnect/types": "2.0.0-beta.23",
|
||||||
|
"@walletconnect/utils": "2.0.0-beta.23",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"blockies-ts": "^1.0.0",
|
||||||
|
"caip-api": "^2.0.0-beta.1",
|
||||||
|
"cosmos-wallet": "^1.1.0",
|
||||||
|
"eth-sig-util": "^2.5.3",
|
||||||
|
"ethereumjs-util": "^7.0.6",
|
||||||
|
"ethers": "^5.3.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"qr-image": "^3.2.0",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-scripts": "^4.0.3",
|
||||||
|
"styled-components": "^5.2.0",
|
||||||
|
"typescript": "^4.3.2",
|
||||||
|
"web-vitals": "^0.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.1",
|
||||||
|
"@testing-library/react": "^12.1.2",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/bn.js": "^5.1.0",
|
||||||
|
"@types/eth-sig-util": "^2.1.1",
|
||||||
|
"@types/jest": "^27.4.0",
|
||||||
|
"@types/node": "^17.0.14",
|
||||||
|
"@types/pino": "^7.0.5",
|
||||||
|
"@types/prop-types": "^15.7.4",
|
||||||
|
"@types/qr-image": "^3.2.5",
|
||||||
|
"@types/react": "^17.0.38",
|
||||||
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"@types/styled-components": "^5.1.21",
|
||||||
|
"prettier": "^2.5.1"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
dapps/react-dapp-v2-with-ethers/public/favicon.ico
Normal file
BIN
dapps/react-dapp-v2-with-ethers/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
20
dapps/react-dapp-v2-with-ethers/public/index.html
Normal file
20
dapps/react-dapp-v2-with-ethers/public/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<title>React App</title>
|
||||||
|
<meta name="description" content="React App for WalletConnect" />
|
||||||
|
<style>
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,500,600,700,800");
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
15
dapps/react-dapp-v2-with-ethers/public/manifest.json
Normal file
15
dapps/react-dapp-v2-with-ethers/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "WalletConnect",
|
||||||
|
"name": "WalletConnect React App",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
312
dapps/react-dapp-v2-with-ethers/src/App.tsx
Normal file
312
dapps/react-dapp-v2-with-ethers/src/App.tsx
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { version } from "@walletconnect/client/package.json";
|
||||||
|
import * as encoding from "@walletconnect/encoding";
|
||||||
|
|
||||||
|
import Banner from "./components/Banner";
|
||||||
|
import Blockchain from "./components/Blockchain";
|
||||||
|
import Column from "./components/Column";
|
||||||
|
import Header from "./components/Header";
|
||||||
|
import Modal from "./components/Modal";
|
||||||
|
import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants";
|
||||||
|
import {
|
||||||
|
AccountAction,
|
||||||
|
formatTestTransaction,
|
||||||
|
getLocalStorageTestnetFlag,
|
||||||
|
setLocaleStorageTestnetFlag,
|
||||||
|
} from "./helpers";
|
||||||
|
import Toggle from "./components/Toggle";
|
||||||
|
import RequestModal from "./modals/RequestModal";
|
||||||
|
import PingModal from "./modals/PingModal";
|
||||||
|
import {
|
||||||
|
SAccounts,
|
||||||
|
SAccountsContainer,
|
||||||
|
SButtonContainer,
|
||||||
|
SContent,
|
||||||
|
SLanding,
|
||||||
|
SLayout,
|
||||||
|
SToggleContainer,
|
||||||
|
} from "./components/app";
|
||||||
|
import { useWalletConnectClient } from "./contexts/ClientContext";
|
||||||
|
import { utils } from "ethers";
|
||||||
|
|
||||||
|
interface IFormattedRpcResponse {
|
||||||
|
method: string;
|
||||||
|
address: string;
|
||||||
|
valid: boolean;
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag());
|
||||||
|
const [isRpcRequestPending, setIsRpcRequestPending] = useState(false);
|
||||||
|
const [rpcResult, setRpcResult] = useState<IFormattedRpcResponse | null>();
|
||||||
|
|
||||||
|
const [modal, setModal] = useState("");
|
||||||
|
const [selectedChainId, setSelectedChainId] = useState<string>();
|
||||||
|
|
||||||
|
const closeModal = () => setModal("");
|
||||||
|
const openPingModal = () => setModal("ping");
|
||||||
|
const openRequestModal = () => setModal("request");
|
||||||
|
|
||||||
|
// Initialize the WalletConnect client.
|
||||||
|
const {
|
||||||
|
client,
|
||||||
|
session,
|
||||||
|
disconnect,
|
||||||
|
chain,
|
||||||
|
accounts,
|
||||||
|
balances,
|
||||||
|
chainData,
|
||||||
|
isFetchingBalances,
|
||||||
|
isInitializing,
|
||||||
|
onEnable,
|
||||||
|
web3Provider,
|
||||||
|
} = useWalletConnectClient();
|
||||||
|
|
||||||
|
const ping = async () => {
|
||||||
|
if (typeof client === "undefined") {
|
||||||
|
throw new Error("WalletConnect Client is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsRpcRequestPending(true);
|
||||||
|
const _session = await client.session.get(client.session.topics[0]);
|
||||||
|
await client.session.ping(_session.topic);
|
||||||
|
setRpcResult({
|
||||||
|
address: "",
|
||||||
|
method: "ping",
|
||||||
|
valid: true,
|
||||||
|
result: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("RPC request failed:", error);
|
||||||
|
} finally {
|
||||||
|
setIsRpcRequestPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPing = async () => {
|
||||||
|
openPingModal();
|
||||||
|
await ping();
|
||||||
|
};
|
||||||
|
|
||||||
|
const testSignTransaction: () => Promise<IFormattedRpcResponse> = async () => {
|
||||||
|
if (!web3Provider) {
|
||||||
|
throw new Error("web3Provider not connected");
|
||||||
|
}
|
||||||
|
const address = accounts[0];
|
||||||
|
const tx = await formatTestTransaction(selectedChainId + ":" + address);
|
||||||
|
|
||||||
|
const signature = await web3Provider.send("eth_signTransaction", [tx]);
|
||||||
|
return {
|
||||||
|
method: "eth_signTransaction",
|
||||||
|
address,
|
||||||
|
valid: true,
|
||||||
|
result: signature,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const testSignMessage: () => Promise<IFormattedRpcResponse> = async () => {
|
||||||
|
if (!web3Provider) {
|
||||||
|
throw new Error("web3Provider not connected");
|
||||||
|
}
|
||||||
|
const msg = "hello world";
|
||||||
|
const hexMsg = encoding.utf8ToHex(msg, true);
|
||||||
|
const address = accounts[0];
|
||||||
|
const signature = await web3Provider.send("personal_sign", [hexMsg, address]);
|
||||||
|
const valid = utils.verifyMessage(msg, signature) === address;
|
||||||
|
return {
|
||||||
|
method: "personal_sign",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result: signature,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const testEthSign: () => Promise<IFormattedRpcResponse> = async () => {
|
||||||
|
if (!web3Provider) {
|
||||||
|
throw new Error("web3Provider not connected");
|
||||||
|
}
|
||||||
|
const msg = "hello world";
|
||||||
|
const hexMsg = encoding.utf8ToHex(msg, true);
|
||||||
|
const address = accounts[0];
|
||||||
|
const signature = await web3Provider.send("eth_sign", [address, hexMsg]);
|
||||||
|
const valid = utils.verifyMessage(msg, signature) === address;
|
||||||
|
return {
|
||||||
|
method: "eth_sign (standard)",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result: signature,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const testSignTypedData: () => Promise<IFormattedRpcResponse> = async () => {
|
||||||
|
if (!web3Provider) {
|
||||||
|
throw new Error("web3Provider not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const typedData = {
|
||||||
|
types: {
|
||||||
|
Person: [
|
||||||
|
{ name: "name", type: "string" },
|
||||||
|
{ name: "wallet", type: "address" },
|
||||||
|
],
|
||||||
|
Mail: [
|
||||||
|
{ name: "from", type: "Person" },
|
||||||
|
{ name: "to", type: "Person" },
|
||||||
|
{ name: "contents", type: "string" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
primaryType: "Mail",
|
||||||
|
domain: {
|
||||||
|
name: "Ether Mail",
|
||||||
|
version: "1",
|
||||||
|
chainId: 1,
|
||||||
|
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
from: {
|
||||||
|
name: "Cow",
|
||||||
|
wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
name: "Bob",
|
||||||
|
wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
|
||||||
|
},
|
||||||
|
contents: "Hello, Bob!",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = JSON.stringify(typedData);
|
||||||
|
|
||||||
|
const address = accounts[0];
|
||||||
|
|
||||||
|
// eth_signTypedData params
|
||||||
|
const params = [address, message];
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const signature = await web3Provider.send("eth_signTypedData", params);
|
||||||
|
const valid =
|
||||||
|
utils.verifyTypedData(typedData.domain, typedData.types, typedData.message, signature) ===
|
||||||
|
address;
|
||||||
|
return {
|
||||||
|
method: "eth_signTypedData",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result: signature,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEthereumActions = (): AccountAction[] => {
|
||||||
|
const wrapRpcRequest = (rpcRequest: () => Promise<IFormattedRpcResponse>) => async () => {
|
||||||
|
openRequestModal();
|
||||||
|
try {
|
||||||
|
setIsRpcRequestPending(true);
|
||||||
|
const result = await rpcRequest();
|
||||||
|
setRpcResult(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("RPC request failed:", error);
|
||||||
|
} finally {
|
||||||
|
setIsRpcRequestPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
// { method: "eth_sendTransaction", callback: onSendTransaction },
|
||||||
|
{ method: "eth_signTransaction", callback: wrapRpcRequest(testSignTransaction) },
|
||||||
|
{ method: "personal_sign", callback: wrapRpcRequest(testSignMessage) },
|
||||||
|
{ method: "eth_sign (standard)", callback: wrapRpcRequest(testEthSign) },
|
||||||
|
{ method: "eth_signTypedData", callback: wrapRpcRequest(testSignTypedData) },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBlockchainActions = (chainId: string) => {
|
||||||
|
const [namespace] = chainId.split(":");
|
||||||
|
switch (namespace) {
|
||||||
|
case "eip155":
|
||||||
|
return getEthereumActions();
|
||||||
|
case "cosmos":
|
||||||
|
return [];
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle between displaying testnet or mainnet chains as selection options.
|
||||||
|
const toggleTestnets = () => {
|
||||||
|
const nextIsTestnetState = !isTestnet;
|
||||||
|
setIsTestnet(nextIsTestnetState);
|
||||||
|
setLocaleStorageTestnetFlag(nextIsTestnetState);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConnect = (chainId: string) => {
|
||||||
|
setSelectedChainId(chainId);
|
||||||
|
onEnable(chainId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders the appropriate model for the given request that is currently in-flight.
|
||||||
|
const renderModal = () => {
|
||||||
|
switch (modal) {
|
||||||
|
case "request":
|
||||||
|
return <RequestModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||||
|
case "ping":
|
||||||
|
return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS;
|
||||||
|
return !accounts.length && !Object.keys(balances).length ? (
|
||||||
|
<SLanding center>
|
||||||
|
<Banner />
|
||||||
|
<h6>
|
||||||
|
<span>{`Using v${version || "2.0.0-beta"}`}</span>
|
||||||
|
</h6>
|
||||||
|
<SButtonContainer>
|
||||||
|
<h6>Select an Ethereum chain:</h6>
|
||||||
|
<SToggleContainer>
|
||||||
|
<p>Testnets Only?</p>
|
||||||
|
<Toggle active={isTestnet} onClick={toggleTestnets} />
|
||||||
|
</SToggleContainer>
|
||||||
|
{chainOptions.map(chainId => (
|
||||||
|
<Blockchain key={chainId} chainId={chainId} chainData={chainData} onClick={onConnect} />
|
||||||
|
))}
|
||||||
|
</SButtonContainer>
|
||||||
|
</SLanding>
|
||||||
|
) : (
|
||||||
|
<SAccountsContainer>
|
||||||
|
<h3>Account</h3>
|
||||||
|
<SAccounts>
|
||||||
|
{accounts.map(account => {
|
||||||
|
return (
|
||||||
|
<Blockchain
|
||||||
|
key={account}
|
||||||
|
active={true}
|
||||||
|
chainData={chainData}
|
||||||
|
fetching={isFetchingBalances}
|
||||||
|
address={account}
|
||||||
|
chainId={chain}
|
||||||
|
balances={balances}
|
||||||
|
actions={getBlockchainActions(chain)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SAccounts>
|
||||||
|
</SAccountsContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SLayout>
|
||||||
|
<Column maxWidth={1000} spanHeight>
|
||||||
|
<Header ping={onPing} disconnect={disconnect} session={session} />
|
||||||
|
<SContent>{isInitializing ? "Loading..." : renderContent()}</SContent>
|
||||||
|
</Column>
|
||||||
|
<Modal show={!!modal} closeModal={closeModal}>
|
||||||
|
{renderModal()}
|
||||||
|
</Modal>
|
||||||
|
</SLayout>
|
||||||
|
);
|
||||||
|
}
|
13
dapps/react-dapp-v2-with-ethers/src/assets/erc20.svg
Normal file
13
dapps/react-dapp-v2-with-ethers/src/assets/erc20.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.8 KiB |
7
dapps/react-dapp-v2-with-ethers/src/assets/eth.svg
Normal file
7
dapps/react-dapp-v2-with-ethers/src/assets/eth.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="26" viewBox="0 0 16 26">
|
||||||
|
<path
|
||||||
|
fill="#0C0C0D"
|
||||||
|
fillRule="nonzero"
|
||||||
|
d="M8 19.096l7.998-4.733L8 25.637 0 14.363l8 4.733zM8 0l8 12.6-8 4.733L0 12.6 8 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 234 B |
BIN
dapps/react-dapp-v2-with-ethers/src/assets/walletconnect.png
Normal file
BIN
dapps/react-dapp-v2-with-ethers/src/assets/walletconnect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
38
dapps/react-dapp-v2-with-ethers/src/chains/cosmos.ts
Normal file
38
dapps/react-dapp-v2-with-ethers/src/chains/cosmos.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||||
|
|
||||||
|
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
|
||||||
|
|
||||||
|
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers";
|
||||||
|
|
||||||
|
export const CosmosMetadata: NamespaceMetadata = {
|
||||||
|
"cosmoshub-4": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "cosmos:cosmoshub-4.png",
|
||||||
|
rgb: "27, 31, 53",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getChainMetadata(chainId: string): ChainMetadata {
|
||||||
|
const reference = chainId.split(":")[1];
|
||||||
|
const metadata = CosmosMetadata[reference];
|
||||||
|
if (typeof metadata === "undefined") {
|
||||||
|
throw new Error(`No chain metadata found for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||||
|
let params = [{ label: "Method", value: request.method }];
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
|
default:
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{
|
||||||
|
label: "params",
|
||||||
|
value: JSON.stringify(request.params, null, "\t"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
148
dapps/react-dapp-v2-with-ethers/src/chains/eip155.ts
Normal file
148
dapps/react-dapp-v2-with-ethers/src/chains/eip155.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
NamespaceMetadata,
|
||||||
|
ChainMetadata,
|
||||||
|
ChainRequestRender,
|
||||||
|
convertHexToNumber,
|
||||||
|
convertHexToUtf8,
|
||||||
|
} from "../helpers";
|
||||||
|
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
|
||||||
|
|
||||||
|
export const EIP155Colors = {
|
||||||
|
ethereum: "99, 125, 234",
|
||||||
|
optimism: "233, 1, 1",
|
||||||
|
goerli: "189, 174, 155",
|
||||||
|
xdai: "73, 169, 166",
|
||||||
|
polygon: "130, 71, 229",
|
||||||
|
celo: "60, 203, 132",
|
||||||
|
arbitrum: "44, 55, 75",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EIP155Metadata: NamespaceMetadata = {
|
||||||
|
"1": {
|
||||||
|
name: "Ethereum",
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:1.png",
|
||||||
|
rgb: EIP155Colors.ethereum,
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:5.png",
|
||||||
|
rgb: EIP155Colors.goerli,
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
name: "Optimism",
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:10.png",
|
||||||
|
rgb: EIP155Colors.optimism,
|
||||||
|
},
|
||||||
|
"42": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:42.png",
|
||||||
|
rgb: EIP155Colors.ethereum,
|
||||||
|
},
|
||||||
|
"69": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:69.png",
|
||||||
|
rgb: EIP155Colors.optimism,
|
||||||
|
},
|
||||||
|
"100": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:100.png",
|
||||||
|
rgb: EIP155Colors.xdai,
|
||||||
|
},
|
||||||
|
"137": {
|
||||||
|
name: "Polygon",
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:137.png",
|
||||||
|
rgb: EIP155Colors.polygon,
|
||||||
|
},
|
||||||
|
"80001": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:80001.png",
|
||||||
|
rgb: EIP155Colors.polygon,
|
||||||
|
},
|
||||||
|
"42161": {
|
||||||
|
name: "Arbitrum",
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:42161.png",
|
||||||
|
rgb: EIP155Colors.arbitrum,
|
||||||
|
},
|
||||||
|
"42220": {
|
||||||
|
name: "Celo",
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:42220.png",
|
||||||
|
rgb: EIP155Colors.celo,
|
||||||
|
},
|
||||||
|
"44787": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:44787.png",
|
||||||
|
rgb: EIP155Colors.celo,
|
||||||
|
},
|
||||||
|
"421611": {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "eip155:421611.png",
|
||||||
|
rgb: EIP155Colors.arbitrum,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export function getChainMetadata(chainId: string): ChainMetadata {
|
||||||
|
const reference = chainId.split(":")[1];
|
||||||
|
const metadata = EIP155Metadata[reference];
|
||||||
|
if (typeof metadata === "undefined") {
|
||||||
|
throw new Error(`No chain metadata found for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||||
|
let params = [{ label: "Method", value: request.method }];
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
|
case "eth_sendTransaction":
|
||||||
|
case "eth_signTransaction":
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{ label: "From", value: request.params[0].from },
|
||||||
|
{ label: "To", value: request.params[0].to },
|
||||||
|
{
|
||||||
|
label: "Gas Limit",
|
||||||
|
value: request.params[0].gas
|
||||||
|
? convertHexToNumber(request.params[0].gas)
|
||||||
|
: request.params[0].gasLimit
|
||||||
|
? convertHexToNumber(request.params[0].gasLimit)
|
||||||
|
: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Gas Price",
|
||||||
|
value: convertHexToNumber(request.params[0].gasPrice),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Nonce",
|
||||||
|
value: convertHexToNumber(request.params[0].nonce),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Value",
|
||||||
|
value: request.params[0].value ? convertHexToNumber(request.params[0].value) : "",
|
||||||
|
},
|
||||||
|
{ label: "Data", value: request.params[0].data },
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "eth_sign":
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{ label: "Address", value: request.params[0] },
|
||||||
|
{ label: "Message", value: request.params[1] },
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case "personal_sign":
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{ label: "Address", value: request.params[1] },
|
||||||
|
{
|
||||||
|
label: "Message",
|
||||||
|
value: convertHexToUtf8(request.params[0]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{
|
||||||
|
label: "params",
|
||||||
|
value: JSON.stringify(request.params, null, "\t"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
38
dapps/react-dapp-v2-with-ethers/src/chains/index.ts
Normal file
38
dapps/react-dapp-v2-with-ethers/src/chains/index.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||||
|
|
||||||
|
import * as eip155 from "./eip155";
|
||||||
|
import * as cosmos from "./cosmos";
|
||||||
|
import * as polkadot from "./polkadot";
|
||||||
|
|
||||||
|
import { ChainMetadata, ChainRequestRender } from "../helpers";
|
||||||
|
|
||||||
|
export function getChainMetadata(chainId: string): ChainMetadata {
|
||||||
|
const namespace = chainId.split(":")[0];
|
||||||
|
switch (namespace) {
|
||||||
|
case "eip155":
|
||||||
|
return eip155.getChainMetadata(chainId);
|
||||||
|
case "cosmos":
|
||||||
|
return cosmos.getChainMetadata(chainId);
|
||||||
|
case "polkadot":
|
||||||
|
return polkadot.getChainMetadata(chainId);
|
||||||
|
default:
|
||||||
|
throw new Error(`No metadata handler for namespace ${namespace}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChainRequestRender(
|
||||||
|
request: JsonRpcRequest,
|
||||||
|
chainId: string,
|
||||||
|
): ChainRequestRender[] {
|
||||||
|
const namespace = chainId.split(":")[0];
|
||||||
|
switch (namespace) {
|
||||||
|
case "eip155":
|
||||||
|
return eip155.getChainRequestRender(request);
|
||||||
|
case "cosmos":
|
||||||
|
return cosmos.getChainRequestRender(request);
|
||||||
|
case "polkadot":
|
||||||
|
return polkadot.getChainRequestRender(request);
|
||||||
|
default:
|
||||||
|
throw new Error(`No render handler for namespace ${namespace}`);
|
||||||
|
}
|
||||||
|
}
|
38
dapps/react-dapp-v2-with-ethers/src/chains/polkadot.ts
Normal file
38
dapps/react-dapp-v2-with-ethers/src/chains/polkadot.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||||
|
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
|
||||||
|
|
||||||
|
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers";
|
||||||
|
|
||||||
|
export const PolkadotMetadata: NamespaceMetadata = {
|
||||||
|
// eslint-disable-next-line no-useless-computed-key
|
||||||
|
["91b171bb158e2d3848fa23a9f1c25182"]: {
|
||||||
|
logo: BLOCKCHAIN_LOGO_BASE_URL + "polkadot:91b171bb158e2d3848fa23a9f1c25182.png",
|
||||||
|
rgb: "230, 1, 122",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getChainMetadata(chainId: string): ChainMetadata {
|
||||||
|
const reference = chainId.split(":")[1];
|
||||||
|
const metadata = PolkadotMetadata[reference];
|
||||||
|
if (typeof metadata === "undefined") {
|
||||||
|
throw new Error(`No chain metadata found for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||||
|
let params = [{ label: "Method", value: request.method }];
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
|
default:
|
||||||
|
params = [
|
||||||
|
...params,
|
||||||
|
{
|
||||||
|
label: "params",
|
||||||
|
value: JSON.stringify(request.params, null, "\t"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
75
dapps/react-dapp-v2-with-ethers/src/components/Asset.tsx
Normal file
75
dapps/react-dapp-v2-with-ethers/src/components/Asset.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import Icon from "./Icon";
|
||||||
|
|
||||||
|
import { AssetData } from "../helpers";
|
||||||
|
|
||||||
|
import eth from "../assets/eth.svg";
|
||||||
|
import erc20 from "../assets/erc20.svg";
|
||||||
|
import { getChainMetadata } from "../chains";
|
||||||
|
|
||||||
|
const xdai = getChainMetadata("eip155:100").logo;
|
||||||
|
const matic = getChainMetadata("eip155:137").logo;
|
||||||
|
|
||||||
|
const SAsset = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
const SAssetLeft = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SAssetName = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SAssetRight = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SAssetBalance = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
function getAssetIcon(asset: AssetData): JSX.Element {
|
||||||
|
if (!!asset.contractAddress) {
|
||||||
|
const src = `https://raw.githubusercontent.com/TrustWallet/tokens/master/tokens/${asset.contractAddress.toLowerCase()}.png`;
|
||||||
|
return <Icon src={src} fallback={erc20} />;
|
||||||
|
}
|
||||||
|
switch (asset.symbol.toLowerCase()) {
|
||||||
|
case "eth":
|
||||||
|
return <Icon src={eth} />;
|
||||||
|
case "xdai":
|
||||||
|
return <Icon src={xdai} />;
|
||||||
|
case "matic":
|
||||||
|
return <Icon src={matic} />;
|
||||||
|
default:
|
||||||
|
return <Icon src={erc20} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AssetProps {
|
||||||
|
asset: { symbol: string; balance: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const Asset = (props: AssetProps) => {
|
||||||
|
const { asset } = props;
|
||||||
|
return (
|
||||||
|
<SAsset {...props}>
|
||||||
|
<SAssetLeft>
|
||||||
|
{/* {getAssetIcon(asset)} */}
|
||||||
|
<SAssetName>{asset.symbol}</SAssetName>
|
||||||
|
</SAssetLeft>
|
||||||
|
<SAssetRight>
|
||||||
|
<SAssetBalance>{`${asset.balance}`}</SAssetBalance>
|
||||||
|
</SAssetRight>
|
||||||
|
</SAsset>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Asset;
|
25
dapps/react-dapp-v2-with-ethers/src/components/Banner.tsx
Normal file
25
dapps/react-dapp-v2-with-ethers/src/components/Banner.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import logo from "../assets/walletconnect.png";
|
||||||
|
|
||||||
|
const SBannerWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SBanner = styled.div`
|
||||||
|
width: 275px;
|
||||||
|
height: 45px;
|
||||||
|
background: url(${logo}) no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Banner = () => (
|
||||||
|
<SBannerWrapper>
|
||||||
|
<SBanner />
|
||||||
|
</SBannerWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Banner;
|
176
dapps/react-dapp-v2-with-ethers/src/components/Blockchain.tsx
Normal file
176
dapps/react-dapp-v2-with-ethers/src/components/Blockchain.tsx
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import React, { PropsWithChildren, FC } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { ChainData } from "caip-api";
|
||||||
|
|
||||||
|
import Asset from "./Asset";
|
||||||
|
import Button from "./Button";
|
||||||
|
import Column from "./Column";
|
||||||
|
import Loader from "./Loader";
|
||||||
|
|
||||||
|
import { getChainMetadata } from "../chains";
|
||||||
|
import { AccountAction, ellipseAddress, ChainMetadata, ChainNamespaces } from "../helpers";
|
||||||
|
import { fonts } from "../styles";
|
||||||
|
|
||||||
|
interface AccountStyleProps {
|
||||||
|
rgb: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SAccount = styled.div<AccountStyleProps>`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border: ${({ rgb }) => `2px solid rgb(${rgb})`};
|
||||||
|
&.active {
|
||||||
|
box-shadow: ${({ rgb }) => `0 0 8px rgb(${rgb})`};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SChain = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
& p {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
& img {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SContainer = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
word-break: break-word;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SFullWidthContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SAction = styled(Button as any)`
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: ${fonts.size.medium};
|
||||||
|
height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 12px 0;
|
||||||
|
background-color: ${({ rgb }) => `rgb(${rgb})`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SBlockchainChildrenContainer = styled(SFullWidthContainer)`
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface BlockchainProps {
|
||||||
|
chainData: ChainNamespaces;
|
||||||
|
fetching?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
chainId: string;
|
||||||
|
address?: string;
|
||||||
|
onClick?: (chain: string) => void;
|
||||||
|
balances?: any[];
|
||||||
|
actions?: AccountAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockchainDisplayData {
|
||||||
|
data: ChainData;
|
||||||
|
meta: ChainMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlockchainDisplayData(
|
||||||
|
chainId: string,
|
||||||
|
chainData: ChainNamespaces,
|
||||||
|
): BlockchainDisplayData | undefined {
|
||||||
|
const [namespace, reference] = chainId.split(":");
|
||||||
|
let meta: ChainMetadata;
|
||||||
|
try {
|
||||||
|
meta = getChainMetadata(chainId);
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const data: ChainData = chainData[namespace][reference];
|
||||||
|
if (typeof data === "undefined") return undefined;
|
||||||
|
return { data, meta };
|
||||||
|
}
|
||||||
|
|
||||||
|
const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
|
||||||
|
props: PropsWithChildren<BlockchainProps>,
|
||||||
|
) => {
|
||||||
|
const { chainData, fetching, chainId, address, onClick, balances, active, actions } = props;
|
||||||
|
|
||||||
|
if (!Object.keys(chainData).length) return null;
|
||||||
|
|
||||||
|
const chain = getBlockchainDisplayData(chainId, chainData);
|
||||||
|
if (typeof chain === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const name = chain.meta.name || chain.data.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<SAccount
|
||||||
|
rgb={chain.meta.rgb}
|
||||||
|
onClick={() => onClick && onClick(props.chainId)}
|
||||||
|
className={active ? "active" : ""}
|
||||||
|
>
|
||||||
|
<SChain>
|
||||||
|
<img src={chain.meta.logo} alt={name} />
|
||||||
|
<p>{name}</p>
|
||||||
|
</SChain>
|
||||||
|
{!!address && <p>{ellipseAddress(address)}</p>}
|
||||||
|
<SBlockchainChildrenContainer>
|
||||||
|
{fetching ? (
|
||||||
|
<Column center>
|
||||||
|
<SContainer>
|
||||||
|
<Loader rgb={`rgb(${chain.meta.rgb})`} />
|
||||||
|
</SContainer>
|
||||||
|
</Column>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{!!balances && balances.length ? (
|
||||||
|
<SFullWidthContainer>
|
||||||
|
<h6>Balances</h6>
|
||||||
|
<Column center>
|
||||||
|
{balances.map(balance => (
|
||||||
|
<Asset key={balance.symbol} asset={balance} />
|
||||||
|
))}
|
||||||
|
</Column>
|
||||||
|
</SFullWidthContainer>
|
||||||
|
) : null}
|
||||||
|
{!!actions && actions.length ? (
|
||||||
|
<SFullWidthContainer>
|
||||||
|
<h6>Methods</h6>
|
||||||
|
{actions.map(action => (
|
||||||
|
<SAction
|
||||||
|
key={action.method}
|
||||||
|
left
|
||||||
|
rgb={chain.meta.rgb}
|
||||||
|
onClick={() => action.callback(chainId)}
|
||||||
|
>
|
||||||
|
{action.method}
|
||||||
|
</SAction>
|
||||||
|
))}
|
||||||
|
</SFullWidthContainer>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SBlockchainChildrenContainer>
|
||||||
|
</SAccount>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Blockchain;
|
128
dapps/react-dapp-v2-with-ethers/src/components/Button.tsx
Normal file
128
dapps/react-dapp-v2-with-ethers/src/components/Button.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Loader from "./Loader";
|
||||||
|
import { colors, fonts, shadows, transitions } from "../styles";
|
||||||
|
|
||||||
|
interface ButtonStyleProps {
|
||||||
|
fetching: boolean;
|
||||||
|
outline: boolean;
|
||||||
|
type: "button" | "submit" | "reset";
|
||||||
|
color: string;
|
||||||
|
disabled: boolean;
|
||||||
|
icon: any;
|
||||||
|
left: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ButtonProps extends ButtonStyleProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIcon = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
margin: 0 8px;
|
||||||
|
top: calc((100% - 15px) / 2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SHoverLayer = styled.div`
|
||||||
|
transition: ${transitions.button};
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgb(${colors.white}, 0.1);
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SButton = styled.button<ButtonStyleProps>`
|
||||||
|
transition: ${transitions.button};
|
||||||
|
position: relative;
|
||||||
|
border: none;
|
||||||
|
border-style: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: ${({ outline, color }) => (outline ? "transparent" : `rgb(${colors[color]})`)};
|
||||||
|
border: ${({ outline, color }) => (outline ? `1px solid rgb(${colors[color]})` : "none")};
|
||||||
|
color: ${({ outline, color }) => (outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`)};
|
||||||
|
box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)};
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: ${fonts.size.medium};
|
||||||
|
font-weight: ${fonts.weight.semibold};
|
||||||
|
padding: ${({ icon, left }) =>
|
||||||
|
icon ? (left ? "7px 12px 8px 28px" : "7px 28px 8px 12px") : "8px 12px"};
|
||||||
|
cursor: ${({ disabled }) => (disabled ? "auto" : "pointer")};
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
&:hover {
|
||||||
|
transform: ${({ disabled }) => (!disabled ? "translateY(-1px)" : "none")};
|
||||||
|
box-shadow: ${({ disabled, outline }) =>
|
||||||
|
!disabled ? (outline ? "none" : `${shadows.hover}`) : `${shadows.soft}`};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover ${SHoverLayer} {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: ${({ disabled }) => (!disabled ? "translateY(1px)" : "none")};
|
||||||
|
box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)};
|
||||||
|
color: ${({ outline, color }) =>
|
||||||
|
outline ? `rgb(${colors[color]})` : `rgba(${colors.white}, 0.24)`};
|
||||||
|
|
||||||
|
& ${SIcon} {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ${SIcon} {
|
||||||
|
right: ${({ left }) => (left ? "auto" : "0")};
|
||||||
|
left: ${({ left }) => (left ? "0" : "auto")};
|
||||||
|
display: ${({ icon }) => (icon ? "block" : "none")};
|
||||||
|
mask: ${({ icon }) => (icon ? `url(${icon}) center no-repeat` : "none")};
|
||||||
|
background-color: ${({ outline, color }) =>
|
||||||
|
outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`};
|
||||||
|
transition: 0.15s ease;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = (props: ButtonProps) => (
|
||||||
|
<SButton
|
||||||
|
{...props}
|
||||||
|
type={props.type}
|
||||||
|
outline={props.outline}
|
||||||
|
color={props.color}
|
||||||
|
disabled={props.disabled}
|
||||||
|
icon={props.icon}
|
||||||
|
left={props.left}
|
||||||
|
>
|
||||||
|
<SHoverLayer />
|
||||||
|
<SIcon />
|
||||||
|
{props.fetching ? <Loader size={20} color="white" /> : props.children}
|
||||||
|
</SButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
fetching: false,
|
||||||
|
outline: false,
|
||||||
|
type: "button",
|
||||||
|
color: "lightBlue",
|
||||||
|
disabled: false,
|
||||||
|
icon: null,
|
||||||
|
left: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
49
dapps/react-dapp-v2-with-ethers/src/components/Column.tsx
Normal file
49
dapps/react-dapp-v2-with-ethers/src/components/Column.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as PropTypes from "prop-types";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
interface ColumnStyleProps {
|
||||||
|
spanHeight: boolean;
|
||||||
|
maxWidth: number;
|
||||||
|
center: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColumnProps extends ColumnStyleProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SColumn = styled.div<ColumnStyleProps>`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: ${({ spanHeight }) => (spanHeight ? "100%" : "auto")};
|
||||||
|
max-width: ${({ maxWidth }) => `${maxWidth}px`};
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: ${({ center }) => (center ? "center" : "flex-start")};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Column = (props: ColumnProps) => {
|
||||||
|
const { children, spanHeight, maxWidth, center } = props;
|
||||||
|
return (
|
||||||
|
<SColumn {...props} spanHeight={spanHeight} maxWidth={maxWidth} center={center}>
|
||||||
|
{children}
|
||||||
|
</SColumn>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Column.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
spanHeight: PropTypes.bool,
|
||||||
|
maxWidth: PropTypes.number,
|
||||||
|
center: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
Column.defaultProps = {
|
||||||
|
spanHeight: false,
|
||||||
|
maxWidth: 600,
|
||||||
|
center: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Column;
|
80
dapps/react-dapp-v2-with-ethers/src/components/Header.tsx
Normal file
80
dapps/react-dapp-v2-with-ethers/src/components/Header.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { SessionTypes } from "@walletconnect/types";
|
||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { fonts, responsive } from "../styles";
|
||||||
|
import Button from "./Button";
|
||||||
|
|
||||||
|
const SHeader = styled.div`
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
@media screen and (${responsive.sm.max}) {
|
||||||
|
font-size: ${fonts.size.small};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SHeaderActions = styled.div`
|
||||||
|
display: flex;
|
||||||
|
& > button:first-child {
|
||||||
|
margin-right: 10px !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SActiveAccount = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SActiveSession = styled(SActiveAccount as any)`
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
align-items: flex-start;
|
||||||
|
& p {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
& p:nth-child(n + 2) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
ping: () => Promise<void>;
|
||||||
|
disconnect: () => Promise<void>;
|
||||||
|
session: SessionTypes.Created | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header = (props: HeaderProps) => {
|
||||||
|
const { ping, disconnect, session } = props;
|
||||||
|
return (
|
||||||
|
<SHeader {...props}>
|
||||||
|
{session ? (
|
||||||
|
<>
|
||||||
|
<SActiveSession>
|
||||||
|
<p>{`Connected to`}</p>
|
||||||
|
<p>{session.peer.metadata.name}</p>
|
||||||
|
</SActiveSession>
|
||||||
|
<SHeaderActions>
|
||||||
|
<Button outline color="black" onClick={ping}>
|
||||||
|
{"Ping"}
|
||||||
|
</Button>
|
||||||
|
<Button outline color="red" onClick={disconnect}>
|
||||||
|
{"Disconnect"}
|
||||||
|
</Button>
|
||||||
|
</SHeaderActions>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</SHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
42
dapps/react-dapp-v2-with-ethers/src/components/Icon.tsx
Normal file
42
dapps/react-dapp-v2-with-ethers/src/components/Icon.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as PropTypes from "prop-types";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
interface IconStyleProps {
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIcon = styled.img<IconStyleProps>`
|
||||||
|
width: ${({ size }) => `${size}px`};
|
||||||
|
height: ${({ size }) => `${size}px`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Icon = (props: any) => {
|
||||||
|
const { src, fallback, size } = props;
|
||||||
|
return (
|
||||||
|
<SIcon
|
||||||
|
{...props}
|
||||||
|
src={src}
|
||||||
|
size={size}
|
||||||
|
onError={(event: any) => {
|
||||||
|
if (fallback) {
|
||||||
|
event.target.src = fallback;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.propTypes = {
|
||||||
|
src: PropTypes.string,
|
||||||
|
fallback: PropTypes.string,
|
||||||
|
size: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.defaultProps = {
|
||||||
|
src: null,
|
||||||
|
fallback: "",
|
||||||
|
size: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Icon;
|
68
dapps/react-dapp-v2-with-ethers/src/components/Loader.tsx
Normal file
68
dapps/react-dapp-v2-with-ethers/src/components/Loader.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as PropTypes from "prop-types";
|
||||||
|
import styled, { keyframes } from "styled-components";
|
||||||
|
import { colors } from "../styles";
|
||||||
|
|
||||||
|
const load = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
5% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
95% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface LoaderStyleProps {
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoaderProps extends LoaderStyleProps {
|
||||||
|
color: string;
|
||||||
|
rgb?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SLoader = styled.svg<LoaderStyleProps>`
|
||||||
|
width: ${({ size }) => `${size}px`};
|
||||||
|
height: ${({ size }) => `${size}px`};
|
||||||
|
animation: ${load} 1s infinite cubic-bezier(0.25, 0, 0.75, 1);
|
||||||
|
transform: translateZ(0);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Loader = (props: LoaderProps) => {
|
||||||
|
const { size, color } = props;
|
||||||
|
const rgb = props.rgb || `rgb(${colors[color]})`;
|
||||||
|
return (
|
||||||
|
<SLoader viewBox="0 0 186 187" size={size}>
|
||||||
|
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||||
|
<path
|
||||||
|
d="M60,10.34375 C32.3857625,10.34375 10,32.7295125 10,60.34375 L10,126.34375 C10,153.957987 32.3857625,176.34375 60,176.34375 L126,176.34375 C153.614237,176.34375 176,153.957987 176,126.34375 L176,60.34375 C176,32.7295125 153.614237,10.34375 126,10.34375 L60,10.34375 Z M60,0.34375 L126,0.34375 C159.137085,0.34375 186,27.206665 186,60.34375 L186,126.34375 C186,159.480835 159.137085,186.34375 126,186.34375 L60,186.34375 C26.862915,186.34375 0,159.480835 0,126.34375 L0,60.34375 C0,27.206665 26.862915,0.34375 60,0.34375 Z"
|
||||||
|
id="Rectangle-Copy"
|
||||||
|
fill={rgb}
|
||||||
|
fillRule="nonzero"
|
||||||
|
/>
|
||||||
|
<rect id="Rectangle" fill={rgb} x="44" y="44.34375" width="98" height="98" rx="35" />
|
||||||
|
</g>
|
||||||
|
</SLoader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Loader.propTypes = {
|
||||||
|
size: PropTypes.number,
|
||||||
|
color: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Loader.defaultProps = {
|
||||||
|
size: 40,
|
||||||
|
color: "lightBlue",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loader;
|
141
dapps/react-dapp-v2-with-ethers/src/components/Modal.tsx
Normal file
141
dapps/react-dapp-v2-with-ethers/src/components/Modal.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { colors, transitions } from "../styles";
|
||||||
|
|
||||||
|
const SLightbox = styled.div<{
|
||||||
|
show: boolean;
|
||||||
|
offset: number;
|
||||||
|
opacity?: number;
|
||||||
|
}>`
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: -50vw;
|
||||||
|
top: ${({ offset }) => (offset ? `-${offset}px` : 0)};
|
||||||
|
left: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
will-change: opacity;
|
||||||
|
background-color: ${({ opacity }) => {
|
||||||
|
let alpha = 0.4;
|
||||||
|
if (typeof opacity === "number") {
|
||||||
|
alpha = opacity;
|
||||||
|
}
|
||||||
|
return `rgba(0, 0, 0, ${alpha})`;
|
||||||
|
}};
|
||||||
|
opacity: ${({ show }) => (show ? 1 : 0)};
|
||||||
|
visibility: ${({ show }) => (show ? "visible" : "hidden")};
|
||||||
|
pointer-events: ${({ show }) => (show ? "auto" : "none")};
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SModalContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SHitbox = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface CloseButtonStyleProps {
|
||||||
|
size: number;
|
||||||
|
color: string;
|
||||||
|
onClick?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCloseButton = styled.div<CloseButtonStyleProps>`
|
||||||
|
transition: ${transitions.short};
|
||||||
|
position: absolute;
|
||||||
|
width: ${({ size }) => `${size}px`};
|
||||||
|
height: ${({ size }) => `${size}px`};
|
||||||
|
right: ${({ size }) => `${size / 1.6667}px`};
|
||||||
|
top: ${({ size }) => `${size / 1.6667}px`};
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
content: " ";
|
||||||
|
height: ${({ size }) => `${size}px`};
|
||||||
|
width: 2px;
|
||||||
|
background: ${({ color }) => `rgb(${colors[color]})`};
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SCard = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 25px;
|
||||||
|
background-color: rgb(${colors.white});
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SModalContent = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
word-wrap: break-word;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
show: boolean;
|
||||||
|
closeModal: () => void;
|
||||||
|
opacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Modal({ children, show, opacity, closeModal }: IProps) {
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
const lightboxRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lightboxRef.current) {
|
||||||
|
const lightboxRect = lightboxRef.current.getBoundingClientRect();
|
||||||
|
const nextOffset = lightboxRect.top > 0 ? lightboxRect.top : 0;
|
||||||
|
|
||||||
|
if (nextOffset !== 0 && nextOffset !== offset) {
|
||||||
|
setOffset(nextOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [offset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SLightbox show={show} offset={offset} opacity={opacity} ref={lightboxRef}>
|
||||||
|
<SModalContainer>
|
||||||
|
<SHitbox onClick={closeModal} />
|
||||||
|
|
||||||
|
<SCard>
|
||||||
|
<SCloseButton size={25} color={"dark"} onClick={closeModal} />
|
||||||
|
<SModalContent>{children}</SModalContent>
|
||||||
|
</SCard>
|
||||||
|
</SModalContainer>
|
||||||
|
</SLightbox>
|
||||||
|
);
|
||||||
|
}
|
35
dapps/react-dapp-v2-with-ethers/src/components/Pairing.tsx
Normal file
35
dapps/react-dapp-v2-with-ethers/src/components/Pairing.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { PairingTypes } from "@walletconnect/types";
|
||||||
|
|
||||||
|
import Peer from "./Peer";
|
||||||
|
|
||||||
|
interface PairingProps {
|
||||||
|
pairing: PairingTypes.Settled;
|
||||||
|
onClick?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPairingContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Pairing = (props: PairingProps) => {
|
||||||
|
const {
|
||||||
|
state: { metadata },
|
||||||
|
} = props.pairing;
|
||||||
|
return (
|
||||||
|
<SPairingContainer onClick={props.onClick}>
|
||||||
|
<div>
|
||||||
|
{typeof metadata !== "undefined" ? (
|
||||||
|
<Peer oneLiner metadata={metadata} />
|
||||||
|
) : (
|
||||||
|
<div>{`Unknown`}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SPairingContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pairing;
|
74
dapps/react-dapp-v2-with-ethers/src/components/Peer.tsx
Normal file
74
dapps/react-dapp-v2-with-ethers/src/components/Peer.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { AppMetadata } from "@walletconnect/types";
|
||||||
|
import { colors, fonts } from "../styles";
|
||||||
|
|
||||||
|
const SPeerOneLiner = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid rgb(${colors.darkGrey});
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
& > div {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SPeerCard = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid rgb(${colors.darkGrey});
|
||||||
|
padding: 5px;
|
||||||
|
& > div {
|
||||||
|
margin: 4px auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SIcon = styled.img`
|
||||||
|
width: 100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SCenter = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SUrl = styled(SCenter as any)`
|
||||||
|
font-size: ${fonts.size.small};
|
||||||
|
opacity: 0.8;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SName = styled(SCenter as any)`
|
||||||
|
font-weight: bold;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface PeerProps {
|
||||||
|
oneLiner?: boolean;
|
||||||
|
metadata: AppMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Peer = (props: PeerProps) =>
|
||||||
|
props.oneLiner ? (
|
||||||
|
<SPeerOneLiner>
|
||||||
|
<img src={props.metadata.icons[0]} alt={props.metadata.name} />
|
||||||
|
<div>{props.metadata.name}</div>
|
||||||
|
</SPeerOneLiner>
|
||||||
|
) : (
|
||||||
|
<SPeerCard>
|
||||||
|
<SIcon src={props.metadata.icons[0]} alt={props.metadata.name} />
|
||||||
|
<SName>{props.metadata.name}</SName>
|
||||||
|
<SCenter>{props.metadata.description}</SCenter>
|
||||||
|
<SUrl>{props.metadata.url}</SUrl>
|
||||||
|
</SPeerCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Peer;
|
74
dapps/react-dapp-v2-with-ethers/src/components/Toggle.tsx
Normal file
74
dapps/react-dapp-v2-with-ethers/src/components/Toggle.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as PropTypes from "prop-types";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import { colors, transitions } from "../styles";
|
||||||
|
|
||||||
|
interface IToggleStyleProps {
|
||||||
|
color: string;
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SToggle = styled.div<IToggleStyleProps>`
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: ${transitions.base};
|
||||||
|
& div {
|
||||||
|
transition: ${transitions.base};
|
||||||
|
appearance: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: ${({ active, color }) =>
|
||||||
|
active
|
||||||
|
? `inset 0px 0px 0px 20px rgb(${colors[color]})`
|
||||||
|
: `inset 0px 0px 0px 1px rgb(${colors.grey})`};
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: rgb(${colors.white});
|
||||||
|
padding: 1px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 46px;
|
||||||
|
height: 26px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0px;
|
||||||
|
vertical-align: bottom;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
& div:after {
|
||||||
|
transition: ${transitions.base};
|
||||||
|
box-shadow: inset 0 1px 0 rgb(${colors.grey}), 0px 2px 2px 1px rgba(${colors.black}, 0.2);
|
||||||
|
border-radius: 1rem;
|
||||||
|
left: ${({ active }) => (active ? `20px` : `0`)};
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgb(${colors.white});
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface IToggleProps extends IToggleStyleProps {
|
||||||
|
onClick?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Toggle = (props: IToggleProps) => (
|
||||||
|
<SToggle color={props.color} active={props.active} onClick={props.onClick}>
|
||||||
|
<div />
|
||||||
|
</SToggle>
|
||||||
|
);
|
||||||
|
|
||||||
|
Toggle.propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
color: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
Toggle.defaultProps = {
|
||||||
|
active: false,
|
||||||
|
color: "green",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toggle;
|
50
dapps/react-dapp-v2-with-ethers/src/components/Wrapper.tsx
Normal file
50
dapps/react-dapp-v2-with-ethers/src/components/Wrapper.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as PropTypes from "prop-types";
|
||||||
|
import styled, { keyframes } from "styled-components";
|
||||||
|
|
||||||
|
const fadeIn = keyframes`
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface WrapperStyleProps {
|
||||||
|
center: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SWrapper = styled.div<WrapperStyleProps>`
|
||||||
|
will-change: transform, opacity;
|
||||||
|
animation: ${fadeIn} 0.7s ease 0s normal 1;
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: ${({ center }) => (center ? `center` : `flex-start`)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface WrapperProps extends WrapperStyleProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = (props: WrapperProps) => {
|
||||||
|
const { children, center } = props;
|
||||||
|
return (
|
||||||
|
<SWrapper {...props} center={center}>
|
||||||
|
{children}
|
||||||
|
</SWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Wrapper.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
center: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
Wrapper.defaultProps = {
|
||||||
|
center: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Wrapper;
|
73
dapps/react-dapp-v2-with-ethers/src/components/app/index.tsx
Normal file
73
dapps/react-dapp-v2-with-ethers/src/components/app/index.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import { fonts } from "../../styles";
|
||||||
|
import Button from "../Button";
|
||||||
|
import Column from "../Column";
|
||||||
|
import Wrapper from "../Wrapper";
|
||||||
|
|
||||||
|
export const SLayout = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SContent = styled(Wrapper as any)`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SLanding = styled(Column as any)`
|
||||||
|
/* height: 600px; */
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SButtonContainer = styled(Column as any)`
|
||||||
|
width: 250px;
|
||||||
|
margin: 50px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SConnectButton = styled(Button as any)`
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: ${fonts.size.medium};
|
||||||
|
height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 12px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SAccountsContainer = styled(SLanding as any)`
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
& h3 {
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SToggleContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px auto;
|
||||||
|
& > p {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SFullWidthContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SAccounts = styled(SFullWidthContainer)`
|
||||||
|
justify-content: space-between;
|
||||||
|
& > div {
|
||||||
|
margin: 12px 0;
|
||||||
|
flex: 1 0 100%;
|
||||||
|
@media (min-width: 648px) {
|
||||||
|
flex: 0 1 48%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@ -0,0 +1,32 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
export const SContainer = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
word-break: break-word;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const STable = styled(SContainer as any)`
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SRow = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin: 6px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SKey = styled.div`
|
||||||
|
width: 30%;
|
||||||
|
font-weight: 700;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SValue = styled.div`
|
||||||
|
width: 70%;
|
||||||
|
font-family: monospace;
|
||||||
|
`;
|
37
dapps/react-dapp-v2-with-ethers/src/constants/default.ts
Normal file
37
dapps/react-dapp-v2-with-ethers/src/constants/default.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export const DEFAULT_MAIN_CHAINS = [
|
||||||
|
// mainnets
|
||||||
|
"eip155:1",
|
||||||
|
"eip155:10",
|
||||||
|
"eip155:100",
|
||||||
|
"eip155:137",
|
||||||
|
"eip155:42161",
|
||||||
|
"eip155:42220",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_TEST_CHAINS = [
|
||||||
|
// testnets
|
||||||
|
"eip155:42",
|
||||||
|
"eip155:69",
|
||||||
|
"eip155:80001",
|
||||||
|
"eip155:421611",
|
||||||
|
"eip155:44787",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
|
||||||
|
|
||||||
|
export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID;
|
||||||
|
|
||||||
|
export const DEFAULT_INFURA_ID = process.env.REACT_APP_INFURA_ID;
|
||||||
|
|
||||||
|
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_LOGGER = "debug";
|
||||||
|
|
||||||
|
export const DEFAULT_APP_METADATA = {
|
||||||
|
name: "React App",
|
||||||
|
description: "React App for WalletConnect",
|
||||||
|
url: "https://walletconnect.com/",
|
||||||
|
icons: ["https://avatars.githubusercontent.com/u/37784886"],
|
||||||
|
};
|
2
dapps/react-dapp-v2-with-ethers/src/constants/index.ts
Normal file
2
dapps/react-dapp-v2-with-ethers/src/constants/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./default";
|
||||||
|
export * from "./logo";
|
1
dapps/react-dapp-v2-with-ethers/src/constants/logo.ts
Normal file
1
dapps/react-dapp-v2-with-ethers/src/constants/logo.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const BLOCKCHAIN_LOGO_BASE_URL = "https://blockchain-api.xyz/logos/";
|
292
dapps/react-dapp-v2-with-ethers/src/contexts/ClientContext.tsx
Normal file
292
dapps/react-dapp-v2-with-ethers/src/contexts/ClientContext.tsx
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
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 {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
DEFAULT_INFURA_ID,
|
||||||
|
DEFAULT_LOGGER,
|
||||||
|
DEFAULT_PROJECT_ID,
|
||||||
|
DEFAULT_RELAY_URL,
|
||||||
|
} from "../constants";
|
||||||
|
import { providers, utils } from "ethers";
|
||||||
|
import { ChainNamespaces, getAllChainNamespaces } from "../helpers";
|
||||||
|
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
interface IContext {
|
||||||
|
client: Client | undefined;
|
||||||
|
session: SessionTypes.Created | undefined;
|
||||||
|
disconnect: () => Promise<void>;
|
||||||
|
isInitializing: boolean;
|
||||||
|
chain: string;
|
||||||
|
pairings: string[];
|
||||||
|
accounts: string[];
|
||||||
|
balances: { symbol: string; balance: string }[];
|
||||||
|
isFetchingBalances: boolean;
|
||||||
|
chainData: ChainNamespaces;
|
||||||
|
onEnable: (chainId: string) => Promise<void>;
|
||||||
|
web3Provider?: providers.Web3Provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
export const ClientContext = createContext<IContext>({} as IContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider
|
||||||
|
*/
|
||||||
|
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||||
|
const [client, setClient] = useState<Client>();
|
||||||
|
const [pairings, setPairings] = useState<string[]>([]);
|
||||||
|
const [session, setSession] = useState<SessionTypes.Created>();
|
||||||
|
|
||||||
|
const [ethereumProvider, setEthereumProvider] = useState<EthereumProvider>();
|
||||||
|
const [web3Provider, setWeb3Provider] = useState<providers.Web3Provider>();
|
||||||
|
|
||||||
|
const [isFetchingBalances, setIsFetchingBalances] = useState(false);
|
||||||
|
const [isInitializing, setIsInitializing] = useState(false);
|
||||||
|
const [hasCheckedPersistedSession, setHasCheckedPersistedSession] = useState(false);
|
||||||
|
|
||||||
|
const [balances, setBalances] = useState<{ symbol: string; balance: string }[]>([]);
|
||||||
|
const [accounts, setAccounts] = useState<string[]>([]);
|
||||||
|
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
||||||
|
const [chain, setChain] = useState<string>("");
|
||||||
|
|
||||||
|
const resetApp = () => {
|
||||||
|
setPairings([]);
|
||||||
|
setSession(undefined);
|
||||||
|
setBalances([]);
|
||||||
|
setAccounts([]);
|
||||||
|
setChain("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadChainData = async () => {
|
||||||
|
const namespaces = getAllChainNamespaces();
|
||||||
|
const chainData: ChainNamespaces = {};
|
||||||
|
await Promise.all(
|
||||||
|
namespaces.map(async namespace => {
|
||||||
|
let chains: ChainsMap | undefined;
|
||||||
|
try {
|
||||||
|
chains = await apiGetChainNamespace(namespace);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
if (typeof chains !== "undefined") {
|
||||||
|
chainData[namespace] = chains;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setChainData(chainData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = useCallback(async () => {
|
||||||
|
if (typeof ethereumProvider === "undefined") {
|
||||||
|
throw new Error("ethereumProvider is not initialized");
|
||||||
|
}
|
||||||
|
await ethereumProvider.disconnect();
|
||||||
|
}, [ethereumProvider]);
|
||||||
|
|
||||||
|
const _subscribeToClientEvents = useCallback(async (_client: Client) => {
|
||||||
|
if (typeof _client === "undefined") {
|
||||||
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => {
|
||||||
|
const { uri } = proposal.signal.params;
|
||||||
|
console.log("EVENT", "QR Code Modal open");
|
||||||
|
QRCodeModal.open(uri, () => {
|
||||||
|
console.log("EVENT", "QR Code Modal closed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_client.on(CLIENT_EVENTS.pairing.created, async () => {
|
||||||
|
setPairings(_client.pairing.topics);
|
||||||
|
});
|
||||||
|
|
||||||
|
_client.on(CLIENT_EVENTS.session.deleted, () => {
|
||||||
|
console.log("EVENT", "session_deleted");
|
||||||
|
resetApp();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const createClient = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsInitializing(true);
|
||||||
|
|
||||||
|
const _client = await Client.init({
|
||||||
|
logger: DEFAULT_LOGGER,
|
||||||
|
relayUrl: DEFAULT_RELAY_URL,
|
||||||
|
projectId: DEFAULT_PROJECT_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
setClient(_client);
|
||||||
|
await _subscribeToClientEvents(_client);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setIsInitializing(false);
|
||||||
|
}
|
||||||
|
}, [_subscribeToClientEvents]);
|
||||||
|
|
||||||
|
const onEnable = useCallback(
|
||||||
|
async (caipChainId: string) => {
|
||||||
|
if (!client) {
|
||||||
|
throw new ReferenceError("WalletConnect Client is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chainId = caipChainId.split(":").pop();
|
||||||
|
|
||||||
|
console.log("Enabling EthereumProvider for chainId: ", chainId);
|
||||||
|
|
||||||
|
const customRpcs = Object.keys(chainData.eip155).reduce(
|
||||||
|
(rpcs: Record<string, string>, chainId) => {
|
||||||
|
rpcs[chainId] = chainData.eip155[chainId].rpc[0];
|
||||||
|
return rpcs;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create WalletConnect Provider
|
||||||
|
const ethereumProvider = new EthereumProvider({
|
||||||
|
chainId: Number(chainId),
|
||||||
|
rpc: {
|
||||||
|
infuraId: DEFAULT_INFURA_ID,
|
||||||
|
custom: customRpcs,
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
});
|
||||||
|
const web3Provider = new providers.Web3Provider(ethereumProvider);
|
||||||
|
|
||||||
|
console.log(ethereumProvider);
|
||||||
|
|
||||||
|
setEthereumProvider(ethereumProvider);
|
||||||
|
setWeb3Provider(web3Provider);
|
||||||
|
|
||||||
|
const _accounts = await ethereumProvider.enable();
|
||||||
|
const _session = await client.session.get(client.session.topics[0]);
|
||||||
|
|
||||||
|
setAccounts(_accounts);
|
||||||
|
setSession(_session);
|
||||||
|
setChain(caipChainId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsFetchingBalances(true);
|
||||||
|
const _balances = await Promise.all(
|
||||||
|
_accounts.map(async account => {
|
||||||
|
const balance = await web3Provider.getBalance(account);
|
||||||
|
return { symbol: "ETH", balance: utils.formatEther(balance) };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setBalances(_balances);
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
setIsFetchingBalances(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRCodeModal.close();
|
||||||
|
},
|
||||||
|
[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(() => {
|
||||||
|
const getPersistedSession = async () => {
|
||||||
|
if (client && !hasCheckedPersistedSession) {
|
||||||
|
await _checkForPersistedSession(client);
|
||||||
|
setHasCheckedPersistedSession(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getPersistedSession();
|
||||||
|
}, [client, _checkForPersistedSession, hasCheckedPersistedSession]);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
pairings,
|
||||||
|
isInitializing,
|
||||||
|
balances,
|
||||||
|
isFetchingBalances,
|
||||||
|
accounts,
|
||||||
|
chain,
|
||||||
|
client,
|
||||||
|
session,
|
||||||
|
disconnect,
|
||||||
|
chainData,
|
||||||
|
onEnable,
|
||||||
|
web3Provider,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
pairings,
|
||||||
|
isInitializing,
|
||||||
|
balances,
|
||||||
|
isFetchingBalances,
|
||||||
|
accounts,
|
||||||
|
chain,
|
||||||
|
client,
|
||||||
|
session,
|
||||||
|
disconnect,
|
||||||
|
chainData,
|
||||||
|
onEnable,
|
||||||
|
web3Provider,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClientContext.Provider
|
||||||
|
value={{
|
||||||
|
...value,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ClientContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWalletConnectClient() {
|
||||||
|
const context = useContext(ClientContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useWalletConnectClient must be used within a ClientContextProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
415
dapps/react-dapp-v2-with-ethers/src/contexts/JsonRpcContext.tsx
Normal file
415
dapps/react-dapp-v2-with-ethers/src/contexts/JsonRpcContext.tsx
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
import { BigNumber } from "ethers";
|
||||||
|
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||||
|
import * as encoding from "@walletconnect/encoding";
|
||||||
|
import { formatDirectSignDoc, stringifySignDocValues } from "cosmos-wallet";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChainNamespaces,
|
||||||
|
eip712,
|
||||||
|
formatTestTransaction,
|
||||||
|
getAllChainNamespaces,
|
||||||
|
hashPersonalMessage,
|
||||||
|
hashTypedDataMessage,
|
||||||
|
verifySignature,
|
||||||
|
} from "../helpers";
|
||||||
|
import { useWalletConnectClient } from "./ClientContext";
|
||||||
|
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
interface IFormattedRpcResponse {
|
||||||
|
method: string;
|
||||||
|
address: string;
|
||||||
|
valid: boolean;
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRpcResult {
|
||||||
|
method: string;
|
||||||
|
valid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IContext {
|
||||||
|
ping: () => Promise<void>;
|
||||||
|
ethereumRpc: {
|
||||||
|
testSendTransaction: (chainId: string) => Promise<void>;
|
||||||
|
testSignPersonalMessage: (chainId: string) => Promise<void>;
|
||||||
|
testSignTypedData: (chainId: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
cosmosRpc: {
|
||||||
|
testSignDirect: (chainId: string) => Promise<void>;
|
||||||
|
testSignAmino: (chainId: string) => Promise<void>;
|
||||||
|
};
|
||||||
|
chainData: ChainNamespaces;
|
||||||
|
rpcResult?: IRpcResult | null;
|
||||||
|
isRpcRequestPending: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
export const JsonRpcContext = createContext<IContext>({} as IContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider
|
||||||
|
*/
|
||||||
|
export function JsonRpcContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||||
|
const [pending, setPending] = useState(false);
|
||||||
|
const [result, setResult] = useState<IRpcResult | null>();
|
||||||
|
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
||||||
|
|
||||||
|
const { client, session, accounts, balances } = useWalletConnectClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadChainData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadChainData = async () => {
|
||||||
|
const namespaces = getAllChainNamespaces();
|
||||||
|
const chainData: ChainNamespaces = {};
|
||||||
|
await Promise.all(
|
||||||
|
namespaces.map(async namespace => {
|
||||||
|
let chains: ChainsMap | undefined;
|
||||||
|
try {
|
||||||
|
chains = await apiGetChainNamespace(namespace);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
if (typeof chains !== "undefined") {
|
||||||
|
chainData[namespace] = chains;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setChainData(chainData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAddressByChainId = (chainId: string) => {
|
||||||
|
const account = accounts.find(account => account.startsWith(chainId));
|
||||||
|
if (account === undefined) throw new Error(`Account for chainId ${chainId} not found.`);
|
||||||
|
const address = account.split(":").pop();
|
||||||
|
if (address === undefined) throw new Error(`Address for account ${account} is invalid`);
|
||||||
|
|
||||||
|
return address;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _createJsonRpcRequestHandler =
|
||||||
|
(rpcRequest: (...requestArgs: [any]) => Promise<IFormattedRpcResponse>) =>
|
||||||
|
async (chainId: string) => {
|
||||||
|
if (typeof client === "undefined") {
|
||||||
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
}
|
||||||
|
if (typeof session === "undefined") {
|
||||||
|
throw new Error("Session is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPending(true);
|
||||||
|
const result = await rpcRequest(chainId);
|
||||||
|
setResult(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ping = async () => {
|
||||||
|
if (typeof client === "undefined") {
|
||||||
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
}
|
||||||
|
if (typeof session === "undefined") {
|
||||||
|
throw new Error("Session is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPending(true);
|
||||||
|
|
||||||
|
let valid = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.session.ping(session.topic);
|
||||||
|
valid = true;
|
||||||
|
} catch (e) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display result
|
||||||
|
setResult({
|
||||||
|
method: "ping",
|
||||||
|
valid,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------- ETHEREUM/EIP155 RPC METHODS --------
|
||||||
|
|
||||||
|
const ethereumRpc = {
|
||||||
|
testSendTransaction: _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// get ethereum address
|
||||||
|
const account = accounts.find(account => account.startsWith(chainId));
|
||||||
|
if (account === undefined) throw new Error("Account is not found");
|
||||||
|
const address = account.split(":").pop();
|
||||||
|
if (address === undefined) throw new Error("Address is invalid");
|
||||||
|
|
||||||
|
const tx = await formatTestTransaction(account);
|
||||||
|
|
||||||
|
const balance = BigNumber.from(balances[0].balance || "0");
|
||||||
|
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
|
||||||
|
return {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
address,
|
||||||
|
valid: false,
|
||||||
|
result: "Insufficient funds for intrinsic transaction cost",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: string = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
params: [tx],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
address,
|
||||||
|
valid: true,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
testSignPersonalMessage: _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// test message
|
||||||
|
const message = `My email is john@doe.com - ${Date.now()}`;
|
||||||
|
|
||||||
|
// encode message (hex)
|
||||||
|
const hexMsg = encoding.utf8ToHex(message, true);
|
||||||
|
|
||||||
|
const address = getAddressByChainId(chainId);
|
||||||
|
|
||||||
|
// personal_sign params
|
||||||
|
const params = [hexMsg, address];
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result: string = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "personal_sign",
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// split chainId
|
||||||
|
const [namespace, reference] = chainId.split(":");
|
||||||
|
|
||||||
|
const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
if (typeof targetChainData === "undefined") {
|
||||||
|
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpcUrl = targetChainData.rpc[0];
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
const hash = hashPersonalMessage(message);
|
||||||
|
const valid = await verifySignature(address, result, hash, rpcUrl);
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "personal_sign",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
testSignTypedData: _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// test message
|
||||||
|
const message = JSON.stringify(eip712.example);
|
||||||
|
|
||||||
|
const address = getAddressByChainId(chainId);
|
||||||
|
|
||||||
|
// eth_signTypedData params
|
||||||
|
const params = [address, message];
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "eth_signTypedData",
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// split chainId
|
||||||
|
const [namespace, reference] = chainId.split(":");
|
||||||
|
|
||||||
|
const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
if (typeof targetChainData === "undefined") {
|
||||||
|
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpcUrl = targetChainData.rpc[0];
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
const hash = hashTypedDataMessage(message);
|
||||||
|
const valid = await verifySignature(address, result, hash, rpcUrl);
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "eth_signTypedData",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------- COSMOS RPC METHODS --------
|
||||||
|
|
||||||
|
const cosmosRpc = {
|
||||||
|
testSignDirect: _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// test direct sign doc inputs
|
||||||
|
const inputs = {
|
||||||
|
fee: [{ amount: "2000", denom: "ucosm" }],
|
||||||
|
pubkey: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW",
|
||||||
|
gasLimit: 200000,
|
||||||
|
accountNumber: 1,
|
||||||
|
sequence: 1,
|
||||||
|
bodyBytes:
|
||||||
|
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
|
||||||
|
authInfoBytes:
|
||||||
|
"0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c",
|
||||||
|
};
|
||||||
|
|
||||||
|
// split chainId
|
||||||
|
const [namespace, reference] = chainId.split(":");
|
||||||
|
|
||||||
|
// format sign doc
|
||||||
|
const signDoc = formatDirectSignDoc(
|
||||||
|
inputs.fee,
|
||||||
|
inputs.pubkey,
|
||||||
|
inputs.gasLimit,
|
||||||
|
inputs.accountNumber,
|
||||||
|
inputs.sequence,
|
||||||
|
inputs.bodyBytes,
|
||||||
|
reference,
|
||||||
|
);
|
||||||
|
|
||||||
|
const address = getAddressByChainId(chainId);
|
||||||
|
|
||||||
|
// cosmos_signDirect params
|
||||||
|
const params = {
|
||||||
|
signerAddress: address,
|
||||||
|
signDoc: stringifySignDocValues(signDoc),
|
||||||
|
};
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "cosmos_signDirect",
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
if (typeof targetChainData === "undefined") {
|
||||||
|
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if valid
|
||||||
|
const valid = true;
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "cosmos_signDirect",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result: result.signature.signature,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
testSignAmino: _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// split chainId
|
||||||
|
const [namespace, reference] = chainId.split(":");
|
||||||
|
|
||||||
|
// test amino sign doc
|
||||||
|
const signDoc = {
|
||||||
|
msgs: [],
|
||||||
|
fee: { amount: [], gas: "23" },
|
||||||
|
chain_id: "foochain",
|
||||||
|
memo: "hello, world",
|
||||||
|
account_number: "7",
|
||||||
|
sequence: "54",
|
||||||
|
};
|
||||||
|
|
||||||
|
const address = getAddressByChainId(chainId);
|
||||||
|
|
||||||
|
// cosmos_signAmino params
|
||||||
|
const params = { signerAddress: address, signDoc };
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "cosmos_signAmino",
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
if (typeof targetChainData === "undefined") {
|
||||||
|
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if valid
|
||||||
|
const valid = true;
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "cosmos_signAmino",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result: result.signature.signature,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JsonRpcContext.Provider
|
||||||
|
value={{
|
||||||
|
chainData,
|
||||||
|
ping,
|
||||||
|
ethereumRpc,
|
||||||
|
cosmosRpc,
|
||||||
|
rpcResult: result,
|
||||||
|
isRpcRequestPending: pending,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</JsonRpcContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useJsonRpc() {
|
||||||
|
const context = useContext(JsonRpcContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useJsonRpc must be used within a JsonRpcContextProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
45
dapps/react-dapp-v2-with-ethers/src/helpers/api.ts
Normal file
45
dapps/react-dapp-v2-with-ethers/src/helpers/api.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import { AssetData, GasPrices, ParsedTx } from "./types";
|
||||||
|
|
||||||
|
const ethereumApi: AxiosInstance = axios.create({
|
||||||
|
baseURL: "https://ethereum-api.xyz",
|
||||||
|
timeout: 30000, // 30 secs
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function apiGetAccountAssets(address: string, chainId: string): Promise<AssetData[]> {
|
||||||
|
const ethChainId = chainId.split(":")[1];
|
||||||
|
const response = await ethereumApi.get(
|
||||||
|
`/account-assets?address=${address}&chainId=${ethChainId}`,
|
||||||
|
);
|
||||||
|
const { result } = response.data;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiGetAccountTransactions(
|
||||||
|
address: string,
|
||||||
|
chainId: string,
|
||||||
|
): Promise<ParsedTx[]> {
|
||||||
|
const ethChainId = chainId.split(":")[1];
|
||||||
|
const response = await ethereumApi.get(
|
||||||
|
`/account-transactions?address=${address}&chainId=${ethChainId}`,
|
||||||
|
);
|
||||||
|
const { result } = response.data;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const apiGetAccountNonce = async (address: string, chainId: string): Promise<number> => {
|
||||||
|
const ethChainId = chainId.split(":")[1];
|
||||||
|
const response = await ethereumApi.get(`/account-nonce?address=${address}&chainId=${ethChainId}`);
|
||||||
|
const { result } = response.data;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const apiGetGasPrices = async (): Promise<GasPrices> => {
|
||||||
|
const response = await ethereumApi.get(`/gas-prices`);
|
||||||
|
const { result } = response.data;
|
||||||
|
return result;
|
||||||
|
};
|
55
dapps/react-dapp-v2-with-ethers/src/helpers/eip1271.ts
Normal file
55
dapps/react-dapp-v2-with-ethers/src/helpers/eip1271.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Contract, providers, utils } from "ethers";
|
||||||
|
|
||||||
|
const spec = {
|
||||||
|
magicValue: "0x1626ba7e",
|
||||||
|
abi: [
|
||||||
|
{
|
||||||
|
constant: true,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "_hash",
|
||||||
|
type: "bytes32",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_sig",
|
||||||
|
type: "bytes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "isValidSignature",
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "magicValue",
|
||||||
|
type: "bytes4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "view",
|
||||||
|
type: "function",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function isValidSignature(
|
||||||
|
address: string,
|
||||||
|
sig: string,
|
||||||
|
data: string,
|
||||||
|
provider: providers.Provider,
|
||||||
|
abi = eip1271.spec.abi,
|
||||||
|
magicValue = eip1271.spec.magicValue,
|
||||||
|
): Promise<boolean> {
|
||||||
|
let returnValue;
|
||||||
|
try {
|
||||||
|
returnValue = await new Contract(address, abi, provider).isValidSignature(
|
||||||
|
utils.arrayify(data),
|
||||||
|
sig,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return returnValue.toLowerCase() === magicValue.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eip1271 = {
|
||||||
|
spec,
|
||||||
|
isValidSignature,
|
||||||
|
};
|
50
dapps/react-dapp-v2-with-ethers/src/helpers/eip712.ts
Normal file
50
dapps/react-dapp-v2-with-ethers/src/helpers/eip712.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const example = {
|
||||||
|
types: {
|
||||||
|
EIP712Domain: [
|
||||||
|
{ name: "name", type: "string" },
|
||||||
|
{ name: "version", type: "string" },
|
||||||
|
{ name: "verifyingContract", type: "address" },
|
||||||
|
],
|
||||||
|
RelayRequest: [
|
||||||
|
{ name: "target", type: "address" },
|
||||||
|
{ name: "encodedFunction", type: "bytes" },
|
||||||
|
{ name: "gasData", type: "GasData" },
|
||||||
|
{ name: "relayData", type: "RelayData" },
|
||||||
|
],
|
||||||
|
GasData: [
|
||||||
|
{ name: "gasLimit", type: "uint256" },
|
||||||
|
{ name: "gasPrice", type: "uint256" },
|
||||||
|
{ name: "pctRelayFee", type: "uint256" },
|
||||||
|
{ name: "baseRelayFee", type: "uint256" },
|
||||||
|
],
|
||||||
|
RelayData: [
|
||||||
|
{ name: "senderAddress", type: "address" },
|
||||||
|
{ name: "senderNonce", type: "uint256" },
|
||||||
|
{ name: "relayWorker", type: "address" },
|
||||||
|
{ name: "paymaster", type: "address" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
name: "GSN Relayed Transaction",
|
||||||
|
version: "1",
|
||||||
|
chainId: 42,
|
||||||
|
verifyingContract: "0x6453D37248Ab2C16eBd1A8f782a2CBC65860E60B",
|
||||||
|
},
|
||||||
|
primaryType: "RelayRequest",
|
||||||
|
message: {
|
||||||
|
target: "0x9cf40ef3d1622efe270fe6fe720585b4be4eeeff",
|
||||||
|
encodedFunction:
|
||||||
|
"0xa9059cbb0000000000000000000000002e0d94754b348d208d64d52d78bcd443afa9fa520000000000000000000000000000000000000000000000000000000000000007",
|
||||||
|
gasData: { gasLimit: "39507", gasPrice: "1700000000", pctRelayFee: "70", baseRelayFee: "0" },
|
||||||
|
relayData: {
|
||||||
|
senderAddress: "0x22d491bde2303f2f43325b2108d26f1eaba1e32b",
|
||||||
|
senderNonce: "3",
|
||||||
|
relayWorker: "0x3baee457ad824c94bd3953183d725847d023a2cf",
|
||||||
|
paymaster: "0x957F270d45e9Ceca5c5af2b49f1b5dC1Abb0421c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const eip712 = {
|
||||||
|
example,
|
||||||
|
};
|
6
dapps/react-dapp-v2-with-ethers/src/helpers/index.ts
Normal file
6
dapps/react-dapp-v2-with-ethers/src/helpers/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./eip712";
|
||||||
|
export * from "./eip1271";
|
||||||
|
export * from "./tx";
|
||||||
|
export * from "./types";
|
||||||
|
export * from "./utilities";
|
35
dapps/react-dapp-v2-with-ethers/src/helpers/tx.ts
Normal file
35
dapps/react-dapp-v2-with-ethers/src/helpers/tx.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as encoding from "@walletconnect/encoding";
|
||||||
|
|
||||||
|
import { apiGetAccountNonce, apiGetGasPrices } from "./api";
|
||||||
|
import { toWad } from "./utilities";
|
||||||
|
|
||||||
|
export async function getGasPrice(chainId: string): Promise<string> {
|
||||||
|
if (chainId === "eip155:1") return toWad("20", 9).toHexString();
|
||||||
|
const gasPrices = await apiGetGasPrices();
|
||||||
|
return toWad(`${gasPrices.slow.price}`, 9).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function formatTestTransaction(account: string) {
|
||||||
|
const [namespace, reference, address] = account.split(":");
|
||||||
|
const chainId = `${namespace}:${reference}`;
|
||||||
|
// nonce
|
||||||
|
const _nonce = await apiGetAccountNonce(address, chainId);
|
||||||
|
|
||||||
|
const nonce = encoding.sanitizeHex(encoding.numberToHex(_nonce));
|
||||||
|
|
||||||
|
// gasPrice
|
||||||
|
const _gasPrice = await getGasPrice(chainId);
|
||||||
|
const gasPrice = encoding.sanitizeHex(_gasPrice);
|
||||||
|
|
||||||
|
// gasLimit
|
||||||
|
const _gasLimit = 21000;
|
||||||
|
const gasLimit = encoding.sanitizeHex(encoding.numberToHex(_gasLimit));
|
||||||
|
|
||||||
|
// value
|
||||||
|
const _value = 0;
|
||||||
|
const value = encoding.sanitizeHex(encoding.numberToHex(_value));
|
||||||
|
|
||||||
|
const tx = { from: address, to: address, data: "0x", nonce, gasPrice, gasLimit, value };
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
159
dapps/react-dapp-v2-with-ethers/src/helpers/types.ts
Normal file
159
dapps/react-dapp-v2-with-ethers/src/helpers/types.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import { ChainsMap } from "caip-api";
|
||||||
|
|
||||||
|
export interface AssetData {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
decimals: string;
|
||||||
|
contractAddress: string;
|
||||||
|
balance?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChainData {
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
chain: string;
|
||||||
|
network: string;
|
||||||
|
chain_id: number;
|
||||||
|
network_id: number;
|
||||||
|
rpc_url: string;
|
||||||
|
native_currency: AssetData;
|
||||||
|
}
|
||||||
|
export interface TxData {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
nonce: string;
|
||||||
|
gasPrice: string;
|
||||||
|
gasLimit: string;
|
||||||
|
value: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockScoutTx {
|
||||||
|
value: string;
|
||||||
|
txreceipt_status: string;
|
||||||
|
transactionIndex: string;
|
||||||
|
to: string;
|
||||||
|
timeStamp: string;
|
||||||
|
nonce: string;
|
||||||
|
isError: string;
|
||||||
|
input: string;
|
||||||
|
hash: string;
|
||||||
|
gasUsed: string;
|
||||||
|
gasPrice: string;
|
||||||
|
gas: string;
|
||||||
|
from: string;
|
||||||
|
cumulativeGasUsed: string;
|
||||||
|
contractAddress: string;
|
||||||
|
confirmations: string;
|
||||||
|
blockNumber: string;
|
||||||
|
blockHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockScoutTokenTx {
|
||||||
|
value: string;
|
||||||
|
transactionIndex: string;
|
||||||
|
tokenSymbol: string;
|
||||||
|
tokenName: string;
|
||||||
|
tokenDecimal: string;
|
||||||
|
to: string;
|
||||||
|
timeStamp: string;
|
||||||
|
nonce: string;
|
||||||
|
input: string;
|
||||||
|
hash: string;
|
||||||
|
gasUsed: string;
|
||||||
|
gasPrice: string;
|
||||||
|
gas: string;
|
||||||
|
from: string;
|
||||||
|
cumulativeGasUsed: string;
|
||||||
|
contractAddress: string;
|
||||||
|
confirmations: string;
|
||||||
|
blockNumber: string;
|
||||||
|
blockHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParsedTx {
|
||||||
|
timestamp: string;
|
||||||
|
hash: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
nonce: string;
|
||||||
|
gasPrice: string;
|
||||||
|
gasUsed: string;
|
||||||
|
fee: string;
|
||||||
|
value: string;
|
||||||
|
input: string;
|
||||||
|
error: boolean;
|
||||||
|
asset: AssetData;
|
||||||
|
operations: TxOperation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TxOperation {
|
||||||
|
asset: AssetData;
|
||||||
|
value: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
functionName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GasPricesResponse {
|
||||||
|
fastWait: number;
|
||||||
|
avgWait: number;
|
||||||
|
blockNum: number;
|
||||||
|
fast: number;
|
||||||
|
fastest: number;
|
||||||
|
fastestWait: number;
|
||||||
|
safeLow: number;
|
||||||
|
safeLowWait: number;
|
||||||
|
speed: number;
|
||||||
|
block_time: number;
|
||||||
|
average: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GasPrice {
|
||||||
|
time: number;
|
||||||
|
price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GasPrices {
|
||||||
|
timestamp: number;
|
||||||
|
slow: GasPrice;
|
||||||
|
average: GasPrice;
|
||||||
|
fast: GasPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MethodArgument {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Method {
|
||||||
|
signature: string;
|
||||||
|
name: string;
|
||||||
|
args: MethodArgument[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChainRequestRender {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChainMetadata {
|
||||||
|
name?: string;
|
||||||
|
logo: string;
|
||||||
|
rgb: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NamespaceMetadata {
|
||||||
|
[reference: string]: ChainMetadata;
|
||||||
|
}
|
||||||
|
export interface ChainNamespaces {
|
||||||
|
[namespace: string]: ChainsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountAction {
|
||||||
|
method: string;
|
||||||
|
callback: (chainId: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountBalances {
|
||||||
|
[account: string]: AssetData[];
|
||||||
|
}
|
212
dapps/react-dapp-v2-with-ethers/src/helpers/utilities.ts
Normal file
212
dapps/react-dapp-v2-with-ethers/src/helpers/utilities.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { BigNumber, BigNumberish, providers, utils } from "ethers";
|
||||||
|
import * as encoding from "@walletconnect/encoding";
|
||||||
|
import { TypedDataUtils } from "eth-sig-util";
|
||||||
|
import * as ethUtil from "ethereumjs-util";
|
||||||
|
|
||||||
|
import { eip1271 } from "./eip1271";
|
||||||
|
import { DEFAULT_CHAINS } from "../constants";
|
||||||
|
|
||||||
|
export function capitalize(string: string): string {
|
||||||
|
return string
|
||||||
|
.split(" ")
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ellipseText(text = "", maxLength = 9999): string {
|
||||||
|
if (text.length <= maxLength) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const _maxLength = maxLength - 3;
|
||||||
|
let ellipse = false;
|
||||||
|
let currentLength = 0;
|
||||||
|
const result =
|
||||||
|
text
|
||||||
|
.split(" ")
|
||||||
|
.filter(word => {
|
||||||
|
currentLength += word.length;
|
||||||
|
if (ellipse || currentLength >= _maxLength) {
|
||||||
|
ellipse = true;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(" ") + "...";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ellipseAddress(address = "", width = 10): string {
|
||||||
|
return `${address.slice(0, width)}...${address.slice(-width)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataString(func: string, arrVals: any[]): string {
|
||||||
|
let val = "";
|
||||||
|
for (let i = 0; i < arrVals.length; i++) {
|
||||||
|
val += encoding.padLeft(arrVals[i], 64);
|
||||||
|
}
|
||||||
|
const data = func + val;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMobile(): boolean {
|
||||||
|
let mobile = false;
|
||||||
|
|
||||||
|
function hasTouchEvent(): boolean {
|
||||||
|
try {
|
||||||
|
document.createEvent("TouchEvent");
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMobileUserAgent(): boolean {
|
||||||
|
if (
|
||||||
|
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
|
||||||
|
navigator.userAgent,
|
||||||
|
) ||
|
||||||
|
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
|
||||||
|
navigator.userAgent.substr(0, 4),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (hasTouchEvent()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mobile = hasMobileUserAgent();
|
||||||
|
|
||||||
|
return mobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodePersonalMessage(msg: string): string {
|
||||||
|
const data = encoding.utf8ToBuffer(msg);
|
||||||
|
const buf = Buffer.concat([
|
||||||
|
Buffer.from("\u0019Ethereum Signed Message:\n" + data.length.toString(), "utf8"),
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
return ethUtil.bufferToHex(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashPersonalMessage(msg: string): string {
|
||||||
|
const data = encodePersonalMessage(msg);
|
||||||
|
const buf = ethUtil.toBuffer(data);
|
||||||
|
const hash = ethUtil.keccak256(buf);
|
||||||
|
return ethUtil.bufferToHex(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeTypedDataMessage(msg: string): string {
|
||||||
|
const data = TypedDataUtils.sanitizeData(JSON.parse(msg));
|
||||||
|
const buf = Buffer.concat([
|
||||||
|
Buffer.from("1901", "hex"),
|
||||||
|
TypedDataUtils.hashStruct("EIP712Domain", data.domain, data.types),
|
||||||
|
TypedDataUtils.hashStruct(data.primaryType as string, data.message, data.types),
|
||||||
|
]);
|
||||||
|
return ethUtil.bufferToHex(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hashTypedDataMessage(msg: string): string {
|
||||||
|
const data = encodeTypedDataMessage(msg);
|
||||||
|
const buf = ethUtil.toBuffer(data);
|
||||||
|
const hash = ethUtil.keccak256(buf);
|
||||||
|
return ethUtil.bufferToHex(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recoverAddress(sig: string, hash: string): string {
|
||||||
|
const params = ethUtil.fromRpcSig(sig);
|
||||||
|
const result = ethUtil.ecrecover(ethUtil.toBuffer(hash), params.v, params.r, params.s);
|
||||||
|
const signer = ethUtil.bufferToHex(ethUtil.publicToAddress(result));
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recoverPersonalSignature(sig: string, msg: string): string {
|
||||||
|
const hash = hashPersonalMessage(msg);
|
||||||
|
const signer = recoverAddress(sig, hash);
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recoverTypedMessage(sig: string, msg: string): string {
|
||||||
|
const hash = hashTypedDataMessage(msg);
|
||||||
|
const signer = recoverAddress(sig, hash);
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifySignature(
|
||||||
|
address: string,
|
||||||
|
sig: string,
|
||||||
|
hash: string,
|
||||||
|
rpcUrl: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const provider = new providers.JsonRpcProvider(rpcUrl);
|
||||||
|
const bytecode = await provider.getCode(address);
|
||||||
|
if (!bytecode || bytecode === "0x" || bytecode === "0x0" || bytecode === "0x00") {
|
||||||
|
const signer = recoverAddress(sig, hash);
|
||||||
|
return signer.toLowerCase() === address.toLowerCase();
|
||||||
|
} else {
|
||||||
|
return eip1271.isValidSignature(address, sig, hash, provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertHexToNumber(hex: string) {
|
||||||
|
try {
|
||||||
|
return encoding.hexToNumber(hex);
|
||||||
|
} catch (e) {
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertHexToUtf8(hex: string) {
|
||||||
|
try {
|
||||||
|
return encoding.hexToUtf8(hex);
|
||||||
|
} catch (e) {
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sanitizeDecimals = (value: string, decimals = 18): string => {
|
||||||
|
const [integer, fractional] = value.split(".");
|
||||||
|
const _fractional = fractional
|
||||||
|
? fractional.substring(0, decimals).replace(/0+$/gi, "")
|
||||||
|
: undefined;
|
||||||
|
return _fractional ? [integer, _fractional].join(".") : integer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toWad = (amount: string, decimals = 18): BigNumber => {
|
||||||
|
return utils.parseUnits(sanitizeDecimals(amount, decimals), decimals);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fromWad = (wad: BigNumberish, decimals = 18): string => {
|
||||||
|
return sanitizeDecimals(utils.formatUnits(wad, decimals), decimals);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LOCALSTORAGE_KEY_TESTNET = "TESTNET";
|
||||||
|
export const INITIAL_STATE_TESTNET_DEFAULT = true;
|
||||||
|
|
||||||
|
export function setLocaleStorageTestnetFlag(value: boolean): void {
|
||||||
|
window.localStorage.setItem(LOCALSTORAGE_KEY_TESTNET, `${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorageTestnetFlag(): boolean {
|
||||||
|
let value = INITIAL_STATE_TESTNET_DEFAULT;
|
||||||
|
const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET);
|
||||||
|
if (!persisted) {
|
||||||
|
setLocaleStorageTestnetFlag(value);
|
||||||
|
} else {
|
||||||
|
value = persisted === "true" ? true : false;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllChainNamespaces = () => {
|
||||||
|
const namespaces: string[] = [];
|
||||||
|
DEFAULT_CHAINS.forEach(chainId => {
|
||||||
|
const [namespace] = chainId.split(":");
|
||||||
|
if (!namespaces.includes(namespace)) {
|
||||||
|
namespaces.push(namespace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return namespaces;
|
||||||
|
};
|
30
dapps/react-dapp-v2-with-ethers/src/index.tsx
Normal file
30
dapps/react-dapp-v2-with-ethers/src/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { createGlobalStyle } from "styled-components";
|
||||||
|
import { ClientContextProvider } from "./contexts/ClientContext";
|
||||||
|
import { JsonRpcContextProvider } from "./contexts/JsonRpcContext";
|
||||||
|
|
||||||
|
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>
|
||||||
|
<JsonRpcContextProvider>
|
||||||
|
<App />
|
||||||
|
</JsonRpcContextProvider>
|
||||||
|
</ClientContextProvider>
|
||||||
|
</>,
|
||||||
|
document.getElementById("root"),
|
||||||
|
);
|
35
dapps/react-dapp-v2-with-ethers/src/modals/PairingModal.tsx
Normal file
35
dapps/react-dapp-v2-with-ethers/src/modals/PairingModal.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { PairingTypes } from "@walletconnect/types";
|
||||||
|
|
||||||
|
import Button from "../components/Button";
|
||||||
|
import Pairing from "../components/Pairing";
|
||||||
|
import { STable } from "../components/shared";
|
||||||
|
|
||||||
|
import { SModalContainer, SModalTitle } from "./shared";
|
||||||
|
|
||||||
|
interface PairingModalProps {
|
||||||
|
pairings: PairingTypes.Settled[];
|
||||||
|
connect: (pairing?: { topic: string }) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PairingModal = (props: PairingModalProps) => {
|
||||||
|
const { pairings, connect } = props;
|
||||||
|
return (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>{"Select available pairing or create new one"}</SModalTitle>
|
||||||
|
<STable>
|
||||||
|
{pairings.map(pairing => (
|
||||||
|
<Pairing
|
||||||
|
key={pairing.topic}
|
||||||
|
pairing={pairing}
|
||||||
|
onClick={() => connect({ topic: pairing.topic })}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</STable>
|
||||||
|
<Button onClick={() => connect()}>{`New Pairing`}</Button>
|
||||||
|
</SModalContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PairingModal;
|
39
dapps/react-dapp-v2-with-ethers/src/modals/PingModal.tsx
Normal file
39
dapps/react-dapp-v2-with-ethers/src/modals/PingModal.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import Loader from "../components/Loader";
|
||||||
|
import { SContainer } from "../components/shared";
|
||||||
|
|
||||||
|
import { SModalContainer, SModalTitle } from "./shared";
|
||||||
|
|
||||||
|
interface PingModalProps {
|
||||||
|
pending: boolean;
|
||||||
|
result: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PingModal = (props: PingModalProps) => {
|
||||||
|
const { pending, result } = props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{pending ? (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>{"Pending Session Ping"}</SModalTitle>
|
||||||
|
<SContainer>
|
||||||
|
<Loader />
|
||||||
|
</SContainer>
|
||||||
|
</SModalContainer>
|
||||||
|
) : result ? (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>
|
||||||
|
{result.valid ? "Successful Session Ping" : "Failed Session Ping"}
|
||||||
|
</SModalTitle>
|
||||||
|
</SModalContainer>
|
||||||
|
) : (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>{"Unknown Error with Session Ping"}</SModalTitle>
|
||||||
|
</SModalContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PingModal;
|
48
dapps/react-dapp-v2-with-ethers/src/modals/RequestModal.tsx
Normal file
48
dapps/react-dapp-v2-with-ethers/src/modals/RequestModal.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import Loader from "../components/Loader";
|
||||||
|
import { SContainer, STable, SRow, SKey, SValue } from "../components/shared";
|
||||||
|
|
||||||
|
import { SModalContainer, SModalTitle, SModalParagraph } from "./shared";
|
||||||
|
|
||||||
|
interface RequestModalProps {
|
||||||
|
pending: boolean;
|
||||||
|
result: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RequestModal = (props: RequestModalProps) => {
|
||||||
|
const { pending, result } = props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{pending ? (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>{"Pending JSON-RPC Request"}</SModalTitle>
|
||||||
|
<SContainer>
|
||||||
|
<Loader />
|
||||||
|
<SModalParagraph>{"Approve or reject request using your wallet"}</SModalParagraph>
|
||||||
|
</SContainer>
|
||||||
|
</SModalContainer>
|
||||||
|
) : result ? (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>
|
||||||
|
{result.valid ? "JSON-RPC Request Approved" : "JSON-RPC Request Failed"}
|
||||||
|
</SModalTitle>
|
||||||
|
<STable>
|
||||||
|
{Object.keys(result).map(key => (
|
||||||
|
<SRow key={key}>
|
||||||
|
<SKey>{key}</SKey>
|
||||||
|
<SValue>{result[key].toString()}</SValue>
|
||||||
|
</SRow>
|
||||||
|
))}
|
||||||
|
</STable>
|
||||||
|
</SModalContainer>
|
||||||
|
) : (
|
||||||
|
<SModalContainer>
|
||||||
|
<SModalTitle>{"JSON-RPC Request Rejected"}</SModalTitle>
|
||||||
|
</SModalContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestModal;
|
17
dapps/react-dapp-v2-with-ethers/src/modals/shared/index.ts
Normal file
17
dapps/react-dapp-v2-with-ethers/src/modals/shared/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
export const SModalContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
word-wrap: break-word;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SModalTitle = styled.div`
|
||||||
|
margin: 1em 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SModalParagraph = styled.p`
|
||||||
|
margin-top: 30px;
|
||||||
|
`;
|
1
dapps/react-dapp-v2-with-ethers/src/react-app-env.d.ts
vendored
Normal file
1
dapps/react-dapp-v2-with-ethers/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
219
dapps/react-dapp-v2-with-ethers/src/styles.ts
Normal file
219
dapps/react-dapp-v2-with-ethers/src/styles.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
export const colors: Record<string, string> = {
|
||||||
|
white: "255, 255, 255",
|
||||||
|
black: "0, 0, 0",
|
||||||
|
dark: "12, 12, 13",
|
||||||
|
grey: "169, 169, 188",
|
||||||
|
darkGrey: "113, 119, 138",
|
||||||
|
lightGrey: "212, 212, 212",
|
||||||
|
blue: "101, 127, 230",
|
||||||
|
lightBlue: "64, 153, 255",
|
||||||
|
yellow: "250, 188, 45",
|
||||||
|
orange: "246, 133, 27",
|
||||||
|
green: "84, 209, 146",
|
||||||
|
pink: "255, 51, 102",
|
||||||
|
red: "214, 75, 71",
|
||||||
|
purple: "110, 107, 233",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fonts = {
|
||||||
|
size: {
|
||||||
|
tiny: "10px",
|
||||||
|
small: "14px",
|
||||||
|
medium: "16px",
|
||||||
|
large: "18px",
|
||||||
|
h1: "60px",
|
||||||
|
h2: "50px",
|
||||||
|
h3: "40px",
|
||||||
|
h4: "32px",
|
||||||
|
h5: "24px",
|
||||||
|
h6: "20px",
|
||||||
|
},
|
||||||
|
weight: {
|
||||||
|
normal: 400,
|
||||||
|
medium: 500,
|
||||||
|
semibold: 600,
|
||||||
|
bold: 700,
|
||||||
|
extrabold: 800,
|
||||||
|
},
|
||||||
|
family: {
|
||||||
|
OpenSans: `"Open Sans", sans-serif`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transitions = {
|
||||||
|
short: "all 0.1s ease-in-out",
|
||||||
|
base: "all 0.2s ease-in-out",
|
||||||
|
long: "all 0.3s ease-in-out",
|
||||||
|
button: "all 0.15s ease-in-out",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadows = {
|
||||||
|
soft: "0 4px 6px 0 rgba(50, 50, 93, 0.11), 0 1px 3px 0 rgba(0, 0, 0, 0.08), inset 0 0 1px 0 rgba(0, 0, 0, 0.06)",
|
||||||
|
medium:
|
||||||
|
"0 3px 6px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(50, 50, 93, 0.02), 0 5px 10px 0 rgba(59, 59, 92, 0.08)",
|
||||||
|
big: "0 15px 35px 0 rgba(50, 50, 93, 0.06), 0 5px 15px 0 rgba(50, 50, 93, 0.15)",
|
||||||
|
hover:
|
||||||
|
"0 7px 14px 0 rgba(50, 50, 93, 0.1), 0 3px 6px 0 rgba(0, 0, 0, 0.08), inset 0 0 1px 0 rgba(0, 0, 0, 0.06)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const responsive = {
|
||||||
|
xs: {
|
||||||
|
min: "min-width: 467px",
|
||||||
|
max: "max-width: 468px",
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
min: "min-width: 639px",
|
||||||
|
max: "max-width: 640px",
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
min: "min-width: 959px",
|
||||||
|
max: "max-width: 960px",
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
min: "min-width: 1023px",
|
||||||
|
max: "max-width: 1024px",
|
||||||
|
},
|
||||||
|
xl: {
|
||||||
|
min: "min-width: 1399px",
|
||||||
|
max: "max-width: 1400px",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const globalStyle = `
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: ${fonts.family.OpenSans};
|
||||||
|
font-style: normal;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-weight: ${fonts.weight.normal};
|
||||||
|
font-size: ${fonts.size.medium};
|
||||||
|
background-color: rgb(${colors.white});
|
||||||
|
color: rgb(${colors.dark});
|
||||||
|
overflow-y:auto;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-style: none;
|
||||||
|
line-height: 1em;
|
||||||
|
background-image: none;
|
||||||
|
outline: 0;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tabindex] {
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, p, h1, h2, h3, h4, h5, h6 {
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.7em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: ${fonts.size.h1}
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: ${fonts.size.h2}
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: ${fonts.size.h3}
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: ${fonts.size.h4}
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: ${fonts.size.h5}
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
font-size: ${fonts.size.h6}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-text-decoration-skip: objects;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: inherit;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, li {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
main,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section,
|
||||||
|
summary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
progress,
|
||||||
|
video {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="color"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="datetime"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="month"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="search"],
|
||||||
|
input[type="tel"],
|
||||||
|
input[type="text"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="url"],
|
||||||
|
input[type="week"],
|
||||||
|
select:focus,
|
||||||
|
textarea {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
`;
|
26
dapps/react-dapp-v2-with-ethers/tsconfig.json
Normal file
26
dapps/react-dapp-v2-with-ethers/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
13260
dapps/react-dapp-v2-with-ethers/yarn.lock
Normal file
13260
dapps/react-dapp-v2-with-ethers/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user