synapsecns/sanguine

View on GitHub
contrib/promexporter/exporters/relayer.go

Summary

Maintainability
A
1 hr
Test Coverage
package exporters

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "math/big"
    "net/http"
    "slices"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/params"
    "github.com/lmittmann/w3/module/eth"
    "github.com/lmittmann/w3/w3types"
    "github.com/synapsecns/sanguine/contrib/promexporter/internal/decoders"
    rfqAPIModel "github.com/synapsecns/sanguine/services/rfq/api/model"
)

// TODO: This is ugly. We can probably get this from the config.
var usdcAddresses = map[int]string{
    1:      "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    10:     "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
    42161:  "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
    8453:   "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    534352: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4",
    59144:  "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
}

// TODO: this function does too many things.
//
//nolint:cyclop
func (e *exporter) fetchRelayerBalances(ctx context.Context, url string) error {
    // Fetch relayer addresses
    quotes, err := e.fetchAllQuotes(ctx, url)
    if err != nil {
        return fmt.Errorf("could not fetch relayer addresses: %w", err)
    }

    // chainIDs is a map of chain ID to relayer addresses
    chainIDToRelayers := make(map[int][]string)

    // Get all chain IDs
    for _, quote := range quotes {
        if !slices.Contains(chainIDToRelayers[quote.OriginChainID], quote.RelayerAddr) {
            chainIDToRelayers[quote.OriginChainID] = append(chainIDToRelayers[quote.OriginChainID], quote.RelayerAddr)
        }

        if !slices.Contains(chainIDToRelayers[quote.DestChainID], quote.RelayerAddr) {
            chainIDToRelayers[quote.DestChainID] = append(chainIDToRelayers[quote.DestChainID], quote.RelayerAddr)
        }
    }

    for chainID := range chainIDToRelayers {
        chainIDToRelayers[chainID] = append(chainIDToRelayers[chainID], "0x2156BfA195C033CA2DF4Ff14e6Da0c617B8cb4F7")
    }

    for chainID, relayers := range chainIDToRelayers {
        client, err := e.omnirpcClient.GetConfirmationsClient(ctx, chainID, 1)
        if err != nil {
            return fmt.Errorf("could not get confirmations client: %w", err)
        }

        var relayerBalances []*big.Int
        var usdcBalances []*big.Int
        for range relayers {
            relayerBalances = append(relayerBalances, new(big.Int))
            usdcBalances = append(usdcBalances, new(big.Int))
        }

        var callsForCurrentChainID []w3types.Caller
        for i, relayer := range relayers {
            callsForCurrentChainID = append(
                callsForCurrentChainID,
                eth.Balance(common.HexToAddress(relayer), nil).Returns(relayerBalances[i]),
            )
            callsForCurrentChainID = append(
                callsForCurrentChainID,
                eth.CallFunc(
                    decoders.FuncBalanceOf(),
                    common.HexToAddress(usdcAddresses[chainID]),
                    common.HexToAddress(relayer)).Returns(usdcBalances[i]),
            )
        }

        _ = e.batchCalls(ctx, client, callsForCurrentChainID)

        for i := range relayerBalances {
            balanceFloat, _ := new(big.Float).SetInt(relayerBalances[i]).Float64()
            usdcBalanceFloat, _ := new(big.Float).SetInt(usdcBalances[i]).Float64()
            relayerMetadata := relayerMetadata{
                address:     common.HexToAddress(relayers[i]),
                balance:     balanceFloat / params.Ether,
                usdcBalance: usdcBalanceFloat / 1e6,
            }
            // the line of interest, where we record each relayer data for the respective chainID
            e.otelRecorder.RecordRelayerBalance(chainID, relayerMetadata)
        }
    }

    return nil
}

func (e *exporter) fetchAllQuotes(ctx context.Context, url string) ([]rfqAPIModel.GetQuoteResponse, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, fmt.Errorf("could not get quotes: %w", err)
    }

    res, err := e.client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("could not get quotes: %w", err)
    }
    defer func() {
        _ = res.Body.Close()
    }()

    body, err := io.ReadAll(res.Body)
    if err != nil {
        return nil, fmt.Errorf("could not read body: %w", err)
    }

    var quotes []rfqAPIModel.GetQuoteResponse
    err = json.Unmarshal(body, &quotes)
    if err != nil {
        return nil, fmt.Errorf("could not unmarshal quotes: %w", err)
    }

    return quotes, nil
}