Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Feat/dashboard truffle configured nets #4937

49 changes: 48 additions & 1 deletion packages/core/lib/commands/dashboard/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,54 @@ module.exports = async function (options) {
}
];

const dashboardServerOptions = { port, host, verbose, publicChains };
const mainnet = publicChains[0];
const mergedChains = [...publicChains];
if (config.networks) {
const chainNames = Object.keys(config.networks);

// filters out all truffle-config chains that wouldn't make valid dashboard
// networks. for the rest, it assembles the relevant data and fills in
// mainnet data where needed to make a `dashboardChain`
const configuredNetworks = chainNames.reduce((filtered, chainName) => {
const chain = config.networks[chainName];
if (
chainName !== "dashboard" &&
chainName !== "test" &&
chainName !== "develop" &&
((chain.host && chain.port) || // either has a host/port
(chain.rpcUrls && chain.rpcUrls.length > 0)) // or they provided an rpc url
) {
// if they didn't provide an rpc url and thus are using local host,
// assume this is their own local instance of a chain
const isLocalChain = !chain.rpcUrls || chain.rpcUrls.length === 0;
filtered.push({
chainId: chain.chainId ? chain.chainId : undefined,
chainName: chainName,
nativeCurrency: chain.nativeCurrency
? chain.nativeCurrency
: mainnet.nativeCurrency,
rpcUrls: chain.rpcUrls
? chain.rpcUrls
: [`http://${chain.host}:${chain.port}`],
blockExplorerUrls: chain.blockExplorerUrls
? chain.blockExplorerUrls
: undefined,
isLocalChain
});
}
return filtered;
}, []);

mergedChains.push(...configuredNetworks);
console.log(mergedChains);
}

const dashboardServerOptions = {
port,
host,
verbose,
dashboardChains: mergedChains
};
const dashboardServer = new DashboardServer(dashboardServerOptions);
await dashboardServer.start();

Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard-message-bus/lib/message/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface InvalidateMessage extends Message {
export interface InitializeMessage extends Message {
type: "initialize";
payload: {
publicChains: object[];
dashboardChains: object[];
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/bin/start-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const options = {
host: "localhost",
verbose: true,
autoOpen: false,
publicChains: [
dashboardChains: [
{
chainId: "0x1",
chainName: "mainnet",
Expand Down
11 changes: 6 additions & 5 deletions packages/dashboard/lib/DashboardServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import debugModule from "debug";
* Public ethereum chains that can be added to a wallet and switched via the
* dashboard's network manager. Currently based off of https://docs.metamask.io/guide/rpc-api.html#unrestricted-methods
*/
export interface PublicChain {
export interface DashboardChain {
chainId: string;
chainName: string;
nativeCurrency: {
Expand All @@ -32,6 +32,7 @@ export interface PublicChain {
rpcUrls: string[];
blockExplorerUrls?: string[];
iconUrls?: string[];
isLocalChain?: boolean;
}

export interface DashboardServerOptions {
Expand All @@ -48,7 +49,7 @@ export interface DashboardServerOptions {
autoOpen?: boolean;

/** Chain array used to populate the list of public chains to display in the dashboard network manager. */
publicChains: PublicChain[];
dashboardChains: DashboardChain[];
}

export class DashboardServer {
Expand All @@ -57,7 +58,7 @@ export class DashboardServer {
verbose: boolean;
autoOpen: boolean;
frontendPath: string;
publicChains: PublicChain[];
dashboardChains: DashboardChain[];

private expressApp?: Application;
private httpServer?: Server;
Expand All @@ -78,7 +79,7 @@ export class DashboardServer {
"dashboard-frontend",
"build"
);
this.publicChains = options.publicChains;
this.dashboardChains = options.dashboardChains;

this.boundTerminateListener = () => this.stop();
}
Expand Down Expand Up @@ -209,7 +210,7 @@ export class DashboardServer {
if (isInitializeMessage(message)) {
const responseMessage = {
id: message.id,
payload: { publicChains: this.publicChains }
payload: { dashboardChains: this.dashboardChains }
};
socket.send(jsonToBase64(responseMessage));
}
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard/src/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function Dashboard() {
const [dashboardProviderRequests, setDashboardProviderRequests] = useState<
DashboardProviderMessage[]
>([]);
const [publicChains, setPublicChains] = useState<object[]>([]);
const [dashboardChains, setDashboardChains] = useState<object[]>([]);

const [{ data }] = useNetwork();
const [{}, disconnect] = useAccount();
Expand Down Expand Up @@ -107,7 +107,7 @@ function Dashboard() {
// since our socket is open, request some initial data from the server
const message = createMessage("initialize", ""); // no payload needed
const response = await sendAndAwait(socket, message);
setPublicChains(response.payload.publicChains);
setDashboardChains(response.payload.dashboardChains);

setPublishSocket(socket);
};
Expand All @@ -126,7 +126,7 @@ function Dashboard() {

return (
<div className="h-full min-h-screen bg-gradient-to-b from-truffle-lighter to-truffle-light">
<Header disconnect={disconnectAccount} publicChains={publicChains} />
<Header disconnect={disconnectAccount} dashboardChains={dashboardChains} />
{paused && chainId && connectedChainId && (
<ConfirmNetworkChanged
newChainId={chainId}
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import Button from "../common/Button";

interface Props {
disconnect: () => void;
publicChains: object[];
dashboardChains: object[];
}

function Header({ disconnect, publicChains }: Props) {
function Header({ disconnect, dashboardChains }: Props) {
const [displayName, setDisplayName] = useState<string>();

const [{ data: accountData }] = useAccount();
Expand Down Expand Up @@ -56,7 +56,7 @@ function Header({ disconnect, publicChains }: Props) {
(networkSwitchingSupported ? (
<NetworkSwitcher
chainId={networkData.chain.id}
publicChains={publicChains}
dashboardChains={dashboardChains}
/>
) : (
[
Expand Down
66 changes: 56 additions & 10 deletions packages/dashboard/src/components/common/NetworkSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useWeb3React } from "@web3-react/core";
import axios from "axios";
import { providers } from "ethers";
import { useEffect, useState } from "react";
import {
Expand All @@ -8,13 +9,13 @@ import {

interface Props {
chainId: number;
publicChains?: object[];
dashboardChains?: object[];
}

function NetworkSwitcher({ chainId, publicChains }: Props) {
function NetworkSwitcher({ chainId, dashboardChains }: Props) {
const [networkName, setNetworkName] = useState<string>(`Chain ID ${chainId}`);
const textColor = chainId === 1 ? "text-truffle-red" : "";
const { library } = useWeb3React<providers.Web3Provider>();
const { library, account } = useWeb3React<providers.Web3Provider>();

useEffect(() => {
const updateNetwork = async (chainId: number) => {
Expand All @@ -25,7 +26,41 @@ function NetworkSwitcher({ chainId, publicChains }: Props) {

if (!chainId) return;
updateNetwork(chainId);
}, [chainId, publicChains]);
}, [chainId, dashboardChains]);

const postRpc = async (url: string, method: string, params: any[] = []) => {
try {
const { data } = await axios.post(url, {
jsonrpc: "2.0",
method,
params,
id: 0
});
console.log(`${method} result: ${JSON.stringify(data)}`);
return data.result;
} catch (e) {
console.log(e);
}
};
async function fundAccount(chain: any) {
const rpcUrl = chain.rpcUrls[0];
const clientVersion = await postRpc(rpcUrl, "web3_clientVersion");
if (clientVersion.includes("Ganache")) {
const funded = await postRpc(rpcUrl, "evm_setAccountBalance", [
account,
"0x56BC75E2D63100000"
]); // give them 100 ETH for good measure
if (!funded) {
console.warn(`Something went wrong when funding account ${account}`);
}
} else if (clientVersion.includes("HardhatNetwork")) {
console.warn("Hardhat Network account funding not yet supported.");
}
}
async function getVerifiedChainId(chain: any) {
const result = await postRpc(chain.rpcUrls[0], "eth_chainId");
return result;
}

async function addNetwork(chain: any) {
if (!library) return; // handle better
Expand All @@ -41,23 +76,31 @@ function NetworkSwitcher({ chainId, publicChains }: Props) {
addNetworkPayload
);
if (addNetworkResponse.error) {
console.log(
console.error(
"add network error: " + JSON.stringify(addNetworkResponse.error)
);
} else {
console.log("added network!" + JSON.stringify(addNetworkResponse));
}
}

async function setOrAddNetwork(chain: any) {
if (!library) return; // handle better
const provider = library.provider;
if (!chain.chainId) {
chain.chainId = await getVerifiedChainId(chain);
if (!chain.chainId) {
console.error(
`Chain ${chain.chainName} does not have a valid chainId and the provided RPC URL is invalid.`
);
return;
}
}
const switchNetworkPayload = {
jsonrpc: "2.0",
method: "wallet_switchEthereumChain",
params: [{ chainId: chain.chainId }],
id: 0
};
console.log(switchNetworkPayload);
const switchNetworkResponse = await forwardDashboardProviderRequest(
provider,
switchNetworkPayload
Expand All @@ -68,7 +111,7 @@ function NetworkSwitcher({ chainId, publicChains }: Props) {
addNetwork(chain);
} else {
// handle other errors
console.log(
console.error(
"some other switch network error: " + switchNetworkResponse.error
);
}
Expand All @@ -78,11 +121,14 @@ function NetworkSwitcher({ chainId, publicChains }: Props) {
// imported from web3React will be changed, which will call the useEffect
// for all components that are dependent on that chainId
console.log("switched network! " + JSON.stringify(switchNetworkResponse));
if (chain.isLocalChain) {
await fundAccount(chain);
}
}
}
const chainIdHex = `0x${chainId.toString(16)}`;
const chainOptions = publicChains ? (
publicChains.map((chain: any) => {
const chainOptions = dashboardChains ? (
dashboardChains.map((chain: any) => {
return (
<div
key={chain.chainId}
Expand Down