refactor: creates standalone JsonRpcContext
This commit is contained in:
parent
34e175f24c
commit
7eeefd4f65
@ -1,9 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import * as encoding from "@walletconnect/encoding";
|
// import { formatDirectSignDoc, stringifySignDocValues } from "cosmos-wallet";
|
||||||
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";
|
||||||
@ -11,18 +8,7 @@ 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 { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants";
|
import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants";
|
||||||
import {
|
import { AccountAction, setInitialStateTestnet, getInitialStateTestnet } from "./helpers";
|
||||||
AccountAction,
|
|
||||||
eip712,
|
|
||||||
hashPersonalMessage,
|
|
||||||
hashTypedDataMessage,
|
|
||||||
verifySignature,
|
|
||||||
formatTestTransaction,
|
|
||||||
ChainNamespaces,
|
|
||||||
setInitialStateTestnet,
|
|
||||||
getInitialStateTestnet,
|
|
||||||
getAllChainNamespaces,
|
|
||||||
} 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";
|
||||||
@ -38,27 +24,13 @@ import {
|
|||||||
SToggleContainer,
|
SToggleContainer,
|
||||||
} from "./components/app";
|
} from "./components/app";
|
||||||
import { useWalletConnectClient } from "./contexts/ClientContext";
|
import { useWalletConnectClient } from "./contexts/ClientContext";
|
||||||
|
import { useJsonRpc } from "./contexts/JsonRpcContext";
|
||||||
interface FormattedRpcResponse {
|
|
||||||
method: string;
|
|
||||||
address: string;
|
|
||||||
valid: boolean;
|
|
||||||
result: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [pending, setPending] = useState(false);
|
|
||||||
const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
|
const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
|
||||||
|
|
||||||
const [modal, setModal] = useState("");
|
const [modal, setModal] = useState("");
|
||||||
|
|
||||||
const [result, setResult] = useState<{
|
|
||||||
method: string;
|
|
||||||
valid: boolean;
|
|
||||||
} | null>();
|
|
||||||
|
|
||||||
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
|
||||||
|
|
||||||
const closeModal = () => setModal("");
|
const closeModal = () => setModal("");
|
||||||
const openPairingModal = () => setModal("pairing");
|
const openPairingModal = () => setModal("pairing");
|
||||||
const openPingModal = () => setModal("ping");
|
const openPingModal = () => setModal("ping");
|
||||||
@ -77,9 +49,15 @@ export default function App() {
|
|||||||
setChains,
|
setChains,
|
||||||
} = useWalletConnectClient();
|
} = useWalletConnectClient();
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
loadChainData();
|
chainData,
|
||||||
}, []);
|
ping,
|
||||||
|
testSendTransaction,
|
||||||
|
testSignPersonalMessage,
|
||||||
|
testSignTypedData,
|
||||||
|
isRpcRequestPending,
|
||||||
|
rpcResult,
|
||||||
|
} = useJsonRpc();
|
||||||
|
|
||||||
const onConnect = () => {
|
const onConnect = () => {
|
||||||
if (typeof client === "undefined") {
|
if (typeof client === "undefined") {
|
||||||
@ -91,399 +69,38 @@ export default function App() {
|
|||||||
connect();
|
connect();
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadChainData = async () => {
|
const onPing = async () => {
|
||||||
const namespaces = getAllChainNamespaces();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setChainData(chainData);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----- EVM RPC -----
|
|
||||||
|
|
||||||
const createJsonRpcRequestHandler =
|
|
||||||
(rpcRequest: (...requestArgs: [any]) => Promise<FormattedRpcResponse>) =>
|
|
||||||
async (chainId: string) => {
|
|
||||||
if (typeof client === "undefined") {
|
|
||||||
throw new Error("WalletConnect is not initialized");
|
|
||||||
}
|
|
||||||
if (typeof session === "undefined") {
|
|
||||||
throw new Error("Session is not connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setPending(true);
|
|
||||||
const result = await rpcRequest(chainId);
|
|
||||||
setResult(result);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
setResult(null);
|
|
||||||
} finally {
|
|
||||||
setPending(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ping = async () => {
|
|
||||||
if (typeof client === "undefined") {
|
|
||||||
throw new Error("WalletConnect is not initialized");
|
|
||||||
}
|
|
||||||
if (typeof session === "undefined") {
|
|
||||||
throw new Error("Session is not connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setPending(true);
|
|
||||||
openPingModal();
|
openPingModal();
|
||||||
|
await ping();
|
||||||
let valid = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.session.ping(session.topic);
|
|
||||||
valid = true;
|
|
||||||
} catch (e) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// display result
|
|
||||||
setResult({
|
|
||||||
method: "ping",
|
|
||||||
valid,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
setResult(null);
|
|
||||||
} finally {
|
|
||||||
setPending(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testSendTransaction = createJsonRpcRequestHandler(async (chainId: string) => {
|
const onSendTransaction = async (chainId: string) => {
|
||||||
// get ethereum address
|
|
||||||
const account = 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
|
|
||||||
openRequestModal();
|
openRequestModal();
|
||||||
|
await testSendTransaction(chainId);
|
||||||
const tx = await formatTestTransaction(account);
|
|
||||||
|
|
||||||
const balance = BigNumber.from(balances[account][0].balance || "0");
|
|
||||||
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
|
|
||||||
return {
|
|
||||||
method: "eth_sendTransaction",
|
|
||||||
address,
|
|
||||||
valid: false,
|
|
||||||
result: "Insufficient funds for intrinsic transaction cost",
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const result: string = await client!.request({
|
const onSignPersonalMessage = async (chainId: string) => {
|
||||||
topic: session!.topic,
|
|
||||||
chainId,
|
|
||||||
request: {
|
|
||||||
method: "eth_sendTransaction",
|
|
||||||
params: [tx],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// format displayed result
|
|
||||||
return {
|
|
||||||
method: "eth_sendTransaction",
|
|
||||||
address,
|
|
||||||
valid: true,
|
|
||||||
result,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const testSignPersonalMessage = createJsonRpcRequestHandler(async (chainId: string) => {
|
|
||||||
// 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 = 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
|
|
||||||
openRequestModal();
|
openRequestModal();
|
||||||
|
await testSignPersonalMessage(chainId);
|
||||||
// send message
|
|
||||||
const result: string = await client!.request({
|
|
||||||
topic: session!.topic,
|
|
||||||
chainId,
|
|
||||||
request: {
|
|
||||||
method: "personal_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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpcUrl = targetChainData.rpc[0];
|
|
||||||
|
|
||||||
// verify signature
|
|
||||||
const hash = hashPersonalMessage(message);
|
|
||||||
const valid = await verifySignature(address, result, hash, rpcUrl);
|
|
||||||
|
|
||||||
// format displayed result
|
|
||||||
return {
|
|
||||||
method: "personal_sign",
|
|
||||||
address,
|
|
||||||
valid,
|
|
||||||
result,
|
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const testSignTypedData = createJsonRpcRequestHandler(async (chainId: string) => {
|
const onSignTypedData = async (chainId: string) => {
|
||||||
// test message
|
|
||||||
const message = JSON.stringify(eip712.example);
|
|
||||||
|
|
||||||
// get ethereum address
|
|
||||||
const account = 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
|
|
||||||
openRequestModal();
|
openRequestModal();
|
||||||
|
await testSignTypedData(chainId);
|
||||||
// send message
|
|
||||||
const result = await client!.request({
|
|
||||||
topic: session!.topic,
|
|
||||||
chainId,
|
|
||||||
request: {
|
|
||||||
method: "eth_signTypedData",
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpcUrl = targetChainData.rpc[0];
|
|
||||||
|
|
||||||
// verify signature
|
|
||||||
const hash = hashTypedDataMessage(message);
|
|
||||||
const valid = await verifySignature(address, result, hash, rpcUrl);
|
|
||||||
|
|
||||||
// format displayed result
|
|
||||||
return {
|
|
||||||
method: "eth_signTypedData",
|
|
||||||
address,
|
|
||||||
valid,
|
|
||||||
result,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// ------ COSMOS RPC ------
|
|
||||||
|
|
||||||
const testSignDirect = async (chainId: string) => {
|
|
||||||
if (typeof client === "undefined") {
|
|
||||||
throw new Error("WalletConnect is not initialized");
|
|
||||||
}
|
|
||||||
if (typeof 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 = 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
|
|
||||||
openRequestModal();
|
|
||||||
|
|
||||||
// send message
|
|
||||||
const result = await client.request({
|
|
||||||
topic: session.topic,
|
|
||||||
chainId,
|
|
||||||
request: {
|
|
||||||
method: "cosmos_signDirect",
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const targetChainData = chainData[namespace][reference];
|
|
||||||
|
|
||||||
if (typeof targetChainData === "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
|
|
||||||
setResult(formattedResult);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
setResult(null);
|
|
||||||
} finally {
|
|
||||||
setPending(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const testSignAmino = async (chainId: string) => {
|
|
||||||
if (typeof client === "undefined") {
|
|
||||||
throw new Error("WalletConnect is not initialized");
|
|
||||||
}
|
|
||||||
if (typeof 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 = 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
|
|
||||||
openRequestModal();
|
|
||||||
|
|
||||||
// send message
|
|
||||||
const result = await client.request({
|
|
||||||
topic: session.topic,
|
|
||||||
chainId,
|
|
||||||
request: {
|
|
||||||
method: "cosmos_signAmino",
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const targetChainData = chainData[namespace][reference];
|
|
||||||
|
|
||||||
if (typeof targetChainData === "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
|
|
||||||
setResult(formattedResult);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
setResult(null);
|
|
||||||
} finally {
|
|
||||||
setPending(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEthereumActions = (): AccountAction[] => {
|
const getEthereumActions = (): AccountAction[] => {
|
||||||
return [
|
return [
|
||||||
{ method: "eth_sendTransaction", callback: testSendTransaction },
|
{ method: "eth_sendTransaction", callback: onSendTransaction },
|
||||||
{ method: "personal_sign", callback: testSignPersonalMessage },
|
{ method: "personal_sign", callback: onSignPersonalMessage },
|
||||||
{ method: "eth_signTypedData", callback: testSignTypedData },
|
{ method: "eth_signTypedData", callback: onSignTypedData },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCosmosActions = (): AccountAction[] => {
|
const getCosmosActions = (): AccountAction[] => {
|
||||||
return [
|
return [
|
||||||
{ method: "cosmos_signDirect", callback: testSignDirect },
|
// { method: "cosmos_signDirect", callback: testSignDirect },
|
||||||
{ method: "cosmos_signAmino", callback: testSignAmino },
|
// { method: "cosmos_signAmino", callback: testSignAmino },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -522,9 +139,9 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
return <PairingModal pairings={client.pairing.values} connect={connect} />;
|
return <PairingModal pairings={client.pairing.values} connect={connect} />;
|
||||||
case "request":
|
case "request":
|
||||||
return <RequestModal pending={pending} result={result} />;
|
return <RequestModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||||
case "ping":
|
case "ping":
|
||||||
return <PingModal pending={pending} result={result} />;
|
return <PingModal pending={isRpcRequestPending} result={rpcResult} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -586,7 +203,7 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<SLayout>
|
<SLayout>
|
||||||
<Column maxWidth={1000} spanHeight>
|
<Column maxWidth={1000} spanHeight>
|
||||||
<Header ping={ping} disconnect={disconnect} session={session} />
|
<Header ping={onPing} disconnect={disconnect} session={session} />
|
||||||
<SContent>{loading ? "Loading..." : renderContent()}</SContent>
|
<SContent>{loading ? "Loading..." : renderContent()}</SContent>
|
||||||
</Column>
|
</Column>
|
||||||
<Modal show={!!modal} closeModal={closeModal}>
|
<Modal show={!!modal} closeModal={closeModal}>
|
||||||
|
453
dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx
Normal file
453
dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
import { BigNumber } from "ethers";
|
||||||
|
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||||
|
import * as encoding from "@walletconnect/encoding";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChainNamespaces,
|
||||||
|
eip712,
|
||||||
|
formatTestTransaction,
|
||||||
|
getAllChainNamespaces,
|
||||||
|
hashPersonalMessage,
|
||||||
|
hashTypedDataMessage,
|
||||||
|
verifySignature,
|
||||||
|
} from "../helpers";
|
||||||
|
import { useWalletConnectClient } from "./ClientContext";
|
||||||
|
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
interface IFormattedRpcResponse {
|
||||||
|
method: string;
|
||||||
|
address: string;
|
||||||
|
valid: boolean;
|
||||||
|
result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRpcResult {
|
||||||
|
method: string;
|
||||||
|
valid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IContext {
|
||||||
|
ping: () => Promise<void>;
|
||||||
|
testSendTransaction: (chainId: string) => Promise<void>;
|
||||||
|
testSignPersonalMessage: (chainId: string) => Promise<void>;
|
||||||
|
testSignTypedData: (chainId: string) => Promise<void>;
|
||||||
|
chainData: ChainNamespaces;
|
||||||
|
rpcResult?: IRpcResult | null;
|
||||||
|
isRpcRequestPending: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
export const JsonRpcContext = createContext<IContext>({} as IContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider
|
||||||
|
*/
|
||||||
|
export function JsonRpcContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||||
|
const [pending, setPending] = useState(false);
|
||||||
|
const [result, setResult] = useState<IRpcResult | null>();
|
||||||
|
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
||||||
|
|
||||||
|
const { client, session, accounts, balances } = useWalletConnectClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadChainData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadChainData = async () => {
|
||||||
|
const namespaces = getAllChainNamespaces();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setChainData(chainData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _createJsonRpcRequestHandler =
|
||||||
|
(rpcRequest: (...requestArgs: [any]) => Promise<IFormattedRpcResponse>) =>
|
||||||
|
async (chainId: string) => {
|
||||||
|
if (typeof client === "undefined") {
|
||||||
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
}
|
||||||
|
if (typeof session === "undefined") {
|
||||||
|
throw new Error("Session is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPending(true);
|
||||||
|
const result = await rpcRequest(chainId);
|
||||||
|
setResult(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ping = async () => {
|
||||||
|
if (typeof client === "undefined") {
|
||||||
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
}
|
||||||
|
if (typeof session === "undefined") {
|
||||||
|
throw new Error("Session is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPending(true);
|
||||||
|
|
||||||
|
let valid = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.session.ping(session.topic);
|
||||||
|
valid = true;
|
||||||
|
} catch (e) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display result
|
||||||
|
setResult({
|
||||||
|
method: "ping",
|
||||||
|
valid,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setPending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testSendTransaction = _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// get ethereum address
|
||||||
|
const account = 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");
|
||||||
|
|
||||||
|
const tx = await formatTestTransaction(account);
|
||||||
|
|
||||||
|
const balance = BigNumber.from(balances[account][0].balance || "0");
|
||||||
|
if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) {
|
||||||
|
return {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
address,
|
||||||
|
valid: false,
|
||||||
|
result: "Insufficient funds for intrinsic transaction cost",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: string = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
params: [tx],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "eth_sendTransaction",
|
||||||
|
address,
|
||||||
|
valid: true,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const testSignPersonalMessage = _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// 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 = 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];
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result: string = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "personal_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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpcUrl = targetChainData.rpc[0];
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
const hash = hashPersonalMessage(message);
|
||||||
|
const valid = await verifySignature(address, result, hash, rpcUrl);
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "personal_sign",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const testSignTypedData = _createJsonRpcRequestHandler(async (chainId: string) => {
|
||||||
|
// test message
|
||||||
|
const message = JSON.stringify(eip712.example);
|
||||||
|
|
||||||
|
// get ethereum address
|
||||||
|
const account = 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];
|
||||||
|
|
||||||
|
// send message
|
||||||
|
const result = await client!.request({
|
||||||
|
topic: session!.topic,
|
||||||
|
chainId,
|
||||||
|
request: {
|
||||||
|
method: "eth_signTypedData",
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpcUrl = targetChainData.rpc[0];
|
||||||
|
|
||||||
|
// verify signature
|
||||||
|
const hash = hashTypedDataMessage(message);
|
||||||
|
const valid = await verifySignature(address, result, hash, rpcUrl);
|
||||||
|
|
||||||
|
// format displayed result
|
||||||
|
return {
|
||||||
|
method: "eth_signTypedData",
|
||||||
|
address,
|
||||||
|
valid,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JsonRpcContext.Provider
|
||||||
|
value={{
|
||||||
|
chainData,
|
||||||
|
ping,
|
||||||
|
testSendTransaction,
|
||||||
|
testSignPersonalMessage,
|
||||||
|
testSignTypedData,
|
||||||
|
rpcResult: result,
|
||||||
|
isRpcRequestPending: pending,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</JsonRpcContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useJsonRpc() {
|
||||||
|
const context = useContext(JsonRpcContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useJsonRpc must be used within a JsonRpcContextProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ COSMOS RPC ------
|
||||||
|
|
||||||
|
// const testSignDirect = async (chainId: string) => {
|
||||||
|
// if (typeof client === "undefined") {
|
||||||
|
// throw new Error("WalletConnect is not initialized");
|
||||||
|
// }
|
||||||
|
// if (typeof 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 = 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
|
||||||
|
// openRequestModal();
|
||||||
|
|
||||||
|
// // send message
|
||||||
|
// const result = await client.request({
|
||||||
|
// topic: session.topic,
|
||||||
|
// chainId,
|
||||||
|
// request: {
|
||||||
|
// method: "cosmos_signDirect",
|
||||||
|
// params,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
// if (typeof targetChainData === "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
|
||||||
|
// setResult(formattedResult);
|
||||||
|
// } catch (e) {
|
||||||
|
// console.error(e);
|
||||||
|
// setResult(null);
|
||||||
|
// } finally {
|
||||||
|
// setPending(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const testSignAmino = async (chainId: string) => {
|
||||||
|
// if (typeof client === "undefined") {
|
||||||
|
// throw new Error("WalletConnect is not initialized");
|
||||||
|
// }
|
||||||
|
// if (typeof 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 = 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
|
||||||
|
// openRequestModal();
|
||||||
|
|
||||||
|
// // send message
|
||||||
|
// const result = await client.request({
|
||||||
|
// topic: session.topic,
|
||||||
|
// chainId,
|
||||||
|
// request: {
|
||||||
|
// method: "cosmos_signAmino",
|
||||||
|
// params,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const targetChainData = chainData[namespace][reference];
|
||||||
|
|
||||||
|
// if (typeof targetChainData === "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
|
||||||
|
// setResult(formattedResult);
|
||||||
|
// } catch (e) {
|
||||||
|
// console.error(e);
|
||||||
|
// setResult(null);
|
||||||
|
// } finally {
|
||||||
|
// setPending(false);
|
||||||
|
// }
|
||||||
|
// };
|
@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { createGlobalStyle } from "styled-components";
|
import { createGlobalStyle } from "styled-components";
|
||||||
import { ClientContextProvider } from "./contexts/ClientContext";
|
import { ClientContextProvider } from "./contexts/ClientContext";
|
||||||
|
import { JsonRpcContextProvider } from "./contexts/JsonRpcContext";
|
||||||
|
|
||||||
import HooksApp from "./HooksApp";
|
import HooksApp from "./HooksApp";
|
||||||
import { globalStyle } from "./styles";
|
import { globalStyle } from "./styles";
|
||||||
@ -20,7 +21,9 @@ ReactDOM.render(
|
|||||||
<>
|
<>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<ClientContextProvider>
|
<ClientContextProvider>
|
||||||
|
<JsonRpcContextProvider>
|
||||||
<HooksApp />
|
<HooksApp />
|
||||||
|
</JsonRpcContextProvider>
|
||||||
</ClientContextProvider>
|
</ClientContextProvider>
|
||||||
</>,
|
</>,
|
||||||
document.getElementById("root"),
|
document.getElementById("root"),
|
||||||
|
Loading…
Reference in New Issue
Block a user