Feat/make dapp nextjs (#41)

This commit is contained in:
Celine Sarafa 2022-08-08 12:32:24 +03:00 committed by GitHub
parent c87a77ef10
commit 31ec2ecb9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 3573 additions and 2747 deletions

View File

@ -1,3 +1,2 @@
REACT_APP_PROJECT_ID=39bc... PROJECT_ID=39bc...
REACT_APP_RELAY_URL=wss://relay.walletconnect.com RELAY_URL=wss://relay.walletconnect.com

View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

View File

@ -8,18 +8,29 @@
# testing # testing
/coverage /coverage
# next.js
/.next/
/out/
# production # production
/build /build
# misc # misc
.DS_Store .DS_Store
.env.local *.pem
.env.development.local
.env.test.local
.env.production.local
# debug
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.pnpm-debug.log*
.eslintcache # local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -1,7 +0,0 @@
{
"tabWidth": 2,
"useTabs": false,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "avoid"
}

View File

@ -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.

View File

@ -29,13 +29,13 @@ cp .env.local.example .env.local
Your `.env.local` now contains the following environment variables: Your `.env.local` now contains the following environment variables:
- `REACT_APP_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com - `PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
- `REACT_APP_RELAY_URL` (already set) - `RELAY_URL` (already set)
## Develop ## Develop
```bash ```bash
yarn start yarn dev
``` ```
## Test ## Test
@ -49,3 +49,4 @@ yarn test
```bash ```bash
yarn build yarn build
``` ```

View File

@ -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";

View File

@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
distDir: 'build'
};
module.exports = nextConfig;

View File

@ -1,31 +1,13 @@
{ {
"name": "react-dapp-v2", "name": "react-dapp-v2-next",
"version": "2.0.0-6de2113.0", "version": "2.0.0-6de2113.0",
"private": true, "private": true,
"keywords": [
"walletconnect",
"ethereum",
"web3",
"crypto"
],
"author": "WalletConnect, Inc. <walletconnect.com>",
"license": "MIT",
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "next dev",
"build": "react-scripts build", "build": "next build",
"test": "react-scripts test", "start": "next start",
"eject": "react-scripts eject", "lint": "next lint",
"prettier": "prettier --check '**/*.{js,ts,jsx,tsx}'" "prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx}'"
},
"repository": {
"type": "git",
"url": "git+https://github.com/walletconnect/walletconnect-monorepo.git"
},
"bugs": {
"url": "https://github.com/walletconnect/walletconnect-monorepo/issues"
},
"resolutions": {
"react-error-overlay": "6.0.9"
}, },
"dependencies": { "dependencies": {
"@ethereumjs/tx": "^3.5.0", "@ethereumjs/tx": "^3.5.0",
@ -43,6 +25,7 @@
"eth-sig-util": "^2.5.3", "eth-sig-util": "^2.5.3",
"ethereumjs-util": "^7.0.6", "ethereumjs-util": "^7.0.6",
"ethers": "^5.3.0", "ethers": "^5.3.0",
"next": "12.2.4",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"react": "^17.0.2", "react": "^17.0.2",
@ -50,41 +33,22 @@
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"solana-wallet": "^1.0.1", "solana-wallet": "^1.0.1",
"styled-components": "^5.2.0", "styled-components": "^5.2.0",
"typescript": "^4.3.2",
"web-vitals": "^0.2.4" "web-vitals": "^0.2.4"
}, },
"devDependencies": { "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/eth-sig-util": "^2.1.1",
"@types/jest": "^27.4.0", "@types/jest": "^27.4.0",
"@types/node": "^17.0.14", "@types/node": "^17.0.14",
"@types/pino": "^7.0.5", "@types/pino": "^7.0.5",
"@types/prop-types": "^15.7.4", "@types/prop-types": "^15.7.4",
"@types/qr-image": "^3.2.5", "@types/qr-image": "^3.2.5",
"@types/react": "^17.0.38", "@types/react": "18.0.15",
"@types/react-dom": "^17.0.11", "@types/react-dom": "18.0.6",
"@types/styled-components": "^5.1.21", "@types/styled-components": "^5.1.25",
"prettier": "^2.5.1" "better-sqlite3": "^7.6.2",
}, "eslint": "8.21.0",
"eslintConfig": { "eslint-config-next": "12.2.4",
"extends": [ "prettier": "^2.7.1",
"react-app", "typescript": "^4.7.4"
"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"
]
} }
} }

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 234 B

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -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>

View File

@ -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"
}

View File

@ -2,7 +2,11 @@ import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants"; import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers"; import {
NamespaceMetadata,
ChainMetadata,
ChainRequestRender,
} from "../helpers";
export const CosmosMetadata: NamespaceMetadata = { export const CosmosMetadata: NamespaceMetadata = {
"cosmoshub-4": { "cosmoshub-4": {
@ -20,7 +24,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
return metadata; return metadata;
} }
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] { export function getChainRequestRender(
request: JsonRpcRequest
): ChainRequestRender[] {
let params = [{ label: "Method", value: request.method }]; let params = [{ label: "Method", value: request.method }];
switch (request.method) { switch (request.method) {

View File

@ -83,7 +83,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
return metadata; return metadata;
} }
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] { export function getChainRequestRender(
request: JsonRpcRequest
): ChainRequestRender[] {
let params = [{ label: "Method", value: request.method }]; let params = [{ label: "Method", value: request.method }];
switch (request.method) { switch (request.method) {
@ -111,7 +113,9 @@ export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRend
}, },
{ {
label: "Value", 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 }, { label: "Data", value: request.params[0].data },
]; ];

View File

@ -25,7 +25,7 @@ export function getChainMetadata(chainId: string): ChainMetadata {
export function getChainRequestRender( export function getChainRequestRender(
request: JsonRpcRequest, request: JsonRpcRequest,
chainId: string, chainId: string
): ChainRequestRender[] { ): ChainRequestRender[] {
const namespace = chainId.split(":")[0]; const namespace = chainId.split(":")[0];
switch (namespace) { switch (namespace) {

View File

@ -1,12 +1,18 @@
import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils"; import { JsonRpcRequest } from "@walletconnect/jsonrpc-utils";
import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants"; import { BLOCKCHAIN_LOGO_BASE_URL } from "../constants";
import { NamespaceMetadata, ChainMetadata, ChainRequestRender } from "../helpers"; import {
NamespaceMetadata,
ChainMetadata,
ChainRequestRender,
} from "../helpers";
export const PolkadotMetadata: NamespaceMetadata = { export const PolkadotMetadata: NamespaceMetadata = {
// eslint-disable-next-line no-useless-computed-key // eslint-disable-next-line no-useless-computed-key
["91b171bb158e2d3848fa23a9f1c25182"]: { ["91b171bb158e2d3848fa23a9f1c25182"]: {
logo: BLOCKCHAIN_LOGO_BASE_URL + "polkadot:91b171bb158e2d3848fa23a9f1c25182.png", logo:
BLOCKCHAIN_LOGO_BASE_URL +
"polkadot:91b171bb158e2d3848fa23a9f1c25182.png",
rgb: "230, 1, 122", rgb: "230, 1, 122",
}, },
}; };
@ -20,7 +26,9 @@ export function getChainMetadata(chainId: string): ChainMetadata {
return metadata; return metadata;
} }
export function getChainRequestRender(request: JsonRpcRequest): ChainRequestRender[] { export function getChainRequestRender(
request: JsonRpcRequest
): ChainRequestRender[] {
let params = [{ label: "Method", value: request.method }]; let params = [{ label: "Method", value: request.method }];
switch (request.method) { switch (request.method) {

View File

@ -6,7 +6,10 @@ export const SolanaChainData: ChainsMap = {
"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": { "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": {
id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ",
name: "Solana Mainnet", 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, slip44: 501,
testnet: false, testnet: false,
}, },
@ -22,12 +25,12 @@ export const SolanaChainData: ChainsMap = {
export const SolanaMetadata: NamespaceMetadata = { export const SolanaMetadata: NamespaceMetadata = {
// Solana Mainnet // Solana Mainnet
"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": { "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ": {
logo: "/solana_logo.png", logo: "/assets/solana_logo.png",
rgb: "0, 0, 0", rgb: "0, 0, 0",
}, },
// Solana Devnet // Solana Devnet
"8E9rvCKLFQia2Y35HXjjpWzj8weVo44K": { "8E9rvCKLFQia2Y35HXjjpWzj8weVo44K": {
logo: "/solana_logo.png", logo: "/assets/solana_logo.png",
rgb: "0, 0, 0", rgb: "0, 0, 0",
}, },
}; };

View File

@ -5,8 +5,6 @@ import Icon from "./Icon";
import { AssetData, fromWad } from "../helpers"; import { AssetData, fromWad } from "../helpers";
import eth from "../assets/eth.svg";
import erc20 from "../assets/erc20.svg";
import { getChainMetadata } from "../chains"; import { getChainMetadata } from "../chains";
const xdai = getChainMetadata("eip155:100").logo; const xdai = getChainMetadata("eip155:100").logo;
@ -38,17 +36,17 @@ const SAssetBalance = styled.div`
function getAssetIcon(asset: AssetData): JSX.Element { function getAssetIcon(asset: AssetData): JSX.Element {
if (!!asset.contractAddress) { if (!!asset.contractAddress) {
const src = `https://raw.githubusercontent.com/TrustWallet/tokens/master/tokens/${asset.contractAddress.toLowerCase()}.png`; 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()) { switch (asset.symbol.toLowerCase()) {
case "eth": case "eth":
return <Icon src={eth} />; return <Icon src={"/assets/eth.svg"} />;
case "xdai": case "xdai":
return <Icon src={xdai} />; return <Icon src={xdai} />;
case "matic": case "matic":
return <Icon src={matic} />; return <Icon src={matic} />;
default: default:
return <Icon src={erc20} />; return <Icon src={"/assets/eth20.svg"} />;
} }
} }
@ -65,7 +63,9 @@ const Asset = (props: AssetProps) => {
<SAssetName>{asset.name}</SAssetName> <SAssetName>{asset.name}</SAssetName>
</SAssetLeft> </SAssetLeft>
<SAssetRight> <SAssetRight>
<SAssetBalance>{`${fromWad(asset.balance || "0")} ${asset.symbol}`}</SAssetBalance> <SAssetBalance>{`${fromWad(asset.balance || "0")} ${
asset.symbol
}`}</SAssetBalance>
</SAssetRight> </SAssetRight>
</SAsset> </SAsset>
); );

View File

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

View File

@ -99,7 +99,7 @@ interface BlockchainDisplayData {
function getBlockchainDisplayData( function getBlockchainDisplayData(
chainId: string, chainId: string,
chainData: ChainNamespaces, chainData: ChainNamespaces
): BlockchainDisplayData | undefined { ): BlockchainDisplayData | undefined {
const [namespace, reference] = chainId.split(":"); const [namespace, reference] = chainId.split(":");
let meta: ChainMetadata; let meta: ChainMetadata;
@ -114,9 +114,18 @@ function getBlockchainDisplayData(
} }
const Blockchain: FC<PropsWithChildren<BlockchainProps>> = ( 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; if (!Object.keys(chainData).length) return null;
const chain = getBlockchainDisplayData(chainId, chainData); const chain = getBlockchainDisplayData(chainId, chainData);
@ -124,9 +133,12 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
if (typeof chain === "undefined") return null; if (typeof chain === "undefined") return null;
const name = chain.meta.name || chain.data.name; 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 = const assets =
typeof account !== "undefined" && typeof balances !== "undefined" ? balances[account] : []; typeof account !== "undefined" && typeof balances !== "undefined"
? balances[account]
: [];
return ( return (
<React.Fragment> <React.Fragment>
<SAccount <SAccount
@ -152,8 +164,10 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
<SFullWidthContainer> <SFullWidthContainer>
<h6>Balances</h6> <h6>Balances</h6>
<Column center> <Column center>
{assets.map(asset => {assets.map((asset) =>
asset.symbol ? <Asset key={asset.symbol} asset={asset} /> : null, asset.symbol ? (
<Asset key={asset.symbol} asset={asset} />
) : null
)} )}
</Column> </Column>
</SFullWidthContainer> </SFullWidthContainer>
@ -161,7 +175,7 @@ const Blockchain: FC<PropsWithChildren<BlockchainProps>> = (
{address && !!actions && actions.length ? ( {address && !!actions && actions.length ? (
<SFullWidthContainer> <SFullWidthContainer>
<h6>Methods</h6> <h6>Methods</h6>
{actions.map(action => ( {actions.map((action) => (
<SAction <SAction
key={action.method} key={action.method}
left left

View File

@ -47,9 +47,12 @@ const SButton = styled.button<ButtonStyleProps>`
border: none; border: none;
border-style: none; border-style: none;
box-sizing: border-box; box-sizing: border-box;
background-color: ${({ outline, color }) => (outline ? "transparent" : `rgb(${colors[color]})`)}; background-color: ${({ outline, color }) =>
border: ${({ outline, color }) => (outline ? `1px solid rgb(${colors[color]})` : "none")}; outline ? "transparent" : `rgb(${colors[color]})`};
color: ${({ outline, color }) => (outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`)}; 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}`)}; box-shadow: ${({ outline }) => (outline ? "none" : `${shadows.soft}`)};
border-radius: 8px; border-radius: 8px;
font-size: ${fonts.size.medium}; font-size: ${fonts.size.medium};
@ -68,7 +71,11 @@ const SButton = styled.button<ButtonStyleProps>`
&:hover { &:hover {
transform: ${({ disabled }) => (!disabled ? "translateY(-1px)" : "none")}; transform: ${({ disabled }) => (!disabled ? "translateY(-1px)" : "none")};
box-shadow: ${({ disabled, outline }) => box-shadow: ${({ disabled, outline }) =>
!disabled ? (outline ? "none" : `${shadows.hover}`) : `${shadows.soft}`}; !disabled
? outline
? "none"
: `${shadows.hover}`
: `${shadows.soft}`};
} }
&:hover ${SHoverLayer} { &:hover ${SHoverLayer} {

View File

@ -27,7 +27,12 @@ const SColumn = styled.div<ColumnStyleProps>`
const Column = (props: ColumnProps) => { const Column = (props: ColumnProps) => {
const { children, spanHeight, maxWidth, center } = props; const { children, spanHeight, maxWidth, center } = props;
return ( return (
<SColumn {...props} spanHeight={spanHeight} maxWidth={maxWidth} center={center}> <SColumn
{...props}
spanHeight={spanHeight}
maxWidth={maxWidth}
center={center}
>
{children} {children}
</SColumn> </SColumn>
); );

View File

@ -49,7 +49,15 @@ const Loader = (props: LoaderProps) => {
fill={rgb} fill={rgb}
fillRule="nonzero" 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> </g>
</SLoader> </SLoader>
); );

View File

@ -38,7 +38,8 @@ const SToggle = styled.div<IToggleStyleProps>`
} }
& div:after { & div:after {
transition: ${transitions.base}; 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; border-radius: 1rem;
left: ${({ active }) => (active ? `20px` : `0`)}; left: ${({ active }) => (active ? `20px` : `0`)};
content: ""; content: "";

View File

@ -22,9 +22,9 @@ export const DEFAULT_TEST_CHAINS = [
export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS]; export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS];
export const DEFAULT_PROJECT_ID = process.env.REACT_APP_PROJECT_ID; export const DEFAULT_PROJECT_ID = process.env.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"; export const DEFAULT_LOGGER = "debug";

View File

@ -1,5 +1,11 @@
import { apiGetChainNamespace, ChainsMap } from "caip-api"; 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 { SolanaChainData } from "../chains/solana";
import { ChainNamespaces, getAllChainNamespaces } from "../helpers"; import { ChainNamespaces, getAllChainNamespaces } from "../helpers";
@ -19,14 +25,18 @@ export const ChainDataContext = createContext<IContext>({} as IContext);
/** /**
* Provider * Provider
*/ */
export function ChainDataContextProvider({ children }: { children: ReactNode | ReactNode[] }) { export function ChainDataContextProvider({
children,
}: {
children: ReactNode | ReactNode[];
}) {
const [chainData, setChainData] = useState<ChainNamespaces>({}); const [chainData, setChainData] = useState<ChainNamespaces>({});
const loadChainData = async () => { const loadChainData = async () => {
const namespaces = getAllChainNamespaces(); const namespaces = getAllChainNamespaces();
const chainData: ChainNamespaces = {}; const chainData: ChainNamespaces = {};
await Promise.all( await Promise.all(
namespaces.map(async namespace => { namespaces.map(async (namespace) => {
let chains: ChainsMap | undefined; let chains: ChainsMap | undefined;
try { try {
if (namespace === "solana") { if (namespace === "solana") {
@ -40,7 +50,7 @@ export function ChainDataContextProvider({ children }: { children: ReactNode | R
if (typeof chains !== "undefined") { if (typeof chains !== "undefined") {
chainData[namespace] = chains; chainData[namespace] = chains;
} }
}), })
); );
setChainData(chainData); setChainData(chainData);
@ -64,7 +74,9 @@ export function ChainDataContextProvider({ children }: { children: ReactNode | R
export function useChainData() { export function useChainData() {
const context = useContext(ChainDataContext); const context = useContext(ChainDataContext);
if (context === undefined) { 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; return context;
} }

View File

@ -49,7 +49,11 @@ export const ClientContext = createContext<IContext>({} as IContext);
/** /**
* Provider * Provider
*/ */
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) { export function ClientContextProvider({
children,
}: {
children: ReactNode | ReactNode[];
}) {
const [client, setClient] = useState<Client>(); const [client, setClient] = useState<Client>();
const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]); const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
const [session, setSession] = useState<SessionTypes.Struct>(); const [session, setSession] = useState<SessionTypes.Struct>();
@ -59,7 +63,8 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
const [balances, setBalances] = useState<AccountBalances>({}); const [balances, setBalances] = useState<AccountBalances>({});
const [accounts, setAccounts] = useState<string[]>([]); 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 [chains, setChains] = useState<string[]>([]);
const reset = () => { const reset = () => {
@ -73,12 +78,12 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
setIsFetchingBalances(true); setIsFetchingBalances(true);
try { try {
const arr = await Promise.all( const arr = await Promise.all(
_accounts.map(async account => { _accounts.map(async (account) => {
const [namespace, reference, address] = account.split(":"); const [namespace, reference, address] = account.split(":");
const chainId = `${namespace}:${reference}`; const chainId = `${namespace}:${reference}`;
const assets = await apiGetAccountBalance(address, chainId); const assets = await apiGetAccountBalance(address, chainId);
return { account, assets: [assets] }; return { account, assets: [assets] };
}), })
); );
const balances: AccountBalances = {}; const balances: AccountBalances = {};
@ -93,28 +98,34 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
} }
}; };
const onSessionConnected = useCallback(async (_session: SessionTypes.Struct) => { const onSessionConnected = useCallback(
const allNamespaceAccounts = Object.values(_session.namespaces) async (_session: SessionTypes.Struct) => {
.map(namespace => namespace.accounts) const allNamespaceAccounts = Object.values(_session.namespaces)
.flat(); .map((namespace) => namespace.accounts)
const allNamespaceChains = Object.keys(_session.namespaces); .flat();
const allNamespaceChains = Object.keys(_session.namespaces);
setSession(_session); setSession(_session);
setChains(allNamespaceChains); setChains(allNamespaceChains);
setAccounts(allNamespaceAccounts); setAccounts(allNamespaceAccounts);
setSolanaPublicKeys(getPublicKeysFromAccounts(allNamespaceAccounts)); setSolanaPublicKeys(getPublicKeysFromAccounts(allNamespaceAccounts));
await getAccountBalances(allNamespaceAccounts); await getAccountBalances(allNamespaceAccounts);
}, []); },
[]
);
const connect = useCallback( const connect = useCallback(
async pairing => { async (pairing: any) => {
if (typeof client === "undefined") { if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
console.log("connect, pairing topic is:", pairing?.topic); console.log("connect, pairing topic is:", pairing?.topic);
try { try {
const requiredNamespaces = getRequiredNamespaces(chains); const requiredNamespaces = getRequiredNamespaces(chains);
console.log("requiredNamespaces config for connect:", requiredNamespaces); console.log(
"requiredNamespaces config for connect:",
requiredNamespaces
);
const { uri, approval } = await client.connect({ const { uri, approval } = await client.connect({
pairingTopic: pairing?.topic, pairingTopic: pairing?.topic,
@ -141,7 +152,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
QRCodeModal.close(); QRCodeModal.close();
} }
}, },
[chains, client, onSessionConnected], [chains, client, onSessionConnected]
); );
const disconnect = useCallback(async () => { const disconnect = useCallback(async () => {
@ -165,11 +176,11 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
_client.on("session_ping", args => { _client.on("session_ping", (args) => {
console.log("EVENT", "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); console.log("EVENT", "session_event", args);
}); });
@ -186,7 +197,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
reset(); reset();
}); });
}, },
[onSessionConnected], [onSessionConnected]
); );
const _checkPersistedState = useCallback( const _checkPersistedState = useCallback(
@ -196,19 +207,24 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
} }
// populates existing pairings to state // populates existing pairings to state
setPairings(_client.pairing.getAll({ active: true })); 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; if (typeof session !== "undefined") return;
// populates (the last) existing session to state // populates (the last) existing session to state
if (_client.session.length) { if (_client.session.length) {
const lastKeyIndex = _client.session.keys.length - 1; 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); console.log("RESTORED SESSION:", _session);
await onSessionConnected(_session); await onSessionConnected(_session);
return _session; return _session;
} }
}, },
[session, onSessionConnected], [session, onSessionConnected]
); );
const createClient = useCallback(async () => { const createClient = useCallback(async () => {
@ -267,7 +283,7 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
connect, connect,
disconnect, disconnect,
setChains, setChains,
], ]
); );
return ( return (
@ -284,7 +300,9 @@ export function ClientContextProvider({ children }: { children: ReactNode | Reac
export function useWalletConnectClient() { export function useWalletConnectClient() {
const context = useContext(ClientContext); const context = useContext(ClientContext);
if (context === undefined) { 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; return context;
} }

View File

@ -19,7 +19,11 @@ import {
Transaction as SolanaTransaction, Transaction as SolanaTransaction,
} from "@solana/web3.js"; } from "@solana/web3.js";
import { eip712, formatTestTransaction, getLocalStorageTestnetFlag } from "../helpers"; import {
eip712,
formatTestTransaction,
getLocalStorageTestnetFlag,
} from "../helpers";
import { useWalletConnectClient } from "./ClientContext"; import { useWalletConnectClient } from "./ClientContext";
import { import {
DEFAULT_COSMOS_METHODS, DEFAULT_COSMOS_METHODS,
@ -71,17 +75,27 @@ export const JsonRpcContext = createContext<IContext>({} as IContext);
/** /**
* Provider * Provider
*/ */
export function JsonRpcContextProvider({ children }: { children: ReactNode | ReactNode[] }) { export function JsonRpcContextProvider({
children,
}: {
children: ReactNode | ReactNode[];
}) {
const [pending, setPending] = useState(false); const [pending, setPending] = useState(false);
const [result, setResult] = useState<IFormattedRpcResponse | null>(); const [result, setResult] = useState<IFormattedRpcResponse | null>();
const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag()); const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag());
const { client, session, accounts, balances, solanaPublicKeys } = useWalletConnectClient(); const { client, session, accounts, balances, solanaPublicKeys } =
useWalletConnectClient();
const { chainData } = useChainData(); const { chainData } = useChainData();
const _createJsonRpcRequestHandler = const _createJsonRpcRequestHandler =
(rpcRequest: (chainId: string, address: string) => Promise<IFormattedRpcResponse>) => (
rpcRequest: (
chainId: string,
address: string
) => Promise<IFormattedRpcResponse>
) =>
async (chainId: string, address: string) => { async (chainId: string, address: string) => {
if (typeof client === "undefined") { if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized"); 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) => const _verifyEip155MessageSignature = (
utils.verifyMessage(message, signature).toLowerCase() === address.toLowerCase(); message: string,
signature: string,
address: string
) =>
utils.verifyMessage(message, signature).toLowerCase() ===
address.toLowerCase();
const ping = async () => { const ping = async () => {
if (typeof client === "undefined") { if (typeof client === "undefined") {
@ -146,65 +165,77 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
// -------- ETHEREUM/EIP155 RPC METHODS -------- // -------- ETHEREUM/EIP155 RPC METHODS --------
const ethereumRpc = { const ethereumRpc = {
testSendTransaction: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { testSendTransaction: _createJsonRpcRequestHandler(
const caipAccountAddress = `${chainId}:${address}`; async (chainId: string, address: string) => {
const account = accounts.find(account => account === caipAccountAddress); const caipAccountAddress = `${chainId}:${address}`;
if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`); 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"); const balance = BigNumber.from(balances[account][0].balance || "0");
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) { 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 { return {
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
address, address,
valid: false, valid: true,
result: "Insufficient funds for intrinsic transaction cost", 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>({ const tx = await formatTestTransaction(account);
topic: session!.topic,
chainId,
request: {
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
params: [tx],
},
});
// format displayed result const signedTx = await client!.request<string>({
return { topic: session!.topic,
method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, chainId,
address, request: {
valid: true, method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
result, params: [tx],
}; },
}), });
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 tx = await formatTestTransaction(account); const valid = EthTransaction.fromSerializedTx(
signedTx as any
).verifySignature();
const signedTx = await client!.request<string>({ return {
topic: session!.topic,
chainId,
request: {
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION, method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
params: [tx], address,
}, valid,
}); result: signedTx,
};
const valid = EthTransaction.fromSerializedTx(signedTx as any).verifySignature(); }
),
return {
method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION,
address,
valid,
result: signedTx,
};
}),
testSignPersonalMessage: _createJsonRpcRequestHandler( testSignPersonalMessage: _createJsonRpcRequestHandler(
async (chainId: string, address: string) => { async (chainId: string, address: string) => {
// test message // test message
@ -235,7 +266,11 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
throw new Error(`Missing chain data for chainId: ${chainId}`); throw new Error(`Missing chain data for chainId: ${chainId}`);
} }
const valid = _verifyEip155MessageSignature(message, signature, address); const valid = _verifyEip155MessageSignature(
message,
signature,
address
);
// format displayed result // format displayed result
return { return {
@ -244,201 +279,233 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
valid, valid,
result: signature, 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 // split chainId
return { const [namespace, reference] = chainId.split(":");
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 targetChainData = chainData[namespace][reference];
const params = [address, message];
// send message if (typeof targetChainData === "undefined") {
const signature = await client!.request<string>({ throw new Error(`Missing chain data for chainId: ${chainId}`);
topic: session!.topic, }
chainId,
request: { 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, method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA,
params, address,
}, valid,
}); result: signature,
};
// 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,
};
}),
}; };
// -------- COSMOS RPC METHODS -------- // -------- COSMOS RPC METHODS --------
const cosmosRpc = { const cosmosRpc = {
testSignDirect: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { testSignDirect: _createJsonRpcRequestHandler(
// test direct sign doc inputs async (chainId: string, address: string) => {
const inputs = { // test direct sign doc inputs
fee: [{ amount: "2000", denom: "ucosm" }], const inputs = {
pubkey: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW", fee: [{ amount: "2000", denom: "ucosm" }],
gasLimit: 200000, pubkey: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW",
accountNumber: 1, gasLimit: 200000,
sequence: 1, accountNumber: 1,
bodyBytes: sequence: 1,
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637", bodyBytes:
authInfoBytes: "0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
"0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c", authInfoBytes:
}; "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c",
};
// split chainId // split chainId
const [namespace, reference] = chainId.split(":"); const [namespace, reference] = chainId.split(":");
// format sign doc // format sign doc
const signDoc = formatDirectSignDoc( const signDoc = formatDirectSignDoc(
inputs.fee, inputs.fee,
inputs.pubkey, inputs.pubkey,
inputs.gasLimit, inputs.gasLimit,
inputs.accountNumber, inputs.accountNumber,
inputs.sequence, inputs.sequence,
inputs.bodyBytes, inputs.bodyBytes,
reference, reference
); );
// cosmos_signDirect params // cosmos_signDirect params
const params = { const params = {
signerAddress: address, signerAddress: address,
signDoc: stringifySignDocValues(signDoc), signDoc: stringifySignDocValues(signDoc),
}; };
// send message // send message
const result = await client!.request<{ signature: string }>({ const result = await client!.request<{ signature: string }>({
topic: session!.topic, topic: session!.topic,
chainId, chainId,
request: { 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, method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_DIRECT,
params, address,
}, valid,
}); result: result.signature,
};
const targetChainData = chainData[namespace][reference];
if (typeof targetChainData === "undefined") {
throw new Error(`Missing chain data for chainId: ${chainId}`);
} }
),
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 // cosmos_signAmino params
return { const params = { signerAddress: address, signDoc };
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(":");
// test amino sign doc // send message
const signDoc = { const result = await client!.request<{ signature: string }>({
msgs: [], topic: session!.topic,
fee: { amount: [], gas: "23" }, chainId,
chain_id: "foochain", request: {
memo: "hello, world", method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
account_number: "7", params,
sequence: "54", },
}; });
// cosmos_signAmino params const targetChainData = chainData[namespace][reference];
const params = { signerAddress: address, signDoc };
// send message if (typeof targetChainData === "undefined") {
const result = await client!.request<{ signature: string }>({ throw new Error(`Missing chain data for chainId: ${chainId}`);
topic: session!.topic, }
chainId,
request: { const valid = await verifyAminoSignature(
address,
result.signature,
signDoc
);
// format displayed result
return {
method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO, method: DEFAULT_COSMOS_METHODS.COSMOS_SIGN_AMINO,
params, address,
}, valid,
}); result: result.signature,
};
const targetChainData = chainData[namespace][reference];
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,
address,
valid,
result: result.signature,
};
}),
}; };
// -------- SOLANA RPC METHODS -------- // -------- SOLANA RPC METHODS --------
const solanaRpc = { const solanaRpc = {
testSignTransaction: _createJsonRpcRequestHandler( testSignTransaction: _createJsonRpcRequestHandler(
async (chainId: string, address: string): Promise<IFormattedRpcResponse> => { async (
chainId: string,
address: string
): Promise<IFormattedRpcResponse> => {
if (!solanaPublicKeys) { if (!solanaPublicKeys) {
throw new Error("Could not find Solana PublicKeys."); throw new Error("Could not find Solana PublicKeys.");
} }
const senderPublicKey = solanaPublicKeys[address]; 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` // Using deprecated `getRecentBlockhash` over `getLatestBlockhash` here, since `mainnet-beta`
// cluster only seems to support `connection.getRecentBlockhash` currently. // cluster only seems to support `connection.getRecentBlockhash` currently.
@ -452,7 +519,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
fromPubkey: senderPublicKey, fromPubkey: senderPublicKey,
toPubkey: Keypair.generate().publicKey, toPubkey: Keypair.generate().publicKey,
lamports: 1, lamports: 1,
}), })
); );
try { try {
@ -464,10 +531,10 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
params: { params: {
feePayer: transaction.feePayer!.toBase58(), feePayer: transaction.feePayer!.toBase58(),
recentBlockhash: transaction.recentBlockhash, recentBlockhash: transaction.recentBlockhash,
instructions: transaction.instructions.map(i => ({ instructions: transaction.instructions.map((i) => ({
programId: i.programId.toBase58(), programId: i.programId.toBase58(),
data: bs58.encode(i.data), data: bs58.encode(i.data),
keys: i.keys.map(k => ({ keys: i.keys.map((k) => ({
isSigner: k.isSigner, isSigner: k.isSigner,
isWritable: k.isWritable, isWritable: k.isWritable,
pubkey: k.pubkey.toBase58(), 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`. // We only need `Buffer.from` here to satisfy the `Buffer` param type for `addSignature`.
// The resulting `UInt8Array` is equivalent to just `bs58.decode(...)`. // 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(); const valid = transaction.verifySignatures();
@ -492,10 +562,13 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
} catch (error: any) { } catch (error: any) {
throw new Error(error); throw new Error(error);
} }
}, }
), ),
testSignMessage: _createJsonRpcRequestHandler( testSignMessage: _createJsonRpcRequestHandler(
async (chainId: string, address: string): Promise<IFormattedRpcResponse> => { async (
chainId: string,
address: string
): Promise<IFormattedRpcResponse> => {
if (!solanaPublicKeys) { if (!solanaPublicKeys) {
throw new Error("Could not find Solana PublicKeys."); 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`. // Encode message to `UInt8Array` first via `TextEncoder` so we can pass it to `bs58.encode`.
const message = 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 { try {
@ -523,7 +598,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
const valid = verifyMessageSignature( const valid = verifyMessageSignature(
senderPublicKey.toBase58(), senderPublicKey.toBase58(),
result.signature, result.signature,
message, message
); );
return { return {
@ -535,7 +610,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea
} catch (error: any) { } catch (error: any) {
throw new Error(error); throw new Error(error);
} }
}, }
), ),
}; };

View File

@ -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 ethChainId = chainId.split(":")[1];
const rpc = rpcProvidersByChainId[Number(ethChainId)]; const rpc = rpcProvidersByChainId[Number(ethChainId)];
if (!rpc) { if (!rpc) {
@ -119,7 +122,10 @@ export async function apiGetAccountBalance(address: string, chainId: string): Pr
return { balance, ...token }; 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 ethChainId = chainId.split(":")[1];
const { baseURL } = rpcProvidersByChainId[Number(ethChainId)]; const { baseURL } = rpcProvidersByChainId[Number(ethChainId)];
const response = await api.post(baseURL, { const response = await api.post(baseURL, {

View File

@ -35,13 +35,13 @@ async function isValidSignature(
data: string, data: string,
provider: providers.Provider, provider: providers.Provider,
abi = eip1271.spec.abi, abi = eip1271.spec.abi,
magicValue = eip1271.spec.magicValue, magicValue = eip1271.spec.magicValue
): Promise<boolean> { ): Promise<boolean> {
let returnValue; let returnValue;
try { try {
returnValue = await new Contract(address, abi, provider).isValidSignature( returnValue = await new Contract(address, abi, provider).isValidSignature(
utils.arrayify(data), utils.arrayify(data),
sig, sig
); );
} catch (e) { } catch (e) {
return false; return false;

View File

@ -10,7 +10,7 @@ import {
export const getNamespacesFromChains = (chains: string[]) => { export const getNamespacesFromChains = (chains: string[]) => {
const supportedNamespaces: string[] = []; const supportedNamespaces: string[] = [];
chains.forEach(chainId => { chains.forEach((chainId) => {
const [namespace] = chainId.split(":"); const [namespace] = chainId.split(":");
if (!supportedNamespaces.includes(namespace)) { if (!supportedNamespaces.includes(namespace)) {
supportedNamespaces.push(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); const selectedNamespaces = getNamespacesFromChains(chains);
console.log("selected namespaces:", selectedNamespaces); console.log("selected namespaces:", selectedNamespaces);
return Object.fromEntries( return Object.fromEntries(
selectedNamespaces.map(namespace => [ selectedNamespaces.map((namespace) => [
namespace, namespace,
{ {
methods: getSupportedMethodsByNamespace(namespace), methods: getSupportedMethodsByNamespace(namespace),
chains: chains.filter(chain => chain.startsWith(namespace)), chains: chains.filter((chain) => chain.startsWith(namespace)),
events: getSupportedEventsByNamespace(namespace) as any[], events: getSupportedEventsByNamespace(namespace) as any[],
}, },
]), ])
); );
}; };

View File

@ -4,12 +4,14 @@ export function getPublicKeysFromAccounts(accounts: string[]) {
return ( return (
accounts accounts
// Filter out any non-solana accounts. // Filter out any non-solana accounts.
.filter(account => account.startsWith("solana:")) .filter((account) => account.startsWith("solana:"))
// Create a map of Solana address -> publicKey. // Create a map of Solana address -> publicKey.
.reduce((map: Record<string, PublicKey>, account) => { .reduce((map: Record<string, PublicKey>, account) => {
const address = account.split(":").pop(); const address = account.split(":").pop();
if (!address) { 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); map[address] = new PublicKey(address);
return map; return map;

View File

@ -10,7 +10,9 @@ export async function formatTestTransaction(account: string) {
try { try {
_nonce = await apiGetAccountNonce(address, chainId); _nonce = await apiGetAccountNonce(address, chainId);
} catch (error) { } 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)); const nonce = encoding.sanitizeHex(encoding.numberToHex(_nonce));
@ -27,7 +29,15 @@ export async function formatTestTransaction(account: string) {
const _value = 0; const _value = 0;
const value = encoding.sanitizeHex(encoding.numberToHex(_value)); 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; return tx;
} }

View File

@ -9,7 +9,7 @@ import { DEFAULT_CHAINS } from "../constants";
export function capitalize(string: string): string { export function capitalize(string: string): string {
return string return string
.split(" ") .split(" ")
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" "); .join(" ");
} }
@ -23,7 +23,7 @@ export function ellipseText(text = "", maxLength = 9999): string {
const result = const result =
text text
.split(" ") .split(" ")
.filter(word => { .filter((word) => {
currentLength += word.length; currentLength += word.length;
if (ellipse || currentLength >= _maxLength) { if (ellipse || currentLength >= _maxLength) {
ellipse = true; ellipse = true;
@ -64,10 +64,10 @@ export function isMobile(): boolean {
function hasMobileUserAgent(): boolean { function hasMobileUserAgent(): boolean {
if ( 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( /(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( /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; return true;
@ -85,7 +85,10 @@ export function isMobile(): boolean {
export function encodePersonalMessage(msg: string): string { export function encodePersonalMessage(msg: string): string {
const data = encoding.utf8ToBuffer(msg); const data = encoding.utf8ToBuffer(msg);
const buf = Buffer.concat([ 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, data,
]); ]);
return ethUtil.bufferToHex(buf); return ethUtil.bufferToHex(buf);
@ -103,7 +106,11 @@ export function encodeTypedDataMessage(msg: string): string {
const buf = Buffer.concat([ const buf = Buffer.concat([
Buffer.from("1901", "hex"), Buffer.from("1901", "hex"),
TypedDataUtils.hashStruct("EIP712Domain", data.domain, data.types), 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); return ethUtil.bufferToHex(buf);
} }
@ -117,7 +124,12 @@ export function hashTypedDataMessage(msg: string): string {
export function recoverAddress(sig: string, hash: string): string { export function recoverAddress(sig: string, hash: string): string {
const params = ethUtil.fromRpcSig(sig); 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)); const signer = ethUtil.bufferToHex(ethUtil.publicToAddress(result));
return signer; return signer;
} }
@ -138,11 +150,16 @@ export async function verifySignature(
address: string, address: string,
sig: string, sig: string,
hash: string, hash: string,
rpcUrl: string, rpcUrl: string
): Promise<boolean> { ): Promise<boolean> {
const provider = new providers.JsonRpcProvider(rpcUrl); const provider = new providers.JsonRpcProvider(rpcUrl);
const bytecode = await provider.getCode(address); 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); const signer = recoverAddress(sig, hash);
return signer.toLowerCase() === address.toLowerCase(); return signer.toLowerCase() === address.toLowerCase();
} else { } else {
@ -186,10 +203,13 @@ export const LOCALSTORAGE_KEY_TESTNET = "TESTNET";
export const INITIAL_STATE_TESTNET_DEFAULT = true; export const INITIAL_STATE_TESTNET_DEFAULT = true;
export function setLocaleStorageTestnetFlag(value: boolean): void { 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 { export function getLocalStorageTestnetFlag(): boolean {
if (typeof window === "undefined") return false;
let value = INITIAL_STATE_TESTNET_DEFAULT; let value = INITIAL_STATE_TESTNET_DEFAULT;
const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET); const persisted = window.localStorage.getItem(LOCALSTORAGE_KEY_TESTNET);
if (!persisted) { if (!persisted) {
@ -202,7 +222,7 @@ export function getLocalStorageTestnetFlag(): boolean {
export const getAllChainNamespaces = () => { export const getAllChainNamespaces = () => {
const namespaces: string[] = []; const namespaces: string[] = [];
DEFAULT_CHAINS.forEach(chainId => { DEFAULT_CHAINS.forEach((chainId) => {
const [namespace] = chainId.split(":"); const [namespace] = chainId.split(":");
if (!namespaces.includes(namespace)) { if (!namespaces.includes(namespace)) {
namespaces.push(namespace); namespaces.push(namespace);

View File

@ -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"),
);

View File

@ -19,7 +19,7 @@ const PairingModal = (props: PairingModalProps) => {
<SModalContainer> <SModalContainer>
<SModalTitle>{"Select available pairing or create new one"}</SModalTitle> <SModalTitle>{"Select available pairing or create new one"}</SModalTitle>
<STable> <STable>
{pairings.map(pairing => ( {pairings.map((pairing) => (
<Pairing <Pairing
key={pairing.topic} key={pairing.topic}
pairing={pairing} pairing={pairing}

View File

@ -19,16 +19,20 @@ const RequestModal = (props: RequestModalProps) => {
<SModalTitle>{"Pending JSON-RPC Request"}</SModalTitle> <SModalTitle>{"Pending JSON-RPC Request"}</SModalTitle>
<SContainer> <SContainer>
<Loader /> <Loader />
<SModalParagraph>{"Approve or reject request using your wallet"}</SModalParagraph> <SModalParagraph>
{"Approve or reject request using your wallet"}
</SModalParagraph>
</SContainer> </SContainer>
</SModalContainer> </SModalContainer>
) : result ? ( ) : result ? (
<SModalContainer> <SModalContainer>
<SModalTitle> <SModalTitle>
{result.valid ? "JSON-RPC Request Approved" : "JSON-RPC Request Failed"} {result.valid
? "JSON-RPC Request Approved"
: "JSON-RPC Request Failed"}
</SModalTitle> </SModalTitle>
<STable> <STable>
{Object.keys(result).map(key => ( {Object.keys(result).map((key) => (
<SRow key={key}> <SRow key={key}>
<SKey>{key}</SKey> <SKey>{key}</SKey>
<SValue>{result[key].toString()}</SValue> <SValue>{result[key].toString()}</SValue>

View File

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

View 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;

View File

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

View File

@ -1,23 +1,23 @@
import type { NextPage } from "next";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { version } from "@walletconnect/sign-client/package.json";
import Banner from "./components/Banner"; import Banner from "../components/Banner";
import Blockchain from "./components/Blockchain"; import Blockchain from "../components/Blockchain";
import Column from "./components/Column"; import Column from "../components/Column";
import Header from "./components/Header"; import Header from "../components/Header";
import Modal from "./components/Modal"; import Modal from "../components/Modal";
import { import {
DEFAULT_COSMOS_METHODS, DEFAULT_COSMOS_METHODS,
DEFAULT_EIP155_METHODS, DEFAULT_EIP155_METHODS,
DEFAULT_MAIN_CHAINS, DEFAULT_MAIN_CHAINS,
DEFAULT_SOLANA_METHODS, DEFAULT_SOLANA_METHODS,
DEFAULT_TEST_CHAINS, DEFAULT_TEST_CHAINS,
} from "./constants"; } from "../constants";
import { AccountAction, setLocaleStorageTestnetFlag } from "./helpers"; import { AccountAction, setLocaleStorageTestnetFlag } from "../helpers";
import Toggle from "./components/Toggle"; import Toggle from "../components/Toggle";
import RequestModal from "./modals/RequestModal"; import RequestModal from "../modals/RequestModal";
import PairingModal from "./modals/PairingModal"; import PairingModal from "../modals/PairingModal";
import PingModal from "./modals/PingModal"; import PingModal from "../modals/PingModal";
import { import {
SAccounts, SAccounts,
SAccountsContainer, SAccountsContainer,
@ -27,12 +27,15 @@ import {
SLanding, SLanding,
SLayout, SLayout,
SToggleContainer, SToggleContainer,
} from "./components/app"; } from "../components/app";
import { useWalletConnectClient } from "./contexts/ClientContext"; import { useWalletConnectClient } from "../contexts/ClientContext";
import { useJsonRpc } from "./contexts/JsonRpcContext"; import { useJsonRpc } from "../contexts/JsonRpcContext";
import { useChainData } from "./contexts/ChainDataContext"; 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 [modal, setModal] = useState("");
const closeModal = () => setModal(""); const closeModal = () => setModal("");
@ -117,11 +120,26 @@ export default function App() {
}; };
return [ return [
{ method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION, callback: onSendTransaction }, {
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN_TRANSACTION, callback: onSignTransaction }, method: DEFAULT_EIP155_METHODS.ETH_SEND_TRANSACTION,
{ method: DEFAULT_EIP155_METHODS.PERSONAL_SIGN, callback: onSignPersonalMessage }, callback: onSendTransaction,
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN + " (standard)", callback: onEthSign }, },
{ method: DEFAULT_EIP155_METHODS.ETH_SIGN_TYPED_DATA, callback: onSignTypedData }, {
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); await cosmosRpc.testSignAmino(chainId, address);
}; };
return [ 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); await solanaRpc.testSignMessage(chainId, address);
}; };
return [ 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) => { const handleChainSelectionClick = (chainId: string) => {
if (chains.includes(chainId)) { if (chains.includes(chainId)) {
setChains(chains.filter(chain => chain !== chainId)); setChains(chains.filter((chain) => chain !== chainId));
} else { } else {
setChains([...chains, chainId]); setChains([...chains, chainId]);
} }
@ -193,7 +223,9 @@ export default function App() {
} }
return <PairingModal pairings={pairings} connect={connect} />; return <PairingModal pairings={pairings} connect={connect} />;
case "request": case "request":
return <RequestModal pending={isRpcRequestPending} result={rpcResult} />; return (
<RequestModal pending={isRpcRequestPending} result={rpcResult} />
);
case "ping": case "ping":
return <PingModal pending={isRpcRequestPending} result={rpcResult} />; return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
default: default:
@ -213,7 +245,7 @@ export default function App() {
<p>Testnets Only?</p> <p>Testnets Only?</p>
<Toggle active={isTestnet} onClick={toggleTestnets} /> <Toggle active={isTestnet} onClick={toggleTestnets} />
</SToggleContainer> </SToggleContainer>
{chainOptions.map(chainId => ( {chainOptions.map((chainId) => (
<Blockchain <Blockchain
key={chainId} key={chainId}
chainId={chainId} chainId={chainId}
@ -231,7 +263,7 @@ export default function App() {
<SAccountsContainer> <SAccountsContainer>
<h3>Accounts</h3> <h3>Accounts</h3>
<SAccounts> <SAccounts>
{accounts.map(account => { {accounts.map((account) => {
const [namespace, reference, address] = account.split(":"); const [namespace, reference, address] = account.split(":");
const chainId = `${namespace}:${reference}`; const chainId = `${namespace}:${reference}`;
return ( return (
@ -263,4 +295,6 @@ export default function App() {
</Modal> </Modal>
</SLayout> </SLayout>
); );
} };
export default Home;

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View 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);
}
}

View 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;
}
}

View File

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

File diff suppressed because it is too large Load Diff