├── .nvmrc ├── tsconfig.prod.json ├── .env ├── public ├── favicon.ico ├── manifest.json ├── index.html └── blockies.min.js ├── src ├── assets │ ├── xdai.png │ ├── walletconnect-banner.png │ ├── eth.svg │ └── erc20.svg ├── index.tsx ├── components │ ├── Banner.tsx │ ├── ERC20Icon.tsx │ ├── Icon.tsx │ ├── Blockie.tsx │ ├── Wrapper.tsx │ ├── Column.tsx │ ├── AccountAssets.tsx │ ├── AssetRow.tsx │ ├── Loader.tsx │ ├── Header.tsx │ ├── Button.tsx │ └── Modal.tsx ├── helpers │ ├── eip712.ts │ ├── eip1271.ts │ ├── api.ts │ ├── types.ts │ ├── bignumber.ts │ ├── chains.ts │ └── utilities.ts ├── styles.ts └── App.tsx ├── .prettierrc ├── tsconfig.test.json ├── images.d.ts ├── README.md ├── .gitignore ├── tslint.json ├── tsconfig.json ├── LICENSE.md ├── .eslintrc └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.2.0 -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_VERSION=$npm_package_version 2 | SKIP_PREFLIGHT_CHECK=true 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WalletConnect/walletconnect-example-dapp/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/xdai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WalletConnect/walletconnect-example-dapp/HEAD/src/assets/xdai.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "trailingComma": "all", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /src/assets/walletconnect-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WalletConnect/walletconnect-example-dapp/HEAD/src/assets/walletconnect-banner.png -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | declare module '*.jpeg' 5 | declare module '*.gif' 6 | declare module '*.bmp' 7 | declare module '*.tiff' 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WalletConnect Example Dapp 2 | 3 | ## Develop 4 | 5 | ```bash 6 | npm run start 7 | ``` 8 | 9 | ## Test 10 | 11 | ```bash 12 | npm run test 13 | ``` 14 | 15 | ## Build 16 | 17 | ```bash 18 | npm run build 19 | ``` 20 | -------------------------------------------------------------------------------- /src/assets/eth.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "WalletConnect Example", 3 | "name": "WalletConnect Example", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { createGlobalStyle } from "styled-components"; 4 | 5 | import App from "./App"; 6 | import { globalStyle } from "./styles"; 7 | const GlobalStyle = createGlobalStyle` 8 | ${globalStyle} 9 | `; 10 | 11 | declare global { 12 | // tslint:disable-next-line 13 | interface Window { 14 | blockies: any; 15 | } 16 | } 17 | 18 | ReactDOM.render( 19 | <> 20 | 21 | 22 | , 23 | document.getElementById("root"), 24 | ); 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "rules": { 4 | "prefer-for-of": false, 5 | "no-shadowed-variable": false, 6 | "no-bitwise": false, 7 | "variable-name": false, 8 | "object-literal-sort-keys": false, 9 | "ordered-imports": false, 10 | "jsx-no-lambda": false, 11 | "jsx-boolean-value": false, 12 | "no-console": false 13 | }, 14 | "linterOptions": { 15 | "exclude": [ 16 | "config/**/*.js", 17 | "node_modules/**/*.ts", 18 | "coverage/lcov-report/*.js" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | import banner from "../assets/walletconnect-banner.png"; 4 | 5 | const SBannerWrapper = styled.div` 6 | display: flex; 7 | align-items: center; 8 | position: relative; 9 | `; 10 | 11 | const SBanner = styled.div` 12 | width: 275px; 13 | height: 45px; 14 | background: url(${banner}) no-repeat; 15 | background-size: cover; 16 | background-position: center; 17 | `; 18 | 19 | const Banner = () => ( 20 | 21 | 22 | 23 | ); 24 | 25 | export default Banner; 26 | -------------------------------------------------------------------------------- /src/components/ERC20Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import Icon from "./Icon"; 4 | import erc20 from "../assets/erc20.svg"; 5 | 6 | const ERC20Icon = (props: any) => { 7 | const src = `https://raw.githubusercontent.com/TrustWallet/tokens/master/tokens/${props.contractAddress.toLowerCase()}.png`; 8 | return ; 9 | }; 10 | 11 | ERC20Icon.propTypes = { 12 | contractAddress: PropTypes.string, 13 | size: PropTypes.number, 14 | }; 15 | 16 | ERC20Icon.defaultProps = { 17 | contractAddress: null, 18 | size: 20, 19 | }; 20 | 21 | export default ERC20Icon; 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WalletConnect Example 11 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | 5 | interface IIconStyleProps { 6 | size: number; 7 | } 8 | 9 | const SIcon = styled.img` 10 | width: ${({ size }) => `${size}px`}; 11 | height: ${({ size }) => `${size}px`}; 12 | `; 13 | 14 | const Icon = (props: any) => { 15 | const { src, fallback, size } = props; 16 | return ( 17 | { 22 | if (fallback) { 23 | event.target.src = fallback; 24 | } 25 | }} 26 | /> 27 | ); 28 | }; 29 | 30 | Icon.propTypes = { 31 | src: PropTypes.string, 32 | fallback: PropTypes.string, 33 | size: PropTypes.number, 34 | }; 35 | 36 | Icon.defaultProps = { 37 | src: null, 38 | fallback: "", 39 | size: 20, 40 | }; 41 | 42 | export default Icon; 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es7", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "importHelpers": true, 18 | "skipLibCheck": true, 19 | "strictNullChecks": true, 20 | "allowSyntheticDefaultImports": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "noUnusedLocals": true, 23 | "resolveJsonModule": true, 24 | "types": ["react", "jest"] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "build", 29 | "scripts", 30 | "acceptance-tests", 31 | "webpack", 32 | "jest", 33 | "src/setupTests.ts" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Blockie.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | 4 | interface IBlockieStyleProps { 5 | size?: number; 6 | } 7 | 8 | interface IBlockieProps extends IBlockieStyleProps { 9 | address: string; 10 | } 11 | 12 | const SBlockieWrapper = styled.div` 13 | width: ${({ size }) => `${size}px`}; 14 | height: ${({ size }) => `${size}px`}; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | border-radius: 6px; 19 | overflow: hidden; 20 | & img { 21 | width: 100%; 22 | } 23 | `; 24 | 25 | const Blockie = (props: IBlockieProps) => { 26 | const seed = props.address.toLowerCase() || ""; 27 | const imgUrl = window.blockies 28 | .create({ 29 | seed, 30 | }) 31 | .toDataURL(); 32 | return ( 33 | 34 | {props.address} 35 | 36 | ); 37 | }; 38 | 39 | Blockie.defaultProps = { 40 | address: "0x0000000000000000000000000000000000000000", 41 | size: 30, 42 | }; 43 | 44 | export default Blockie; 45 | -------------------------------------------------------------------------------- /src/helpers/eip712.ts: -------------------------------------------------------------------------------- 1 | const example = { 2 | types: { 3 | EIP712Domain: [ 4 | { name: "name", type: "string" }, 5 | { name: "version", type: "string" }, 6 | { name: "chainId", type: "uint256" }, 7 | { name: "verifyingContract", type: "address" }, 8 | ], 9 | Permit: [ 10 | { name: "owner", type: "address" }, 11 | { name: "spender", type: "address" }, 12 | { name: "value", type: "uint256" }, 13 | { name: "nonce", type: "uint256" }, 14 | { name: "deadline", type: "uint256" }, 15 | ], 16 | }, 17 | domain: { 18 | name: "Liquid staked Ether 2.0", 19 | version: "2", 20 | chainId: 1, 21 | verifyingContract: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", 22 | }, 23 | primaryType: "Permit", 24 | message: { 25 | owner: "0x01Badd833a247cABf0c2da49a61d06eCb1b7E993", 26 | spender: "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1", 27 | value: "10000978944812", 28 | nonce: "0x0", 29 | deadline: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 30 | }, 31 | }; 32 | 33 | export const eip712 = { 34 | example, 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WalletConnect, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import styled, { keyframes } from "styled-components"; 4 | 5 | const fadeIn = keyframes` 6 | 0% { 7 | opacity: 0; 8 | } 9 | 100% { 10 | opacity: 1; 11 | } 12 | `; 13 | 14 | interface IWrapperStyleProps { 15 | center: boolean; 16 | } 17 | 18 | const SWrapper = styled.div` 19 | will-change: transform, opacity; 20 | animation: ${fadeIn} 0.7s ease 0s normal 1; 21 | min-height: 200px; 22 | display: flex; 23 | flex-wrap: wrap; 24 | justify-content: center; 25 | align-items: ${({ center }) => (center ? `center` : `flex-start`)}; 26 | `; 27 | 28 | interface IWrapperProps extends IWrapperStyleProps { 29 | children: React.ReactNode; 30 | } 31 | 32 | const Wrapper = (props: IWrapperProps) => { 33 | const { children, center } = props; 34 | return ( 35 | 36 | {children} 37 | 38 | ); 39 | }; 40 | 41 | Wrapper.propTypes = { 42 | children: PropTypes.node.isRequired, 43 | center: PropTypes.bool, 44 | }; 45 | 46 | Wrapper.defaultProps = { 47 | center: false, 48 | }; 49 | 50 | export default Wrapper; 51 | -------------------------------------------------------------------------------- /src/helpers/eip1271.ts: -------------------------------------------------------------------------------- 1 | import { Contract, providers, utils } from "ethers"; 2 | 3 | const spec = { 4 | magicValue: "0x1626ba7e", 5 | abi: [ 6 | { 7 | constant: true, 8 | inputs: [ 9 | { 10 | name: "_hash", 11 | type: "bytes32", 12 | }, 13 | { 14 | name: "_sig", 15 | type: "bytes", 16 | }, 17 | ], 18 | name: "isValidSignature", 19 | outputs: [ 20 | { 21 | name: "magicValue", 22 | type: "bytes4", 23 | }, 24 | ], 25 | payable: false, 26 | stateMutability: "view", 27 | type: "function", 28 | }, 29 | ], 30 | }; 31 | 32 | async function isValidSignature( 33 | address: string, 34 | sig: string, 35 | data: string, 36 | provider: providers.Provider, 37 | abi = eip1271.spec.abi, 38 | magicValue = eip1271.spec.magicValue, 39 | ): Promise { 40 | let returnValue; 41 | try { 42 | returnValue = await new Contract(address, abi, provider).isValidSignature( 43 | utils.arrayify(data), 44 | sig, 45 | ); 46 | } catch (e) { 47 | return false; 48 | } 49 | return returnValue.toLowerCase() === magicValue.toLowerCase(); 50 | } 51 | 52 | export const eip1271 = { 53 | spec, 54 | isValidSignature, 55 | }; 56 | -------------------------------------------------------------------------------- /src/components/Column.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | 5 | interface IColumnStyleProps { 6 | spanHeight: boolean; 7 | maxWidth: number; 8 | center: boolean; 9 | } 10 | 11 | interface IColumnProps extends IColumnStyleProps { 12 | children: React.ReactNode; 13 | } 14 | 15 | const SColumn = styled.div` 16 | position: relative; 17 | width: 100%; 18 | height: ${({ spanHeight }) => (spanHeight ? "100%" : "auto")}; 19 | max-width: ${({ maxWidth }) => `${maxWidth}px`}; 20 | margin: 0 auto; 21 | display: flex; 22 | flex-direction: column; 23 | align-items: center; 24 | justify-content: ${({ center }) => (center ? "center" : "flex-start")}; 25 | `; 26 | 27 | const Column = (props: IColumnProps) => { 28 | const { children, spanHeight, maxWidth, center } = props; 29 | return ( 30 | 31 | {children} 32 | 33 | ); 34 | }; 35 | 36 | Column.propTypes = { 37 | children: PropTypes.node.isRequired, 38 | spanHeight: PropTypes.bool, 39 | maxWidth: PropTypes.number, 40 | center: PropTypes.bool, 41 | }; 42 | 43 | Column.defaultProps = { 44 | spanHeight: false, 45 | maxWidth: 600, 46 | center: false, 47 | }; 48 | 49 | export default Column; 50 | -------------------------------------------------------------------------------- /src/helpers/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { IAssetData, IGasPrices, IParsedTx } from "./types"; 3 | 4 | const api: AxiosInstance = axios.create({ 5 | baseURL: "https://ethereum-api.xyz", 6 | timeout: 30000, // 30 secs 7 | headers: { 8 | Accept: "application/json", 9 | "Content-Type": "application/json", 10 | }, 11 | }); 12 | 13 | export async function apiGetAccountAssets(address: string, chainId: number): Promise { 14 | const response = await api.get(`/account-assets?address=${address}&chainId=${chainId}`); 15 | const { result } = response.data; 16 | return result; 17 | } 18 | 19 | export async function apiGetAccountTransactions( 20 | address: string, 21 | chainId: number, 22 | ): Promise { 23 | const response = await api.get(`/account-transactions?address=${address}&chainId=${chainId}`); 24 | const { result } = response.data; 25 | return result; 26 | } 27 | 28 | export const apiGetAccountNonce = async (address: string, chainId: number): Promise => { 29 | const response = await api.get(`/account-nonce?address=${address}&chainId=${chainId}`); 30 | const { result } = response.data; 31 | return result; 32 | }; 33 | 34 | export const apiGetGasPrices = async (): Promise => { 35 | const response = await api.get(`/gas-prices`); 36 | const { result } = response.data; 37 | return result; 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/AccountAssets.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Column from "./Column"; 3 | import AssetRow from "./AssetRow"; 4 | import { IAssetData } from "../helpers/types"; 5 | 6 | const AccountAssets = (props: any) => { 7 | const { assets, chainId } = props; 8 | const defaultNativeCurrency: IAssetData = 9 | chainId === 100 10 | ? { 11 | contractAddress: "", 12 | symbol: "xDAI", 13 | name: "xDAI", 14 | decimals: "18", 15 | balance: "0", 16 | } 17 | : { 18 | contractAddress: "", 19 | name: "Ethereum", 20 | symbol: "ETH", 21 | decimals: "18", 22 | balance: "0", 23 | }; 24 | 25 | let nativeCurrency: IAssetData = defaultNativeCurrency; 26 | let tokens: IAssetData[] = []; 27 | if (assets && assets.length) { 28 | const filteredNativeCurrency = assets.filter((asset: IAssetData) => 29 | asset && asset.symbol 30 | ? asset.symbol.toLowerCase() === nativeCurrency.symbol.toLowerCase() 31 | : false, 32 | ); 33 | nativeCurrency = 34 | filteredNativeCurrency && filteredNativeCurrency.length 35 | ? filteredNativeCurrency[0] 36 | : defaultNativeCurrency; 37 | tokens = assets.filter((asset: IAssetData) => 38 | asset && asset.symbol 39 | ? asset.symbol.toLowerCase() !== nativeCurrency.symbol.toLowerCase() 40 | : false, 41 | ); 42 | } 43 | return ( 44 | 45 | 46 | {tokens.map(token => ( 47 | 48 | ))} 49 | 50 | ); 51 | }; 52 | 53 | export default AccountAssets; 54 | -------------------------------------------------------------------------------- /src/components/AssetRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | import Icon from "./Icon"; 4 | import ERC20Icon from "./ERC20Icon"; 5 | import eth from "../assets/eth.svg"; 6 | import xdai from "../assets/xdai.png"; 7 | import { handleSignificantDecimals, convertAmountFromRawNumber } from "../helpers/bignumber"; 8 | 9 | const SAssetRow = styled.div` 10 | width: 100%; 11 | padding: 20px; 12 | display: flex; 13 | justify-content: space-between; 14 | `; 15 | const SAssetRowLeft = styled.div` 16 | display: flex; 17 | `; 18 | const SAssetName = styled.div` 19 | display: flex; 20 | margin-left: 10px; 21 | `; 22 | const SAssetRowRight = styled.div` 23 | display: flex; 24 | `; 25 | const SAssetBalance = styled.div` 26 | display: flex; 27 | `; 28 | 29 | const AssetRow = (props: any) => { 30 | const { asset } = props; 31 | const nativeCurrencyIcon = 32 | asset.symbol && asset.symbol.toLowerCase() === "eth" 33 | ? eth 34 | : asset.symbol && asset.symbol.toLowerCase() === "xdai" 35 | ? xdai 36 | : null; 37 | return ( 38 | 39 | 40 | {nativeCurrencyIcon ? ( 41 | 42 | ) : ( 43 | 44 | )} 45 | {asset.name} 46 | 47 | 48 | 49 | {`${handleSignificantDecimals(convertAmountFromRawNumber(asset.balance), 8)} ${ 50 | asset.symbol 51 | }`} 52 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default AssetRow; 59 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/ban-ts-ignore": ["off"], 4 | "@typescript-eslint/camelcase": ["off"], 5 | "@typescript-eslint/explicit-function-return-type": ["off"], 6 | "@typescript-eslint/interface-name-prefix": ["off"], 7 | "@typescript-eslint/no-explicit-any": ["off"], 8 | "@typescript-eslint/no-unused-expressions": ["off"], 9 | "@typescript-eslint/no-var-requires": ["off"], 10 | "@typescript-eslint/no-use-before-define": ["off"], 11 | "@typescript-eslint/no-unused-vars": ["off"], 12 | "comma-dangle": ["error", "always-multiline"], 13 | "no-async-promise-executor": ["off"], 14 | "no-empty-pattern": ["off"], 15 | "no-undef": ["error"], 16 | "no-var": ["error"], 17 | "object-curly-spacing": ["error", "always"], 18 | "quotes": ["error", "double", { "allowTemplateLiterals": true }], 19 | "semi": ["error", "always"], 20 | "spaced-comment": ["off"], 21 | "no-prototype-builtins": ["off"], 22 | "sort-keys": ["off"], 23 | "space-before-function-paren": ["off"], 24 | "indent": ["off"], 25 | "no-console": ["off"], 26 | "no-useless-catch": ["off"] 27 | }, 28 | "settings": { 29 | "react": { 30 | "pragma": "React", 31 | "version": "detect" 32 | } 33 | }, 34 | "env": { 35 | "browser": true, 36 | "jasmine": true, 37 | "jest": true, 38 | "es6": true 39 | }, 40 | "extends": [ 41 | "standard", 42 | "eslint:recommended", 43 | "plugin:react/recommended", 44 | "plugin:@typescript-eslint/eslint-recommended", 45 | "plugin:@typescript-eslint/recommended", 46 | "prettier/@typescript-eslint", 47 | "plugin:prettier/recommended" 48 | ], 49 | "parser": "@typescript-eslint/parser", 50 | "plugins": ["react", "@typescript-eslint", "prettier"] 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import styled, { keyframes } from "styled-components"; 4 | import { colors } from "../styles"; 5 | 6 | const load = keyframes` 7 | 0% { 8 | transform: scale(1.0); 9 | } 10 | 5% { 11 | transform: scale(1.0); 12 | } 13 | 50% { 14 | transform: scale(0.8); 15 | } 16 | 95% { 17 | transform: scale(1.0); 18 | } 19 | 100% { 20 | transform: scale(1.0); 21 | } 22 | `; 23 | 24 | interface ILoaderStyleProps { 25 | size: number; 26 | } 27 | 28 | interface ILoaderProps extends ILoaderStyleProps { 29 | color: string; 30 | } 31 | 32 | const SLoader = styled.svg` 33 | width: ${({ size }) => `${size}px`}; 34 | height: ${({ size }) => `${size}px`}; 35 | animation: ${load} 1s infinite cubic-bezier(0.25, 0, 0.75, 1); 36 | transform: translateZ(0); 37 | `; 38 | 39 | const Loader = (props: ILoaderProps) => { 40 | const { size, color } = props; 41 | const rgb = `rgb(${colors[color]})`; 42 | return ( 43 | 44 | 45 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | Loader.propTypes = { 58 | size: PropTypes.number, 59 | color: PropTypes.string, 60 | }; 61 | 62 | Loader.defaultProps = { 63 | size: 40, 64 | color: "lightBlue", 65 | }; 66 | 67 | export default Loader; 68 | -------------------------------------------------------------------------------- /public/blockies.min.js: -------------------------------------------------------------------------------- 1 | !(function() { 2 | function e(e) { 3 | for (var o = 0; o < c.length; o++) c[o] = 0; 4 | for (var o = 0; o < e.length; o++) 5 | c[o % 4] = (c[o % 4] << 5) - c[o % 4] + e.charCodeAt(o); 6 | } 7 | function o() { 8 | var e = c[0] ^ (c[0] << 11); 9 | return ( 10 | (c[0] = c[1]), 11 | (c[1] = c[2]), 12 | (c[2] = c[3]), 13 | (c[3] = c[3] ^ (c[3] >> 19) ^ e ^ (e >> 8)), 14 | (c[3] >>> 0) / ((1 << 31) >>> 0) 15 | ); 16 | } 17 | function r() { 18 | var e = Math.floor(360 * o()), 19 | r = 60 * o() + 40 + "%", 20 | t = 25 * (o() + o() + o() + o()) + "%", 21 | l = "hsl(" + e + "," + r + "," + t + ")"; 22 | return l; 23 | } 24 | function t(e) { 25 | for ( 26 | var r = e, t = e, l = Math.ceil(r / 2), n = r - l, a = [], c = 0; 27 | t > c; 28 | c++ 29 | ) { 30 | for (var i = [], f = 0; l > f; f++) i[f] = Math.floor(2.3 * o()); 31 | var s = i.slice(0, n); 32 | s.reverse(), (i = i.concat(s)); 33 | for (var h = 0; h < i.length; h++) a.push(i[h]); 34 | } 35 | return a; 36 | } 37 | function l(o) { 38 | var t = {}; 39 | return ( 40 | (t.seed = 41 | o.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16)), 42 | e(t.seed), 43 | (t.size = o.size || 8), 44 | (t.scale = o.scale || 4), 45 | (t.color = o.color || r()), 46 | (t.bgcolor = o.bgcolor || r()), 47 | (t.spotcolor = o.spotcolor || r()), 48 | t 49 | ); 50 | } 51 | function n(e, o) { 52 | var r = t(e.size), 53 | l = Math.sqrt(r.length); 54 | o.width = o.height = e.size * e.scale; 55 | var n = o.getContext("2d"); 56 | (n.fillStyle = e.bgcolor), 57 | n.fillRect(0, 0, o.width, o.height), 58 | (n.fillStyle = e.color); 59 | for (var a = 0; a < r.length; a++) 60 | if (r[a]) { 61 | var c = Math.floor(a / l), 62 | i = a % l; 63 | (n.fillStyle = 1 == r[a] ? e.color : e.spotcolor), 64 | n.fillRect(i * e.scale, c * e.scale, e.scale, e.scale); 65 | } 66 | return o; 67 | } 68 | function a(e) { 69 | var e = l(e || {}), 70 | o = document.createElement("canvas"); 71 | return n(e, o), o; 72 | } 73 | var c = new Array(4), 74 | i = { create: a, render: n }; 75 | "undefined" != typeof module && (module.exports = i), 76 | "undefined" != typeof window && (window.blockies = i); 77 | })(); 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "walletconnect-example-dapp", 3 | "version": "1.8.0", 4 | "private": true, 5 | "keywords": [ 6 | "walletconnect", 7 | "ethereum", 8 | "web3", 9 | "crypto" 10 | ], 11 | "author": "WalletConnect, Inc. ", 12 | "homepage": "https://walletconnect.org", 13 | "license": "MIT", 14 | "scripts": { 15 | "start": "react-scripts-ts start", 16 | "build": "react-scripts-ts build", 17 | "test": "react-scripts-ts test --env=jsdom", 18 | "eject": "react-scripts-ts eject", 19 | "lint": "eslint -c './.eslintrc' --fix './src/**/*{.ts,.tsx}'" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/walletconnect/walletconnect-example-dapp.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/walletconnect/walletconnect-example-dapp/issues" 27 | }, 28 | "dependencies": { 29 | "@types/eth-sig-util": "^2.1.1", 30 | "@walletconnect/client": "^1.8.0", 31 | "@walletconnect/qrcode-modal": "^1.8.0", 32 | "axios": "^0.25.0", 33 | "bignumber.js": "^9.0.1", 34 | "blockies-ts": "^1.0.0", 35 | "eth-sig-util": "^2.5.3", 36 | "ethereumjs-util": "^7.0.6", 37 | "ethers": "^5.0.17", 38 | "node-gyp": "^8.1.0", 39 | "prop-types": "^15.7.2", 40 | "qr-image": "^3.2.0", 41 | "react": "^17.0.2", 42 | "react-dom": "^17.0.2", 43 | "react-scripts-ts": "^4.0.8", 44 | "styled-components": "^5.2.0" 45 | }, 46 | "devDependencies": { 47 | "@types/axios": "^0.14.0", 48 | "@types/better-sqlite3": "^5.4.1", 49 | "@types/bignumber.js": "^5.0.0", 50 | "@types/bn.js": "^4.11.5", 51 | "@types/eth-sig-util": "^2.1.1", 52 | "@types/jest": "^24.0.25", 53 | "@types/node": "^13.1.6", 54 | "@types/prop-types": "^15.7.3", 55 | "@types/qr-image": "^3.2.3", 56 | "@types/react": "^17.0.38", 57 | "@types/react-dom": "^17.0.11", 58 | "@types/styled-components": "^5.1.3", 59 | "@typescript-eslint/eslint-plugin": "^2.20.0", 60 | "@typescript-eslint/parser": "^2.20.0", 61 | "eslint-config-prettier": "^6.12.0", 62 | "eslint-config-react": "^1.1.7", 63 | "eslint-config-standard": "^14.1.1", 64 | "eslint-plugin-import": "^2.22.1", 65 | "eslint-plugin-node": "^11.1.0", 66 | "eslint-plugin-prettier": "^3.1.4", 67 | "eslint-plugin-promise": "^4.2.1", 68 | "eslint-plugin-standard": "^4.0.1", 69 | "prettier": "^1.19.1", 70 | "typescript": "^3.7.4" 71 | }, 72 | "eslintConfig": { 73 | "extends": "react-app" 74 | }, 75 | "browserslist": [ 76 | ">0.2%", 77 | "not dead", 78 | "not ie <= 11", 79 | "not op_mini all" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /src/helpers/types.ts: -------------------------------------------------------------------------------- 1 | export interface IAssetData { 2 | symbol: string; 3 | name: string; 4 | decimals: string; 5 | contractAddress: string; 6 | balance?: string; 7 | } 8 | 9 | export interface IChainData { 10 | name: string; 11 | short_name: string; 12 | chain: string; 13 | network: string; 14 | chain_id: number; 15 | network_id: number; 16 | rpc_url: string; 17 | native_currency: IAssetData; 18 | } 19 | export interface ITxData { 20 | from: string; 21 | to: string; 22 | nonce: string; 23 | gasPrice: string; 24 | gasLimit: string; 25 | value: string; 26 | data: string; 27 | } 28 | 29 | export interface IBlockScoutTx { 30 | value: string; 31 | txreceipt_status: string; 32 | transactionIndex: string; 33 | to: string; 34 | timeStamp: string; 35 | nonce: string; 36 | isError: string; 37 | input: string; 38 | hash: string; 39 | gasUsed: string; 40 | gasPrice: string; 41 | gas: string; 42 | from: string; 43 | cumulativeGasUsed: string; 44 | contractAddress: string; 45 | confirmations: string; 46 | blockNumber: string; 47 | blockHash: string; 48 | } 49 | 50 | export interface IBlockScoutTokenTx { 51 | value: string; 52 | transactionIndex: string; 53 | tokenSymbol: string; 54 | tokenName: string; 55 | tokenDecimal: string; 56 | to: string; 57 | timeStamp: string; 58 | nonce: string; 59 | input: string; 60 | hash: string; 61 | gasUsed: string; 62 | gasPrice: string; 63 | gas: string; 64 | from: string; 65 | cumulativeGasUsed: string; 66 | contractAddress: string; 67 | confirmations: string; 68 | blockNumber: string; 69 | blockHash: string; 70 | } 71 | 72 | export interface IParsedTx { 73 | timestamp: string; 74 | hash: string; 75 | from: string; 76 | to: string; 77 | nonce: string; 78 | gasPrice: string; 79 | gasUsed: string; 80 | fee: string; 81 | value: string; 82 | input: string; 83 | error: boolean; 84 | asset: IAssetData; 85 | operations: ITxOperation[]; 86 | } 87 | 88 | export interface ITxOperation { 89 | asset: IAssetData; 90 | value: string; 91 | from: string; 92 | to: string; 93 | functionName: string; 94 | } 95 | 96 | export interface IGasPricesResponse { 97 | fastWait: number; 98 | avgWait: number; 99 | blockNum: number; 100 | fast: number; 101 | fastest: number; 102 | fastestWait: number; 103 | safeLow: number; 104 | safeLowWait: number; 105 | speed: number; 106 | block_time: number; 107 | average: number; 108 | } 109 | 110 | export interface IGasPrice { 111 | time: number; 112 | price: number; 113 | } 114 | 115 | export interface IGasPrices { 116 | timestamp: number; 117 | slow: IGasPrice; 118 | average: IGasPrice; 119 | fast: IGasPrice; 120 | } 121 | 122 | export interface IMethodArgument { 123 | type: string; 124 | } 125 | 126 | export interface IMethod { 127 | signature: string; 128 | name: string; 129 | args: IMethodArgument[]; 130 | } 131 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import Blockie from "./Blockie"; 4 | import { ellipseAddress, getChainData } from "../helpers/utilities"; 5 | import { transitions } from "../styles"; 6 | 7 | const SHeader = styled.div` 8 | margin-top: -1px; 9 | margin-bottom: 1px; 10 | width: 100%; 11 | height: 100px; 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | padding: 0 16px; 16 | `; 17 | 18 | const SActiveAccount = styled.div` 19 | display: flex; 20 | align-items: center; 21 | position: relative; 22 | font-weight: 500; 23 | `; 24 | 25 | const SActiveChain = styled(SActiveAccount as any)` 26 | flex-direction: column; 27 | text-align: left; 28 | align-items: flex-start; 29 | & p { 30 | font-size: 0.8em; 31 | margin: 0; 32 | padding: 0; 33 | } 34 | & p:nth-child(2) { 35 | font-weight: bold; 36 | } 37 | `; 38 | 39 | const SBlockie = styled(Blockie as any)` 40 | margin-right: 10px; 41 | `; 42 | 43 | interface IHeaderStyle { 44 | connected: boolean; 45 | } 46 | 47 | const SAddress = styled.p` 48 | transition: ${transitions.base}; 49 | font-weight: bold; 50 | margin: ${({ connected }) => (connected ? "-2px auto 0.7em" : "0")}; 51 | `; 52 | 53 | const SUnsupportedChain = styled.div` 54 | transition: ${transitions.base}; 55 | font-weight: bold; 56 | color: red; 57 | `; 58 | 59 | const SDisconnect = styled.div` 60 | transition: ${transitions.button}; 61 | font-size: 12px; 62 | font-family: monospace; 63 | position: absolute; 64 | right: 0; 65 | top: 20px; 66 | opacity: 0.7; 67 | cursor: pointer; 68 | 69 | opacity: ${({ connected }) => (connected ? 1 : 0)}; 70 | visibility: ${({ connected }) => (connected ? "visible" : "hidden")}; 71 | pointer-events: ${({ connected }) => (connected ? "auto" : "none")}; 72 | 73 | &:hover { 74 | transform: translateY(-1px); 75 | opacity: 0.5; 76 | } 77 | `; 78 | 79 | interface IHeaderProps { 80 | killSession: () => void; 81 | connected: boolean; 82 | address: string; 83 | chainId: number; 84 | } 85 | 86 | const Header = ({ connected, address, chainId, killSession }: IHeaderProps) => { 87 | let activeChain = null; 88 | 89 | try { 90 | activeChain = chainId ? getChainData(chainId).name : null; 91 | } catch (error) { 92 | console.error(error); 93 | } 94 | 95 | return ( 96 | 97 | {connected && ( 98 | 99 | {activeChain ? ( 100 | <> 101 |

Connected to

102 |

{activeChain}

103 | 104 | ) : ( 105 | 106 |

Chain not supported.

107 |

Please switch to a supported chain in your wallet.

108 |
109 | )} 110 |
111 | )} 112 | {address && ( 113 | 114 | 115 | {ellipseAddress(address)} 116 | 117 | {"Disconnect"} 118 | 119 | 120 | )} 121 |
122 | ); 123 | }; 124 | 125 | export default Header; 126 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | import Loader from "./Loader"; 4 | import { colors, fonts, shadows, transitions } from "../styles"; 5 | 6 | interface IButtonStyleProps { 7 | fetching: boolean; 8 | outline: boolean; 9 | type: "button" | "submit" | "reset"; 10 | color: string; 11 | disabled: boolean; 12 | icon: any; 13 | left: boolean; 14 | } 15 | 16 | interface IButtonProps extends IButtonStyleProps { 17 | children: React.ReactNode; 18 | onClick?: any; 19 | } 20 | 21 | const SIcon = styled.div` 22 | position: absolute; 23 | height: 15px; 24 | width: 15px; 25 | margin: 0 8px; 26 | top: calc((100% - 15px) / 2); 27 | `; 28 | 29 | const SHoverLayer = styled.div` 30 | transition: ${transitions.button}; 31 | position: absolute; 32 | height: 100%; 33 | width: 100%; 34 | background-color: rgb(${colors.white}, 0.1); 35 | top: 0; 36 | bottom: 0; 37 | right: 0; 38 | left: 0; 39 | pointer-events: none; 40 | opacity: 0; 41 | visibility: hidden; 42 | `; 43 | 44 | const SButton = styled.button` 45 | transition: ${transitions.button}; 46 | position: relative; 47 | border: none; 48 | border-style: none; 49 | box-sizing: border-box; 50 | background-color: ${({ outline, color }) => (outline ? "transparent" : `rgb(${colors[color]})`)}; 51 | border: ${({ outline, color }) => (outline ? `1px solid rgb(${colors[color]})` : "none")}; 52 | color: ${({ outline, color }) => (outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`)}; 53 | box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)}; 54 | border-radius: 8px; 55 | font-size: ${fonts.size.medium}; 56 | font-weight: ${fonts.weight.semibold}; 57 | padding: ${({ icon, left }) => 58 | icon ? (left ? "7px 12px 8px 28px" : "7px 28px 8px 12px") : "8px 12px"}; 59 | cursor: ${({ disabled }) => (disabled ? "auto" : "pointer")}; 60 | will-change: transform; 61 | 62 | &:disabled { 63 | opacity: 0.6; 64 | box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)}; 65 | } 66 | 67 | @media (hover: hover) { 68 | &:hover { 69 | transform: ${({ disabled }) => (!disabled ? "translateY(-1px)" : "none")}; 70 | box-shadow: ${({ disabled, outline }) => 71 | !disabled ? (outline ? "none" : `${shadows.hover}`) : `${shadows.soft}`}; 72 | } 73 | 74 | &:hover ${SHoverLayer} { 75 | opacity: 1; 76 | visibility: visible; 77 | } 78 | } 79 | 80 | &:active { 81 | transform: ${({ disabled }) => (!disabled ? "translateY(1px)" : "none")}; 82 | box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)}; 83 | color: ${({ outline, color }) => 84 | outline ? `rgb(${colors[color]})` : `rgba(${colors.white}, 0.24)`}; 85 | 86 | & ${SIcon} { 87 | opacity: 0.8; 88 | } 89 | } 90 | 91 | & ${SIcon} { 92 | right: ${({ left }) => (left ? "auto" : "0")}; 93 | left: ${({ left }) => (left ? "0" : "auto")}; 94 | display: ${({ icon }) => (icon ? "block" : "none")}; 95 | mask: ${({ icon }) => (icon ? `url(${icon}) center no-repeat` : "none")}; 96 | background-color: ${({ outline, color }) => 97 | outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`}; 98 | transition: 0.15s ease; 99 | } 100 | `; 101 | 102 | const Button = (props: IButtonProps) => ( 103 | 112 | 113 | 114 | {props.fetching ? : props.children} 115 | 116 | ); 117 | 118 | Button.defaultProps = { 119 | fetching: false, 120 | outline: false, 121 | type: "button", 122 | color: "lightBlue", 123 | disabled: false, 124 | icon: null, 125 | left: false, 126 | }; 127 | 128 | export default Button; 129 | -------------------------------------------------------------------------------- /src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | import { colors, transitions } from "../styles"; 5 | 6 | interface ILightboxStyleProps { 7 | show: boolean; 8 | offset: number; 9 | opacity?: number; 10 | } 11 | 12 | const SLightbox = styled.div` 13 | transition: opacity 0.1s ease-in-out; 14 | text-align: center; 15 | position: absolute; 16 | width: 100vw; 17 | height: 100vh; 18 | margin-left: -50vw; 19 | top: ${({ offset }) => (offset ? `-${offset}px` : 0)}; 20 | left: 50%; 21 | z-index: 2; 22 | will-change: opacity; 23 | background-color: ${({ opacity }) => { 24 | let alpha = 0.4; 25 | if (typeof opacity === "number") { 26 | alpha = opacity; 27 | } 28 | return `rgba(0, 0, 0, ${alpha})`; 29 | }}; 30 | opacity: ${({ show }) => (show ? 1 : 0)}; 31 | visibility: ${({ show }) => (show ? "visible" : "hidden")}; 32 | pointer-events: ${({ show }) => (show ? "auto" : "none")}; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | `; 37 | 38 | const SModalContainer = styled.div` 39 | position: relative; 40 | width: 100%; 41 | height: 100%; 42 | padding: 15px; 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | `; 47 | 48 | const SHitbox = styled.div` 49 | position: absolute; 50 | top: 0; 51 | left: 0; 52 | right: 0; 53 | bottom: 0; 54 | `; 55 | 56 | interface ICloseButtonStyleProps { 57 | size: number; 58 | color: string; 59 | onClick?: any; 60 | } 61 | 62 | const SCloseButton = styled.div` 63 | transition: ${transitions.short}; 64 | position: absolute; 65 | width: ${({ size }) => `${size}px`}; 66 | height: ${({ size }) => `${size}px`}; 67 | right: ${({ size }) => `${size / 1.6667}px`}; 68 | top: ${({ size }) => `${size / 1.6667}px`}; 69 | opacity: 0.5; 70 | cursor: pointer; 71 | &:hover { 72 | opacity: 1; 73 | } 74 | &:before, 75 | &:after { 76 | position: absolute; 77 | content: " "; 78 | height: ${({ size }) => `${size}px`}; 79 | width: 2px; 80 | background: ${({ color }) => `rgb(${colors[color]})`}; 81 | } 82 | &:before { 83 | transform: rotate(45deg); 84 | } 85 | &:after { 86 | transform: rotate(-45deg); 87 | } 88 | `; 89 | 90 | const SCard = styled.div` 91 | position: relative; 92 | width: 100%; 93 | max-width: 500px; 94 | padding: 25px; 95 | background-color: rgb(${colors.white}); 96 | border-radius: 6px; 97 | display: flex; 98 | flex-direction: column; 99 | justify-content: center; 100 | align-items: center; 101 | `; 102 | 103 | const SModalContent = styled.div` 104 | position: relative; 105 | width: 100%; 106 | position: relative; 107 | word-wrap: break-word; 108 | `; 109 | 110 | interface IModalState { 111 | offset: number; 112 | } 113 | 114 | interface IModalProps { 115 | children: React.ReactNode; 116 | show: boolean; 117 | toggleModal: any; 118 | opacity?: number; 119 | } 120 | 121 | const INITIAL_STATE: IModalState = { 122 | offset: 0, 123 | }; 124 | 125 | class Modal extends React.Component { 126 | public static propTypes = { 127 | children: PropTypes.node.isRequired, 128 | show: PropTypes.bool.isRequired, 129 | toggleModal: PropTypes.func.isRequired, 130 | opacity: PropTypes.number, 131 | }; 132 | 133 | public lightbox?: HTMLDivElement | null; 134 | 135 | public state: IModalState = { 136 | ...INITIAL_STATE, 137 | }; 138 | 139 | public componentDidUpdate() { 140 | if (this.lightbox) { 141 | const lightboxRect = this.lightbox.getBoundingClientRect(); 142 | const offset = lightboxRect.top > 0 ? lightboxRect.top : 0; 143 | 144 | if (offset !== INITIAL_STATE.offset && offset !== this.state.offset) { 145 | this.setState({ offset }); 146 | } 147 | } 148 | } 149 | 150 | public toggleModal = async () => { 151 | const d = typeof window !== "undefined" ? document : ""; 152 | const body = d ? d.body || d.getElementsByTagName("body")[0] : ""; 153 | if (body) { 154 | if (this.props.show) { 155 | body.style.position = ""; 156 | } else { 157 | body.style.position = "fixed"; 158 | } 159 | } 160 | this.props.toggleModal(); 161 | }; 162 | 163 | public render() { 164 | const { offset } = this.state; 165 | const { children, show, opacity } = this.props; 166 | return ( 167 | (this.lightbox = c)}> 168 | 169 | 170 | 171 | 172 | 173 | {children} 174 | 175 | 176 | 177 | ); 178 | } 179 | } 180 | 181 | export default Modal; 182 | -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | white: "255, 255, 255", 3 | black: "0, 0, 0", 4 | dark: "12, 12, 13", 5 | grey: "169, 169, 188", 6 | darkGrey: "113, 119, 138", 7 | lightGrey: "212, 212, 212", 8 | blue: "101, 127, 230", 9 | lightBlue: "64, 153, 255", 10 | yellow: "250, 188, 45", 11 | orange: "246, 133, 27", 12 | green: "84, 209, 146", 13 | pink: "255, 51, 102", 14 | red: "214, 75, 71", 15 | purple: "110, 107, 233", 16 | }; 17 | 18 | export const fonts = { 19 | size: { 20 | tiny: "10px", 21 | small: "14px", 22 | medium: "16px", 23 | large: "18px", 24 | h1: "60px", 25 | h2: "50px", 26 | h3: "40px", 27 | h4: "32px", 28 | h5: "24px", 29 | h6: "20px", 30 | }, 31 | weight: { 32 | normal: 400, 33 | medium: 500, 34 | semibold: 600, 35 | bold: 700, 36 | extrabold: 800, 37 | }, 38 | family: { 39 | OpenSans: `"Open Sans", sans-serif`, 40 | }, 41 | }; 42 | 43 | export const transitions = { 44 | short: "all 0.1s ease-in-out", 45 | base: "all 0.2s ease-in-out", 46 | long: "all 0.3s ease-in-out", 47 | button: "all 0.15s ease-in-out", 48 | }; 49 | 50 | export const shadows = { 51 | soft: 52 | "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)", 53 | medium: 54 | "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)", 55 | big: "0 15px 35px 0 rgba(50, 50, 93, 0.06), 0 5px 15px 0 rgba(50, 50, 93, 0.15)", 56 | hover: 57 | "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)", 58 | }; 59 | 60 | export const responsive = { 61 | xs: { 62 | min: "min-width: 467px", 63 | max: "max-width: 468px", 64 | }, 65 | sm: { 66 | min: "min-width: 639px", 67 | max: "max-width: 640px", 68 | }, 69 | md: { 70 | min: "min-width: 959px", 71 | max: "max-width: 960px", 72 | }, 73 | lg: { 74 | min: "min-width: 1023px", 75 | max: "max-width: 1024px", 76 | }, 77 | xl: { 78 | min: "min-width: 1399px", 79 | max: "max-width: 1400px", 80 | }, 81 | }; 82 | 83 | export const globalStyle = ` 84 | 85 | html, body, #root { 86 | height: 100%; 87 | width: 100%; 88 | margin: 0; 89 | padding: 0; 90 | } 91 | 92 | body { 93 | font-family: ${fonts.family.OpenSans}; 94 | font-style: normal; 95 | font-stretch: normal; 96 | font-weight: ${fonts.weight.normal}; 97 | font-size: ${fonts.size.medium}; 98 | background-color: rgb(${colors.white}); 99 | color: rgb(${colors.dark}); 100 | overflow-y:auto; 101 | text-rendering: optimizeLegibility; 102 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 103 | -webkit-font-smoothing: antialiased; 104 | -moz-osx-font-smoothing: grayscale; 105 | -webkit-text-size-adjust: 100%; 106 | -webkit-overflow-scrolling: touch; 107 | -ms-text-size-adjust: 100%; 108 | -webkit-text-size-adjust: 100%; 109 | } 110 | 111 | button { 112 | border-style: none; 113 | line-height: 1em; 114 | background-image: none; 115 | outline: 0; 116 | -webkit-box-shadow: none; 117 | box-shadow: none; 118 | } 119 | 120 | [tabindex] { 121 | outline: none; 122 | width: 100%; 123 | height: 100%; 124 | } 125 | 126 | a, p, h1, h2, h3, h4, h5, h6 { 127 | text-decoration: none; 128 | margin: 0; 129 | padding: 0; 130 | margin: 0.7em 0; 131 | } 132 | 133 | h1 { 134 | font-size: ${fonts.size.h1} 135 | } 136 | h2 { 137 | font-size: ${fonts.size.h2} 138 | } 139 | h3 { 140 | font-size: ${fonts.size.h3} 141 | } 142 | h4 { 143 | font-size: ${fonts.size.h4} 144 | } 145 | h5 { 146 | font-size: ${fonts.size.h5} 147 | } 148 | h6 { 149 | font-size: ${fonts.size.h6} 150 | } 151 | 152 | a { 153 | background-color: transparent; 154 | -webkit-text-decoration-skip: objects; 155 | text-decoration: none; 156 | color: inherit; 157 | outline: none; 158 | } 159 | 160 | b, 161 | strong { 162 | font-weight: inherit; 163 | font-weight: bolder; 164 | } 165 | 166 | ul, li { 167 | list-style: none; 168 | margin: 0; 169 | padding: 0; 170 | } 171 | 172 | * { 173 | box-sizing: border-box !important; 174 | } 175 | 176 | 177 | input { 178 | -webkit-appearance: none; 179 | } 180 | 181 | article, 182 | aside, 183 | details, 184 | figcaption, 185 | figure, 186 | footer, 187 | header, 188 | main, 189 | menu, 190 | nav, 191 | section, 192 | summary { 193 | display: block; 194 | } 195 | audio, 196 | canvas, 197 | progress, 198 | video { 199 | display: inline-block; 200 | } 201 | 202 | input[type="color"], 203 | input[type="date"], 204 | input[type="datetime"], 205 | input[type="datetime-local"], 206 | input[type="email"], 207 | input[type="month"], 208 | input[type="number"], 209 | input[type="password"], 210 | input[type="search"], 211 | input[type="tel"], 212 | input[type="text"], 213 | input[type="time"], 214 | input[type="url"], 215 | input[type="week"], 216 | select:focus, 217 | textarea { 218 | font-size: 16px; 219 | } 220 | `; 221 | -------------------------------------------------------------------------------- /src/helpers/bignumber.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | 3 | export function isNaN(value: string | number): boolean { 4 | return new BigNumber(`${value}`).isNaN(); 5 | } 6 | 7 | export function isNumber(value: string | number): boolean { 8 | const isNaNResult = isNaN(value); 9 | return !isNaNResult; 10 | } 11 | 12 | export function isInteger(value: string | number): boolean { 13 | return new BigNumber(`${value}`).isInteger(); 14 | } 15 | 16 | export function isPositive(value: string | number): boolean { 17 | return new BigNumber(`${value}`).isPositive(); 18 | } 19 | 20 | export function isNegative(value: string | number): boolean { 21 | return new BigNumber(`${value}`).isNegative(); 22 | } 23 | 24 | export function isZero(value: string | number): boolean { 25 | return new BigNumber(`${value}`).isZero(); 26 | } 27 | 28 | export function countDecimalPlaces(value: string | number): number { 29 | return new BigNumber(`${value}`).dp() || 0; 30 | } 31 | 32 | export function convertNumberToString(value: string | number): string { 33 | return new BigNumber(`${value}`).toString(); 34 | } 35 | 36 | export function convertStringToNumber(value: string | number): number { 37 | return new BigNumber(`${value}`).toNumber(); 38 | } 39 | 40 | export function convertHexToString(hex: string): string { 41 | return new BigNumber(`${hex}`).toString(); 42 | } 43 | 44 | export function convertStringToHex(value: string | number): string { 45 | return new BigNumber(`${value}`).toString(16); 46 | } 47 | 48 | export function greaterThan(numberOne: number | string, numberTwo: number | string): boolean { 49 | return new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) === 1; 50 | } 51 | 52 | export function greaterThanOrEqual(numberOne: number, numberTwo: number): boolean { 53 | return new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) >= 0; 54 | } 55 | 56 | export function smallerThan(numberOne: number | string, numberTwo: number | string): boolean { 57 | return new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) === -1; 58 | } 59 | 60 | export function smallerThanOrEqual(numberOne: number, numberTwo: number): boolean { 61 | return new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) <= 0; 62 | } 63 | 64 | export function multiply(numberOne: number | string, numberTwo: number | string): string { 65 | return new BigNumber(`${numberOne}`).times(new BigNumber(`${numberTwo}`)).toString(); 66 | } 67 | 68 | export function divide(numberOne: number | string, numberTwo: number | string): string { 69 | return new BigNumber(`${numberOne}`).dividedBy(new BigNumber(`${numberTwo}`)).toString(); 70 | } 71 | 72 | export function floorDivide(numberOne: number | string, numberTwo: number | string): string { 73 | return new BigNumber(`${numberOne}`).dividedToIntegerBy(new BigNumber(`${numberTwo}`)).toString(); 74 | } 75 | 76 | export function mod(numberOne: number | string, numberTwo: number | string): string { 77 | return new BigNumber(`${numberOne}`).mod(new BigNumber(`${numberTwo}`)).toString(); 78 | } 79 | 80 | export function add(numberOne: number | string, numberTwo: number | string): string { 81 | return new BigNumber(`${numberOne}`).plus(new BigNumber(`${numberTwo}`)).toString(); 82 | } 83 | 84 | export function subtract(numberOne: number | string, numberTwo: number | string): string { 85 | return new BigNumber(`${numberOne}`).minus(new BigNumber(`${numberTwo}`)).toString(); 86 | } 87 | 88 | export function convertAmountToRawNumber(value: string | number, decimals = 18): string { 89 | return new BigNumber(`${value}`).times(new BigNumber("10").pow(decimals)).toString(); 90 | } 91 | 92 | export function convertAmountFromRawNumber(value: string | number, decimals = 18): string { 93 | return new BigNumber(`${value}`).dividedBy(new BigNumber("10").pow(decimals)).toString(); 94 | } 95 | 96 | export function handleSignificantDecimals( 97 | value: string, 98 | decimals: number, 99 | buffer?: number, 100 | ): string | null { 101 | if ( 102 | !new BigNumber(`${decimals}`).isInteger() || 103 | (buffer && !new BigNumber(`${buffer}`).isInteger()) 104 | ) { 105 | return null; 106 | } 107 | buffer = buffer ? convertStringToNumber(buffer) : 3; 108 | decimals = convertStringToNumber(decimals); 109 | const absolute = new BigNumber(`${value}`).abs().toNumber(); 110 | if (smallerThan(absolute, 1)) { 111 | decimals = value.slice(2).search(/[^0]/g) + buffer; 112 | decimals = decimals < 8 ? decimals : 8; 113 | } else { 114 | decimals = decimals < buffer ? decimals : buffer; 115 | } 116 | let result = new BigNumber(`${value}`).toFixed(decimals); 117 | result = new BigNumber(`${result}`).toString(); 118 | return (new BigNumber(`${result}`).dp() || 0) <= 2 119 | ? new BigNumber(`${result}`).toFormat(2) 120 | : new BigNumber(`${result}`).toFormat(); 121 | } 122 | 123 | export function formatFixedDecimals(value: string, decimals: number): string { 124 | const _value = convertNumberToString(value); 125 | const _decimals = convertStringToNumber(decimals); 126 | const result = new BigNumber(new BigNumber(_value).toFixed(_decimals)).toString(); 127 | return result; 128 | } 129 | 130 | export function formatInputDecimals(inputOne: string, inputTwo: string): string { 131 | const _nativeAmountDecimalPlaces = countDecimalPlaces(inputTwo); 132 | const decimals = _nativeAmountDecimalPlaces > 8 ? _nativeAmountDecimalPlaces : 8; 133 | const result = new BigNumber(formatFixedDecimals(inputOne, decimals)) 134 | .toFormat() 135 | .replace(/,/g, ""); 136 | return result; 137 | } 138 | -------------------------------------------------------------------------------- /src/assets/erc20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/helpers/chains.ts: -------------------------------------------------------------------------------- 1 | import { IChainData } from "../helpers/types"; 2 | 3 | export const SUPPORTED_CHAINS: IChainData[] = [ 4 | { 5 | name: "Ethereum Mainnet", 6 | short_name: "eth", 7 | chain: "ETH", 8 | network: "mainnet", 9 | chain_id: 1, 10 | network_id: 1, 11 | rpc_url: "https://mainnet.infura.io/v3/%API_KEY%", 12 | native_currency: { 13 | symbol: "ETH", 14 | name: "Ether", 15 | decimals: "18", 16 | contractAddress: "", 17 | balance: "", 18 | }, 19 | }, 20 | { 21 | name: "Ethereum Ropsten", 22 | short_name: "rop", 23 | chain: "ETH", 24 | network: "ropsten", 25 | chain_id: 3, 26 | network_id: 3, 27 | rpc_url: "https://ropsten.infura.io/v3/%API_KEY%", 28 | native_currency: { 29 | symbol: "ETH", 30 | name: "Ether", 31 | decimals: "18", 32 | contractAddress: "", 33 | balance: "", 34 | }, 35 | }, 36 | { 37 | name: "Ethereum Rinkeby", 38 | short_name: "rin", 39 | chain: "ETH", 40 | network: "rinkeby", 41 | chain_id: 4, 42 | network_id: 4, 43 | rpc_url: "https://rinkeby.infura.io/v3/%API_KEY%", 44 | native_currency: { 45 | symbol: "ETH", 46 | name: "Ether", 47 | decimals: "18", 48 | contractAddress: "", 49 | balance: "", 50 | }, 51 | }, 52 | { 53 | name: "Ethereum Görli", 54 | short_name: "gor", 55 | chain: "ETH", 56 | network: "goerli", 57 | chain_id: 5, 58 | network_id: 5, 59 | rpc_url: "https://goerli.infura.io/v3/%API_KEY%", 60 | native_currency: { 61 | symbol: "ETH", 62 | name: "Ether", 63 | decimals: "18", 64 | contractAddress: "", 65 | balance: "", 66 | }, 67 | }, 68 | { 69 | name: "RSK Mainnet", 70 | short_name: "rsk", 71 | chain: "RSK", 72 | network: "mainnet", 73 | chain_id: 30, 74 | network_id: 30, 75 | rpc_url: "https://public-node.rsk.co", 76 | native_currency: { 77 | symbol: "RSK", 78 | name: "RSK", 79 | decimals: "18", 80 | contractAddress: "", 81 | balance: "", 82 | }, 83 | }, 84 | { 85 | name: "Ethereum Kovan", 86 | short_name: "kov", 87 | chain: "ETH", 88 | network: "kovan", 89 | chain_id: 42, 90 | network_id: 42, 91 | rpc_url: "https://kovan.infura.io/v3/%API_KEY%", 92 | native_currency: { 93 | symbol: "ETH", 94 | name: "Ether", 95 | decimals: "18", 96 | contractAddress: "", 97 | balance: "", 98 | }, 99 | }, 100 | { 101 | name: "Ethereum Classic Mainnet", 102 | short_name: "etc", 103 | chain: "ETC", 104 | network: "mainnet", 105 | chain_id: 61, 106 | network_id: 1, 107 | rpc_url: "https://ethereumclassic.network", 108 | native_currency: { 109 | symbol: "ETH", 110 | name: "Ether Classic", 111 | decimals: "18", 112 | contractAddress: "", 113 | balance: "", 114 | }, 115 | }, 116 | { 117 | name: "POA Network Sokol", 118 | short_name: "poa", 119 | chain: "POA", 120 | network: "sokol", 121 | chain_id: 77, 122 | network_id: 77, 123 | rpc_url: "https://sokol.poa.network", 124 | native_currency: { 125 | symbol: "POA", 126 | name: "POA", 127 | decimals: "18", 128 | contractAddress: "", 129 | balance: "", 130 | }, 131 | }, 132 | { 133 | name: "POA Network Core", 134 | short_name: "skl", 135 | chain: "POA", 136 | network: "core", 137 | chain_id: 99, 138 | network_id: 99, 139 | rpc_url: "https://core.poa.network", 140 | native_currency: { 141 | symbol: "POA", 142 | name: "POA", 143 | decimals: "18", 144 | contractAddress: "", 145 | balance: "", 146 | }, 147 | }, 148 | { 149 | name: "xDAI Chain", 150 | short_name: "xdai", 151 | chain: "POA", 152 | network: "dai", 153 | chain_id: 100, 154 | network_id: 100, 155 | rpc_url: "https://dai.poa.network", 156 | native_currency: { 157 | symbol: "xDAI", 158 | name: "xDAI", 159 | decimals: "18", 160 | contractAddress: "", 161 | balance: "", 162 | }, 163 | }, 164 | { 165 | name: "Callisto Mainnet", 166 | short_name: "clo", 167 | chain: "callisto", 168 | network: "mainnet", 169 | chain_id: 820, 170 | network_id: 1, 171 | rpc_url: "https://clo-geth.0xinfra.com/", 172 | native_currency: { 173 | symbol: "CLO", 174 | name: "CLO", 175 | decimals: "18", 176 | contractAddress: "", 177 | balance: "", 178 | }, 179 | }, 180 | { 181 | name: "Polygon Testnet Mumbai", 182 | chain: "Polygon", 183 | network: "mumbai", 184 | rpc_url: "https://rpc-mumbai.maticvigil.com", 185 | native_currency: { 186 | name: "MATIC", 187 | symbol: "MATIC", 188 | decimals: "18", 189 | contractAddress: "", 190 | balance: "", 191 | }, 192 | short_name: "maticmum", 193 | chain_id: 80001, 194 | network_id: 80001, 195 | }, 196 | { 197 | name: "Polygon Mainnet", 198 | chain: "Polygon", 199 | network: "mainnet", 200 | rpc_url: "https://rpc-mainnet.maticvigil.com", 201 | native_currency: { 202 | name: "MATIC", 203 | symbol: "MATIC", 204 | decimals: "18", 205 | contractAddress: "", 206 | balance: "", 207 | }, 208 | short_name: "MATIC", 209 | chain_id: 137, 210 | network_id: 137, 211 | }, 212 | { 213 | name: "Avalanche Fuji Testnet", 214 | chain: "AVAX", 215 | network: "fuji", 216 | rpc_url: "https://api.avax-test.network/ext/bc/C/rpc", 217 | native_currency: { 218 | name: "Avalanche", 219 | symbol: "AVAX", 220 | decimals: "18", 221 | contractAddress: "", 222 | balance: "", 223 | }, 224 | short_name: "Fuji", 225 | chain_id: 43113, 226 | network_id: 1, 227 | }, 228 | { 229 | name: "Avalanche Mainnet", 230 | chain: "AVAX", 231 | network: "mainnet", 232 | rpc_url: "https://api.avax.network/ext/bc/C/rpc", 233 | native_currency: { 234 | name: "Avalanche", 235 | symbol: "AVAX", 236 | decimals: "18", 237 | contractAddress: "", 238 | balance: "", 239 | }, 240 | short_name: "Avalanche", 241 | chain_id: 43114, 242 | network_id: 43114, 243 | }, 244 | { 245 | name: "Arbitrum One", 246 | chain_id: 42161, 247 | network_id: 42161, 248 | network: "mainnet", 249 | short_name: "arb1", 250 | chain: "ETH", 251 | native_currency: { 252 | name: "Ether", 253 | symbol: "AETH", 254 | decimals: "18", 255 | contractAddress: "", 256 | balance: "", 257 | }, 258 | rpc_url: "https://arbitrum-mainnet.infura.io/v3/%API_KEY%", 259 | }, 260 | { 261 | name: "Optimism", 262 | chain: "ETH", 263 | rpc_url: "https://mainnet.optimism.io/", 264 | network: "mainnet", 265 | native_currency: { 266 | name: "Ether", 267 | symbol: "OETH", 268 | decimals: "18", 269 | contractAddress: "", 270 | balance: "", 271 | }, 272 | short_name: "oeth", 273 | chain_id: 10, 274 | network_id: 10, 275 | }, 276 | ]; 277 | -------------------------------------------------------------------------------- /src/helpers/utilities.ts: -------------------------------------------------------------------------------- 1 | import { providers } from "ethers"; 2 | import { convertUtf8ToHex } from "@walletconnect/utils"; 3 | import { TypedDataUtils } from "eth-sig-util"; 4 | import * as ethUtil from "ethereumjs-util"; 5 | import { IChainData } from "./types"; 6 | import { SUPPORTED_CHAINS } from "./chains"; 7 | import { eip1271 } from "./eip1271"; 8 | 9 | export function capitalize(string: string): string { 10 | return string 11 | .split(" ") 12 | .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) 13 | .join(" "); 14 | } 15 | 16 | export function ellipseText(text = "", maxLength = 9999): string { 17 | if (text.length <= maxLength) { 18 | return text; 19 | } 20 | const _maxLength = maxLength - 3; 21 | let ellipse = false; 22 | let currentLength = 0; 23 | const result = 24 | text 25 | .split(" ") 26 | .filter(word => { 27 | currentLength += word.length; 28 | if (ellipse || currentLength >= _maxLength) { 29 | ellipse = true; 30 | return false; 31 | } else { 32 | return true; 33 | } 34 | }) 35 | .join(" ") + "..."; 36 | return result; 37 | } 38 | 39 | export function ellipseAddress(address = "", width = 10): string { 40 | return `${address.slice(0, width)}...${address.slice(-width)}`; 41 | } 42 | 43 | export function padLeft(n: string, width: number, z?: string): string { 44 | z = z || "0"; 45 | n = n + ""; 46 | return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; 47 | } 48 | 49 | export function sanitizeHex(hex: string): string { 50 | hex = hex.substring(0, 2) === "0x" ? hex.substring(2) : hex; 51 | if (hex === "") { 52 | return ""; 53 | } 54 | hex = hex.length % 2 !== 0 ? "0" + hex : hex; 55 | return "0x" + hex; 56 | } 57 | 58 | export function removeHexPrefix(hex: string): string { 59 | return hex.toLowerCase().replace("0x", ""); 60 | } 61 | 62 | export function getDataString(func: string, arrVals: any[]): string { 63 | let val = ""; 64 | for (let i = 0; i < arrVals.length; i++) { 65 | val += padLeft(arrVals[i], 64); 66 | } 67 | const data = func + val; 68 | return data; 69 | } 70 | 71 | export function isMobile(): boolean { 72 | let mobile = false; 73 | 74 | function hasTouchEvent(): boolean { 75 | try { 76 | document.createEvent("TouchEvent"); 77 | return true; 78 | } catch (e) { 79 | return false; 80 | } 81 | } 82 | 83 | function hasMobileUserAgent(): boolean { 84 | if ( 85 | /(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( 86 | navigator.userAgent, 87 | ) || 88 | /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( 89 | navigator.userAgent.substr(0, 4), 90 | ) 91 | ) { 92 | return true; 93 | } else if (hasTouchEvent()) { 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | mobile = hasMobileUserAgent(); 100 | 101 | return mobile; 102 | } 103 | 104 | export function getChainData(chainId: number): IChainData { 105 | const chainData = SUPPORTED_CHAINS.filter((chain: any) => chain.chain_id === chainId)[0]; 106 | 107 | if (!chainData) { 108 | throw new Error("ChainId missing or not supported"); 109 | } 110 | 111 | const API_KEY = process.env.REACT_APP_INFURA_PROJECT_ID; 112 | 113 | if ( 114 | chainData.rpc_url.includes("infura.io") && 115 | chainData.rpc_url.includes("%API_KEY%") && 116 | API_KEY 117 | ) { 118 | const rpcUrl = chainData.rpc_url.replace("%API_KEY%", API_KEY); 119 | 120 | return { 121 | ...chainData, 122 | rpc_url: rpcUrl, 123 | }; 124 | } 125 | 126 | return chainData; 127 | } 128 | 129 | export function encodePersonalMessage(msg: string): string { 130 | const data = ethUtil.toBuffer(convertUtf8ToHex(msg)); 131 | const buf = Buffer.concat([ 132 | Buffer.from("\u0019Ethereum Signed Message:\n" + data.length.toString(), "utf8"), 133 | data, 134 | ]); 135 | return ethUtil.bufferToHex(buf); 136 | } 137 | 138 | export function hashMessage(msg: string): string { 139 | const data = encodePersonalMessage(msg); 140 | const buf = ethUtil.toBuffer(data); 141 | const hash = ethUtil.keccak256(buf); 142 | return ethUtil.bufferToHex(hash); 143 | } 144 | 145 | export function encodeTypedDataMessage(msg: string): string { 146 | const data = TypedDataUtils.sanitizeData(JSON.parse(msg)); 147 | const buf = Buffer.concat([ 148 | Buffer.from("1901", "hex"), 149 | TypedDataUtils.hashStruct("EIP712Domain", data.domain, data.types), 150 | TypedDataUtils.hashStruct(data.primaryType as string, data.message, data.types), 151 | ]); 152 | return ethUtil.bufferToHex(buf); 153 | } 154 | 155 | export function hashTypedDataMessage(msg: string): string { 156 | const data = encodeTypedDataMessage(msg); 157 | const buf = ethUtil.toBuffer(data); 158 | const hash = ethUtil.keccak256(buf); 159 | return ethUtil.bufferToHex(hash); 160 | } 161 | 162 | export function recoverAddress(sig: string, hash: string): string { 163 | const params = ethUtil.fromRpcSig(sig); 164 | const result = ethUtil.ecrecover(ethUtil.toBuffer(hash), params.v, params.r, params.s); 165 | const signer = ethUtil.bufferToHex(ethUtil.publicToAddress(result)); 166 | return signer; 167 | } 168 | 169 | export function recoverMessageSignature(sig: string, msg: string): string { 170 | const hash = hashMessage(msg); 171 | const signer = recoverAddress(sig, hash); 172 | return signer; 173 | } 174 | 175 | export function recoverTypedMessage(sig: string, msg: string): string { 176 | const hash = hashTypedDataMessage(msg); 177 | const signer = recoverAddress(sig, hash); 178 | return signer; 179 | } 180 | 181 | export async function verifySignature( 182 | address: string, 183 | sig: string, 184 | hash: string, 185 | chainId: number, 186 | ): Promise { 187 | const rpcUrl = getChainData(chainId).rpc_url; 188 | const provider = new providers.JsonRpcProvider(rpcUrl); 189 | const bytecode = await provider.getCode(address); 190 | if (!bytecode || bytecode === "0x" || bytecode === "0x0" || bytecode === "0x00") { 191 | const signer = recoverAddress(sig, hash); 192 | return signer.toLowerCase() === address.toLowerCase(); 193 | } else { 194 | return eip1271.isValidSignature(address, sig, hash, provider); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import styled from "styled-components"; 3 | import WalletConnect from "@walletconnect/client"; 4 | import QRCodeModal from "@walletconnect/qrcode-modal"; 5 | import { convertUtf8ToHex } from "@walletconnect/utils"; 6 | import { IInternalEvent } from "@walletconnect/types"; 7 | import Button from "./components/Button"; 8 | import Column from "./components/Column"; 9 | import Wrapper from "./components/Wrapper"; 10 | import Modal from "./components/Modal"; 11 | import Header from "./components/Header"; 12 | import Loader from "./components/Loader"; 13 | import { fonts } from "./styles"; 14 | import { apiGetAccountAssets, apiGetGasPrices, apiGetAccountNonce } from "./helpers/api"; 15 | import { 16 | sanitizeHex, 17 | verifySignature, 18 | hashTypedDataMessage, 19 | hashMessage, 20 | } from "./helpers/utilities"; 21 | import { convertAmountToRawNumber, convertStringToHex } from "./helpers/bignumber"; 22 | import { IAssetData } from "./helpers/types"; 23 | import Banner from "./components/Banner"; 24 | import AccountAssets from "./components/AccountAssets"; 25 | import { eip712 } from "./helpers/eip712"; 26 | 27 | const SLayout = styled.div` 28 | position: relative; 29 | width: 100%; 30 | /* height: 100%; */ 31 | min-height: 100vh; 32 | text-align: center; 33 | `; 34 | 35 | const SContent = styled(Wrapper as any)` 36 | width: 100%; 37 | height: 100%; 38 | padding: 0 16px; 39 | `; 40 | 41 | const SLanding = styled(Column as any)` 42 | height: 600px; 43 | `; 44 | 45 | const SButtonContainer = styled(Column as any)` 46 | width: 250px; 47 | margin: 50px 0; 48 | `; 49 | 50 | const SConnectButton = styled(Button as any)` 51 | border-radius: 8px; 52 | font-size: ${fonts.size.medium}; 53 | height: 44px; 54 | width: 100%; 55 | margin: 12px 0; 56 | `; 57 | 58 | const SContainer = styled.div` 59 | height: 100%; 60 | min-height: 200px; 61 | display: flex; 62 | flex-direction: column; 63 | justify-content: center; 64 | align-items: center; 65 | word-break: break-word; 66 | `; 67 | 68 | const SModalContainer = styled.div` 69 | width: 100%; 70 | position: relative; 71 | word-wrap: break-word; 72 | `; 73 | 74 | const SModalTitle = styled.div` 75 | margin: 1em 0; 76 | font-size: 20px; 77 | font-weight: 700; 78 | `; 79 | 80 | const SModalParagraph = styled.p` 81 | margin-top: 30px; 82 | `; 83 | 84 | // @ts-ignore 85 | const SBalances = styled(SLanding as any)` 86 | height: 100%; 87 | & h3 { 88 | padding-top: 30px; 89 | } 90 | `; 91 | 92 | const STable = styled(SContainer as any)` 93 | flex-direction: column; 94 | text-align: left; 95 | `; 96 | 97 | const SRow = styled.div` 98 | width: 100%; 99 | display: flex; 100 | margin: 6px 0; 101 | `; 102 | 103 | const SKey = styled.div` 104 | width: 30%; 105 | font-weight: 700; 106 | `; 107 | 108 | const SValue = styled.div` 109 | width: 70%; 110 | font-family: monospace; 111 | `; 112 | 113 | const STestButtonContainer = styled.div` 114 | width: 100%; 115 | display: flex; 116 | justify-content: center; 117 | align-items: center; 118 | flex-wrap: wrap; 119 | `; 120 | 121 | const STestButton = styled(Button as any)` 122 | border-radius: 8px; 123 | font-size: ${fonts.size.medium}; 124 | height: 44px; 125 | width: 100%; 126 | max-width: 200px; 127 | margin: 12px; 128 | `; 129 | 130 | interface IAppState { 131 | connector: WalletConnect | null; 132 | fetching: boolean; 133 | connected: boolean; 134 | chainId: number; 135 | showModal: boolean; 136 | pendingRequest: boolean; 137 | uri: string; 138 | accounts: string[]; 139 | address: string; 140 | result: any | null; 141 | assets: IAssetData[]; 142 | } 143 | 144 | const INITIAL_STATE: IAppState = { 145 | connector: null, 146 | fetching: false, 147 | connected: false, 148 | chainId: 1, 149 | showModal: false, 150 | pendingRequest: false, 151 | uri: "", 152 | accounts: [], 153 | address: "", 154 | result: null, 155 | assets: [], 156 | }; 157 | 158 | class App extends React.Component { 159 | public state: IAppState = { 160 | ...INITIAL_STATE, 161 | }; 162 | 163 | public connect = async () => { 164 | // bridge url 165 | const bridge = "https://bridge.walletconnect.org"; 166 | 167 | // create new connector 168 | const connector = new WalletConnect({ bridge, qrcodeModal: QRCodeModal }); 169 | 170 | await this.setState({ connector }); 171 | 172 | // check if already connected 173 | if (!connector.connected) { 174 | // create new session 175 | await connector.createSession(); 176 | } 177 | 178 | // subscribe to events 179 | await this.subscribeToEvents(); 180 | }; 181 | public subscribeToEvents = () => { 182 | const { connector } = this.state; 183 | 184 | if (!connector) { 185 | return; 186 | } 187 | 188 | connector.on("session_update", async (error, payload) => { 189 | console.log(`connector.on("session_update")`); 190 | 191 | if (error) { 192 | throw error; 193 | } 194 | 195 | const { chainId, accounts } = payload.params[0]; 196 | this.onSessionUpdate(accounts, chainId); 197 | }); 198 | 199 | connector.on("connect", (error, payload) => { 200 | console.log(`connector.on("connect")`); 201 | 202 | if (error) { 203 | throw error; 204 | } 205 | 206 | this.onConnect(payload); 207 | }); 208 | 209 | connector.on("disconnect", (error, payload) => { 210 | console.log(`connector.on("disconnect")`); 211 | 212 | if (error) { 213 | throw error; 214 | } 215 | 216 | this.onDisconnect(); 217 | }); 218 | 219 | if (connector.connected) { 220 | const { chainId, accounts } = connector; 221 | const address = accounts[0]; 222 | this.setState({ 223 | connected: true, 224 | chainId, 225 | accounts, 226 | address, 227 | }); 228 | this.onSessionUpdate(accounts, chainId); 229 | } 230 | 231 | this.setState({ connector }); 232 | }; 233 | 234 | public killSession = async () => { 235 | const { connector } = this.state; 236 | if (connector) { 237 | connector.killSession(); 238 | } 239 | this.resetApp(); 240 | }; 241 | 242 | public resetApp = async () => { 243 | await this.setState({ ...INITIAL_STATE }); 244 | }; 245 | 246 | public onConnect = async (payload: IInternalEvent) => { 247 | const { chainId, accounts } = payload.params[0]; 248 | const address = accounts[0]; 249 | await this.setState({ 250 | connected: true, 251 | chainId, 252 | accounts, 253 | address, 254 | }); 255 | this.getAccountAssets(); 256 | }; 257 | 258 | public onDisconnect = async () => { 259 | this.resetApp(); 260 | }; 261 | 262 | public onSessionUpdate = async (accounts: string[], chainId: number) => { 263 | const address = accounts[0]; 264 | await this.setState({ chainId, accounts, address }); 265 | await this.getAccountAssets(); 266 | }; 267 | 268 | public getAccountAssets = async () => { 269 | const { address, chainId } = this.state; 270 | this.setState({ fetching: true }); 271 | try { 272 | // get account balances 273 | const assets = await apiGetAccountAssets(address, chainId); 274 | 275 | await this.setState({ fetching: false, address, assets }); 276 | } catch (error) { 277 | console.error(error); 278 | await this.setState({ fetching: false }); 279 | } 280 | }; 281 | 282 | public toggleModal = () => this.setState({ showModal: !this.state.showModal }); 283 | 284 | public testSendTransaction = async () => { 285 | const { connector, address, chainId } = this.state; 286 | 287 | if (!connector) { 288 | return; 289 | } 290 | 291 | // from 292 | const from = address; 293 | 294 | // to 295 | const to = address; 296 | 297 | // nonce 298 | const _nonce = await apiGetAccountNonce(address, chainId); 299 | const nonce = sanitizeHex(convertStringToHex(_nonce)); 300 | 301 | // gasPrice 302 | const gasPrices = await apiGetGasPrices(); 303 | const _gasPrice = gasPrices.slow.price; 304 | const gasPrice = sanitizeHex(convertStringToHex(convertAmountToRawNumber(_gasPrice, 9))); 305 | 306 | // gasLimit 307 | const _gasLimit = 21000; 308 | const gasLimit = sanitizeHex(convertStringToHex(_gasLimit)); 309 | 310 | // value 311 | const _value = 0; 312 | const value = sanitizeHex(convertStringToHex(_value)); 313 | 314 | // data 315 | const data = "0x"; 316 | 317 | // test transaction 318 | const tx = { 319 | from, 320 | to, 321 | nonce, 322 | gasPrice, 323 | gasLimit, 324 | value, 325 | data, 326 | }; 327 | 328 | try { 329 | // open modal 330 | this.toggleModal(); 331 | 332 | // toggle pending request indicator 333 | this.setState({ pendingRequest: true }); 334 | 335 | // send transaction 336 | const result = await connector.sendTransaction(tx); 337 | 338 | // format displayed result 339 | const formattedResult = { 340 | method: "eth_sendTransaction", 341 | txHash: result, 342 | from: address, 343 | to: address, 344 | value: `${_value} ETH`, 345 | }; 346 | 347 | // display result 348 | this.setState({ 349 | connector, 350 | pendingRequest: false, 351 | result: formattedResult || null, 352 | }); 353 | } catch (error) { 354 | console.error(error); 355 | this.setState({ connector, pendingRequest: false, result: null }); 356 | } 357 | }; 358 | 359 | public testSignTransaction = async () => { 360 | const { connector, address, chainId } = this.state; 361 | 362 | if (!connector) { 363 | return; 364 | } 365 | 366 | // from 367 | const from = address; 368 | 369 | // to 370 | const to = address; 371 | 372 | // nonce 373 | const _nonce = await apiGetAccountNonce(address, chainId); 374 | const nonce = sanitizeHex(convertStringToHex(_nonce)); 375 | 376 | // gasPrice 377 | const gasPrices = await apiGetGasPrices(); 378 | const _gasPrice = gasPrices.slow.price; 379 | const gasPrice = sanitizeHex(convertStringToHex(convertAmountToRawNumber(_gasPrice, 9))); 380 | 381 | // gasLimit 382 | const _gasLimit = 21000; 383 | const gasLimit = sanitizeHex(convertStringToHex(_gasLimit)); 384 | 385 | // value 386 | const _value = 0; 387 | const value = sanitizeHex(convertStringToHex(_value)); 388 | 389 | // data 390 | const data = "0x"; 391 | 392 | // test transaction 393 | const tx = { 394 | from, 395 | to, 396 | nonce, 397 | gasPrice, 398 | gasLimit, 399 | value, 400 | data, 401 | }; 402 | 403 | try { 404 | // open modal 405 | this.toggleModal(); 406 | 407 | // toggle pending request indicator 408 | this.setState({ pendingRequest: true }); 409 | 410 | // send transaction 411 | const result = await connector.signTransaction(tx); 412 | 413 | // format displayed result 414 | const formattedResult = { 415 | method: "eth_signTransaction", 416 | from: address, 417 | to: address, 418 | value: `${_value} ETH`, 419 | result, 420 | }; 421 | 422 | // display result 423 | this.setState({ 424 | connector, 425 | pendingRequest: false, 426 | result: formattedResult || null, 427 | }); 428 | } catch (error) { 429 | console.error(error); 430 | this.setState({ connector, pendingRequest: false, result: null }); 431 | } 432 | }; 433 | 434 | public testLegacySignMessage = async () => { 435 | const { connector, address, chainId } = this.state; 436 | 437 | if (!connector) { 438 | return; 439 | } 440 | 441 | // test message 442 | const message = `My email is john@doe.com - ${new Date().toUTCString()}`; 443 | 444 | // hash message 445 | const hash = hashMessage(message); 446 | 447 | // eth_sign params 448 | const msgParams = [address, hash]; 449 | 450 | try { 451 | // open modal 452 | this.toggleModal(); 453 | 454 | // toggle pending request indicator 455 | this.setState({ pendingRequest: true }); 456 | 457 | // send message 458 | const result = await connector.signMessage(msgParams); 459 | 460 | // verify signature 461 | const valid = await verifySignature(address, result, hash, chainId); 462 | 463 | // format displayed result 464 | const formattedResult = { 465 | method: "eth_sign (legacy)", 466 | address, 467 | valid, 468 | result, 469 | }; 470 | 471 | // display result 472 | this.setState({ 473 | connector, 474 | pendingRequest: false, 475 | result: formattedResult || null, 476 | }); 477 | } catch (error) { 478 | console.error(error); 479 | this.setState({ connector, pendingRequest: false, result: null }); 480 | } 481 | }; 482 | 483 | public testStandardSignMessage = async () => { 484 | const { connector, address, chainId } = this.state; 485 | 486 | if (!connector) { 487 | return; 488 | } 489 | 490 | // test message 491 | const message = `My email is john@doe.com - ${new Date().toUTCString()}`; 492 | 493 | // encode message (hex) 494 | const hexMsg = convertUtf8ToHex(message); 495 | 496 | // eth_sign params 497 | const msgParams = [address, hexMsg]; 498 | 499 | try { 500 | // open modal 501 | this.toggleModal(); 502 | 503 | // toggle pending request indicator 504 | this.setState({ pendingRequest: true }); 505 | 506 | // send message 507 | const result = await connector.signMessage(msgParams); 508 | 509 | // verify signature 510 | const hash = hashMessage(message); 511 | const valid = await verifySignature(address, result, hash, chainId); 512 | 513 | // format displayed result 514 | const formattedResult = { 515 | method: "eth_sign (standard)", 516 | address, 517 | valid, 518 | result, 519 | }; 520 | 521 | // display result 522 | this.setState({ 523 | connector, 524 | pendingRequest: false, 525 | result: formattedResult || null, 526 | }); 527 | } catch (error) { 528 | console.error(error); 529 | this.setState({ connector, pendingRequest: false, result: null }); 530 | } 531 | }; 532 | 533 | public testPersonalSignMessage = async () => { 534 | const { connector, address, chainId } = this.state; 535 | 536 | if (!connector) { 537 | return; 538 | } 539 | 540 | // test message 541 | const message = `My email is john@doe.com - ${new Date().toUTCString()}`; 542 | 543 | // encode message (hex) 544 | const hexMsg = convertUtf8ToHex(message); 545 | 546 | // eth_sign params 547 | const msgParams = [hexMsg, address]; 548 | 549 | try { 550 | // open modal 551 | this.toggleModal(); 552 | 553 | // toggle pending request indicator 554 | this.setState({ pendingRequest: true }); 555 | 556 | // send message 557 | const result = await connector.signPersonalMessage(msgParams); 558 | 559 | // verify signature 560 | const hash = hashMessage(message); 561 | const valid = await verifySignature(address, result, hash, chainId); 562 | 563 | // format displayed result 564 | const formattedResult = { 565 | method: "personal_sign", 566 | address, 567 | valid, 568 | result, 569 | }; 570 | 571 | // display result 572 | this.setState({ 573 | connector, 574 | pendingRequest: false, 575 | result: formattedResult || null, 576 | }); 577 | } catch (error) { 578 | console.error(error); 579 | this.setState({ connector, pendingRequest: false, result: null }); 580 | } 581 | }; 582 | 583 | public testSignTypedData = async () => { 584 | const { connector, address, chainId } = this.state; 585 | 586 | if (!connector) { 587 | return; 588 | } 589 | 590 | const message = JSON.stringify(eip712.example); 591 | 592 | // eth_signTypedData params 593 | const msgParams = [address, message]; 594 | 595 | try { 596 | // open modal 597 | this.toggleModal(); 598 | 599 | // toggle pending request indicator 600 | this.setState({ pendingRequest: true }); 601 | 602 | // sign typed data 603 | const result = await connector.signTypedData(msgParams); 604 | 605 | // verify signature 606 | const hash = hashTypedDataMessage(message); 607 | const valid = await verifySignature(address, result, hash, chainId); 608 | 609 | // format displayed result 610 | const formattedResult = { 611 | method: "eth_signTypedData_v4", 612 | address, 613 | valid, 614 | result, 615 | }; 616 | 617 | // display result 618 | this.setState({ 619 | connector, 620 | pendingRequest: false, 621 | result: formattedResult || null, 622 | }); 623 | } catch (error) { 624 | console.error(error); 625 | this.setState({ connector, pendingRequest: false, result: null }); 626 | } 627 | }; 628 | 629 | public render = () => { 630 | const { 631 | assets, 632 | address, 633 | connected, 634 | chainId, 635 | fetching, 636 | showModal, 637 | pendingRequest, 638 | result, 639 | } = this.state; 640 | return ( 641 | 642 | 643 |
649 | 650 | {!address && !assets.length ? ( 651 | 652 |

653 | {`Try out WalletConnect`} 654 |
655 | {`v${process.env.REACT_APP_VERSION}`} 656 |

657 | 658 | 659 | {"Connect to WalletConnect"} 660 | 661 | 662 |
663 | ) : ( 664 | 665 | 666 |

Actions

667 | 668 | 669 | 670 | {"eth_sendTransaction"} 671 | 672 | 673 | {"eth_signTransaction"} 674 | 675 | 676 | {"eth_signTypedData_v4"} 677 | 678 | 679 | {"eth_sign (legacy)"} 680 | 681 | 682 | {"eth_sign (standard)"} 683 | 684 | 685 | {"personal_sign"} 686 | 687 | 688 | 689 |

Balances

690 | {!fetching ? ( 691 | 692 | ) : ( 693 | 694 | 695 | 696 | 697 | 698 | )} 699 |
700 | )} 701 |
702 | 703 | 704 | {pendingRequest ? ( 705 | 706 | {"Pending Call Request"} 707 | 708 | 709 | {"Approve or reject request using your wallet"} 710 | 711 | 712 | ) : result ? ( 713 | 714 | {"Call Request Approved"} 715 | 716 | {Object.keys(result).map(key => ( 717 | 718 | {key} 719 | {result[key].toString()} 720 | 721 | ))} 722 | 723 | 724 | ) : ( 725 | 726 | {"Call Request Rejected"} 727 | 728 | )} 729 | 730 | 731 | ); 732 | }; 733 | } 734 | 735 | export default App; 736 | --------------------------------------------------------------------------------