Skip to main content
Glama
dewanshparashar

Arbitrum MCP Server

arbitrum-chain-client.ts11.4 kB
// Arbitrum/Orbit chain specific information import { createPublicClient, http, parseAbi } from "viem"; import { getArbOSVersion } from "@arbitrum/orbit-sdk/utils"; export interface BatchPostingStatus { lastBatchPostedSecondsAgo: number; lastBlockReported: string; latestChildChainBlockNumber: string; backlogSize: string; summary: string; } export interface AssertionStatus { latestCreatedAssertion: string | null; latestConfirmedAssertion: string | null; creationConfirmationGap: string; summary: string; } export interface GasStatus { currentGasPrice: string; // in wei currentGasPriceGwei: string; // in gwei for readability summary: string; } export interface ChainStatus { chainName: string; arbosVersion: string; batchPosting: BatchPostingStatus; assertions: AssertionStatus; gasStatus: GasStatus; } export class ArbitrumChainClient { private rpcUrl: string; private publicClient: any; constructor(rpcUrl: string) { this.rpcUrl = rpcUrl; this.publicClient = createPublicClient({ transport: http(rpcUrl), }); } async getArbOSVersion(): Promise<string> { try { // Try using orbit-sdk first const version = await getArbOSVersion(this.publicClient); return version.toString(); } catch (error) { // Fallback to direct RPC call try { const version = await this.makeRpcCall("arb_getVersion", []); return version; } catch (rpcError) { // Return "Unknown" instead of throwing error for better UX return "Unknown (RPC does not support ArbOS version queries)"; } } } async getLatestBlock(): Promise<any> { return await this.makeRpcCall("eth_getBlockByNumber", ["latest", false]); } async getBlockByNumber(blockNumber: number): Promise<any> { const hexBlockNumber = "0x" + blockNumber.toString(16); return await this.makeRpcCall("eth_getBlockByNumber", [ hexBlockNumber, false, ]); } async getBatchPostingStatus( parentRpcUrl: string, sequencerInboxAddress: string, bridgeAddress: string ): Promise<BatchPostingStatus> { try { const parentClient = createPublicClient({ transport: http(parentRpcUrl), }); const sequencerBatchDeliveredEventAbi = { anonymous: false, inputs: [ { indexed: true, name: "batchSequenceNumber", type: "uint256" }, { indexed: true, name: "beforeAcc", type: "bytes32" }, { indexed: true, name: "afterAcc", type: "bytes32" }, { indexed: false, name: "delayedAcc", type: "bytes32" }, { indexed: false, name: "afterDelayedMessagesRead", type: "uint256" }, { components: [ { name: "minTimestamp", type: "uint64" }, { name: "maxTimestamp", type: "uint64" }, { name: "minBlockNumber", type: "uint64" }, { name: "maxBlockNumber", type: "uint64" }, ], name: "timeBounds", type: "tuple", }, { name: "dataLocation", type: "uint8" }, ], name: "SequencerBatchDelivered", type: "event", } as const; const latestBlockNumber = await parentClient.getBlockNumber(); const fromBlock = latestBlockNumber - BigInt(10000); const logs = await parentClient.getLogs({ address: sequencerInboxAddress as `0x${string}`, event: sequencerBatchDeliveredEventAbi, fromBlock, toBlock: latestBlockNumber, }); if (logs.length === 0) { return { lastBatchPostedSecondsAgo: 999999, lastBlockReported: "0", latestChildChainBlockNumber: "0", backlogSize: "0", summary: "No batches found in recent blocks" }; } const lastLog = logs[logs.length - 1]; const lastBatchBlock = await parentClient.getBlock({ blockNumber: lastLog.blockNumber, }); const lastBatchPostedSecondsAgo = Math.floor(Date.now() / 1000) - Number(lastBatchBlock.timestamp); const lastBlockReported = await parentClient.readContract({ address: bridgeAddress as `0x${string}`, abi: parseAbi([ 'function sequencerReportedSubMessageCount() view returns (uint256)', ]), functionName: 'sequencerReportedSubMessageCount', }); const latestChildChainBlockNumber = await this.publicClient.getBlockNumber(); const backlogSize = latestChildChainBlockNumber - lastBlockReported; const summary = `Last batch posted ${Math.floor(lastBatchPostedSecondsAgo / 3600)}h ${Math.floor((lastBatchPostedSecondsAgo % 3600) / 60)}m ago. Backlog: ${backlogSize} blocks.`; return { lastBatchPostedSecondsAgo, lastBlockReported: lastBlockReported.toString(), latestChildChainBlockNumber: latestChildChainBlockNumber.toString(), backlogSize: backlogSize.toString(), summary }; } catch (error) { return { lastBatchPostedSecondsAgo: 999999, lastBlockReported: "0", latestChildChainBlockNumber: "0", backlogSize: "0", summary: `Error checking batch posting: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async getAssertionStatus( parentRpcUrl: string, rollupAddress: string ): Promise<AssertionStatus> { try { const parentClient = createPublicClient({ transport: http(parentRpcUrl), }); const nodeCreatedEventAbi = { anonymous: false, inputs: [ { indexed: true, name: "nodeNum", type: "uint64" }, { indexed: true, name: "parentNodeHash", type: "bytes32" }, { indexed: true, name: "nodeHash", type: "bytes32" }, { indexed: false, name: "executionHash", type: "bytes32" }, ], name: "NodeCreated", type: "event", } as const; const nodeConfirmedEventAbi = { anonymous: false, inputs: [ { indexed: true, name: "nodeNum", type: "uint64" }, { indexed: false, name: "blockHash", type: "bytes32" }, { indexed: false, name: "sendRoot", type: "bytes32" }, ], name: "NodeConfirmed", type: "event", } as const; const latestBlockNumber = await parentClient.getBlockNumber(); const fromBlock = latestBlockNumber - BigInt(50000); const [createdLogs, confirmedLogs] = await Promise.all([ parentClient.getLogs({ address: rollupAddress as `0x${string}`, event: nodeCreatedEventAbi, fromBlock, toBlock: latestBlockNumber, }), parentClient.getLogs({ address: rollupAddress as `0x${string}`, event: nodeConfirmedEventAbi, fromBlock, toBlock: latestBlockNumber, }) ]); const latestCreatedAssertion = createdLogs.length > 0 ? createdLogs[createdLogs.length - 1].args?.nodeNum || null : null; const latestConfirmedAssertion = confirmedLogs.length > 0 ? confirmedLogs[confirmedLogs.length - 1].args?.nodeNum || null : null; const creationConfirmationGap = (latestCreatedAssertion && latestConfirmedAssertion) ? latestCreatedAssertion - latestConfirmedAssertion : 0n; const summary = `Latest created assertion: ${latestCreatedAssertion || 'None'}, Latest confirmed: ${latestConfirmedAssertion || 'None'}. Gap: ${creationConfirmationGap}`; return { latestCreatedAssertion: latestCreatedAssertion ? latestCreatedAssertion.toString() : null, latestConfirmedAssertion: latestConfirmedAssertion ? latestConfirmedAssertion.toString() : null, creationConfirmationGap: creationConfirmationGap.toString(), summary }; } catch (error) { return { latestCreatedAssertion: null, latestConfirmedAssertion: null, creationConfirmationGap: "0", summary: `Error checking assertions: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async getGasStatus(): Promise<GasStatus> { try { const gasPrice = await this.publicClient.getGasPrice(); const gasPriceGwei = (Number(gasPrice) / 1e9).toFixed(2); const summary = `Current gas price: ${gasPriceGwei} gwei (${gasPrice.toString()} wei)`; return { currentGasPrice: gasPrice.toString(), currentGasPriceGwei: gasPriceGwei, summary }; } catch (error) { return { currentGasPrice: "0", currentGasPriceGwei: "0", summary: `Error checking gas price: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async getComprehensiveChainStatus( chainName: string, parentRpcUrl: string, sequencerInboxAddress: string, bridgeAddress: string, rollupAddress: string ): Promise<ChainStatus> { // Use Promise.allSettled to handle individual failures gracefully const results = await Promise.allSettled([ this.getArbOSVersion(), this.getBatchPostingStatus(parentRpcUrl, sequencerInboxAddress, bridgeAddress), this.getAssertionStatus(parentRpcUrl, rollupAddress), this.getGasStatus() ]); const arbosVersion = results[0].status === 'fulfilled' ? results[0].value : "Error retrieving ArbOS version"; const batchPosting = results[1].status === 'fulfilled' ? results[1].value : { lastBatchPostedSecondsAgo: 999999, lastBlockReported: "0", latestChildChainBlockNumber: "0", backlogSize: "0", summary: "Error retrieving batch posting status" }; const assertions = results[2].status === 'fulfilled' ? results[2].value : { latestCreatedAssertion: null, latestConfirmedAssertion: null, creationConfirmationGap: "0", summary: "Error retrieving assertion status" }; const gasStatus = results[3].status === 'fulfilled' ? results[3].value : { currentGasPrice: "0", currentGasPriceGwei: "0", summary: "Error retrieving gas status" }; return { chainName, arbosVersion, batchPosting, assertions, gasStatus }; } private async makeRpcCall(method: string, params: any[]): Promise<any> { try { const requestBody = { jsonrpc: "2.0", id: Date.now(), method, params, }; console.error(`Making RPC call to ${this.rpcUrl}: ${method}`); const response = await fetch(this.rpcUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(requestBody), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); if (data.error) { throw new Error(`RPC Error: ${data.error.message}`); } return data.result; } catch (error) { console.error(`RPC call failed for ${method} on ${this.rpcUrl}:`, error); if (error instanceof Error) { throw error; } else { throw new Error(`Unknown error during RPC call: ${String(error)}`); } } } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dewanshparashar/arbitrum-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server