lib/eth/chain.rb



# Copyright (c) 2016-2025 The Ruby-Eth Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Provides the {Eth} module.
module Eth

  # Encapsulates {Eth::Chain} IDs and utilities for EIP-155 compatibility.
  # Ref: https://eips.ethereum.org/EIPS/eip-155
  module Chain
    extend self

    # Provides a special replay protection error if EIP-155 is violated.
    class ReplayProtectionError < StandardError; end

    # Chain ID for Ethereum mainnet.
    ETHEREUM = 1.freeze

    # Chain ID for Expanse mainnet.
    EXPANSE = 2.freeze

    # Chain ID for Optimistic Ethereum mainnet.
    OPTIMISM = 10.freeze

    # Chain ID for Cronos mainnet.
    CRONOS = 25.freeze

    # Chain ID for Rootstock mainnet.
    RSK = 30.freeze

    # Chain ID for BNB Smart Chain mainnet.
    BNB = 56.freeze

    # Chain ID for Ethereum Classic mainnet.
    CLASSIC = 61.freeze

    # Chain ID for POA Network mainnet.
    POA_NET = 99.freeze

    # Chain ID for xDAI mainnet (now Gnosis Chain).
    XDAI = 100.freeze

    # Chain ID for Gnosis mainnet (formerly xDAI).
    GNOSIS = XDAI.freeze

    # Chain ID for the Matic mainnet (now Polygon).
    MATIC = 137.freeze

    # Chain ID for the Polygon mainnet (formerly Matic).
    POLYGON = MATIC.freeze

    # Chain ID for Filecoin mainnet.
    FILECOIN = 314.freeze

    # Chain ID for the Cronos zkEVM chain.
    CRONOS_ZK = 388.freeze

    # Chain ID for Redstone Optimistic Rollup.
    REDSTONE = 690.freeze

    # Chain ID for the Polygon zkEVM.
    POLYGON_ZK = 1101.freeze

    # Chain ID for the Lisk layer 2.
    LISK = 1135.freeze

    # Chain ID for Moonbeam
    MOONBEAM = 1284.freeze

    # Chain ID for Base mainnet.
    BASE = 8453.freeze

    # Chain ID for the EVMOS mainnet.
    EVMOS = 9001.freeze

    # Chain ID for the Celo layer 2.
    CELO = 42220.freeze

    # Chain ID for Arbitrum One mainnet.
    ARBITRUM = 42161.freeze

    # Chain ID for Avalance C-Chain mainnet.
    AVALANCHE = 43114.freeze

    # Chain ID for Linea mainnet.
    LINEA = 59144.freeze

    # Chain ID for the Scroll layer 2.
    SCROLL = 534352.freeze

    # Chain ID for Morden (Ethereum) testnet.
    MORDEN = 2.freeze

    # Chain ID for Ropsten testnet.
    ROPSTEN = 3.freeze

    # Chain ID for Rinkeby testnet.
    RINKEBY = 4.freeze

    # Chain ID for Goerli testnet.
    GOERLI = 5.freeze

    # Chain ID for Kotti testnet.
    KOTTI = 6.freeze

    # Chain ID for Kovan testnet.
    KOVAN = 42.freeze

    # Chain ID for Morden (Classic) testnet.
    MORDEN_CLASSIC = 62.freeze

    # Chain ID for Mordor testnet.
    MORDOR = 63.freeze

    # Chain ID for Optimistm Kovan testnet.
    KOVAN_OPTIMISM = 69.freeze

    # Chain ID for Arbitrum xDAI testnet.
    XDAI_ARBITRUM = 200.freeze

    # Chain ID for Optimistic Goerli testnet.
    GOERLI_OPTIMISM = 420.freeze

    # Chain ID for Moonriver testnet
    MOONRIVER = 1285.freeze

    # Chain ID for Moonbase alpha
    MOONBASE = 1287.freeze

    # Chain ID for the Garnet Holesky testnet
    GARNET = 17069.freeze

    # Chain ID for the Polygon Mumbai testnet.
    MUMBAI = 80001.freeze

    # Chain ID for Arbitrum Rinkeby testnet.
    RINKEBY_ARBITRUM = 421611.freeze

    # Chain ID for Arbitrum Goerli testnet.
    GOERLI_ARBITRUM = 421613.freeze

    # Chain ID for Hoodi testnet.
    HOODI = 560048.freeze

    # Chain ID for Sepolia testnet.
    SEPOLIA = 11155111.freeze

    # Chain ID for Holesovice testnet.
    HOLESOVICE = 11166111.freeze

    # Chain ID for Holesovice testnet.
    HOLESKY = HOLESOVICE

    # Chain ID for Basecamp testnet.
    BASECAMP = 123420001114.freeze

    # Chain ID for the geth private network preset.
    PRIVATE_GETH = 1337.freeze

    # Indicates wether the given `v` indicates a legacy chain value
    # used by ledger wallets without EIP-155 replay protection.
    #
    # @param v [Integer] the signature's `v` value.
    # @return [Boolean] true if ledger's legacy value.
    def ledger?(v)
      [0, 1].include? v
    end

    # Indicates wether the given `v` indicates a legacy chain value
    # without EIP-155 replay protection.
    #
    # @param v [Integer] the signature's `v` value.
    # @return [Boolean] true if legacy value.
    def legacy?(v)
      [27, 28].include? v
    end

    # Convert a given `v` value to an ECDSA recovery id for the given
    # EIP-155 chain ID.
    #
    # @param v [Integer] the signature's `v` value.
    # @param chain_id [Integer] the chain id the signature was generated on.
    # @return [Integer] the recovery id corresponding to `v`.
    # @raise [ReplayProtectionError] if the given `v` is invalid.
    def to_recovery_id(v, chain_id = ETHEREUM)
      e = 0 + 2 * chain_id + 35
      i = 1 + 2 * chain_id + 35
      if ledger? v

        # some wallets are using a `v` of 0 or 1 (ledger)
        return v
      elsif legacy? v

        # this is the pre-EIP-155 legacy case
        return v - 27
      elsif [e, i].include? v

        # this is the EIP-155 case
        return v - 35 - 2 * chain_id
      else
        raise ReplayProtectionError, "Invalid v #{v} value for chain ID #{chain_id}. Invalid chain ID?"
      end
    end

    # Converts a recovery ID into the expected `v` on a given chain.
    #
    # @param recovery_id [Integer] signature recovery id.
    # @param chain_id [Integer] the chain id the signature was generated on.
    # @return [Integer] the signature's `v` value.
    def to_v(recovery_id, chain_id = nil)
      if chain_id.nil? or chain_id < 1
        v = 27 + recovery_id
      else
        v = 2 * chain_id + 35 + recovery_id
      end
      return v
    end

    # Converts a `v` value into a chain ID. This does not work for legacy signatures
    # with `v < 36` that do not conform with EIP-155.
    #
    # @param v [Integer] the signature's `v` value.
    # @return [Integer] the chain id as per EIP-155 or `nil` if there is no replay protection.
    def to_chain_id(v)
      return nil if v < 36
      chain_id = (v - 35) / 2
      return nil if chain_id < 1
      return chain_id
    end
  end
end