diff --git a/packages/status-page/src/components/StatusIndicator.svelte b/packages/status-page/src/components/StatusIndicator.svelte index 6164708b82a..db27b3ff5f7 100644 --- a/packages/status-page/src/components/StatusIndicator.svelte +++ b/packages/status-page/src/components/StatusIndicator.svelte @@ -7,7 +7,6 @@ import { fade } from "svelte/transition"; import Tooltip from "./Tooltip.svelte"; import TooltipModal from "./TooltipModal.svelte"; - import DetailsModal from "./DetailsModal.svelte"; export let provider: ethers.providers.JsonRpcProvider; export let contractAddress: string; @@ -23,7 +22,7 @@ provider: ethers.providers.JsonRpcProvider, contractAddress: string, onEvent: (value: Status) => void - ) => void; + ) => () => void; export let colorFunc: (value: Status) => string; @@ -43,6 +42,8 @@ let detailsOpen: boolean = false; + let cancelFunc: () => void = () => {}; + onMount(async () => { try { if (status) { @@ -57,9 +58,13 @@ } if (watchStatusFunc) { - watchStatusFunc(provider, contractAddress, (value: Status) => { - statusValue = value; - }); + cancelFunc = watchStatusFunc( + provider, + contractAddress, + (value: Status) => { + statusValue = value; + } + ); } if (intervalInMs !== 0) { @@ -75,6 +80,7 @@ onDestroy(() => { if (interval) clearInterval(interval); + if (cancelFunc) cancelFunc(); }); diff --git a/packages/status-page/src/constants/abi/TaikoL2.ts b/packages/status-page/src/constants/abi/TaikoL2.ts new file mode 100644 index 00000000000..3986e958a09 --- /dev/null +++ b/packages/status-page/src/constants/abi/TaikoL2.ts @@ -0,0 +1,670 @@ +export default [ + { + inputs: [ + { + internalType: "uint64", + name: "expected", + type: "uint64", + }, + { + internalType: "uint64", + name: "actual", + type: "uint64", + }, + ], + name: "L2_BASEFEE_MISMATCH", + type: "error", + }, + { + inputs: [], + name: "L2_INVALID_1559_PARAMS", + type: "error", + }, + { + inputs: [], + name: "L2_INVALID_CHAIN_ID", + type: "error", + }, + { + inputs: [], + name: "L2_INVALID_GOLDEN_TOUCH_K", + type: "error", + }, + { + inputs: [], + name: "L2_INVALID_SENDER", + type: "error", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "expected", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "actual", + type: "bytes32", + }, + ], + name: "L2_PUBLIC_INPUT_HASH_MISMATCH", + type: "error", + }, + { + inputs: [], + name: "L2_TOO_LATE", + type: "error", + }, + { + inputs: [], + name: "M1559_OUT_OF_STOCK", + type: "error", + }, + { + inputs: [], + name: "M1559_OUT_OF_STOCK", + type: "error", + }, + { + inputs: [ + { + internalType: "uint64", + name: "expected", + type: "uint64", + }, + { + internalType: "uint64", + name: "actual", + type: "uint64", + }, + ], + name: "M1559_UNEXPECTED_CHANGE", + type: "error", + }, + { + inputs: [ + { + internalType: "uint64", + name: "expected", + type: "uint64", + }, + { + internalType: "uint64", + name: "actual", + type: "uint64", + }, + ], + name: "M1559_UNEXPECTED_CHANGE", + type: "error", + }, + { + inputs: [], + name: "Overflow", + type: "error", + }, + { + inputs: [], + name: "RESOLVER_DENIED", + type: "error", + }, + { + inputs: [], + name: "RESOLVER_INVALID_ADDR", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "bytes32", + name: "name", + type: "bytes32", + }, + ], + name: "RESOLVER_ZERO_ADDR", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "addressManager", + type: "address", + }, + ], + name: "AddressManagerChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint64", + name: "number", + type: "uint64", + }, + { + indexed: false, + internalType: "uint64", + name: "basefee", + type: "uint64", + }, + { + indexed: false, + internalType: "uint64", + name: "gaslimit", + type: "uint64", + }, + { + indexed: false, + internalType: "uint64", + name: "timestamp", + type: "uint64", + }, + { + indexed: false, + internalType: "bytes32", + name: "parentHash", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "prevrandao", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "coinbase", + type: "address", + }, + { + indexed: false, + internalType: "uint32", + name: "chainid", + type: "uint32", + }, + ], + name: "Anchored", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "srcHeight", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes32", + name: "blockHash", + type: "bytes32", + }, + { + indexed: false, + internalType: "bytes32", + name: "signalRoot", + type: "bytes32", + }, + ], + name: "CrossChainSynced", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + inputs: [], + name: "GOLDEN_TOUCH_ADDRESS", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "GOLDEN_TOUCH_PRIVATEKEY", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "addressManager", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "l1Hash", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "l1SignalRoot", + type: "bytes32", + }, + { + internalType: "uint64", + name: "l1Height", + type: "uint64", + }, + { + internalType: "uint64", + name: "parentGasUsed", + type: "uint64", + }, + ], + name: "anchor", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "gasExcess", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint32", + name: "timeSinceParent", + type: "uint32", + }, + { + internalType: "uint64", + name: "gasLimit", + type: "uint64", + }, + { + internalType: "uint64", + name: "parentGasUsed", + type: "uint64", + }, + ], + name: "getBasefee", + outputs: [ + { + internalType: "uint256", + name: "_basefee", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "number", + type: "uint256", + }, + ], + name: "getBlockHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "number", + type: "uint256", + }, + ], + name: "getCrossChainBlockHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "number", + type: "uint256", + }, + ], + name: "getCrossChainSignalRoot", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getEIP1559Config", + outputs: [ + { + components: [ + { + internalType: "uint128", + name: "yscale", + type: "uint128", + }, + { + internalType: "uint64", + name: "xscale", + type: "uint64", + }, + { + internalType: "uint64", + name: "gasIssuedPerSecond", + type: "uint64", + }, + ], + internalType: "struct TaikoL2.EIP1559Config", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_addressManager", + type: "address", + }, + { + components: [ + { + internalType: "uint64", + name: "basefee", + type: "uint64", + }, + { + internalType: "uint64", + name: "gasIssuedPerSecond", + type: "uint64", + }, + { + internalType: "uint64", + name: "gasExcessMax", + type: "uint64", + }, + { + internalType: "uint64", + name: "gasTarget", + type: "uint64", + }, + { + internalType: "uint64", + name: "ratio2x1x", + type: "uint64", + }, + ], + internalType: "struct TaikoL2.EIP1559Params", + name: "_param1559", + type: "tuple", + }, + ], + name: "init", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "latestSyncedL1Height", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "parentTimestamp", + outputs: [ + { + internalType: "uint64", + name: "", + type: "uint64", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "publicInputHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "bytes32", + name: "name", + type: "bytes32", + }, + { + internalType: "bool", + name: "allowZeroAddress", + type: "bool", + }, + ], + name: "resolve", + outputs: [ + { + internalType: "address payable", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "name", + type: "bytes32", + }, + { + internalType: "bool", + name: "allowZeroAddress", + type: "bool", + }, + ], + name: "resolve", + outputs: [ + { + internalType: "address payable", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newAddressManager", + type: "address", + }, + ], + name: "setAddressManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "digest", + type: "bytes32", + }, + { + internalType: "uint8", + name: "k", + type: "uint8", + }, + ], + name: "signAnchor", + outputs: [ + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "uint256", + name: "r", + type: "uint256", + }, + { + internalType: "uint256", + name: "s", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +]; diff --git a/packages/status-page/src/pages/home/Home.svelte b/packages/status-page/src/pages/home/Home.svelte index 7f5d6ea4869..e9c37abc8b6 100644 --- a/packages/status-page/src/pages/home/Home.svelte +++ b/packages/status-page/src/pages/home/Home.svelte @@ -20,7 +20,7 @@ onMount(async () => { config = initConfig($layer); - statusIndicators = buildStatusIndicators( + statusIndicators = await buildStatusIndicators( config, (value: Status) => { proverDetailsOpen = true; @@ -37,8 +37,8 @@ config = initConfig(newLayer); statusIndicators = []; - setTimeout(() => { - statusIndicators = buildStatusIndicators( + setTimeout(async () => { + statusIndicators = await buildStatusIndicators( config, (value: Status) => { proverDetailsOpen = true; diff --git a/packages/status-page/src/utils/buildStatusIndicators.ts b/packages/status-page/src/utils/buildStatusIndicators.ts index 62ad179908a..41c5fbb866b 100644 --- a/packages/status-page/src/utils/buildStatusIndicators.ts +++ b/packages/status-page/src/utils/buildStatusIndicators.ts @@ -17,17 +17,29 @@ import { getPendingBlocks } from "./getPendingBlocks"; import { getPendingTransactions } from "./getPendingTransactions"; import { getQueuedTransactions } from "./getQueuedTransactions"; import type { initConfig } from "./initConfig"; -import { truncateString } from "./truncateString"; import { watchHeaderSynced } from "./watchHeaderSynced"; import axios from "axios"; import { getConfig } from "./getConfig"; import { getStateVariables } from "./getStateVariables"; +import TaikoL2 from "../constants/abi/TaikoL2"; -export function buildStatusIndicators( +export async function buildStatusIndicators( config: ReturnType, onProverClick: (value: Status) => void, onProposerClick: (value: Status) => void ) { + const tko: Contract = new Contract( + config.taikoTokenAddress, + TaikoToken, + config.l1Provider + ); + + let decimals: number = 8; + + try { + decimals = await tko.decimals(); + } catch (e) {} + const indicators: StatusIndicatorProp[] = [ { statusFunc: async ( @@ -222,13 +234,54 @@ export function buildStatusIndicators( provider: config.l2Provider, contractAddress: "", header: "Gas Price (gwei)", - intervalInMs: 20000, + intervalInMs: 30000, colorFunc: (value: Status) => { return "green"; }, tooltip: "The current recommended gas price for a transaction on Layer 2.", }, + { + statusFunc: async ( + provider: ethers.providers.JsonRpcProvider, + contractAddress: string + ): Promise => { + const latestBlock = await provider.getBlock("latest"); + return `${ethers.utils.formatUnits(latestBlock.baseFeePerGas, "gwei")}`; + }, + watchStatusFunc: null, + provider: config.l2Provider, + contractAddress: config.l2TaikoAddress, + header: "L2 EIP1559 BaseFee (gwei)", + intervalInMs: 30000, + colorFunc: (value: Status) => { + return "green"; + }, + tooltip: + "The current base fee for an L2 transaction with EIP1559-enabled.", + }, + { + statusFunc: async ( + provider: ethers.providers.JsonRpcProvider, + contractAddress: string + ): Promise => { + const feeData = await provider.getFeeData(); + return `${ethers.utils.formatUnits( + feeData.maxPriorityFeePerGas, + "gwei" + )}`; + }, + watchStatusFunc: null, + provider: config.l2Provider, + contractAddress: config.l2TaikoAddress, + header: "L2 EIP1559 Recommended MaxPriorityFeePerGas (gwei)", + intervalInMs: 30000, + colorFunc: (value: Status) => { + return "green"; + }, + tooltip: + "The current recommend max priority fee per gas for a fast transaction.", + }, ]; try { @@ -243,12 +296,6 @@ export function buildStatusIndicators( provider ); const fee = await contract.getBlockFee(); - const tko: Contract = new Contract( - config.taikoTokenAddress, - TaikoToken, - provider - ); - const decimals = await tko.decimals(); return `${ethers.utils.formatUnits(fee, decimals)} TKO`; }, watchStatusFunc: null, @@ -276,13 +323,6 @@ export function buildStatusIndicators( config.eventIndexerApiUrl ); const fee = await contract.getProofReward(Number(averageProofTime)); - - const tko: Contract = new Contract( - config.taikoTokenAddress, - TaikoToken, - provider - ); - const decimals = await tko.decimals(); return `${ethers.utils.formatUnits(fee, decimals)} ${ import.meta.env.VITE_FEE_TOKEN_SYMBOL ?? "TKO" }`; @@ -310,25 +350,25 @@ export function buildStatusIndicators( onEvent: (value: Status) => void ) => { const contract = new Contract(address, TaikoL1, provider); - contract.on( - "BlockProven", - ( - id, - parentHash, - blockHash, - signalRoot, - prover, - provenAt, - ...args - ) => { - // ignore oracle prover - if ( - prover.toLowerCase() !== config.oracleProverAddress.toLowerCase() - ) { - onEvent(new Date(provenAt).toTimeString()); - } + const listener = ( + id, + parentHash, + blockHash, + signalRoot, + prover, + provenAt, + ...args + ) => { + // ignore oracle prover + if ( + prover.toLowerCase() !== config.oracleProverAddress.toLowerCase() + ) { + onEvent(new Date(provenAt).toTimeString()); } - ); + }; + contract.on("BlockProven", listener); + + return () => contract.off("BlockProven", listener); }, colorFunc: function (status: Status) { return "green"; // todo: whats green, yellow, red? @@ -381,12 +421,6 @@ export function buildStatusIndicators( const resp = await axios.get( `${config.eventIndexerApiUrl}/stats` ); - const tko: Contract = new Contract( - config.taikoTokenAddress, - TaikoToken, - provider - ); - const decimals = await tko.decimals(); return `${ethers.utils.formatUnits( resp.data.averageProofReward, decimals diff --git a/packages/status-page/src/utils/initConfig.ts b/packages/status-page/src/utils/initConfig.ts index 78ad843d667..c41977445ec 100644 --- a/packages/status-page/src/utils/initConfig.ts +++ b/packages/status-page/src/utils/initConfig.ts @@ -2,12 +2,12 @@ import { ethers } from "ethers"; import { Layer } from "../domain/layer"; export function initConfig(layer: Layer) { - const l1Provider = new ethers.providers.JsonRpcProvider( + const l1Provider = new ethers.providers.StaticJsonRpcProvider( layer === Layer.Two ? import.meta.env.VITE_L1_RPC_URL : import.meta.env.VITE_L2_RPC_URL ); - const l2Provider = new ethers.providers.JsonRpcProvider( + const l2Provider = new ethers.providers.StaticJsonRpcProvider( layer === Layer.Two ? import.meta.env.VITE_L2_RPC_URL : import.meta.env.VITE_L3_RPC_URL diff --git a/packages/status-page/src/utils/watchHeaderSynced.ts b/packages/status-page/src/utils/watchHeaderSynced.ts index d96baa41086..74bb60ea9e4 100644 --- a/packages/status-page/src/utils/watchHeaderSynced.ts +++ b/packages/status-page/src/utils/watchHeaderSynced.ts @@ -7,10 +7,10 @@ export const watchHeaderSynced = async ( onEvent: (value: string | number | boolean) => void ) => { const contract: Contract = new Contract(taikoL1Address, TaikoL1, provider); - contract.on( - "CrossChainSynced", - (lastVerifiedBlockId, blockHash, signalRoot) => { - onEvent(blockHash); - } - ); + const listener = (lastVerifiedBlockId, blockHash, signalRoot) => { + onEvent(blockHash); + }; + contract.on("CrossChainSynced", listener); + + return () => contract.off("CrossChainSynced", listener); };