Files
zeekayandHanzo Dev da299df77d the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 13:05:15 -07:00

183 lines
5.2 KiB
Go

// Copyright (C) 2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package evm
import (
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/luxfi/crypto"
"github.com/luxfi/evm/rpc"
)
var ErrUnknownErrorSelector = fmt.Errorf("unknown error selector")
// also used at mocks
var (
rpcDialContext = rpc.DialContext
)
// RawClient wraps over rpc.Client for calls used by SDK. Used to make evm calls not available in ethclient:
// - debug trace call
// - debug trace transaction
// Features:
// - finds out url scheme in case it is missing, to connect to ws/wss/http/https
// - repeats to try to recover from failures, generating its own context for each call
// - logs rpc url in case of failure
type RawClient struct {
RPCClient *rpc.Client
URL string
// also used at mocks
CallContext func(context.Context, interface{}, string, ...interface{}) error
}
// GetRawClient connects a raw evm rpc client to the given [rpcURL].
// Supports [repeatsOnFailure] failures.
func GetRawClient(rpcURL string) (RawClient, error) {
client := RawClient{
URL: rpcURL,
}
hasScheme, err := HasScheme(rpcURL)
if err != nil {
return RawClient{}, err
}
client.RPCClient, err = retryWithContextGen(
apiLargeContext,
func(ctx context.Context) (*rpc.Client, error) {
if hasScheme {
return rpcDialContext(ctx, rpcURL)
} else {
_, scheme, err := GetClientWithoutScheme(rpcURL)
if err != nil {
return nil, err
}
return rpcDialContext(ctx, scheme+rpcURL)
}
},
repeatsOnFailure,
sleepBetweenRepeats,
)
if err != nil {
err = fmt.Errorf("failure connecting to rpc client on %s: %w", rpcURL, err)
}
client.CallContext = client.RPCClient.CallContext
return client, err
}
// Close closes underlying rpc connection.
func (client RawClient) Close() {
client.RPCClient.Close()
}
// DebugTraceTransaction returns a trace for the given [txID] on [client].
// Supports [repeatsOnFailure] failures.
func (client RawClient) DebugTraceTransaction(
txID string,
) (map[string]interface{}, error) {
trace, err := retryWithContextGen(
apiLargeContext,
func(ctx context.Context) (map[string]interface{}, error) {
var trace map[string]interface{}
err := client.CallContext(
ctx,
&trace,
"debug_traceTransaction",
txID,
map[string]string{"tracer": "callTracer"},
)
return trace, err
},
repeatsOnFailure,
sleepBetweenRepeats,
)
if err != nil {
err = fmt.Errorf("failure tracing tx %s on %s: %w", txID, client.URL, err)
}
return trace, err
}
// DebugTraceCall returns a trace for making a call on [client] with the given [data].
// Supports [repeatsOnFailure] failures.
func (client RawClient) DebugTraceCall(
data map[string]string,
) (map[string]interface{}, error) {
trace, err := retryWithContextGen(
apiLargeContext,
func(ctx context.Context) (map[string]interface{}, error) {
var trace map[string]interface{}
err := client.CallContext(
ctx,
&trace,
"debug_traceCall",
data,
"latest",
map[string]interface{}{
"tracer": "callTracer",
"tracerConfig": map[string]interface{}{
"onlyTopCall": false,
},
},
)
return trace, err
},
repeatsOnFailure,
sleepBetweenRepeats,
)
if err != nil {
err = fmt.Errorf("failure tracing call on %s: %w", client.URL, err)
}
return trace, err
}
// GetTxTrace returns a trace for the given [txID] on [rpcURL].
// Supports [repeatsOnFailure] failures.
func GetTxTrace(rpcURL string, txID string) (map[string]interface{}, error) {
client, err := GetRawClient(rpcURL)
if err != nil {
return nil, err
}
return client.DebugTraceTransaction(txID)
}
// GetFunctionSelector returns evm function selector code for the given function signature.
// EVM maps function and error signatures into codes that are then used in traces.
func GetFunctionSelector(functionSignature string) string {
return "0x" + hex.EncodeToString(crypto.Keccak256([]byte(functionSignature))[:4])
}
// GetErrorFromTrace returns golang error associated with [trace] by using [functionSignatureToError]
// to map function signatures to evm function selectors in [trace], and then to golang errors.
// First returned error is the mapped error, second error is for errors obtained
// executing this function.
func GetErrorFromTrace(
trace map[string]interface{},
functionSignatureToError map[string]error,
) (error, error) {
traceOutputI, ok := trace["output"]
if !ok {
return nil, fmt.Errorf("trace does not contain output field")
}
traceOutput, ok := traceOutputI.(string)
if !ok {
return nil, fmt.Errorf("expected type string for trace output, got %T", traceOutputI)
}
traceOutputBytes, err := hex.DecodeString(strings.TrimPrefix(traceOutput, "0x"))
if err != nil {
return nil, fmt.Errorf("failure decoding trace output: %w", err)
}
if len(traceOutputBytes) < 4 {
return nil, fmt.Errorf("less than 4 bytes in trace output")
}
traceErrorSelector := "0x" + hex.EncodeToString(traceOutputBytes[:4])
for errorSignature, err := range functionSignatureToError {
errorSelector := GetFunctionSelector(errorSignature)
if traceErrorSelector == errorSelector {
return err, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrUnknownErrorSelector, traceErrorSelector)
}