Skip to main content
Package rpc implements the JSON-RPC 2.0 client transport layer. ethclient is built on top of it. Use the rpc package directly when you need to call methods that ethclient does not expose — for example, Geth-specific debug_*, admin_*, or txpool_* namespaces.
import "github.com/ethereum/go-ethereum/rpc"

Connecting

func Dial(rawurl string) (*Client, error)
func DialContext(ctx context.Context, rawurl string) (*Client, error)
func DialOptions(ctx context.Context, rawurl string, options ...ClientOption) (*Client, error)
Supported URL schemes:
SchemeTransport
http / httpsHTTP (request/response only; no subscriptions)
ws / wssWebSocket (supports subscriptions)
(no scheme / bare path)Unix domain socket / Windows named pipe (IPC)
// HTTP
client, err := rpc.Dial("http://localhost:8545")

// WebSocket
client, err := rpc.Dial("ws://localhost:8546")

// IPC
client, err := rpc.Dial("/var/run/geth.ipc")

Making a single call

// Call sends a request and unmarshals the result into `result`.
// Pass nil to discard the result.
func (c *Client) Call(result interface{}, method string, args ...interface{}) error

// CallContext is the same but respects context cancellation and deadlines.
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
var blockNumber hexutil.Uint64
if err := client.Call(&blockNumber, "eth_blockNumber"); err != nil {
    log.Fatal(err)
}
fmt.Println("block:", uint64(blockNumber))
result must be a pointer (or nil). Any value accepted by encoding/json.Unmarshal works.

Batch calls

Send multiple requests in a single round-trip:
// BatchElem is one element in a batch.
type BatchElem struct {
    Method string
    Args   []interface{}
    Result interface{} // must be a non-nil pointer
    Error  error       // set by BatchCall if the server returned an error for this element
}

func (c *Client) BatchCall(b []BatchElem) error
func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error
var (
    balance hexutil.Big
    nonce   hexutil.Uint64
)

batch := []rpc.BatchElem{
    {
        Method: "eth_getBalance",
        Args:   []interface{}{addr, "latest"},
        Result: &balance,
    },
    {
        Method: "eth_getTransactionCount",
        Args:   []interface{}{addr, "latest"},
        Result: &nonce,
    },
}

if err := client.BatchCall(batch); err != nil {
    log.Fatal(err) // I/O-level error
}

// Check per-element errors
for _, elem := range batch {
    if elem.Error != nil {
        log.Printf("%s error: %v", elem.Method, elem.Error)
    }
}

fmt.Println("balance:", balance.ToInt())
fmt.Println("nonce:", uint64(nonce))
BatchCall returns only transport-level errors. Application-level errors (e.g. method not found) are set on BatchElem.Error for each element individually.

Subscriptions

Subscriptions are only supported over WebSocket and IPC connections. Calling Subscribe on an HTTP client returns ErrNotificationsUnsupported.
// Subscribe calls "<namespace>_subscribe" with the given args.
// Notifications are delivered to channel.
func (c *Client) Subscribe(
    ctx context.Context,
    namespace string,
    channel interface{},
    args ...interface{},
) (*ClientSubscription, error)

// EthSubscribe is a convenience wrapper for the "eth" namespace.
func (c *Client) EthSubscribe(
    ctx context.Context,
    channel interface{},
    args ...interface{},
) (*ClientSubscription, error)
headers := make(chan map[string]interface{})
sub, err := client.EthSubscribe(ctx, headers, "newHeads")
if err != nil {
    log.Fatal(err)
}
defer sub.Unsubscribe()

for {
    select {
    case h := <-headers:
        fmt.Println("new head:", h["number"])
    case err := <-sub.Err():
        log.Fatal(err)
    }
}
ClientSubscription methods:
func (sub *ClientSubscription) Unsubscribe()
func (sub *ClientSubscription) Err() <-chan error

Checking available methods

func (c *Client) SupportedModules() (map[string]string, error)
Returns a map of "namespace" → "version" for all API namespaces the node exposes.

When to use rpc vs ethclient

Use casePackage
Standard eth_* methods with typed return valuesethclient
Custom or Geth-specific namespaces (debug, admin, txpool, …)rpc
Batch multiple eth_* calls in one round-triprpc
Need access to the underlying transportrpc (retrieve via ethclient.Client.Client())
You can also mix both: get an *ethclient.Client for normal usage and call client.Client() to retrieve the underlying *rpc.Client when you need to call an unlisted method.

Example: call a Geth admin method

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/rpc"
)

func main() {
    // IPC gives access to admin/debug/txpool namespaces
    client, err := rpc.Dial("/var/run/geth.ipc")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // admin_nodeInfo — not available in ethclient
    var info map[string]interface{}
    if err := client.CallContext(context.Background(), &info, "admin_nodeInfo"); err != nil {
        log.Fatal(err)
    }
    fmt.Println("enode:", info["enode"])

    // txpool_status
    var status struct {
        Pending string `json:"pending"`
        Queued  string `json:"queued"`
    }
    if err := client.Call(&status, "txpool_status"); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("pending: %s  queued: %s\n", status.Pending, status.Queued)
}

Error types

var (
    ErrClientQuit                = errors.New("client is closed")
    ErrNoResult                  = errors.New("JSON-RPC response has no result")
    ErrMissingBatchResponse      = errors.New("response batch did not contain a response to this call")
    ErrSubscriptionQueueOverflow = errors.New("subscription queue overflow")
)
The client also exposes the rpc.Error and rpc.DataError interfaces so callers can inspect JSON-RPC error codes and data fields. ethclient.RevertErrorData is a convenience wrapper that extracts the ABI-encoded revert reason from a call error.

Build docs developers (and LLMs) love