Skip to main content

PeerId

Type-safe Ethereum peer identifiers (enode URLs) for peer-to-peer networking.

Overview

Branded string type representing an Ethereum peer identifier in enode URL format. Enode URLs uniquely identify Ethereum nodes on the P2P network and contain the node’s public key, IP address, and connection ports.

Quick Start

import * as PeerId from '@tevm/voltaire/PeerId'

const enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303"

// Create peer ID
const peerId = PeerId.from(enode)

// Convert to string
PeerId.toString(peerId)  // Returns the enode URL

// Compare peer IDs
const other = PeerId.from(enode)
PeerId.equals(peerId, other)  // true

Enode URL Format

enode://<node-id>@<ip>:<port>?discport=<discovery-port>
ComponentDescriptionExample
node-id128-char hex secp256k1 public key6f8a80d14311...
ipIPv4 or IPv6 address10.3.58.6
portTCP port for RLPx30303
discportUDP port for discovery (optional)30301

Type Definitions

// Branded peer ID type
type PeerIdType = string & { readonly [brand]: "PeerId" }

// Parsed enode components
type EnodeComponents = {
  /** Node public key (128 hex chars) */
  readonly publicKey: string
  /** IP address (IPv4 or IPv6) */
  readonly ip: string
  /** TCP port for RLPx */
  readonly port: number
  /** UDP port for discovery (optional) */
  readonly discoveryPort?: number
}

API Reference

Constructors

import * as PeerId from '@tevm/voltaire/PeerId'

// From enode URL string
const peerId = PeerId.from("enode://[email protected]:30303")

Methods

import * as PeerId from '@tevm/voltaire/PeerId'

const peerId = PeerId.from(enodeUrl)

// Convert to string
PeerId.toString(peerId)  // Returns enode URL

// Parse into components
PeerId.parse(peerId)     // Returns EnodeComponents

// Equality check
PeerId.equals(peerId, otherPeerId)  // boolean

Use Cases

Node Discovery

import * as PeerId from '@tevm/voltaire/PeerId'

// Parse discovered node info
function parseDiscoveredNode(enodeUrl: string) {
  const peerId = PeerId.from(enodeUrl)
  const { publicKey, ip, port, discoveryPort } = PeerId.parse(peerId)

  return {
    nodeId: publicKey,
    endpoint: `${ip}:${port}`,
    discoveryEndpoint: discoveryPort ? `${ip}:${discoveryPort}` : `${ip}:${port}`
  }
}

Peer Management

import * as PeerId from '@tevm/voltaire/PeerId'

class PeerList {
  private peers: Map<string, PeerId.PeerIdType> = new Map()

  add(enodeUrl: string): void {
    const peerId = PeerId.from(enodeUrl)
    const { publicKey } = PeerId.parse(peerId)
    this.peers.set(publicKey, peerId)
  }

  has(enodeUrl: string): boolean {
    const peerId = PeerId.from(enodeUrl)
    for (const existing of this.peers.values()) {
      if (PeerId.equals(existing, peerId)) {
        return true
      }
    }
    return false
  }

  getByPublicKey(publicKey: string): PeerId.PeerIdType | undefined {
    return this.peers.get(publicKey)
  }

  list(): string[] {
    return Array.from(this.peers.values()).map(PeerId.toString)
  }
}

Static Node Configuration

import * as PeerId from '@tevm/voltaire/PeerId'

// Geth static-nodes.json format
const staticNodes = [
  "enode://[email protected]:30303",
  "enode://[email protected]:30303"
]

function validateStaticNodes(nodes: string[]): boolean {
  for (const node of nodes) {
    try {
      const peerId = PeerId.from(node)
      const { publicKey, port } = PeerId.parse(peerId)

      // Validate public key length
      if (publicKey.length !== 128) {
        console.error(`Invalid public key length: ${node}`)
        return false
      }

      // Validate port range
      if (port < 1 || port > 65535) {
        console.error(`Invalid port: ${node}`)
        return false
      }
    } catch (err) {
      console.error(`Invalid enode URL: ${node}`)
      return false
    }
  }
  return true
}

RPC Integration

import * as PeerId from '@tevm/voltaire/PeerId'

// Add peer via admin_addPeer
async function addPeer(rpcUrl: string, enodeUrl: string): Promise<boolean> {
  const peerId = PeerId.from(enodeUrl)

  const response = await fetch(rpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'admin_addPeer',
      params: [PeerId.toString(peerId)],
      id: 1
    })
  })
  const { result } = await response.json()
  return result
}

// Remove peer via admin_removePeer
async function removePeer(rpcUrl: string, enodeUrl: string): Promise<boolean> {
  const peerId = PeerId.from(enodeUrl)

  const response = await fetch(rpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'admin_removePeer',
      params: [PeerId.toString(peerId)],
      id: 1
    })
  })
  const { result } = await response.json()
  return result
}

Common Bootnodes

Mainnet

const MAINNET_BOOTNODES = [
  "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303",
  "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
]

Sepolia

const SEPOLIA_BOOTNODES = [
  "enode://9246d00bc8fd1742e5ad2428b80fc4dc45d786283e05ef6edbd9002cbc335d40998444732fbe921cb88e1d2c73d1b1de53bae6a2237996e9bfe14f871baf7066@18.168.182.86:30303"
]

References