# How to Check If an Ethereum Address Is a Contract Using the Arbitrum MCP Server

> Learn how to check if an Ethereum address is a contract with the Arbitrum MCP Server. This guide shows you how to use the is_contract tool for efficient contract detection.

- Repository: [Dewansh/arbitrum-mcp](https://github.com/dewanshparashar/arbitrum-mcp)
- Tags: how-to-guide
- Published: 2026-02-28

---

**The Arbitrum MCP Server exposes an `is_contract` tool that queries the `eth_getCode` RPC method to return `true` if bytecode exists at the address, or `false` if it is an externally-owned account (EOA).**

The `dewanshparashar/arbitrum-mcp` repository implements a Model-Context-Protocol (MCP) server that simplifies Ethereum-compatible interactions. You can check if an Ethereum address is a contract using the Arbitrum MCP Server through a stateless tool that resolves RPC endpoints, executes bytecode lookups, and returns boolean results without requiring private keys or transaction signing.

## How the `is_contract` Tool Works

The contract detection flow follows a deterministic sequence implemented across the server's architecture:

1. **Tool Invocation** – The client sends a `CallToolRequest` with `name: "is_contract"` and the target `address` to the handler registered in [`src/index.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/index.ts).
2. **RPC Resolution** – `ArbitrumMCPServer.resolveRpcUrl` determines the endpoint. It prioritizes user-supplied URLs, falls back to the default RPC URL, or resolves chain names via `ChainLookupService` (defined in [`src/services/chain-lookup.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/services/chain-lookup.ts)).
3. **Client Instantiation** – The server creates a new `EthereumAccountClient` instance with the resolved RPC URL.
4. **Bytecode Retrieval** – `EthereumAccountClient.isContract` invokes `getCode(address)`, which performs an `eth_getCode` JSON-RPC call.
5. **Contract Detection** – The implementation checks if the returned bytecode is non-empty (`!== "0x"`). If present, the address hosts a contract; otherwise, it is classified as an EOA.

## Implementation Architecture

### RPC Resolution and Chain Lookup

Before executing the bytecode query, the server must resolve the target network. The `resolveRpcUrl` method handles three resolution strategies:

- Explicit `rpcUrl` argument provided in the tool call
- Default RPC URL configured via the `set_rpc_url` tool
- Chain name resolution (e.g., "Arbitrum One") through `ChainLookupService`

This resolution logic ensures the `is_contract` tool functions across any Ethereum-compatible network, not just Arbitrum chains.

### The EthereumAccountClient Logic

The core detection mechanism resides in [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts). The `isContract` method relies on the `getCode` helper, which executes the standard Ethereum JSON-RPC `eth_getCode` method. According to the source code, the boolean evaluation follows:

```typescript
// Simplified logic from src/clients/ethereum-account-client.ts
const code = await this.getCode(address);
return code !== "0x";

```

This approach leverages the Ethereum protocol's guarantee that EOAs have no bytecode at their addresses, while smart contracts store their runtime bytecode at the deployment address.

## Practical Code Examples

### Set a Default RPC Endpoint

Configure a persistent RPC URL to avoid passing it with every request:

```typescript
await server.callTool({
  name: "set_rpc_url",
  arguments: { rpcUrl: "https://arb1.arbitrum.io/rpc" }
});

```

*Source:* [`src/index.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/index.ts) – `set_rpc_url` handler registration.

### Query an Address via MCP Tool

Invoke the contract check using the server's tool interface:

```typescript
const response = await server.callTool({
  name: "is_contract",
  arguments: {
    address: "0x1234...abcd",
    // Optional: override the default RPC or specify chain
    // rpcUrl: "https://arb1.arbitrum.io/rpc",
    // chainName: "Arbitrum One"
  }
});

console.log(response.content[0].text); 
// Output: "Is contract: true" or "Is contract: false"

```

*Source:* [`src/index.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/index.ts) – `is_contract` case handler.

### Direct Client Library Usage

For applications requiring programmatic access without the MCP server overhead, import the client directly:

```typescript
import { EthereumAccountClient } from "./src/clients/ethereum-account-client.js";

const client = new EthereumAccountClient("https://arb1.arbitrum.io/rpc");
const isContract = await client.isContract("0x1234...abcd");
console.log(isContract); // boolean: true | false

```

*Source:* [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts) – `isContract` implementation.

## Key Source Files

| File | Responsibility |
|------|----------------|
| [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts) | Implements `getCode` and `isContract` methods; executes `eth_getCode` RPC calls. |
| [`src/index.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/index.ts) | Registers MCP tools including `is_contract`; orchestrates RPC resolution and response formatting. |
| [`src/services/chain-lookup.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/services/chain-lookup.ts) | Provides chain metadata resolution, mapping human-readable names (e.g., "Arbitrum One") to RPC endpoints. |

## Summary

- The **`is_contract`** tool in `dewanshparashar/arbitrum-mcp` provides a standardized MCP interface for determining address types.
- Detection relies on the **`eth_getCode`** RPC method; non-empty bytecode indicates a smart contract, while `0x` indicates an EOA.
- The server supports flexible **RPC resolution** through explicit URLs, defaults, or chain name lookups via `ChainLookupService`.
- Core logic resides in **`EthereumAccountClient.isContract`** within [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts).
- Clients can interact via the MCP server or import **`EthereumAccountClient`** directly for library-only usage.

## Frequently Asked Questions

### Does the `is_contract` tool work on Ethereum mainnet or only Arbitrum?

The tool works on any Ethereum-compatible chain. The `chainName` parameter in [`src/index.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/index.ts) accepts identifiers like "Ethereum" or "Arbitrum One", and the `ChainLookupService` resolves these to appropriate RPC endpoints. You can also bypass chain names entirely by providing a direct `rpcUrl` pointing to Ethereum mainnet, Base, Optimism, or other EVM networks.

### What happens if I don't specify an RPC URL or chain name?

The server follows a fallback hierarchy implemented in `ArbitrumMCPServer.resolveRpcUrl`. It first checks for a user-provided `rpcUrl` argument, then falls back to the default RPC URL configured via `set_rpc_url`. If neither exists, the tool returns an error prompting for network configuration.

### How does the server differentiate between a contract and an EOA?

According to [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts), the `isContract` method calls `getCode(address)` and evaluates whether the returned hex string equals `"0x"`. Ethereum protocol specifications mandate that EOAs have empty code at their addresses, while deployed contracts store their runtime bytecode (even minimal proxy contracts return non-empty values).

### Can I use the client library without running the full MCP server?

Yes. The `EthereumAccountClient` class in [`src/clients/ethereum-account-client.ts`](https://github.com/dewanshparashar/arbitrum-mcp/blob/main/src/clients/ethereum-account-client.ts) operates independently of the MCP protocol. Instantiate it directly with any EVM RPC endpoint to call `isContract` programmatically without setting up the server infrastructure or handling MCP request formatting.