chore: remove old class-based App component
This commit is contained in:
parent
22c0a7c456
commit
601f16b171
@ -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;
|
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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>
|
||||||
</>,
|
</>,
|
||||||
|
Loading…
Reference in New Issue
Block a user