chore: remove old class-based App component

This commit is contained in:
Ben Kremer 2022-02-10 11:45:19 +01:00
parent 22c0a7c456
commit 601f16b171
3 changed files with 122 additions and 1014 deletions

View File

@ -1,849 +1,180 @@
import * as React from "react"; import React, { useEffect, useState } from "react";
import styled from "styled-components"; import { version } from "@walletconnect/client/package.json";
import Client, { CLIENT_EVENTS } from "@walletconnect/client";
import QRCodeModal from "@walletconnect/legacy-modal";
import { PairingTypes, SessionTypes } from "@walletconnect/types";
import { ERROR, getAppMetadata } from "@walletconnect/utils";
import * as encoding from "@walletconnect/encoding";
import { apiGetChainNamespace, ChainsMap } from "caip-api";
import { formatDirectSignDoc, stringifySignDocValues } from "cosmos-wallet";
import { BigNumber } from "ethers";
import Banner from "./components/Banner"; import Banner from "./components/Banner";
import Blockchain from "./components/Blockchain"; import Blockchain from "./components/Blockchain";
import Button from "./components/Button";
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 Wrapper from "./components/Wrapper"; import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants";
import { import { AccountAction, setInitialStateTestnet, getInitialStateTestnet } from "./helpers";
DEFAULT_APP_METADATA,
DEFAULT_MAIN_CHAINS,
DEFAULT_LOGGER,
DEFAULT_EIP155_METHODS,
DEFAULT_COSMOS_METHODS,
DEFAULT_PROJECT_ID,
DEFAULT_RELAY_URL,
DEFAULT_TEST_CHAINS,
DEFAULT_CHAINS,
} from "./constants";
import {
apiGetAccountAssets,
AccountAction,
eip712,
hashPersonalMessage,
hashTypedDataMessage,
verifySignature,
AccountBalances,
formatTestTransaction,
ChainNamespaces,
setInitialStateTestnet,
getInitialStateTestnet,
} from "./helpers";
import { fonts } from "./styles";
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 {
SAccounts,
SAccountsContainer,
SButtonContainer,
SConnectButton,
SContent,
SLanding,
SLayout,
SToggleContainer,
} from "./components/app";
import { useWalletConnectClient } from "./contexts/ClientContext";
import { useJsonRpc } from "./contexts/JsonRpcContext";
const SLayout = styled.div` export default function App() {
position: relative; const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
width: 100%;
min-height: 100vh;
text-align: center;
`;
const SContent = styled(Wrapper as any)` const [modal, setModal] = useState("");
width: 100%;
height: 100%;
padding: 0 16px;
`;
const SLanding = styled(Column as any)` const closeModal = () => setModal("");
/* height: 600px; */ const openPairingModal = () => setModal("pairing");
`; const openPingModal = () => setModal("ping");
const openRequestModal = () => setModal("request");
const SButtonContainer = styled(Column as any)` const {
width: 250px; client,
margin: 50px 0; session,
`; connect,
disconnect,
const SConnectButton = styled(Button as any)`
border-radius: 8px;
font-size: ${fonts.size.medium};
height: 44px;
width: 100%;
margin: 12px 0;
`;
const SAccountsContainer = styled(SLanding as any)`
height: 100%;
padding-bottom: 30px;
& h3 {
padding-top: 30px;
}
`;
const SToggleContainer = styled.div`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin: 10px auto;
& > p {
margin-right: 10px;
}
`;
const SFullWidthContainer = styled.div`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
`;
const SAccounts = styled(SFullWidthContainer)`
justify-content: space-between;
& > div {
margin: 12px 0;
flex: 1 0 100%;
@media (min-width: 648px) {
flex: 0 1 48%;
}
}
`;
interface AppState {
client: Client | undefined;
session: SessionTypes.Created | undefined;
testnet: boolean;
loading: boolean;
fetching: boolean;
chains: string[];
pairings: string[];
modal: string;
pending: boolean;
uri: string;
accounts: string[];
result: any | undefined;
balances: AccountBalances;
chainData: ChainNamespaces;
}
const INITIAL_STATE: AppState = {
client: undefined,
session: undefined,
testnet: true,
loading: false,
fetching: false,
chains: [],
pairings: [],
modal: "",
pending: false,
uri: "",
accounts: [],
result: undefined,
balances: {},
chainData: {},
};
class App extends React.Component<any, any> {
public state: AppState = {
...INITIAL_STATE,
testnet: getInitialStateTestnet(),
};
public componentDidMount() {
this.init();
}
public init = async () => {
this.setState({ loading: true });
try {
await this.loadChainData();
const client = await Client.init({
logger: DEFAULT_LOGGER,
relayUrl: DEFAULT_RELAY_URL,
projectId: DEFAULT_PROJECT_ID,
});
this.setState({ loading: false, client });
this.subscribeToEvents();
await this.checkPersistedState();
} catch (e) {
this.setState({ loading: false });
throw e;
}
};
public getAllNamespaces() {
const namespaces: string[] = [];
DEFAULT_CHAINS.forEach(chainId => {
const [namespace] = chainId.split(":");
if (!namespaces.includes(namespace)) {
namespaces.push(namespace);
}
});
return namespaces;
}
public async loadChainData(): Promise<void> {
const namespaces = this.getAllNamespaces();
const chainData: ChainNamespaces = {};
await Promise.all(
namespaces.map(async namespace => {
let chains: ChainsMap | undefined;
try {
chains = await apiGetChainNamespace(namespace);
} catch (e) {
// ignore error
}
if (typeof chains !== "undefined") {
chainData[namespace] = chains;
}
}),
);
this.setState({ chainData });
}
public subscribeToEvents = () => {
if (typeof this.state.client === "undefined") {
return;
}
this.state.client.on(
CLIENT_EVENTS.pairing.proposal,
async (proposal: PairingTypes.Proposal) => {
const { uri } = proposal.signal.params;
this.setState({ uri });
console.log("EVENT", "QR Code Modal open");
QRCodeModal.open(uri, () => {
console.log("EVENT", "QR Code Modal closed");
});
},
);
this.state.client.on(CLIENT_EVENTS.pairing.created, async (proposal: PairingTypes.Settled) => {
if (typeof this.state.client === "undefined") return;
this.setState({ pairings: this.state.client.pairing.topics });
});
this.state.client.on(CLIENT_EVENTS.session.deleted, (session: SessionTypes.Settled) => {
if (session.topic !== this.state.session?.topic) return;
console.log("EVENT", "session_deleted");
this.resetApp();
});
};
public checkPersistedState = async () => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
// populates existing pairings to state
this.setState({ pairings: this.state.client.pairing.topics });
if (typeof this.state.session !== "undefined") return;
// populates existing session to state (assume only the top one)
if (this.state.client.session.topics.length) {
const session = await this.state.client.session.get(this.state.client.session.topics[0]);
const chains = session.state.accounts.map(account =>
account.split(":").slice(0, -1).join(":"),
);
this.setState({ accounts: session.state.accounts, chains });
this.onSessionConnected(session);
}
};
public connect = async (pairing?: { topic: string }) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
console.log("connect", pairing);
if (this.state.modal === "pairing") {
this.closeModal();
}
try {
const chains = this.state.chains;
const supportedNamespaces: string[] = [];
chains.forEach(chainId => {
const [namespace] = chainId.split(":");
if (!supportedNamespaces.includes(namespace)) {
supportedNamespaces.push(namespace);
}
});
const methods: string[] = supportedNamespaces
.map(namespace => {
switch (namespace) {
case "eip155":
return DEFAULT_EIP155_METHODS;
case "cosmos":
return DEFAULT_COSMOS_METHODS;
default:
throw new Error(`No default methods for namespace: ${namespace}`);
}
})
.flat();
const session = await this.state.client.connect({
metadata: getAppMetadata() || DEFAULT_APP_METADATA,
pairing,
permissions: {
blockchain: {
chains, chains,
}, accounts,
jsonrpc: { balances,
methods, fetching,
}, loading,
}, setChains,
}); } = useWalletConnectClient();
this.onSessionConnected(session); const { chainData, ping, ethereumRpc, cosmosRpc, isRpcRequestPending, rpcResult } = useJsonRpc();
} catch (e) {
// ignore rejection useEffect(() => {
// Close the pairing modal after a session is established.
if (session && modal === "pairing") {
closeModal();
} }
}, [session, modal]);
// close modal in case it was open const onConnect = () => {
QRCodeModal.close(); if (typeof client === "undefined") {
};
public disconnect = async () => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
if (typeof this.state.session === "undefined") { if (client.pairing.topics.length) {
throw new Error("Session is not connected"); return openPairingModal();
} }
await this.state.client.disconnect({ connect();
topic: this.state.session.topic,
reason: ERROR.USER_DISCONNECTED.format(),
});
}; };
public resetApp = async () => { const onPing = async () => {
const { client, chainData } = this.state; openPingModal();
this.setState({ ...INITIAL_STATE, client, chainData }); await ping();
}; };
public toggleTestnets = () => { const getEthereumActions = (): AccountAction[] => {
const testnet = !this.state.testnet; const onSendTransaction = async (chainId: string) => {
this.setState({ testnet }); openRequestModal();
setInitialStateTestnet(testnet); await ethereumRpc.testSendTransaction(chainId);
};
const onSignPersonalMessage = async (chainId: string) => {
openRequestModal();
await ethereumRpc.testSignPersonalMessage(chainId);
};
const onSignTypedData = async (chainId: string) => {
openRequestModal();
await ethereumRpc.testSignTypedData(chainId);
}; };
public onSessionConnected = async (session: SessionTypes.Settled) => { return [
this.setState({ session }); { method: "eth_sendTransaction", callback: onSendTransaction },
this.onSessionUpdate(session.state.accounts, session.permissions.blockchain.chains); { method: "personal_sign", callback: onSignPersonalMessage },
{ method: "eth_signTypedData", callback: onSignTypedData },
];
}; };
public onSessionUpdate = async (accounts: string[], chains: string[]) => { const getCosmosActions = (): AccountAction[] => {
this.setState({ chains, accounts }); const onSignDirect = async (chainId: string) => {
await this.getAccountBalances(); openRequestModal();
await cosmosRpc.testSignDirect(chainId);
};
const onSignAmino = async (chainId: string) => {
openRequestModal();
await cosmosRpc.testSignAmino(chainId);
};
return [
{ method: "cosmos_signDirect", callback: onSignDirect },
{ method: "cosmos_signAmino", callback: onSignAmino },
];
}; };
public getAccountBalances = async () => { const getBlockchainActions = (chainId: string) => {
this.setState({ fetching: true });
try {
const arr = await Promise.all(
this.state.accounts.map(async account => {
const [namespace, reference, address] = account.split(":");
const chainId = `${namespace}:${reference}`;
const assets = await apiGetAccountAssets(address, chainId);
return { account, assets };
}),
);
const balances: AccountBalances = {};
arr.forEach(({ account, assets }) => {
balances[account] = assets;
});
this.setState({ fetching: false, balances });
} catch (e) {
console.error(e);
this.setState({ fetching: false });
}
};
public openPairingModal = () => this.setState({ modal: "pairing" });
public openRequestModal = () => this.setState({ pending: true, modal: "request" });
public openPingModal = () => this.setState({ pending: true, modal: "ping" });
public openModal = (modal: string) => this.setState({ modal });
public closeModal = () => this.setState({ modal: "" });
public onConnect = () => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (this.state.client.pairing.topics.length) {
return this.openPairingModal();
}
this.connect();
};
public testSendTransaction = async (chainId: string) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// get ethereum address
const account = this.state.accounts.find(account => account.startsWith(chainId));
if (account === undefined) throw new Error("Account is not found");
const address = account.split(":").pop();
if (address === undefined) throw new Error("Address is invalid");
// open modal
this.openRequestModal();
const tx = await formatTestTransaction(account);
const balance = BigNumber.from(this.state.balances[account][0].balance || "0");
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
const formattedResult = {
method: "eth_sendTransaction",
address,
valid: false,
result: "Insufficient funds for intrinsic transaction cost",
};
this.setState({ pending: false, result: formattedResult || null });
return;
}
const result = await this.state.client.request({
topic: this.state.session.topic,
chainId,
request: {
method: "eth_sendTransaction",
params: [tx],
},
});
// format displayed result
const formattedResult = {
method: "eth_sendTransaction",
address,
valid: true,
result,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public testSignPersonalMessage = async (chainId: string) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// test message
const message = `My email is john@doe.com - ${Date.now()}`;
// encode message (hex)
const hexMsg = encoding.utf8ToHex(message, true);
// get ethereum address
const account = this.state.accounts.find(account => account.startsWith(chainId));
if (account === undefined) throw new Error("Account is not found");
const address = account.split(":").pop();
if (address === undefined) throw new Error("Address is invalid");
// personal_sign params
const params = [hexMsg, address];
// open modal
this.openRequestModal();
// send message
const result = await this.state.client.request({
topic: this.state.session.topic,
chainId,
request: {
method: "personal_sign",
params,
},
});
// split chainId
const [namespace, reference] = chainId.split(":");
const chainData = this.state.chainData[namespace][reference];
if (typeof chainData === "undefined") {
throw new Error(`Missing chain data for chainId: ${chainId}`);
}
const rpcUrl = chainData.rpc[0];
// verify signature
const hash = hashPersonalMessage(message);
const valid = await verifySignature(address, result, hash, rpcUrl);
// format displayed result
const formattedResult = {
method: "personal_sign",
address,
valid,
result,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public testSignTypedData = async (chainId: string) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// test message
const message = JSON.stringify(eip712.example);
// get ethereum address
const account = this.state.accounts.find(account => account.startsWith(chainId));
if (account === undefined) throw new Error("Account is not found");
const address = account.split(":").pop();
if (address === undefined) throw new Error("Address is invalid");
// eth_signTypedData params
const params = [address, message];
// open modal
this.openRequestModal();
// send message
const result = await this.state.client.request({
topic: this.state.session.topic,
chainId,
request: {
method: "eth_signTypedData",
params,
},
});
// split chainId
const [namespace, reference] = chainId.split(":");
const chainData = this.state.chainData[namespace][reference];
if (typeof chainData === "undefined") {
throw new Error(`Missing chain data for chainId: ${chainId}`);
}
const rpcUrl = chainData.rpc[0];
// verify signature
const hash = hashTypedDataMessage(message);
const valid = await verifySignature(address, result, hash, rpcUrl);
// format displayed result
const formattedResult = {
method: "eth_signTypedData",
address,
valid,
result,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public testSignDirect = async (chainId: string) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// test direct sign doc inputs
const inputs = {
fee: [{ amount: "2000", denom: "ucosm" }],
pubkey: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW",
gasLimit: 200000,
accountNumber: 1,
sequence: 1,
bodyBytes:
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
authInfoBytes:
"0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c",
};
// split chainId
const [namespace, reference] = chainId.split(":");
// format sign doc
const signDoc = formatDirectSignDoc(
inputs.fee,
inputs.pubkey,
inputs.gasLimit,
inputs.accountNumber,
inputs.sequence,
inputs.bodyBytes,
reference,
);
// get cosmos address
const account = this.state.accounts.find(account => account.startsWith(chainId));
if (account === undefined) throw new Error("Account is not found");
const address = account.split(":").pop();
if (address === undefined) throw new Error("Address is invalid");
// cosmos_signDirect params
const params = {
signerAddress: address,
signDoc: stringifySignDocValues(signDoc),
};
// open modal
this.openRequestModal();
// send message
const result = await this.state.client.request({
topic: this.state.session.topic,
chainId,
request: {
method: "cosmos_signDirect",
params,
},
});
const chainData = this.state.chainData[namespace][reference];
if (typeof chainData === "undefined") {
throw new Error(`Missing chain data for chainId: ${chainId}`);
}
// TODO: check if valid
const valid = true;
// format displayed result
const formattedResult = {
method: "cosmos_signDirect",
address,
valid,
result: result.signature.signature,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public testSignAmino = async (chainId: string) => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// split chainId
const [namespace, reference] = chainId.split(":");
// test amino sign doc
const signDoc = {
msgs: [],
fee: { amount: [], gas: "23" },
chain_id: "foochain",
memo: "hello, world",
account_number: "7",
sequence: "54",
};
// get cosmos address
const account = this.state.accounts.find(account => account.startsWith(chainId));
if (account === undefined) throw new Error("Account is not found");
const address = account.split(":").pop();
if (address === undefined) throw new Error("Address is invalid");
// cosmos_signAmino params
const params = { signerAddress: address, signDoc };
// open modal
this.openRequestModal();
// send message
const result = await this.state.client.request({
topic: this.state.session.topic,
chainId,
request: {
method: "cosmos_signAmino",
params,
},
});
const chainData = this.state.chainData[namespace][reference];
if (typeof chainData === "undefined") {
throw new Error(`Missing chain data for chainId: ${chainId}`);
}
// TODO: check if valid
const valid = true;
// format displayed result
const formattedResult = {
method: "cosmos_signAmino",
address,
valid,
result: result.signature.signature,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public ping = async () => {
if (typeof this.state.client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (typeof this.state.session === "undefined") {
throw new Error("Session is not connected");
}
try {
// open modal
this.openPingModal();
let valid = false;
try {
await this.state.client.session.ping(this.state.session.topic);
valid = true;
} catch (e) {
valid = false;
}
// format displayed result
const formattedResult = {
method: "ping",
valid,
};
// display result
this.setState({ pending: false, result: formattedResult || null });
} catch (e) {
console.error(e);
this.setState({ pending: false, result: null });
}
};
public handleChainSelectionClick = (chainId: string) => {
const { chains } = this.state;
if (chains.includes(chainId)) {
this.setState({ chains: chains.filter(x => x !== chainId) });
} else {
this.setState({ chains: [...chains, chainId] });
}
};
public getBlockchainActions = (chainId: string) => {
const [namespace] = chainId.split(":"); const [namespace] = chainId.split(":");
switch (namespace) { switch (namespace) {
case "eip155": case "eip155":
return this.getEthereumActions(); return getEthereumActions();
case "cosmos": case "cosmos":
return this.getCosmosActions(); return getCosmosActions();
default: default:
break; break;
} }
}; };
public getEthereumActions = (): AccountAction[] => { const toggleTestnets = () => {
return [ const nextIsTestnetState = !isTestnet;
{ method: "eth_sendTransaction", callback: this.testSendTransaction }, setIsTestnet(nextIsTestnetState);
{ method: "personal_sign", callback: this.testSignPersonalMessage }, // TODO: rename "setLocalStorage..."
{ method: "eth_signTypedData", callback: this.testSignTypedData }, setInitialStateTestnet(nextIsTestnetState);
];
}; };
public getCosmosActions = (): AccountAction[] => { const handleChainSelectionClick = (chainId: string) => {
return [ if (chains.includes(chainId)) {
{ method: "cosmos_signDirect", callback: this.testSignDirect }, setChains(chains.filter(chain => chain !== chainId));
{ method: "cosmos_signAmino", callback: this.testSignAmino }, } else {
]; setChains([...chains, chainId]);
}
}; };
public renderModal = () => { const renderModal = () => {
switch (this.state.modal) { switch (modal) {
case "pairing": case "pairing":
if (typeof this.state.client === "undefined") { if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized"); throw new Error("WalletConnect is not initialized");
} }
return <PairingModal pairings={this.state.client.pairing.values} connect={this.connect} />; return <PairingModal pairings={client.pairing.values} connect={connect} />;
case "request": case "request":
return <RequestModal pending={this.state.pending} result={this.state.result} />; return <RequestModal pending={isRpcRequestPending} result={rpcResult} />;
case "ping": case "ping":
return <PingModal pending={this.state.pending} result={this.state.result} />; return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
default: default:
return null; return null;
} }
}; };
public renderContent = () => { const renderContent = () => {
const { balances, accounts, chains, chainData, testnet, fetching } = this.state; const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS;
const chainOptions = testnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS;
return !accounts.length && !Object.keys(balances).length ? ( return !accounts.length && !Object.keys(balances).length ? (
<SLanding center> <SLanding center>
<Banner /> <Banner />
<h6> <h6>
<span>{`Using v${process.env.REACT_APP_VERSION || "2.0.0-beta"}`}</span> <span>{`Using v${version || "2.0.0-beta"}`}</span>
</h6> </h6>
<SButtonContainer> <SButtonContainer>
<h6>Select chains:</h6> <h6>Select chains:</h6>
<SToggleContainer> <SToggleContainer>
<p>Testnets Only?</p> <p>Testnets Only?</p>
<Toggle active={testnet} onClick={this.toggleTestnets} /> <Toggle active={isTestnet} onClick={toggleTestnets} />
</SToggleContainer> </SToggleContainer>
{chainOptions.map(chainId => ( {chainOptions.map(chainId => (
<Blockchain <Blockchain
key={chainId} key={chainId}
chainId={chainId} chainId={chainId}
chainData={chainData} chainData={chainData}
onClick={this.handleChainSelectionClick} onClick={handleChainSelectionClick}
active={chains.includes(chainId)} active={chains.includes(chainId)}
/> />
))} ))}
<SConnectButton <SConnectButton left onClick={onConnect} fetching={fetching} disabled={!chains.length}>
left
onClick={this.onConnect}
fetching={fetching}
disabled={!chains.length}
>
{"Connect"} {"Connect"}
</SConnectButton> </SConnectButton>
</SButtonContainer> </SButtonContainer>
@ -852,7 +183,7 @@ class App extends React.Component<any, any> {
<SAccountsContainer> <SAccountsContainer>
<h3>Accounts</h3> <h3>Accounts</h3>
<SAccounts> <SAccounts>
{this.state.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 (
@ -864,7 +195,7 @@ class App extends React.Component<any, any> {
address={address} address={address}
chainId={chainId} chainId={chainId}
balances={balances} balances={balances}
actions={this.getBlockchainActions(chainId)} actions={getBlockchainActions(chainId)}
/> />
); );
})} })}
@ -873,20 +204,15 @@ class App extends React.Component<any, any> {
); );
}; };
public render = () => {
const { loading, session, modal } = this.state;
return ( return (
<SLayout> <SLayout>
<Column maxWidth={1000} spanHeight> <Column maxWidth={1000} spanHeight>
<Header ping={this.ping} disconnect={this.disconnect} session={session} /> <Header ping={onPing} disconnect={disconnect} session={session} />
<SContent>{loading ? "Loading..." : this.renderContent()}</SContent> <SContent>{loading ? "Loading..." : renderContent()}</SContent>
</Column> </Column>
<Modal show={!!modal} closeModal={this.closeModal}> <Modal show={!!modal} closeModal={closeModal}>
{this.renderModal()} {renderModal()}
</Modal> </Modal>
</SLayout> </SLayout>
); );
};
} }
export default App;

View File

@ -1,218 +0,0 @@
import React, { useEffect, useState } from "react";
import { version } from "@walletconnect/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 { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants";
import { AccountAction, setInitialStateTestnet, getInitialStateTestnet } 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,
SButtonContainer,
SConnectButton,
SContent,
SLanding,
SLayout,
SToggleContainer,
} from "./components/app";
import { useWalletConnectClient } from "./contexts/ClientContext";
import { useJsonRpc } from "./contexts/JsonRpcContext";
export default function App() {
const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
const [modal, setModal] = useState("");
const closeModal = () => setModal("");
const openPairingModal = () => setModal("pairing");
const openPingModal = () => setModal("ping");
const openRequestModal = () => setModal("request");
const {
client,
session,
connect,
disconnect,
chains,
accounts,
balances,
fetching,
loading,
setChains,
} = useWalletConnectClient();
const { chainData, ping, ethereumRpc, cosmosRpc, isRpcRequestPending, rpcResult } = useJsonRpc();
useEffect(() => {
// Close the pairing modal after a session is established.
if (session && modal === "pairing") {
closeModal();
}
}, [session, modal]);
const onConnect = () => {
if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
if (client.pairing.topics.length) {
return openPairingModal();
}
connect();
};
const onPing = async () => {
openPingModal();
await ping();
};
const getEthereumActions = (): AccountAction[] => {
const onSendTransaction = async (chainId: string) => {
openRequestModal();
await ethereumRpc.testSendTransaction(chainId);
};
const onSignPersonalMessage = async (chainId: string) => {
openRequestModal();
await ethereumRpc.testSignPersonalMessage(chainId);
};
const onSignTypedData = async (chainId: string) => {
openRequestModal();
await ethereumRpc.testSignTypedData(chainId);
};
return [
{ method: "eth_sendTransaction", callback: onSendTransaction },
{ method: "personal_sign", callback: onSignPersonalMessage },
{ method: "eth_signTypedData", callback: onSignTypedData },
];
};
const getCosmosActions = (): AccountAction[] => {
const onSignDirect = async (chainId: string) => {
openRequestModal();
await cosmosRpc.testSignDirect(chainId);
};
const onSignAmino = async (chainId: string) => {
openRequestModal();
await cosmosRpc.testSignAmino(chainId);
};
return [
{ method: "cosmos_signDirect", callback: onSignDirect },
{ method: "cosmos_signAmino", callback: onSignAmino },
];
};
const getBlockchainActions = (chainId: string) => {
const [namespace] = chainId.split(":");
switch (namespace) {
case "eip155":
return getEthereumActions();
case "cosmos":
return getCosmosActions();
default:
break;
}
};
const toggleTestnets = () => {
const nextIsTestnetState = !isTestnet;
setIsTestnet(nextIsTestnetState);
// TODO: rename "setLocalStorage..."
setInitialStateTestnet(nextIsTestnetState);
};
const handleChainSelectionClick = (chainId: string) => {
if (chains.includes(chainId)) {
setChains(chains.filter(chain => chain !== chainId));
} else {
setChains([...chains, chainId]);
}
};
const renderModal = () => {
switch (modal) {
case "pairing":
if (typeof client === "undefined") {
throw new Error("WalletConnect is not initialized");
}
return <PairingModal pairings={client.pairing.values} connect={connect} />;
case "request":
return <RequestModal pending={isRpcRequestPending} result={rpcResult} />;
case "ping":
return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
default:
return null;
}
};
const renderContent = () => {
const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS;
return !accounts.length && !Object.keys(balances).length ? (
<SLanding center>
<Banner />
<h6>
<span>{`Using v${version || "2.0.0-beta"}`}</span>
</h6>
<SButtonContainer>
<h6>Select chains:</h6>
<SToggleContainer>
<p>Testnets Only?</p>
<Toggle active={isTestnet} onClick={toggleTestnets} />
</SToggleContainer>
{chainOptions.map(chainId => (
<Blockchain
key={chainId}
chainId={chainId}
chainData={chainData}
onClick={handleChainSelectionClick}
active={chains.includes(chainId)}
/>
))}
<SConnectButton left onClick={onConnect} fetching={fetching} disabled={!chains.length}>
{"Connect"}
</SConnectButton>
</SButtonContainer>
</SLanding>
) : (
<SAccountsContainer>
<h3>Accounts</h3>
<SAccounts>
{accounts.map(account => {
const [namespace, reference, address] = account.split(":");
const chainId = `${namespace}:${reference}`;
return (
<Blockchain
key={account}
active={true}
chainData={chainData}
fetching={fetching}
address={address}
chainId={chainId}
balances={balances}
actions={getBlockchainActions(chainId)}
/>
);
})}
</SAccounts>
</SAccountsContainer>
);
};
return (
<SLayout>
<Column maxWidth={1000} spanHeight>
<Header ping={onPing} disconnect={disconnect} session={session} />
<SContent>{loading ? "Loading..." : renderContent()}</SContent>
</Column>
<Modal show={!!modal} closeModal={closeModal}>
{renderModal()}
</Modal>
</SLayout>
);
}

View File

@ -4,7 +4,7 @@ import { createGlobalStyle } from "styled-components";
import { ClientContextProvider } from "./contexts/ClientContext"; import { ClientContextProvider } from "./contexts/ClientContext";
import { JsonRpcContextProvider } from "./contexts/JsonRpcContext"; import { JsonRpcContextProvider } from "./contexts/JsonRpcContext";
import HooksApp from "./HooksApp"; import App from "./App";
import { globalStyle } from "./styles"; import { globalStyle } from "./styles";
const GlobalStyle = createGlobalStyle` const GlobalStyle = createGlobalStyle`
${globalStyle} ${globalStyle}
@ -22,7 +22,7 @@ ReactDOM.render(
<GlobalStyle /> <GlobalStyle />
<ClientContextProvider> <ClientContextProvider>
<JsonRpcContextProvider> <JsonRpcContextProvider>
<HooksApp /> <App />
</JsonRpcContextProvider> </JsonRpcContextProvider>
</ClientContextProvider> </ClientContextProvider>
</>, </>,