Feat/make dapp nextjs (#41)
@ -1,3 +1,2 @@
|
||||
REACT_APP_PROJECT_ID=39bc...
|
||||
REACT_APP_RELAY_URL=wss://relay.walletconnect.com
|
||||
|
||||
PROJECT_ID=39bc...
|
||||
RELAY_URL=wss://relay.walletconnect.com
|
||||
|
3
dapps/react-dapp-v2/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
21
dapps/react-dapp-v2/.gitignore
vendored
@ -8,18 +8,29 @@
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
.eslintcache
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"arrowParens": "avoid"
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
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.
|
@ -29,13 +29,13 @@ cp .env.local.example .env.local
|
||||
|
||||
Your `.env.local` now contains the following environment variables:
|
||||
|
||||
- `REACT_APP_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
|
||||
- `REACT_APP_RELAY_URL` (already set)
|
||||
- `PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
|
||||
- `RELAY_URL` (already set)
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Test
|
||||
@ -49,3 +49,4 @@ yarn test
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
|
7
dapps/react-dapp-v2/images.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
declare module "*.svg";
|
||||
declare module "*.png";
|
||||
declare module "*.jpg";
|
||||
declare module "*.jpeg";
|
||||
declare module "*.gif";
|
||||
declare module "*.bmp";
|
||||
declare module "*.tiff";
|
8
dapps/react-dapp-v2/next.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
distDir: 'build'
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
@ -1,31 +1,13 @@
|
||||
{
|
||||
"name": "react-dapp-v2",
|
||||
"name": "react-dapp-v2-next",
|
||||
"version": "2.0.0-6de2113.0",
|
||||
"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"
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethereumjs/tx": "^3.5.0",
|
||||
@ -43,6 +25,7 @@
|
||||
"eth-sig-util": "^2.5.3",
|
||||
"ethereumjs-util": "^7.0.6",
|
||||
"ethers": "^5.3.0",
|
||||
"next": "12.2.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"qr-image": "^3.2.0",
|
||||
"react": "^17.0.2",
|
||||
@ -50,41 +33,22 @@
|
||||
"react-scripts": "^4.0.3",
|
||||
"solana-wallet": "^1.0.1",
|
||||
"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"
|
||||
]
|
||||
"@types/react": "18.0.15",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"better-sqlite3": "^7.6.2",
|
||||
"eslint": "8.21.0",
|
||||
"eslint-config-next": "12.2.4",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 234 B |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 25 KiB |
@ -1,20 +0,0 @@
|
||||
<!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>
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
@ -2,7 +2,11 @@ import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||
|
||||
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
|
||||
|
||||
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers";
|
||||
import {
|
||||
NamespaceMetadata,
|
||||
ChainMetadata,
|
||||
ChainRequestRender,
|
||||
} from "../helpers";
|
||||
|
||||
export const CosmosMetadata: NamespaceMetadata = {
|
||||
"cosmoshub-4": {
|
||||
@ -20,7 +24,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||
export function getChainRequestRender(
|
||||
request: JsonRpcRequest
|
||||
): ChainRequestRender[] {
|
||||
let params = [{ label: "Method", value: request.method }];
|
||||
|
||||
switch (request.method) {
|
||||
|
@ -83,7 +83,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||
export function getChainRequestRender(
|
||||
request: JsonRpcRequest
|
||||
): ChainRequestRender[] {
|
||||
let params = [{ label: "Method", value: request.method }];
|
||||
|
||||
switch (request.method) {
|
||||
@ -111,7 +113,9 @@ export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRend
|
||||
},
|
||||
{
|
||||
label: "Value",
|
||||
value: request.params[0].value ? convertHexToNumber(request.params[0].value) : "",
|
||||
value: request.params[0].value
|
||||
? convertHexToNumber(request.params[0].value)
|
||||
: "",
|
||||
},
|
||||
{ label: "Data", value: request.params[0].data },
|
||||
];
|
||||
|
@ -25,7 +25,7 @@ export function getChainMetadata(chainId: string): ChainMetadata {
|
||||
|
||||
export function getChainRequestRender(
|
||||
request: JsonRpcRequest,
|
||||
chainId: string,
|
||||
chainId: string
|
||||
): ChainRequestRender[] {
|
||||
const namespace = chainId.split(":")[0];
|
||||
switch (namespace) {
|
||||
|
@ -1,12 +1,18 @@
|
||||
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
|
||||
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
|
||||
|
||||
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers";
|
||||
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",
|
||||
logo:
|
||||
BLOCKCHAIN_LOGO_BASE_URL +
|
||||
"polkadot:91b171bb158e2d3848fa23a9f1c25182.png",
|
||||
rgb: "230, 1, 122",
|
||||
},
|
||||
};
|
||||
@ -20,7 +26,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] {
|
||||
export function getChainRequestRender(
|
||||
request: JsonRpcRequest
|
||||
): ChainRequestRender[] {
|
||||
let params = [{ label: "Method", value: request.method }];
|
||||
|
||||
switch (request.method) {
|
||||
|
@ -6,7 +6,10 @@ export const SolanaChainData: ChainsMap = {
|
||||
"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": {
|
||||
id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ",
|
||||
name: "Solana Mainnet",
|
||||
rpc: ["https://api.mainnet-beta.solana.com", "https://solana-api.projectserum.com"],
|
||||
rpc: [
|
||||
"https://api.mainnet-beta.solana.com",
|
||||
"https://solana-api.projectserum.com",
|
||||
],
|
||||
slip44: 501,
|
||||
testnet: false,
|
||||
},
|
||||
@ -22,12 +25,12 @@ export const SolanaChainData: ChainsMap = {
|
||||
export const SolanaMetadata: NamespaceMetadata = {
|
||||
// Solana Mainnet
|
||||
"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": {
|
||||
logo: "/solana_logo.png",
|
||||
logo: "/assets/solana_logo.png",
|
||||
rgb: "0, 0, 0",
|
||||
},
|
||||
// Solana Devnet
|
||||
"8E9rvCKLFQia2Y35HXjjpWzj8weVo44K": {
|
||||
logo: "/solana_logo.png",
|
||||
logo: "/assets/solana_logo.png",
|
||||
rgb: "0, 0, 0",
|
||||
},
|
||||
};
|
||||
|
@ -5,8 +5,6 @@ import Icon from "./Icon";
|
||||
|
||||
import { AssetData, fromWad } from "../helpers";
|
||||
|
||||
import eth from "../assets/eth.svg";
|
||||
import erc20 from "../assets/erc20.svg";
|
||||
import { getChainMetadata } from "../chains";
|
||||
|
||||
const xdai = getChainMetadata("eip155:100").logo;
|
||||
@ -38,17 +36,17 @@ const SAssetBalance = styled.div`
|
||||
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} />;
|
||||
return <Icon src={src} fallback={"/assets/erc20.svg"} />;
|
||||
}
|
||||
switch (asset.symbol.toLowerCase()) {
|
||||
case "eth":
|
||||
return <Icon src={eth} />;
|
||||
return <Icon src={"/assets/eth.svg"} />;
|
||||
case "xdai":
|
||||
return <Icon src={xdai} />;
|
||||
case "matic":
|
||||
return <Icon src={matic} />;
|
||||
default:
|
||||
return <Icon src={erc20} />;
|
||||
return <Icon src={"/assets/eth20.svg"} />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +63,9 @@ const Asset = (props: AssetProps) => {
|
||||
<SAssetName>{asset.name}</SAssetName>
|
||||
</SAssetLeft>
|
||||
<SAssetRight>
|
||||
<SAssetBalance>{`${fromWad(asset.balance || "0")} ${asset.symbol}`}</SAssetBalance>
|
||||
<SAssetBalance>{`${fromWad(asset.balance || "0")} ${
|
||||
asset.symbol
|
||||
}`}</SAssetBalance>
|
||||
</SAssetRight>
|
||||
</SAsset>
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import logo from "../assets/walletconnect.png";
|
||||
|
||||
const SBannerWrapper = styled.div`
|
||||
display: flex;
|
||||
@ -11,7 +10,7 @@ const SBannerWrapper = styled.div`
|
||||
const SBanner = styled.div`
|
||||
width: 275px;
|
||||
height: 45px;
|
||||
background: url(${logo}) no-repeat;
|
||||
background: url(/assets/walletconnect.png) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
`;
|
||||
|
@ -99,7 +99,7 @@ interface BlockchainDisplayData {
|
||||
|
||||
function getBlockchainDisplayData(
|
||||
chainId: string,
|
||||
chainData: ChainNamespaces,
|
||||
chainData: ChainNamespaces
|
||||
): BlockchainDisplayData | undefined {
|
||||
const [namespace, reference] = chainId.split(":");
|
||||
let meta: ChainMetadata;
|
||||
@ -114,9 +114,18 @@ function getBlockchainDisplayData(
|
||||
}
|
||||
|
||||
const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
|
||||
props: PropsWithChildren<BlockchainProps>,
|
||||
props: PropsWithChildren<BlockchainProps>
|
||||
) => {
|
||||
const { chainData, fetching, chainId, address, onClick, active, balances, actions } = props;
|
||||
const {
|
||||
chainData,
|
||||
fetching,
|
||||
chainId,
|
||||
address,
|
||||
onClick,
|
||||
active,
|
||||
balances,
|
||||
actions,
|
||||
} = props;
|
||||
if (!Object.keys(chainData).length) return null;
|
||||
|
||||
const chain = getBlockchainDisplayData(chainId, chainData);
|
||||
@ -124,9 +133,12 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
|
||||
if (typeof chain === "undefined") return null;
|
||||
|
||||
const name = chain.meta.name || chain.data.name;
|
||||
const account = typeof address !== "undefined" ? `${chainId}:${address}` : undefined;
|
||||
const account =
|
||||
typeof address !== "undefined" ? `${chainId}:${address}` : undefined;
|
||||
const assets =
|
||||
typeof account !== "undefined" && typeof balances !== "undefined" ? balances[account] : [];
|
||||
typeof account !== "undefined" && typeof balances !== "undefined"
|
||||
? balances[account]
|
||||
: [];
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SAccount
|
||||
@ -152,8 +164,10 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
|
||||
<SFullWidthContainer>
|
||||
<h6>Balances</h6>
|
||||
<Column center>
|
||||
{assets.map(asset =>
|
||||
asset.symbol ? <Asset key={asset.symbol} asset={asset} /> : null,
|
||||
{assets.map((asset) =>
|
||||
asset.symbol ? (
|
||||
<Asset key={asset.symbol} asset={asset} />
|
||||
) : null
|
||||
)}
|
||||
</Column>
|
||||
</SFullWidthContainer>
|
||||
@ -161,7 +175,7 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
|
||||
{address && !!actions && actions.length ? (
|
||||
<SFullWidthContainer>
|
||||
<h6>Methods</h6>
|
||||
{actions.map(action => (
|
||||
{actions.map((action) => (
|
||||
<SAction
|
||||
key={action.method}
|
||||
left
|
||||
|
@ -47,9 +47,12 @@ const SButton = styled.button<ButtonStyleProps>`
|
||||
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})`)};
|
||||
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};
|
||||
@ -68,7 +71,11 @@ const SButton = styled.button<ButtonStyleProps>`
|
||||
&:hover {
|
||||
transform: ${({ disabled }) => (!disabled ? "translateY(-1px)" : "none")};
|
||||
box-shadow: ${({ disabled, outline }) =>
|
||||
!disabled ? (outline ? "none" : `${shadows.hover}`) : `${shadows.soft}`};
|
||||
!disabled
|
||||
? outline
|
||||
? "none"
|
||||
: `${shadows.hover}`
|
||||
: `${shadows.soft}`};
|
||||
}
|
||||
|
||||
&:hover ${SHoverLayer} {
|
||||
|
@ -27,7 +27,12 @@ const SColumn = styled.div<ColumnStyleProps>`
|
||||
const Column = (props: ColumnProps) => {
|
||||
const { children, spanHeight, maxWidth, center } = props;
|
||||
return (
|
||||
<SColumn {...props} spanHeight={spanHeight} maxWidth={maxWidth} center={center}>
|
||||
<SColumn
|
||||
{...props}
|
||||
spanHeight={spanHeight}
|
||||
maxWidth={maxWidth}
|
||||
center={center}
|
||||
>
|
||||
{children}
|
||||
</SColumn>
|
||||
);
|
||||
|
@ -49,7 +49,15 @@ const Loader = (props: LoaderProps) => {
|
||||
fill={rgb}
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
<rect id="Rectangle" fill={rgb} x="44" y="44.34375" width="98" height="98" rx="35" />
|
||||
<rect
|
||||
id="Rectangle"
|
||||
fill={rgb}
|
||||
x="44"
|
||||
y="44.34375"
|
||||
width="98"
|
||||
height="98"
|
||||
rx="35"
|
||||
/>
|
||||
</g>
|
||||
</SLoader>
|
||||
);
|
||||
|
@ -38,7 +38,8 @@ const SToggle = styled.div<IToggleStyleProps>`
|
||||
}
|
||||
& div:after {
|
||||
transition: ${transitions.base};
|
||||
box-shadow: inset 0 1px 0 rgb(${colors.grey}), 0px 2px 2px 1px rgba(${colors.black}, 0.2);
|
||||
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: "";
|
||||
|
@ -22,9 +22,9 @@ export const DEFAULT_TEST_CHAINS = [
|
||||
|
||||
export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
|
||||
|
||||
export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID;
|
||||
export const DEFAULT_PROJECT_ID = process.env.PROJECT_ID;
|
||||
|
||||
export const DEFAULT_RELAY_URL = process.env.REACT_APP_RELAY_URL;
|
||||
export const DEFAULT_RELAY_URL = process.env.RELAY_URL;
|
||||
|
||||
export const DEFAULT_LOGGER = "debug";
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { SolanaChainData } from "../chains/solana";
|
||||
|
||||
import { ChainNamespaces, getAllChainNamespaces } from "../helpers";
|
||||
@ -19,14 +25,18 @@ export const ChainDataContext = createContext<IContext>({} as IContext);
|
||||
/**
|
||||
* Provider
|
||||
*/
|
||||
export function ChainDataContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||
export function ChainDataContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode | ReactNode[];
|
||||
}) {
|
||||
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
||||
|
||||
const loadChainData = async () => {
|
||||
const namespaces = getAllChainNamespaces();
|
||||
const chainData: ChainNamespaces = {};
|
||||
await Promise.all(
|
||||
namespaces.map(async namespace => {
|
||||
namespaces.map(async (namespace) => {
|
||||
let chains: ChainsMap | undefined;
|
||||
try {
|
||||
if (namespace === "solana") {
|
||||
@ -40,7 +50,7 @@ export function ChainDataContextProvider({ children }: { children: ReactNode | R
|
||||
if (typeof chains !== "undefined") {
|
||||
chainData[namespace] = chains;
|
||||
}
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
setChainData(chainData);
|
||||
@ -64,7 +74,9 @@ export function ChainDataContextProvider({ children }: { children: ReactNode | R
|
||||
export function useChainData() {
|
||||
const context = useContext(ChainDataContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useChainData must be used within a ChainDataContextProvider");
|
||||
throw new Error(
|
||||
"useChainData must be used within a ChainDataContextProvider"
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -49,7 +49,11 @@ export const ClientContext = createContext<IContext>({} as IContext);
|
||||
/**
|
||||
* Provider
|
||||
*/
|
||||
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||
export function ClientContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode | ReactNode[];
|
||||
}) {
|
||||
const [client, setClient] = useState<Client>();
|
||||
const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
|
||||
const [session, setSession] = useState<SessionTypes.Struct>();
|
||||
@ -59,7 +63,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
|
||||
const [balances, setBalances] = useState<AccountBalances>({});
|
||||
const [accounts, setAccounts] = useState<string[]>([]);
|
||||
const [solanaPublicKeys, setSolanaPublicKeys] = useState<Record<string, PublicKey>>();
|
||||
const [solanaPublicKeys, setSolanaPublicKeys] =
|
||||
useState<Record<string, PublicKey>>();
|
||||
const [chains, setChains] = useState<string[]>([]);
|
||||
|
||||
const reset = () => {
|
||||
@ -73,12 +78,12 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
setIsFetchingBalances(true);
|
||||
try {
|
||||
const arr = await Promise.all(
|
||||
_accounts.map(async account => {
|
||||
_accounts.map(async (account) => {
|
||||
const [namespace, reference, address] = account.split(":");
|
||||
const chainId = `${namespace}:${reference}`;
|
||||
const assets = await apiGetAccountBalance(address, chainId);
|
||||
return { account, assets: [assets] };
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const balances: AccountBalances = {};
|
||||
@ -93,28 +98,34 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
}
|
||||
};
|
||||
|
||||
const onSessionConnected = useCallback(async (_session: SessionTypes.Struct) => {
|
||||
const allNamespaceAccounts = Object.values(_session.namespaces)
|
||||
.map(namespace => namespace.accounts)
|
||||
.flat();
|
||||
const allNamespaceChains = Object.keys(_session.namespaces);
|
||||
const onSessionConnected = useCallback(
|
||||
async (_session: SessionTypes.Struct) => {
|
||||
const allNamespaceAccounts = Object.values(_session.namespaces)
|
||||
.map((namespace) => namespace.accounts)
|
||||
.flat();
|
||||
const allNamespaceChains = Object.keys(_session.namespaces);
|
||||
|
||||
setSession(_session);
|
||||
setChains(allNamespaceChains);
|
||||
setAccounts(allNamespaceAccounts);
|
||||
setSolanaPublicKeys(getPublicKeysFromAccounts(allNamespaceAccounts));
|
||||
await getAccountBalances(allNamespaceAccounts);
|
||||
}, []);
|
||||
setSession(_session);
|
||||
setChains(allNamespaceChains);
|
||||
setAccounts(allNamespaceAccounts);
|
||||
setSolanaPublicKeys(getPublicKeysFromAccounts(allNamespaceAccounts));
|
||||
await getAccountBalances(allNamespaceAccounts);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const connect = useCallback(
|
||||
async pairing => {
|
||||
async (pairing: any) => {
|
||||
if (typeof client === "undefined") {
|
||||
throw new Error("WalletConnect is not initialized");
|
||||
}
|
||||
console.log("connect, pairing topic is:", pairing?.topic);
|
||||
try {
|
||||
const requiredNamespaces = getRequiredNamespaces(chains);
|
||||
console.log("requiredNamespaces config for connect:", requiredNamespaces);
|
||||
console.log(
|
||||
"requiredNamespaces config for connect:",
|
||||
requiredNamespaces
|
||||
);
|
||||
|
||||
const { uri, approval } = await client.connect({
|
||||
pairingTopic: pairing?.topic,
|
||||
@ -141,7 +152,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
QRCodeModal.close();
|
||||
}
|
||||
},
|
||||
[chains, client, onSessionConnected],
|
||||
[chains, client, onSessionConnected]
|
||||
);
|
||||
|
||||
const disconnect = useCallback(async () => {
|
||||
@ -165,11 +176,11 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
throw new Error("WalletConnect is not initialized");
|
||||
}
|
||||
|
||||
_client.on("session_ping", args => {
|
||||
_client.on("session_ping", (args) => {
|
||||
console.log("EVENT", "session_ping", args);
|
||||
});
|
||||
|
||||
_client.on("session_event", args => {
|
||||
_client.on("session_event", (args) => {
|
||||
console.log("EVENT", "session_event", args);
|
||||
});
|
||||
|
||||
@ -186,7 +197,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
reset();
|
||||
});
|
||||
},
|
||||
[onSessionConnected],
|
||||
[onSessionConnected]
|
||||
);
|
||||
|
||||
const _checkPersistedState = useCallback(
|
||||
@ -196,19 +207,24 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
}
|
||||
// populates existing pairings to state
|
||||
setPairings(_client.pairing.getAll({ active: true }));
|
||||
console.log("RESTORED PAIRINGS: ", _client.pairing.getAll({ active: true }));
|
||||
console.log(
|
||||
"RESTORED PAIRINGS: ",
|
||||
_client.pairing.getAll({ active: true })
|
||||
);
|
||||
|
||||
if (typeof session !== "undefined") return;
|
||||
// populates (the last) existing session to state
|
||||
if (_client.session.length) {
|
||||
const lastKeyIndex = _client.session.keys.length - 1;
|
||||
const _session = _client.session.get(_client.session.keys[lastKeyIndex]);
|
||||
const _session = _client.session.get(
|
||||
_client.session.keys[lastKeyIndex]
|
||||
);
|
||||
console.log("RESTORED SESSION:", _session);
|
||||
await onSessionConnected(_session);
|
||||
return _session;
|
||||
}
|
||||
},
|
||||
[session, onSessionConnected],
|
||||
[session, onSessionConnected]
|
||||
);
|
||||
|
||||
const createClient = useCallback(async () => {
|
||||
@ -267,7 +283,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
connect,
|
||||
disconnect,
|
||||
setChains,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -284,7 +300,9 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
|
||||
export function useWalletConnectClient() {
|
||||
const context = useContext(ClientContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useWalletConnectClient must be used within a ClientContextProvider");
|
||||
throw new Error(
|
||||
"useWalletConnectClient must be used within a ClientContextProvider"
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
@ -19,7 +19,11 @@ import {
|
||||
Transaction as SolanaTransaction,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { eip712, formatTestTransaction, getLocalStorageTestnetFlag } from "../helpers";
|
||||
import {
|
||||
eip712,
|
||||
formatTestTransaction,
|
||||
getLocalStorageTestnetFlag,
|
||||
} from "../helpers";
|
||||
import { useWalletConnectClient } from "./ClientContext";
|
||||
import {
|
||||
DEFAULT_COSMOS_METHODS,
|
||||
@ -71,17 +75,27 @@ export const JsonRpcContext = createContext<IContext>({} as IContext);
|
||||
/**
|
||||
* Provider
|
||||
*/
|
||||
export function JsonRpcContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||
export function JsonRpcContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode | ReactNode[];
|
||||
}) {
|
||||
const [pending, setPending] = useState(false);
|
||||
const [result, setResult] = useState<IFormattedRpcResponse | null>();
|
||||
const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag());
|
||||
|
||||
const { client, session, accounts, balances, solanaPublicKeys } = useWalletConnectClient();
|
||||
const { client, session, accounts, balances, solanaPublicKeys } =
|
||||
useWalletConnectClient();
|
||||
|
||||
const { chainData } = useChainData();
|
||||
|
||||
const _createJsonRpcRequestHandler =
|
||||
(rpcRequest: (chainId: string, address: string) => Promise<IFormattedRpcResponse>) =>
|
||||
(
|
||||
rpcRequest: (
|
||||
chainId: string,
|
||||
address: string
|
||||
) => Promise<IFormattedRpcResponse>
|
||||
) =>
|
||||
async (chainId: string, address: string) => {
|
||||
if (typeof client === "undefined") {
|
||||
throw new Error("WalletConnect is not initialized");
|
||||
@ -106,8 +120,13 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
}
|
||||
};
|
||||
|
||||
const _verifyEip155MessageSignature = (message: string, signature: string, address: string) =>
|
||||
utils.verifyMessage(message, signature).toLowerCase() === address.toLowerCase();
|
||||
const _verifyEip155MessageSignature = (
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string
|
||||
) =>
|
||||
utils.verifyMessage(message, signature).toLowerCase() ===
|
||||
address.toLowerCase();
|
||||
|
||||
const ping = async () => {
|
||||
if (typeof client === "undefined") {
|
||||
@ -146,65 +165,77 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
// -------- ETHEREUM/EIP155 RPC METHODS --------
|
||||
|
||||
const ethereumRpc = {
|
||||
testSendTransaction: _createJsonRpcRequestHandler(async (chainId: string, address: string) => {
|
||||
const caipAccountAddress = `${chainId}:${address}`;
|
||||
const account = accounts.find(account => account === caipAccountAddress);
|
||||
if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`);
|
||||
testSendTransaction: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
const caipAccountAddress = `${chainId}:${address}`;
|
||||
const account = accounts.find(
|
||||
(account) => account === caipAccountAddress
|
||||
);
|
||||
if (account === undefined)
|
||||
throw new Error(`Account for ${caipAccountAddress} not found`);
|
||||
|
||||
const tx = await formatTestTransaction(account);
|
||||
const tx = await formatTestTransaction(account);
|
||||
|
||||
const balance = BigNumber.from(balances[account][0].balance || "0");
|
||||
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
|
||||
const balance = BigNumber.from(balances[account][0].balance || "0");
|
||||
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
address,
|
||||
valid: false,
|
||||
result: "Insufficient funds for intrinsic transaction cost",
|
||||
};
|
||||
}
|
||||
|
||||
const result = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
params: [tx],
|
||||
},
|
||||
});
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
address,
|
||||
valid: false,
|
||||
result: "Insufficient funds for intrinsic transaction cost",
|
||||
valid: true,
|
||||
result,
|
||||
};
|
||||
}
|
||||
),
|
||||
testSignTransaction: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
const caipAccountAddress = `${chainId}:${address}`;
|
||||
const account = accounts.find(
|
||||
(account) => account === caipAccountAddress
|
||||
);
|
||||
if (account === undefined)
|
||||
throw new Error(`Account for ${caipAccountAddress} not found`);
|
||||
|
||||
const result = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
params: [tx],
|
||||
},
|
||||
});
|
||||
const tx = await formatTestTransaction(account);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
address,
|
||||
valid: true,
|
||||
result,
|
||||
};
|
||||
}),
|
||||
testSignTransaction: _createJsonRpcRequestHandler(async (chainId: string, address: string) => {
|
||||
const caipAccountAddress = `${chainId}:${address}`;
|
||||
const account = accounts.find(account => account === caipAccountAddress);
|
||||
if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`);
|
||||
const signedTx = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
|
||||
params: [tx],
|
||||
},
|
||||
});
|
||||
|
||||
const tx = await formatTestTransaction(account);
|
||||
const valid = EthTransaction.fromSerializedTx(
|
||||
signedTx as any
|
||||
).verifySignature();
|
||||
|
||||
const signedTx = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
|
||||
params: [tx],
|
||||
},
|
||||
});
|
||||
|
||||
const valid = EthTransaction.fromSerializedTx(signedTx as any).verifySignature();
|
||||
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
|
||||
address,
|
||||
valid,
|
||||
result: signedTx,
|
||||
};
|
||||
}),
|
||||
address,
|
||||
valid,
|
||||
result: signedTx,
|
||||
};
|
||||
}
|
||||
),
|
||||
testSignPersonalMessage: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
// test message
|
||||
@ -235,7 +266,11 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
}
|
||||
|
||||
const valid = _verifyEip155MessageSignature(message, signature, address);
|
||||
const valid = _verifyEip155MessageSignature(
|
||||
message,
|
||||
signature,
|
||||
address
|
||||
);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
@ -244,201 +279,233 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
valid,
|
||||
result: signature,
|
||||
};
|
||||
},
|
||||
),
|
||||
testEthSign: _createJsonRpcRequestHandler(async (chainId: string, address: string) => {
|
||||
// test message
|
||||
const message = `My email is john@doe.com - ${Date.now()}`;
|
||||
// encode message (hex)
|
||||
const hexMsg = encoding.utf8ToHex(message, true);
|
||||
// eth_sign params
|
||||
const params = [address, hexMsg];
|
||||
|
||||
// send message
|
||||
const signature = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_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}`);
|
||||
}
|
||||
),
|
||||
testEthSign: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
// test message
|
||||
const message = `My email is john@doe.com - ${Date.now()}`;
|
||||
// encode message (hex)
|
||||
const hexMsg = encoding.utf8ToHex(message, true);
|
||||
// eth_sign params
|
||||
const params = [address, hexMsg];
|
||||
|
||||
const valid = _verifyEip155MessageSignature(message, signature, address);
|
||||
// send message
|
||||
const signature = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN + " (standard)",
|
||||
address,
|
||||
valid,
|
||||
result: signature,
|
||||
};
|
||||
}),
|
||||
testSignTypedData: _createJsonRpcRequestHandler(async (chainId: string, address: string) => {
|
||||
const message = JSON.stringify(eip712.example);
|
||||
// split chainId
|
||||
const [namespace, reference] = chainId.split(":");
|
||||
|
||||
// eth_signTypedData params
|
||||
const params = [address, message];
|
||||
const targetChainData = chainData[namespace][reference];
|
||||
|
||||
// send message
|
||||
const signature = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
if (typeof targetChainData === "undefined") {
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
}
|
||||
|
||||
const valid = _verifyEip155MessageSignature(
|
||||
message,
|
||||
signature,
|
||||
address
|
||||
);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN + " (standard)",
|
||||
address,
|
||||
valid,
|
||||
result: signature,
|
||||
};
|
||||
}
|
||||
),
|
||||
testSignTypedData: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
const message = JSON.stringify(eip712.example);
|
||||
|
||||
// eth_signTypedData params
|
||||
const params = [address, message];
|
||||
|
||||
// send message
|
||||
const signature = await client!.request<string>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
// Separate `EIP712Domain` type from remaining types to verify, otherwise `ethers.utils.verifyTypedData`
|
||||
// will throw due to "unused" `EIP712Domain` type.
|
||||
// See: https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
|
||||
const {
|
||||
EIP712Domain,
|
||||
...nonDomainTypes
|
||||
}: Record<string, TypedDataField[]> = eip712.example.types;
|
||||
|
||||
const valid =
|
||||
utils
|
||||
.verifyTypedData(
|
||||
eip712.example.domain,
|
||||
nonDomainTypes,
|
||||
eip712.example.message,
|
||||
signature
|
||||
)
|
||||
.toLowerCase() === address.toLowerCase();
|
||||
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
// Separate `EIP712Domain` type from remaining types to verify, otherwise `ethers.utils.verifyTypedData`
|
||||
// will throw due to "unused" `EIP712Domain` type.
|
||||
// See: https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
|
||||
const { EIP712Domain, ...nonDomainTypes }: Record<string, TypedDataField[]> =
|
||||
eip712.example.types;
|
||||
|
||||
const valid =
|
||||
utils
|
||||
.verifyTypedData(eip712.example.domain, nonDomainTypes, eip712.example.message, signature)
|
||||
.toLowerCase() === address.toLowerCase();
|
||||
|
||||
return {
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA,
|
||||
address,
|
||||
valid,
|
||||
result: signature,
|
||||
};
|
||||
}),
|
||||
address,
|
||||
valid,
|
||||
result: signature,
|
||||
};
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
// -------- COSMOS RPC METHODS --------
|
||||
|
||||
const cosmosRpc = {
|
||||
testSignDirect: _createJsonRpcRequestHandler(async (chainId: string, address: 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",
|
||||
};
|
||||
testSignDirect: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: 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(":");
|
||||
// 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,
|
||||
);
|
||||
// format sign doc
|
||||
const signDoc = formatDirectSignDoc(
|
||||
inputs.fee,
|
||||
inputs.pubkey,
|
||||
inputs.gasLimit,
|
||||
inputs.accountNumber,
|
||||
inputs.sequence,
|
||||
inputs.bodyBytes,
|
||||
reference
|
||||
);
|
||||
|
||||
// cosmos_signDirect params
|
||||
const params = {
|
||||
signerAddress: address,
|
||||
signDoc: stringifySignDocValues(signDoc),
|
||||
};
|
||||
// cosmos_signDirect params
|
||||
const params = {
|
||||
signerAddress: address,
|
||||
signDoc: stringifySignDocValues(signDoc),
|
||||
};
|
||||
|
||||
// send message
|
||||
const result = await client!.request<{ signature: string }>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
// send message
|
||||
const result = await client!.request<{ signature: string }>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
const targetChainData = chainData[namespace][reference];
|
||||
|
||||
if (typeof targetChainData === "undefined") {
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
}
|
||||
|
||||
const valid = await verifyDirectSignature(
|
||||
address,
|
||||
result.signature,
|
||||
signDoc
|
||||
);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
const targetChainData = chainData[namespace][reference];
|
||||
|
||||
if (typeof targetChainData === "undefined") {
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
address,
|
||||
valid,
|
||||
result: result.signature,
|
||||
};
|
||||
}
|
||||
),
|
||||
testSignAmino: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string) => {
|
||||
// split chainId
|
||||
const [namespace, reference] = chainId.split(":");
|
||||
|
||||
const valid = await verifyDirectSignature(address, result.signature, signDoc);
|
||||
// test amino sign doc
|
||||
const signDoc = {
|
||||
msgs: [],
|
||||
fee: { amount: [], gas: "23" },
|
||||
chain_id: "foochain",
|
||||
memo: "hello, world",
|
||||
account_number: "7",
|
||||
sequence: "54",
|
||||
};
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT,
|
||||
address,
|
||||
valid,
|
||||
result: result.signature,
|
||||
};
|
||||
}),
|
||||
testSignAmino: _createJsonRpcRequestHandler(async (chainId: string, address: string) => {
|
||||
// split chainId
|
||||
const [namespace, reference] = chainId.split(":");
|
||||
// cosmos_signAmino params
|
||||
const params = { signerAddress: address, signDoc };
|
||||
|
||||
// test amino sign doc
|
||||
const signDoc = {
|
||||
msgs: [],
|
||||
fee: { amount: [], gas: "23" },
|
||||
chain_id: "foochain",
|
||||
memo: "hello, world",
|
||||
account_number: "7",
|
||||
sequence: "54",
|
||||
};
|
||||
// send message
|
||||
const result = await client!.request<{ signature: string }>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
// cosmos_signAmino params
|
||||
const params = { signerAddress: address, signDoc };
|
||||
const targetChainData = chainData[namespace][reference];
|
||||
|
||||
// send message
|
||||
const result = await client!.request<{ signature: string }>({
|
||||
topic: session!.topic,
|
||||
chainId,
|
||||
request: {
|
||||
if (typeof targetChainData === "undefined") {
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
}
|
||||
|
||||
const valid = await verifyAminoSignature(
|
||||
address,
|
||||
result.signature,
|
||||
signDoc
|
||||
);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
const targetChainData = chainData[namespace][reference];
|
||||
|
||||
if (typeof targetChainData === "undefined") {
|
||||
throw new Error(`Missing chain data for chainId: ${chainId}`);
|
||||
address,
|
||||
valid,
|
||||
result: result.signature,
|
||||
};
|
||||
}
|
||||
|
||||
const valid = await verifyAminoSignature(address, result.signature, signDoc);
|
||||
|
||||
// format displayed result
|
||||
return {
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
|
||||
address,
|
||||
valid,
|
||||
result: result.signature,
|
||||
};
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
// -------- SOLANA RPC METHODS --------
|
||||
|
||||
const solanaRpc = {
|
||||
testSignTransaction: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string): Promise<IFormattedRpcResponse> => {
|
||||
async (
|
||||
chainId: string,
|
||||
address: string
|
||||
): Promise<IFormattedRpcResponse> => {
|
||||
if (!solanaPublicKeys) {
|
||||
throw new Error("Could not find Solana PublicKeys.");
|
||||
}
|
||||
|
||||
const senderPublicKey = solanaPublicKeys[address];
|
||||
|
||||
const connection = new Connection(clusterApiUrl(isTestnet ? "testnet" : "mainnet-beta"));
|
||||
const connection = new Connection(
|
||||
clusterApiUrl(isTestnet ? "testnet" : "mainnet-beta")
|
||||
);
|
||||
|
||||
// Using deprecated `getRecentBlockhash` over `getLatestBlockhash` here, since `mainnet-beta`
|
||||
// cluster only seems to support `connection.getRecentBlockhash` currently.
|
||||
@ -452,7 +519,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
fromPubkey: senderPublicKey,
|
||||
toPubkey: Keypair.generate().publicKey,
|
||||
lamports: 1,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
@ -464,10 +531,10 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
params: {
|
||||
feePayer: transaction.feePayer!.toBase58(),
|
||||
recentBlockhash: transaction.recentBlockhash,
|
||||
instructions: transaction.instructions.map(i => ({
|
||||
instructions: transaction.instructions.map((i) => ({
|
||||
programId: i.programId.toBase58(),
|
||||
data: bs58.encode(i.data),
|
||||
keys: i.keys.map(k => ({
|
||||
keys: i.keys.map((k) => ({
|
||||
isSigner: k.isSigner,
|
||||
isWritable: k.isWritable,
|
||||
pubkey: k.pubkey.toBase58(),
|
||||
@ -479,7 +546,10 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
|
||||
// We only need `Buffer.from` here to satisfy the `Buffer` param type for `addSignature`.
|
||||
// The resulting `UInt8Array` is equivalent to just `bs58.decode(...)`.
|
||||
transaction.addSignature(senderPublicKey, Buffer.from(bs58.decode(result.signature)));
|
||||
transaction.addSignature(
|
||||
senderPublicKey,
|
||||
Buffer.from(bs58.decode(result.signature))
|
||||
);
|
||||
|
||||
const valid = transaction.verifySignatures();
|
||||
|
||||
@ -492,10 +562,13 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
testSignMessage: _createJsonRpcRequestHandler(
|
||||
async (chainId: string, address: string): Promise<IFormattedRpcResponse> => {
|
||||
async (
|
||||
chainId: string,
|
||||
address: string
|
||||
): Promise<IFormattedRpcResponse> => {
|
||||
if (!solanaPublicKeys) {
|
||||
throw new Error("Could not find Solana PublicKeys.");
|
||||
}
|
||||
@ -504,7 +577,9 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
|
||||
// Encode message to `UInt8Array` first via `TextEncoder` so we can pass it to `bs58.encode`.
|
||||
const message = bs58.encode(
|
||||
new TextEncoder().encode(`This is an example message to be signed - ${Date.now()}`),
|
||||
new TextEncoder().encode(
|
||||
`This is an example message to be signed - ${Date.now()}`
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
@ -523,7 +598,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
const valid = verifyMessageSignature(
|
||||
senderPublicKey.toBase58(),
|
||||
result.signature,
|
||||
message,
|
||||
message
|
||||
);
|
||||
|
||||
return {
|
||||
@ -535,7 +610,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -101,7 +101,10 @@ const api: AxiosInstance = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
export async function apiGetAccountBalance(address: string, chainId: string): Promise<AssetData> {
|
||||
export async function apiGetAccountBalance(
|
||||
address: string,
|
||||
chainId: string
|
||||
): Promise<AssetData> {
|
||||
const ethChainId = chainId.split(":")[1];
|
||||
const rpc = rpcProvidersByChainId[Number(ethChainId)];
|
||||
if (!rpc) {
|
||||
@ -119,7 +122,10 @@ export async function apiGetAccountBalance(address: string, chainId: string): Pr
|
||||
return { balance, ...token };
|
||||
}
|
||||
|
||||
export const apiGetAccountNonce = async (address: string, chainId: string): Promise<number> => {
|
||||
export const apiGetAccountNonce = async (
|
||||
address: string,
|
||||
chainId: string
|
||||
): Promise<number> => {
|
||||
const ethChainId = chainId.split(":")[1];
|
||||
const { baseURL } = rpcProvidersByChainId[Number(ethChainId)];
|
||||
const response = await api.post(baseURL, {
|
||||
|
@ -35,13 +35,13 @@ async function isValidSignature(
|
||||
data: string,
|
||||
provider: providers.Provider,
|
||||
abi = eip1271.spec.abi,
|
||||
magicValue = eip1271.spec.magicValue,
|
||||
magicValue = eip1271.spec.magicValue
|
||||
): Promise<boolean> {
|
||||
let returnValue;
|
||||
try {
|
||||
returnValue = await new Contract(address, abi, provider).isValidSignature(
|
||||
utils.arrayify(data),
|
||||
sig,
|
||||
sig
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
|
||||
export const getNamespacesFromChains = (chains: string[]) => {
|
||||
const supportedNamespaces: string[] = [];
|
||||
chains.forEach(chainId => {
|
||||
chains.forEach((chainId) => {
|
||||
const [namespace] = chainId.split(":");
|
||||
if (!supportedNamespaces.includes(namespace)) {
|
||||
supportedNamespaces.push(namespace);
|
||||
@ -46,18 +46,20 @@ export const getSupportedEventsByNamespace = (namespace: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getRequiredNamespaces = (chains: string[]): ProposalTypes.RequiredNamespaces => {
|
||||
export const getRequiredNamespaces = (
|
||||
chains: string[]
|
||||
): ProposalTypes.RequiredNamespaces => {
|
||||
const selectedNamespaces = getNamespacesFromChains(chains);
|
||||
console.log("selected namespaces:", selectedNamespaces);
|
||||
|
||||
return Object.fromEntries(
|
||||
selectedNamespaces.map(namespace => [
|
||||
selectedNamespaces.map((namespace) => [
|
||||
namespace,
|
||||
{
|
||||
methods: getSupportedMethodsByNamespace(namespace),
|
||||
chains: chains.filter(chain => chain.startsWith(namespace)),
|
||||
chains: chains.filter((chain) => chain.startsWith(namespace)),
|
||||
events: getSupportedEventsByNamespace(namespace) as any[],
|
||||
},
|
||||
]),
|
||||
])
|
||||
);
|
||||
};
|
||||
|
@ -4,12 +4,14 @@ export function getPublicKeysFromAccounts(accounts: string[]) {
|
||||
return (
|
||||
accounts
|
||||
// Filter out any non-solana accounts.
|
||||
.filter(account => account.startsWith("solana:"))
|
||||
.filter((account) => account.startsWith("solana:"))
|
||||
// Create a map of Solana address -> publicKey.
|
||||
.reduce((map: Record<string, PublicKey>, account) => {
|
||||
const address = account.split(":").pop();
|
||||
if (!address) {
|
||||
throw new Error(`Could not derive Solana address from CAIP account: ${account}`);
|
||||
throw new Error(
|
||||
`Could not derive Solana address from CAIP account: ${account}`
|
||||
);
|
||||
}
|
||||
map[address] = new PublicKey(address);
|
||||
return map;
|
||||
|
@ -10,7 +10,9 @@ export async function formatTestTransaction(account: string) {
|
||||
try {
|
||||
_nonce = await apiGetAccountNonce(address, chainId);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch nonce for address ${address} on chain ${chainId}`);
|
||||
throw new Error(
|
||||
`Failed to fetch nonce for address ${address} on chain ${chainId}`
|
||||
);
|
||||
}
|
||||
|
||||
const nonce = encoding.sanitizeHex(encoding.numberToHex(_nonce));
|
||||
@ -27,7 +29,15 @@ export async function formatTestTransaction(account: string) {
|
||||
const _value = 0;
|
||||
const value = encoding.sanitizeHex(encoding.numberToHex(_value));
|
||||
|
||||
const tx = { from: address, to: address, data: "0x", nonce, gasPrice, gasLimit, value };
|
||||
const tx = {
|
||||
from: address,
|
||||
to: address,
|
||||
data: "0x",
|
||||
nonce,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
value,
|
||||
};
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import { DEFAULT_CHAINS } from "../constants";
|
||||
export function capitalize(string: string): string {
|
||||
return string
|
||||
.split(" ")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export function ellipseText(text = "", maxLength = 9999): string {
|
||||
const result =
|
||||
text
|
||||
.split(" ")
|
||||
.filter(word => {
|
||||
.filter((word) => {
|
||||
currentLength += word.length;
|
||||
if (ellipse || currentLength >= _maxLength) {
|
||||
ellipse = true;
|
||||
@ -64,10 +64,10 @@ export function isMobile(): boolean {
|
||||
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,
|
||||
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),
|
||||
navigator.userAgent.substr(0, 4)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
@ -85,7 +85,10 @@ export function isMobile(): boolean {
|
||||
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"),
|
||||
Buffer.from(
|
||||
"\u0019Ethereum Signed Message:\n" + data.length.toString(),
|
||||
"utf8"
|
||||
),
|
||||
data,
|
||||
]);
|
||||
return ethUtil.bufferToHex(buf);
|
||||
@ -103,7 +106,11 @@ export function encodeTypedDataMessage(msg: string): string {
|
||||
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),
|
||||
TypedDataUtils.hashStruct(
|
||||
data.primaryType as string,
|
||||
data.message,
|
||||
data.types
|
||||
),
|
||||
]);
|
||||
return ethUtil.bufferToHex(buf);
|
||||
}
|
||||
@ -117,7 +124,12 @@ export function hashTypedDataMessage(msg: string): string {
|
||||
|
||||
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 result = ethUtil.ecrecover(
|
||||
ethUtil.toBuffer(hash),
|
||||
params.v,
|
||||
params.r,
|
||||
params.s
|
||||
);
|
||||
const signer = ethUtil.bufferToHex(ethUtil.publicToAddress(result));
|
||||
return signer;
|
||||
}
|
||||
@ -138,11 +150,16 @@ export async function verifySignature(
|
||||
address: string,
|
||||
sig: string,
|
||||
hash: string,
|
||||
rpcUrl: 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") {
|
||||
if (
|
||||
!bytecode ||
|
||||
bytecode === "0x" ||
|
||||
bytecode === "0x0" ||
|
||||
bytecode === "0x00"
|
||||
) {
|
||||
const signer = recoverAddress(sig, hash);
|
||||
return signer.toLowerCase() === address.toLowerCase();
|
||||
} else {
|
||||
@ -186,10 +203,13 @@ 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}`);
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(LOCALSTORAGE_KEY_TESTNET, `${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalStorageTestnetFlag(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
let value = INITIAL_STATE_TESTNET_DEFAULT;
|
||||
const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET);
|
||||
if (!persisted) {
|
||||
@ -202,7 +222,7 @@ export function getLocalStorageTestnetFlag(): boolean {
|
||||
|
||||
export const getAllChainNamespaces = () => {
|
||||
const namespaces: string[] = [];
|
||||
DEFAULT_CHAINS.forEach(chainId => {
|
||||
DEFAULT_CHAINS.forEach((chainId) => {
|
||||
const [namespace] = chainId.split(":");
|
||||
if (!namespaces.includes(namespace)) {
|
||||
namespaces.push(namespace);
|
||||
|
@ -1,34 +0,0 @@
|
||||
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 { ChainDataContextProvider } from "./contexts/ChainDataContext";
|
||||
|
||||
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 />
|
||||
<ChainDataContextProvider>
|
||||
<ClientContextProvider>
|
||||
<JsonRpcContextProvider>
|
||||
<App />
|
||||
</JsonRpcContextProvider>
|
||||
</ClientContextProvider>
|
||||
</ChainDataContextProvider>
|
||||
</>,
|
||||
document.getElementById("root"),
|
||||
);
|
@ -19,7 +19,7 @@ const PairingModal = (props: PairingModalProps) => {
|
||||
<SModalContainer>
|
||||
<SModalTitle>{"Select available pairing or create new one"}</SModalTitle>
|
||||
<STable>
|
||||
{pairings.map(pairing => (
|
||||
{pairings.map((pairing) => (
|
||||
<Pairing
|
||||
key={pairing.topic}
|
||||
pairing={pairing}
|
||||
|
@ -19,16 +19,20 @@ const RequestModal = (props: RequestModalProps) => {
|
||||
<SModalTitle>{"Pending JSON-RPC Request"}</SModalTitle>
|
||||
<SContainer>
|
||||
<Loader />
|
||||
<SModalParagraph>{"Approve or reject request using your wallet"}</SModalParagraph>
|
||||
<SModalParagraph>
|
||||
{"Approve or reject request using your wallet"}
|
||||
</SModalParagraph>
|
||||
</SContainer>
|
||||
</SModalContainer>
|
||||
) : result ? (
|
||||
<SModalContainer>
|
||||
<SModalTitle>
|
||||
{result.valid ? "JSON-RPC Request Approved" : "JSON-RPC Request Failed"}
|
||||
{result.valid
|
||||
? "JSON-RPC Request Approved"
|
||||
: "JSON-RPC Request Failed"}
|
||||
</SModalTitle>
|
||||
<STable>
|
||||
{Object.keys(result).map(key => (
|
||||
{Object.keys(result).map((key) => (
|
||||
<SRow key={key}>
|
||||
<SKey>{key}</SKey>
|
||||
<SValue>{result[key].toString()}</SValue>
|
||||
|
3
dapps/react-dapp-v2/src/pages/404.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function FourOhFour() {
|
||||
return <h1>404 Page Not Found</h1>;
|
||||
}
|
29
dapps/react-dapp-v2/src/pages/_app.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import "../styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
|
||||
import { ClientContextProvider } from "../contexts/ClientContext";
|
||||
import { JsonRpcContextProvider } from "../contexts/JsonRpcContext";
|
||||
import { ChainDataContextProvider } from "../contexts/ChainDataContext";
|
||||
|
||||
import { globalStyle } from "../styles";
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
${globalStyle}
|
||||
`;
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<ChainDataContextProvider>
|
||||
<ClientContextProvider>
|
||||
<JsonRpcContextProvider>
|
||||
<Component {...pageProps} />
|
||||
</JsonRpcContextProvider>
|
||||
</ClientContextProvider>
|
||||
</ChainDataContextProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
3
dapps/react-dapp-v2/src/pages/_error.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function Error() {
|
||||
return <div>An error as occured</div>;
|
||||
}
|
@ -1,23 +1,23 @@
|
||||
import type { NextPage } from "next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { version } from "@walletconnect/sign-client/package.json";
|
||||
|
||||
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 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_COSMOS_METHODS,
|
||||
DEFAULT_EIP155_METHODS,
|
||||
DEFAULT_MAIN_CHAINS,
|
||||
DEFAULT_SOLANA_METHODS,
|
||||
DEFAULT_TEST_CHAINS,
|
||||
} from "./constants";
|
||||
import { AccountAction, setLocaleStorageTestnetFlag } from "./helpers";
|
||||
import Toggle from "./components/Toggle";
|
||||
import RequestModal from "./modals/RequestModal";
|
||||
import PairingModal from "./modals/PairingModal";
|
||||
import PingModal from "./modals/PingModal";
|
||||
} from "../constants";
|
||||
import { AccountAction, setLocaleStorageTestnetFlag } from "../helpers";
|
||||
import Toggle from "../components/Toggle";
|
||||
import RequestModal from "../modals/RequestModal";
|
||||
import PairingModal from "../modals/PairingModal";
|
||||
import PingModal from "../modals/PingModal";
|
||||
import {
|
||||
SAccounts,
|
||||
SAccountsContainer,
|
||||
@ -27,12 +27,15 @@ import {
|
||||
SLanding,
|
||||
SLayout,
|
||||
SToggleContainer,
|
||||
} from "./components/app";
|
||||
import { useWalletConnectClient } from "./contexts/ClientContext";
|
||||
import { useJsonRpc } from "./contexts/JsonRpcContext";
|
||||
import { useChainData } from "./contexts/ChainDataContext";
|
||||
} from "../components/app";
|
||||
import { useWalletConnectClient } from "../contexts/ClientContext";
|
||||
import { useJsonRpc } from "../contexts/JsonRpcContext";
|
||||
import { useChainData } from "../contexts/ChainDataContext";
|
||||
|
||||
export default function App() {
|
||||
// Normal import does not work here
|
||||
const { version } = require("@walletconnect/sign-client/package.json");
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const [modal, setModal] = useState("");
|
||||
|
||||
const closeModal = () => setModal("");
|
||||
@ -117,11 +120,26 @@ export default function App() {
|
||||
};
|
||||
|
||||
return [
|
||||
{ method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, callback: onSendTransaction },
|
||||
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION, callback: onSignTransaction },
|
||||
{ method: DEFAULT_EIP155_METHODS.PERSONAL_SIGN, callback: onSignPersonalMessage },
|
||||
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN + " (standard)", callback: onEthSign },
|
||||
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA, callback: onSignTypedData },
|
||||
{
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
|
||||
callback: onSendTransaction,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
|
||||
callback: onSignTransaction,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_EIP155_METHODS.PERSONAL_SIGN,
|
||||
callback: onSignPersonalMessage,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN + " (standard)",
|
||||
callback: onEthSign,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA,
|
||||
callback: onSignTypedData,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -135,8 +153,14 @@ export default function App() {
|
||||
await cosmosRpc.testSignAmino(chainId, address);
|
||||
};
|
||||
return [
|
||||
{ method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT, callback: onSignDirect },
|
||||
{ method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO, callback: onSignAmino },
|
||||
{
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT,
|
||||
callback: onSignDirect,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
|
||||
callback: onSignAmino,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -150,8 +174,14 @@ export default function App() {
|
||||
await solanaRpc.testSignMessage(chainId, address);
|
||||
};
|
||||
return [
|
||||
{ method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION, callback: onSignTransaction },
|
||||
{ method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE, callback: onSignMessage },
|
||||
{
|
||||
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_TRANSACTION,
|
||||
callback: onSignTransaction,
|
||||
},
|
||||
{
|
||||
method: DEFAULT_SOLANA_METHODS.SOL_SIGN_MESSAGE,
|
||||
callback: onSignMessage,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -178,7 +208,7 @@ export default function App() {
|
||||
|
||||
const handleChainSelectionClick = (chainId: string) => {
|
||||
if (chains.includes(chainId)) {
|
||||
setChains(chains.filter(chain => chain !== chainId));
|
||||
setChains(chains.filter((chain) => chain !== chainId));
|
||||
} else {
|
||||
setChains([...chains, chainId]);
|
||||
}
|
||||
@ -193,7 +223,9 @@ export default function App() {
|
||||
}
|
||||
return <PairingModal pairings={pairings} connect={connect} />;
|
||||
case "request":
|
||||
return <RequestModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||
return (
|
||||
<RequestModal pending={isRpcRequestPending} result={rpcResult} />
|
||||
);
|
||||
case "ping":
|
||||
return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||
default:
|
||||
@ -213,7 +245,7 @@ export default function App() {
|
||||
<p>Testnets Only?</p>
|
||||
<Toggle active={isTestnet} onClick={toggleTestnets} />
|
||||
</SToggleContainer>
|
||||
{chainOptions.map(chainId => (
|
||||
{chainOptions.map((chainId) => (
|
||||
<Blockchain
|
||||
key={chainId}
|
||||
chainId={chainId}
|
||||
@ -231,7 +263,7 @@ export default function App() {
|
||||
<SAccountsContainer>
|
||||
<h3>Accounts</h3>
|
||||
<SAccounts>
|
||||
{accounts.map(account => {
|
||||
{accounts.map((account) => {
|
||||
const [namespace, reference, address] = account.split(":");
|
||||
const chainId = `${namespace}:${reference}`;
|
||||
return (
|
||||
@ -263,4 +295,6 @@ export default function App() {
|
||||
</Modal>
|
||||
</SLayout>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Home;
|
1
dapps/react-dapp-v2/src/react-app-env.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
129
dapps/react-dapp-v2/src/styles/Home.module.css
Normal file
@ -0,0 +1,129 @@
|
||||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
26
dapps/react-dapp-v2/src/styles/globals.css
Normal file
@ -0,0 +1,26 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
@ -1,26 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|