bridgeevm

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: MIT Imports: 15 Imported by: 0

README

bridgeevm

CI Go Reference Go Report Card License

Detect cross-chain bridge events from EVM transaction logs.

Status: pre-1.0. The public API may change in any 0.x.0 release; patch releases (0.x.y) will not break callers. See CHANGELOG.md.

Pass any *types.Log to Detector.Detect; if the log matches a known bridge event, you get back the bridge name, the leg type (source or destination), and a correlation ID that links the leg to its counterpart on the other chain.

import (
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/miradorlabs/bridgeevm"
)

detector, err := bridgeevm.New(bridgeevm.ChainEthereum)
if err != nil {
    log.Fatal(err)
}

result, ok, err := detector.Detect(log)
if err != nil {
    // log matched a bridge but the data was malformed
}
if !ok {
    // not a bridge log
    return
}

fmt.Printf("%s leg of %s (correlation %s)\n",
    result.LegType, result.BridgeName, result.CorrelationID)

Coverage

Bridge ethereum polygon arbitrum base optimism bsc
Across V2 src
Across V2 dst
Across V3 src
Across V3 dst
Stargate V1 src
Stargate V2 src
Stargate V2 dst
CCTP src/dst
CCTP V2 src/dst
deBridge
USDT0
1inch Fusion+

API

type Detector struct{ /* ... */ }

func New(chainName string) (*Detector, error)
func (d *Detector) Detect(log *types.Log) (Result, bool, error)
func (d *Detector) ChainName() string
func (d *Detector) Len() int

type Result struct {
    BridgeName        string
    BridgeDescription string
    LegType           LegType         // "source" or "destination"
    CorrelationID     string
    Contract          common.Address
    EventTopic        common.Hash
    EventName         string
}

type LegType string
const (
    LegTypeSource      LegType = "source"
    LegTypeDestination LegType = "destination"
)

Detect returns (_, false, nil) for logs that don't match any known bridge — this is the common case, not an error. The error return is non-nil only when a bridge event matched but its data was malformed.

A Detector is read-only and safe to share across goroutines.

How it works

Bridge configurations are embedded JSON, one file per protocol per chain. Each config declares the contract address, event topic hash, and how to extract the correlation ID from topics, data, packed bytes, ABI-encoded dynamic bytes, or a keccak hash thereof.

New builds an O(1) (address, topic[0]) → subscription map; Detect is a single map lookup followed by correlation extraction. No network calls and no ABI decoding beyond the configured field. The lookup itself is allocation-free (verified by BenchmarkDetect_Miss and BenchmarkLookupKey); the hit path allocates only the resulting correlation ID string.

Receipt-level helpers

This module deliberately operates on a single log at a time. If you need per-receipt counts or cross-log dedup (e.g. Across V3 SpokePool emits FundsDeposited twice in the same tx with the same depositId), wrap Detect in a small loop in your application.

License

MIT. See LICENSE.

Documentation

Overview

Package bridgeevm detects cross-chain bridge events from EVM transaction logs.

A Detector is built once per chain. Pass any *types.Log to Detect; if the log matches a known bridge event, Detect returns a Result describing the bridge, the leg type (source or destination), and the extracted correlation ID that links the leg to its counterpart on the other chain.

Bridge configurations are embedded JSON, covering Across, Stargate, CCTP, deBridge, USDT0, and 1inch across Ethereum, Polygon, Arbitrum, Base, Optimism, and BSC. See the README for the full coverage matrix.

Index

Examples

Constants

View Source
const (
	ChainArbitrum = "arbitrum"
	ChainBase     = "base"
	ChainBSC      = "bsc"
	ChainEthereum = "ethereum"
	ChainOptimism = "optimism"
	ChainPolygon  = "polygon"
)

Supported chain identifiers for use with New.

New accepts any string and returns a usable Detector with zero subscriptions for unrecognized chains; passing one of these constants is the recommended way to avoid typos and to discover the supported set from godoc.

Variables

This section is empty.

Functions

This section is empty.

Types

type Detector

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

func New

func New(chainName string) (*Detector, error)

New loads the embedded bridge configs for the given chain (case-insensitive) and returns a Detector with an O(1) address+topic lookup. Pass one of the Chain* constants for the supported set; any other string returns a usable Detector with no subscriptions, and Detect will always return ok=false.

Example

ExampleNew shows how to construct a Detector for a specific chain. New is cheap and safe to call once at process start.

package main

import (
	"fmt"

	"github.com/miradorlabs/bridgeevm"
)

func main() {
	d, err := bridgeevm.New("ethereum")
	if err != nil {
		panic(err)
	}
	fmt.Println(d.ChainName(), d.Len() > 0)
}
Output:
ethereum true

func (*Detector) Addresses added in v0.2.1

func (d *Detector) Addresses() []common.Address

func (*Detector) ChainName

func (d *Detector) ChainName() string

ChainName returns the chain this detector was built for, lowercased.

func (*Detector) Detect

func (d *Detector) Detect(log *types.Log) (Result, bool, error)

Detect returns the bridge details for log if it matches a known bridge.

The boolean indicates whether a configured bridge matched the log's (address, topic[0]) pair. The error is non-nil only when a bridge matched but correlation extraction failed (malformed log data); in that case the boolean is true and Result is the zero value. Callers that just want to know "is this a bridge?" can ignore the error.

Example

ExampleDetector_Detect shows the common path: hand any *types.Log to Detect and read the bridge name, leg, and correlation ID off the result.

package main

import (
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/miradorlabs/bridgeevm"
)

func main() {
	d, _ := bridgeevm.New("arbitrum")

	log := &types.Log{
		Address: common.HexToAddress("0xe35e9842fceaCA96570B734083f4a58e8F7C5f2A"),
		Topics: []common.Hash{
			common.HexToHash("0x32ed1a409ef04c7b0227189c3a103dc5ac10e775a15b785dcc510201f7c25ad3"),
			common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000002105"),
			common.HexToHash("0x6f1b280c20fb309b3653566de6f876cd100c6d26bc9722649865147ce22480e7"),
			common.HexToHash("0x0000000000000000000000006892ca799bbb10cb13e3cc2a7b587365ba2f3597"),
		},
	}

	result, ok, err := d.Detect(log)
	if err != nil {
		// A bridge matched but its data was malformed; the boolean is true.
		panic(err)
	}
	if !ok {
		fmt.Println("not a bridge log")
		return
	}
	fmt.Printf("%s %s leg, correlation %s\n",
		result.BridgeName, result.LegType, result.CorrelationID)
}
Output:
across source leg, correlation 50254707460338143966371593114785553611174598192372265612265721490268521529575
Example (UnknownLog)

ExampleDetector_Detect_unknownLog shows that Detect returns ok=false (with no error) for the common case of a log that does not match any configured bridge.

package main

import (
	"errors"
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/miradorlabs/bridgeevm"
)

func main() {
	d, _ := bridgeevm.New("ethereum")

	log := &types.Log{
		Address: common.HexToAddress("0x1234567890123456789012345678901234567890"),
		Topics:  []common.Hash{common.HexToHash("0xdeadbeef")},
	}

	_, ok, err := d.Detect(log)
	fmt.Println(ok, errors.Is(err, nil))
}
Output:
false true

func (*Detector) Len

func (d *Detector) Len() int

Len returns the number of (address, topic) subscriptions configured for this chain. A Detector with Len() == 0 will always return ok=false from Detect; callers can use this to detect chains that have no embedded coverage.

func (*Detector) Subscriptions added in v0.2.0

func (d *Detector) Subscriptions() []Subscription

Subscriptions returns every (address, topic) pair this Detector watches. Order is deterministic across calls, sorted by address bytes then topic bytes, so callers can build stable FilterQuery payloads. The returned slice is freshly allocated on each call; callers may mutate it.

func (*Detector) Topics added in v0.2.1

func (d *Detector) Topics() []common.Hash

type LegType

type LegType string
const (
	LegTypeSource      LegType = "source"
	LegTypeDestination LegType = "destination"
)

type Result

type Result struct {
	BridgeName        string
	BridgeDescription string
	LegType           LegType
	CorrelationID     string
	Contract          common.Address
	EventTopic        common.Hash
	EventName         string
}

type Subscription added in v0.2.0

type Subscription struct {
	BridgeAddress  common.Address
	EventSignature common.Hash
}

Subscription is a (contract, event-topic) pair the Detector matches against. Consumers building an eth_subscribeFilterLogs FilterQuery iterate Subscriptions and pass the deduped addresses and topics into the query.

Jump to

Keyboard shortcuts

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