Skip to main content

Overview

Opcode: 0x46 Introduced: Istanbul (EIP-1344) CHAINID retrieves the unique identifier for the current blockchain network. This enables contracts to implement replay protection and chain-specific behavior, preventing transactions from one chain being replayed on another.

Specification

Stack Input:
(none)
Stack Output:
chain_id (u256)
Gas Cost: 2 (GasQuickStep) Operation:
stack.push(chainId)
Hardfork: Available from Istanbul onwards

Behavior

CHAINID pushes the chain identifier onto the stack as a 256-bit unsigned integer:
Ethereum Mainnet:   1
Sepolia Testnet:    11155111
Polygon:            137
Arbitrum One:       42161
Optimism:           10
Base:               8453
If called before Istanbul hardfork, the instruction is invalid and returns an error.

Examples

Basic Usage

import { chainid } from '@tevm/voltaire/evm/block';
import { createFrame } from '@tevm/voltaire/evm/Frame';

const frame = createFrame({
  stack: [],
  hardfork: 'ISTANBUL',
  blockContext: {
    chain_id: 1n // Ethereum mainnet
  }
});

const err = chainid(frame);
console.log(frame.stack); // [1n]
console.log(frame.gasRemaining); // Original - 2

Pre-Istanbul Error

// Before Istanbul hardfork
const preIstanbulFrame = createFrame({
  stack: [],
  hardfork: 'PETERSBURG',
  blockContext: { chain_id: 1n }
});

const err = chainid(preIstanbulFrame);
console.log(err); // { type: "InvalidOpcode" }

Chain Detection

// Detect specific chains
const MAINNET = 1n;
const SEPOLIA = 11155111n;
const POLYGON = 137n;

chainid(frame);
const currentChain = frame.stack[0];

if (currentChain === MAINNET) {
  console.log("Running on Ethereum mainnet");
} else if (currentChain === SEPOLIA) {
  console.log("Running on Sepolia testnet");
}

Gas Cost

Cost: 2 gas (GasQuickStep) CHAINID is one of the cheapest operations in the EVM. Comparison:
  • CHAINID: 2 gas
  • NUMBER, TIMESTAMP, GASLIMIT: 2 gas
  • COINBASE: 2 gas
  • SELFBALANCE: 5 gas

Common Usage

Chain-Specific Token Addresses

contract MultiChainToken {
    function getTokenAddress() public view returns (address) {
        if (block.chainid == 1) {
            return 0x123...; // Mainnet USDC
        } else if (block.chainid == 137) {
            return 0x456...; // Polygon USDC
        } else if (block.chainid == 42161) {
            return 0x789...; // Arbitrum USDC
        }
        revert("Unsupported chain");
    }
}

Cross-Chain Message Verification

contract CrossChainBridge {
    struct Message {
        uint256 sourceChain;
        uint256 destinationChain;
        bytes data;
        bytes signature;
    }

    function verifyMessage(Message memory msg) public view returns (bool) {
        require(msg.destinationChain == block.chainid, "Wrong chain");
        // Verify signature and process
        return true;
    }
}

Replay Protection

contract ReplayProtected {
    mapping(bytes32 => bool) public executed;

    function executeTransaction(
        address to,
        uint256 value,
        bytes memory data,
        uint256 nonce,
        bytes memory signature
    ) external {
        // Include chainid in hash to prevent replay
        bytes32 txHash = keccak256(abi.encodePacked(
            block.chainid,
            to,
            value,
            data,
            nonce
        ));

        require(!executed[txHash], "Already executed");
        require(verify(txHash, signature), "Invalid signature");

        executed[txHash] = true;
        // Execute transaction
    }
}

Chain-Specific Configuration

contract ChainConfig {
    function getBlockTime() public view returns (uint256) {
        if (block.chainid == 1) {
            return 12; // Ethereum: 12 seconds
        } else if (block.chainid == 137) {
            return 2; // Polygon: 2 seconds
        } else if (block.chainid == 42161) {
            return 1; // Arbitrum: ~1 second
        }
        return 12; // Default
    }

    function getGasToken() public view returns (string memory) {
        if (block.chainid == 1) return "ETH";
        if (block.chainid == 137) return "MATIC";
        if (block.chainid == 56) return "BNB";
        return "ETH";
    }
}

Multi-Chain Deployment Detection

contract DeploymentTracker {
    struct Deployment {
        uint256 chainId;
        address contractAddress;
        uint256 blockNumber;
    }

    Deployment[] public deployments;

    constructor() {
        deployments.push(Deployment({
            chainId: block.chainid,
            contractAddress: address(this),
            blockNumber: block.number
        }));
    }

    function isMainnet() public view returns (bool) {
        return block.chainid == 1;
    }

    function isTestnet() public view returns (bool) {
        return block.chainid == 11155111 || // Sepolia
               block.chainid == 5 ||        // Goerli (deprecated)
               block.chainid == 17000;      // Holesky
    }
}

Security Considerations

EIP-155 Replay Protection

CHAINID enables EIP-155 replay protection in transactions:
contract EIP155Aware {
    function getTransactionHash(
        uint256 nonce,
        uint256 gasPrice,
        uint256 gasLimit,
        address to,
        uint256 value,
        bytes memory data
    ) public view returns (bytes32) {
        // EIP-155: Include chainId in transaction hash
        return keccak256(abi.encodePacked(
            nonce,
            gasPrice,
            gasLimit,
            to,
            value,
            data,
            block.chainid,
            uint256(0),
            uint256(0)
        ));
    }
}

Fork Safety

During chain forks, chainid prevents replay:
contract ForkSafe {
    // Transaction signed for chain 1 can't be replayed on chain 10
    function sensitiveOperation(bytes memory signature) external {
        bytes32 messageHash = keccak256(abi.encodePacked(
            "Action",
            msg.sender,
            block.chainid // Different on forked chains
        ));

        require(verify(messageHash, signature), "Invalid signature");
        // Execute
    }
}

Testnet vs Mainnet Safety

contract ProductionGuard {
    modifier mainnetOnly() {
        require(block.chainid == 1, "Mainnet only");
        _;
    }

    modifier testnetOnly() {
        require(
            block.chainid == 11155111 || // Sepolia
            block.chainid == 17000,       // Holesky
            "Testnet only"
        );
        _;
    }

    function dangerousOperation() external mainnetOnly {
        // Critical mainnet-only logic
    }

    function experimentalFeature() external testnetOnly {
        // Testing-only features
    }
}

Cross-Chain Attack Prevention

contract CrossChainSafe {
    // VULNERABLE: No chain verification
    function vulnerableTransfer(
        address to,
        uint256 amount,
        bytes memory signature
    ) external {
        bytes32 hash = keccak256(abi.encodePacked(to, amount));
        require(verify(hash, signature), "Invalid sig");
        // Signature from mainnet could work on testnet!
    }

    // SAFE: Include chainid
    function safeTransfer(
        address to,
        uint256 amount,
        bytes memory signature
    ) external {
        bytes32 hash = keccak256(abi.encodePacked(
            to,
            amount,
            block.chainid // Prevents cross-chain replay
        ));
        require(verify(hash, signature), "Invalid sig");
        // Execute
    }
}

Pre-Istanbul Compatibility

contract BackwardCompatible {
    // Check if CHAINID is available
    function getChainId() public view returns (uint256) {
        uint256 chainId;
        assembly {
            // CHAINID opcode (0x46)
            chainId := chainid()
        }

        // If chainId is 0, might be pre-Istanbul
        // (or actual chainId is 0, which is unlikely)
        return chainId;
    }

    // Fallback for pre-Istanbul
    function getChainIdLegacy() public pure returns (uint256) {
        // Must be hardcoded or use assembly checks
        return 1; // Assume mainnet
    }
}

Implementation

/**
 * CHAINID opcode (0x46) - Get chain ID
 * Available: Istanbul+
 */
export function chainid(frame: FrameType): EvmError | null {
  // Check hardfork availability
  if (frame.evm.hardfork.isBefore('ISTANBUL')) {
    return { type: "InvalidOpcode" };
  }

  // Consume gas (GasQuickStep = 2)
  frame.gasRemaining -= 2n;
  if (frame.gasRemaining < 0n) {
    frame.gasRemaining = 0n;
    return { type: "OutOfGas" };
  }

  // Push chain ID to stack
  if (frame.stack.length >= 1024) return { type: "StackOverflow" };
  frame.stack.push(frame.evm.blockContext.chain_id);

  frame.pc += 1;
  return null;
}

Edge Cases

Pre-Istanbul Execution

// Before Istanbul: InvalidOpcode
const frame = createFrame({
  hardfork: 'CONSTANTINOPLE',
  blockContext: { chain_id: 1n }
});

const err = chainid(frame);
console.log(err); // { type: "InvalidOpcode" }

Uncommon Chain IDs

// Private network with custom chain ID
const frame = createFrame({
  hardfork: 'ISTANBUL',
  blockContext: { chain_id: 999999n }
});

chainid(frame);
console.log(frame.stack); // [999999n]

Maximum Chain ID

// Theoretical maximum (u256)
const frame = createFrame({
  hardfork: 'ISTANBUL',
  blockContext: { chain_id: (1n << 256n) - 1n }
});

chainid(frame);
console.log(frame.stack); // [max u256]

Known Chain IDs

// Major networks
uint256 constant ETHEREUM_MAINNET = 1;
uint256 constant SEPOLIA = 11155111;
uint256 constant HOLESKY = 17000;

// L2s
uint256 constant OPTIMISM = 10;
uint256 constant ARBITRUM_ONE = 42161;
uint256 constant BASE = 8453;
uint256 constant ZKSYNC_ERA = 324;

// Alt-L1s
uint256 constant POLYGON = 137;
uint256 constant BNB_CHAIN = 56;
uint256 constant AVALANCHE = 43114;

Benchmarks

Performance:
  • Hardfork check: O(1)
  • Stack push: O(1)
Gas efficiency:
  • 2 gas per query
  • ~500,000 queries per million gas

References