fasteth

package module
v0.0.0-...-6123cce Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 21 Imported by: 0

README

fasteth — Ethereum RPC Client in Go

A high-performance Ethereum RPC client built on fasthttp and gobwas/ws. No go-ethereum dependency.

Features

  • Fasthttp-based HTTP — 3–5x faster than net/http for JSON-RPC
  • WebSocket subscriptions — real-time log and header streaming
  • Multicall2 — aggregate and tryAggregate with typed calls and automatic ABI decoding
  • Gas estimation — adaptive eth_estimateGas with automatic multiplier
  • Nonce management — concurrent-safe eth_getTransactionCount with caching
  • Transaction building — EIP-1559 (type 2) with legacy fallback, RLP encoding
  • Wallet — secp256k1 key management and signing (via decred/dcrd/secp256k1)
  • Price oracle — on-demand token price via known DEX pairs

Quick Start

import "github.com/cryptoddev/fasteth"

client := fasteth.NewClient("http://node:8545", "ws://node:8546")

// Direct eth_call
result, err := client.CallContract(ctx, &fasteth.CallMsg{
    To:   "0x...",
    Data: "0x70a08231...",
}, 0)

// Multicall aggregate
call := fasteth.BuildCall(pool, "factory()")
results, _ := client.Aggregate3(ctx, []fasteth.Call{call}, 100)

Multicall

Aggregate up to 100 calls in a single RPC request. Supports both aggregate and tryAggregate (per-call failure tolerance).

symCall := fasteth.BuildCall(token, "symbol()")
symCall.OutputTypes = []fastabi.ParamType{fastabi.TString()}

balCall := fasteth.BuildCallWithArgs(token, "balanceOf(address)",
    []fastabi.ParamType{fastabi.TAddress()},
    []any{userAddr},
)

results, _ := client.Aggregate3(ctx, []fasteth.Call{symCall, balCall}, 100)
for _, r := range results {
    if r.Success {
        log.Printf("decoded: %v", r.Decoded)
    }
}

Testing

# Unit tests
go test -short ./...

# Integration tests (requires a running RPC node)
go test -tags=integration -run TestMultiCall ./
go test -tags=integration -run TestAggregate3_RealNode .

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BSCStablecoins = map[string]int64{
	"0x55d398326f99059fF775485246999027B3197955": 1_000_000,
	"0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d": 1_000_000,
	"0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56": 1_000_000,
	"0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3": 1_000_000,
	"0x14016E85a25aeb13065688cAFB43044C2ef86784": 1_000_000,
	"0x049d68029688eAbF473097a2fC38ef61633A3C7A": 1_000_000,
	"0x5B6DcF557E2aBE2323c48445E8CC948910d8c2c9": 1_000_000,
}

Functions

func BuildAggregateCalldataCustom

func BuildAggregateCalldataCustom(calls []MCCall) []byte

Some BSC RPC nodes use non-standard ABI where element offsets = N*32 (sequential) instead of standard ABI relative offsets.

func BuildTryAggregateCalldataCustom

func BuildTryAggregateCalldataCustom(calls []MCCall) []byte

func CallRaw

func CallRaw(ctx context.Context, c *Client, method string, params ...any) ([]byte, error)

func RPC

func RPC[T any](ctx context.Context, c *Client, method string, params ...any) (T, error)

func RPCWithRetry

func RPCWithRetry[T any](ctx context.Context, c *Client, method string, params ...any) (T, error)

RPCWithRetry executes a JSON-RPC call with automatic retry logic for transient errors.

func Selector

func Selector(sig string) []byte

Selector computes the 4-byte ABI selector for a function signature.

func Subscribe

func Subscribe[T any](ctx context.Context, c *Client, topic string, params ...any) (<-chan *T, error)

Types

type AccessList

type AccessList []AccessTuple

type AccessTuple

type AccessTuple struct {
	Address     [20]byte
	StorageKeys [][32]byte
}

AccessTuple represents an EIP-2930 access list entry.

type BatchResult

type BatchResult struct {
	Success    bool
	ReturnData []byte
	Decoded    []any
}

type Block

type Block struct {
	Number       string   `json:"number"`
	Hash         string   `json:"hash"`
	ParentHash   string   `json:"parentHash"`
	Timestamp    string   `json:"timestamp"`
	Transactions []string `json:"transactions"`
	GasUsed      string   `json:"gasUsed"`
	GasLimit     string   `json:"gasLimit"`
}

type BlockHeader

type BlockHeader struct {
	Number     string `json:"number"`
	Hash       string `json:"hash"`
	ParentHash string `json:"parentHash"`
	Timestamp  string `json:"timestamp"`
}

type Call

type Call struct {
	Target       fastabi.Address
	Signature    string
	InputTypes   []fastabi.ParamType
	Inputs       []any
	OutputTypes  []fastabi.ParamType
	AllowFailure bool
}

Call defines a multicall request with ABI-typed parameters. Leave InputTypes/Inputs empty for functions with no arguments. Leave OutputTypes empty if you don't need decoding — raw bytes will be in ReturnData.

func BuildCall

func BuildCall(target fastabi.Address, sig string) Call

func BuildCallWithArgs

func BuildCallWithArgs(target fastabi.Address, sig string, inputTypes []fastabi.ParamType, inputs []any) Call

type CallMsg

type CallMsg struct {
	From     string `json:"from,omitempty"`
	To       string `json:"to"`
	Gas      uint64 `json:"gas,omitempty"`
	GasPrice string `json:"gasPrice,omitempty"`
	Value    string `json:"value,omitempty"`
	Data     string `json:"data"`
}

type Client

type Client struct {
	Wallet *Wallet
	Nonce  *NonceManager
	Gas    *GasCalculator
	Oracle *PriceOracle
	// contains filtered or unexported fields
}

func NewClient

func NewClient(rpcURL, wsURL string, opts ...ClientOption) *Client

func (*Client) Aggregate3

func (c *Client) Aggregate3(ctx context.Context, calls []Call, maxBatch int) ([]BatchResult, error)

Aggregate3 executes a batch of typed multicall requests. Calls are automatically split into batches of maxBatch size and run in parallel. If maxBatch <= 0, defaults to 100.

func (*Client) BlockByHash

func (c *Client) BlockByHash(ctx context.Context, hash string) (*Block, error)

func (*Client) BlockByNumber

func (c *Client) BlockByNumber(ctx context.Context, number uint64) (*Block, error)

func (*Client) BlockNumber

func (c *Client) BlockNumber(ctx context.Context) (uint64, error)

func (*Client) CallContract

func (c *Client) CallContract(ctx context.Context, msg *CallMsg, blockNumber uint64) ([]byte, error)

func (*Client) ChainID

func (c *Client) ChainID(ctx context.Context) (uint64, error)

func (*Client) Close

func (c *Client) Close() error

func (*Client) EstimateGas

func (c *Client) EstimateGas(ctx context.Context, msg *CallMsg) (uint64, error)

func (*Client) GasPrice

func (c *Client) GasPrice(ctx context.Context) (uint64, error)

func (*Client) GetGasPrice

func (c *Client) GetGasPrice() uint64

func (*Client) GetLogs

func (c *Client) GetLogs(ctx context.Context, filter *FilterQuery) ([]Log, error)

func (*Client) GetNextNonce

func (c *Client) GetNextNonce(ctx context.Context) (uint64, error)

func (*Client) GetTransactionCount

func (c *Client) GetTransactionCount(ctx context.Context, addr string, tag string) (uint64, error)

func (*Client) SendRawTransaction

func (c *Client) SendRawTransaction(ctx context.Context, rawTxHex string) (string, error)

func (*Client) SignDynamicFeeTx

func (c *Client) SignDynamicFeeTx(tx *DynamicFeeTx) ([]byte, error)

func (*Client) SignTransaction

func (c *Client) SignTransaction(tx *LegacyTx) ([]byte, error)

func (*Client) Stats

func (c *Client) Stats() (requests, errors uint64)

func (*Client) SubscribeNewHeads

func (c *Client) SubscribeNewHeads(ctx context.Context) (<-chan *BlockHeader, error)

func (*Client) TransactionReceipt

func (c *Client) TransactionReceipt(ctx context.Context, txHash string) (*Receipt, error)

func (*Client) TryAggregate

func (c *Client) TryAggregate(ctx context.Context, calls []Call, maxBatch int) ([]BatchResult, error)

TryAggregate executes a batch of typed multicall requests using tryAggregate. Unlike Aggregate3, individual call failures do not revert the entire batch. Calls are automatically split into batches of maxBatch size and run in parallel. If maxBatch <= 0, defaults to 100.

type ClientOption

type ClientOption func(*Client)

func WithGasCalculator

func WithGasCalculator(g *GasCalculator) ClientOption

func WithGasMultiplier

func WithGasMultiplier(multiplier float64) ClientOption

func WithMaxConns

func WithMaxConns(n int) ClientOption

func WithMulticallAddress

func WithMulticallAddress(addr string) ClientOption

func WithMulticallMode

func WithMulticallMode(mode MulticallMode) ClientOption

func WithNonceManager

func WithNonceManager(n *NonceManager) ClientOption

func WithPrivateKey

func WithPrivateKey(privateKeyHex string) ClientOption

func WithWallet

func WithWallet(w *Wallet) ClientOption

type DynamicFeeTx

type DynamicFeeTx struct {
	ChainID              uint64
	Nonce                uint64
	MaxPriorityFeePerGas *big.Int
	MaxFeePerGas         *big.Int
	Gas                  uint64
	To                   *[20]byte
	Value                *big.Int
	Data                 []byte
	AccessList           AccessList

	V *big.Int
	R *big.Int
	S *big.Int
}

DynamicFeeTx represents an EIP-1559 dynamic fee transaction.

func (*DynamicFeeTx) Encode

func (tx *DynamicFeeTx) Encode() []byte

func (*DynamicFeeTx) SigningHash

func (tx *DynamicFeeTx) SigningHash() []byte

SigningHash returns the keccak256 of the EIP-1559 signing payload.

type EventDecoder

type EventDecoder struct {
	Signature string
	Topic0    [32]byte
	Inputs    []EventParam
}

func ParseEvent

func ParseEvent(sig string) (*EventDecoder, error)

func (*EventDecoder) Decode

func (d *EventDecoder) Decode(log Log) (map[string]any, error)

Decode decodes a log entry into a map of parameter names to values.

type EventParam

type EventParam struct {
	Name    string
	Type    fastabi.ParamType
	Indexed bool
}

type FilterQuery

type FilterQuery struct {
	FromBlock string     `json:"fromBlock,omitempty"`
	ToBlock   string     `json:"toBlock,omitempty"`
	Addresses []string   `json:"addresses,omitempty"`
	Topics    [][]string `json:"topics,omitempty"`
}

type GasCalculator

type GasCalculator struct {
	// contains filtered or unexported fields
}

GasCalculator computes gas costs for EVM transactions. Uses legacy gas pricing (not EIP-1559), suitable for BSC and similar chains.

func NewGasCalculator

func NewGasCalculator(multiplier float64) *GasCalculator

func (*GasCalculator) CalculateNetProfit

func (gc *GasCalculator) CalculateNetProfit(grossProfitCents, gasLimit uint64, nativePriceCents uint64) int64

CalculateNetProfit calculates profit minus gas costs (8x base price for worst-case).

func (*GasCalculator) EstimateGasCost

func (gc *GasCalculator) EstimateGasCost(gasLimit uint64, nativePriceCents uint64) GasEstimate

func (*GasCalculator) GetCurrentPrice

func (gc *GasCalculator) GetCurrentPrice() uint64

func (*GasCalculator) GetRecommendedGasPrice

func (gc *GasCalculator) GetRecommendedGasPrice() uint64

GetRecommendedGasPrice returns a gas price for fast inclusion.

func (*GasCalculator) IsProfitable

func (gc *GasCalculator) IsProfitable(profitCents uint64, gasLimit uint64, nativePriceCents uint64) bool

IsProfitable checks if profit after gas costs is positive.

func (*GasCalculator) UpdatePrice

func (gc *GasCalculator) UpdatePrice(priceWei uint64)

type GasEstimate

type GasEstimate struct {
	GasPrice     uint64
	GasLimit     uint64
	TxCost       uint64
	TxCostNative float64
	TxCostCents  uint64
}

type GasSample

type GasSample struct {
	Price     uint64
	Timestamp time.Time
}

type LegacyTx

type LegacyTx struct {
	Nonce    uint64
	GasPrice *big.Int
	Gas      uint64
	To       *[20]byte
	Value    *big.Int
	Data     []byte

	V *big.Int
	R *big.Int
	S *big.Int

	ChainID uint64
}

LegacyTx represents an Ethereum legacy transaction with EIP-155 replay protection.

func (*LegacyTx) Encode

func (tx *LegacyTx) Encode() []byte

func (*LegacyTx) SigningHash

func (tx *LegacyTx) SigningHash() []byte

SigningHash returns the keccak256 of the RLP-encoded signing payload (without V,R,S but with chainID for EIP-155).

type Log

type Log struct {
	Address          string   `json:"address"`
	Topics           []string `json:"topics"`
	Data             string   `json:"data"`
	BlockNumber      string   `json:"blockNumber"`
	TransactionHash  string   `json:"transactionHash"`
	TransactionIndex string   `json:"transactionIndex"`
	BlockHash        string   `json:"blockHash"`
	LogIndex         string   `json:"logIndex"`
	Removed          bool     `json:"removed"`
}

type MCCall

type MCCall struct {
	Target       fastabi.Address
	CallData     []byte
	AllowFailure bool
}

type MulticallMode

type MulticallMode int

MulticallMode selects the ABI encoding strategy for multicall calls.

const (
	// MulticallCustom uses custom ABI encoding with sequential element offsets.
	// Required for some BSC RPC nodes with non-standard ABI interpretation.
	MulticallCustom MulticallMode = iota
	// MulticallStandard uses standard ABI encoding via fastabi.
	MulticallStandard
)

type NonceManager

type NonceManager struct {
	// contains filtered or unexported fields
}

NonceManager tracks the current nonce for an address. Uses optimistic local increment — fetches from RPC only on first use or after reset.

func NewNonceManager

func NewNonceManager() *NonceManager

func (*NonceManager) Next

func (nm *NonceManager) Next(ctx context.Context, c *Client, addr string) (uint64, error)

func (*NonceManager) Reset

func (nm *NonceManager) Reset()

Reset forces a re-fetch from RPC on the next call. Call this after a transaction fails with nonce error.

func (*NonceManager) Sync

func (nm *NonceManager) Sync(ctx context.Context, c *Client, addr string) error

Sync re-fetches the nonce from RPC and updates the local counter. Only updates if RPC nonce is higher (doesn't go backwards).

type PriceOracle

type PriceOracle struct {
	// contains filtered or unexported fields
}

PriceOracle fetches token prices in USD from on-chain sources. Uses WBNB/USDT pool for BNB price, and stablecoin pegs for stable tokens.

func NewPriceOracle

func NewPriceOracle(client *Client) *PriceOracle

func (*PriceOracle) IsStablecoin

func (o *PriceOracle) IsStablecoin(addr fastabi.Address) bool

func (*PriceOracle) LastUpdate

func (o *PriceOracle) LastUpdate() time.Time

func (*PriceOracle) PriceMicro

func (o *PriceOracle) PriceMicro() int64

func (*PriceOracle) Start

func (o *PriceOracle) Start(ctx context.Context)

func (*PriceOracle) TokenPriceMicro

func (o *PriceOracle) TokenPriceMicro(addr fastabi.Address) int64

type RPCError

type RPCError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    string `json:"data,omitempty"`
}

func (*RPCError) Error

func (e *RPCError) Error() string

type Receipt

type Receipt struct {
	TransactionHash   string `json:"transactionHash"`
	TransactionIndex  string `json:"transactionIndex"`
	BlockHash         string `json:"blockHash"`
	BlockNumber       string `json:"blockNumber"`
	From              string `json:"from"`
	To                string `json:"to"`
	GasUsed           string `json:"gasUsed"`
	CumulativeGasUsed string `json:"cumulativeGasUsed"`
	Status            string `json:"status"`
	Logs              []Log  `json:"logs"`
}

type Result

type Result struct {
	Success    bool
	ReturnData []byte
}

type Wallet

type Wallet struct {
	Address fastabi.Address
	// contains filtered or unexported fields
}

func NewWalletFromPrivateKey

func NewWalletFromPrivateKey(privateKeyHex string) (*Wallet, error)

func (*Wallet) GetAddress

func (w *Wallet) GetAddress() fastabi.Address

func (*Wallet) PrivateKeyHex

func (w *Wallet) PrivateKeyHex() string

func (*Wallet) Sign

func (w *Wallet) Sign(hash []byte) ([]byte, error)

func (*Wallet) SignDynamicFeeTx

func (w *Wallet) SignDynamicFeeTx(tx *DynamicFeeTx) ([]byte, error)

SignDynamicFeeTx signs an EIP-1559 dynamic fee transaction.

func (*Wallet) SignTransaction

func (w *Wallet) SignTransaction(tx *LegacyTx) ([]byte, error)

SignTransaction signs an Ethereum legacy transaction with EIP-155 replay protection.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL