synapsecns/sanguine

View on GitHub
tools/abigen/internal/etherscan/etherscan.go

Summary

Maintainability
A
0 mins
Test Coverage
package etherscan

import (
    "context"
    "fmt"
    "github.com/nanmu42/etherscan-api"
    "github.com/synapsecns/sanguine/core/config"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "strings"
    "time"
)

// Client implements an etherscan client.
type Client struct {
    *etherscan.Client
    // rateLimiter is a file based rate limiter
    rateLimiter *fileRateLimiter
}

// timeout is the http timeout for a request.
const timeout = time.Second * 30

// newEtherscanABIClient creates a new etherscan client.
func newEtherscanABIClient(parentCtx context.Context, chainID uint32, url string, disableRateLimiter bool) (*Client, error) {
    var client Client
    ctx, cancel := context.WithCancel(parentCtx)

    // Try chain-specific key first, fall back to generic ETHERSCAN_KEY
    apiKeyEnv := strings.ToUpper(fmt.Sprintf("%d_KEY", chainID))
    apiKey := os.Getenv(apiKeyEnv)
    if apiKey == "" {
        apiKey = os.Getenv("ETHERSCAN_KEY")
    }

    customization := etherscan.Customization{
        Client: &http.Client{
            Timeout: timeout,
        },
        BaseURL: url,
        Key:     apiKey,
        Verbose: true, // Enable verbose logging
    }

    // waitBetweenRequest is how long to wait between requests. If an analytics key is enabled, rate limiting is disabled
    waitBetweenRequests := time.Second * 5
    rateLimiterEnabled := apiKey == "" && !disableRateLimiter

    if rateLimiterEnabled {
        configDir, err := config.GetConfigDir()
        if err != nil {
            cancel()
            return nil, fmt.Errorf("could not create file rate limiter: %w", err)
        }

        rateLimitDir := filepath.Join(configDir, strconv.Itoa(int(chainID)))

        client.rateLimiter, err = newFileRateLimiter(ctx, rateLimitDir, waitBetweenRequests)
        if err != nil {
            cancel()
            return nil, fmt.Errorf("could not create file rate limiter: %w", err)
        }

        customization.BeforeRequest = func(_, _ string, _ map[string]interface{}) error {
            _, err := client.rateLimiter.obtainLock(ctx)
            return err
        }

        customization.AfterRequest = func(_, action string, _ map[string]interface{}, _ interface{}, _ error) {
            _, err = client.rateLimiter.releaseLock()
            if err != nil {
                logger.Error(err)
                cancel()
            }
        }
    } else {
        // context cancellation is handled by the parent
        _ = cancel
    }

    client.Client = etherscan.NewCustomized(customization)
    return &client, nil
}