synapsecns/sanguine

View on GitHub
services/rfq/relayer/relconfig/getters.go

Summary

Maintainability
F
4 days
Test Coverage
package relconfig

import (
    "fmt"
    "math/big"
    "reflect"
    "time"

    "github.com/synapsecns/sanguine/core"

    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
    "github.com/synapsecns/sanguine/ethergo/signer/config"
)

const (
    tokenDecimalsBase = 10
)

// DefaultChainConfig is the default chain config.
var DefaultChainConfig = ChainConfig{
    DeadlineBufferSeconds:   600,
    OriginGasEstimate:       160000,
    DestGasEstimate:         100000,
    MinGasToken:             "100000000000000000", // 1 ETH
    QuotePct:                NewFloatPtr(100),
    QuoteFixedFeeMultiplier: NewFloatPtr(1),
    RelayFixedFeeMultiplier: NewFloatPtr(1),
}

// NewFloatPtr returns a pointer to a float64.
func NewFloatPtr(val float64) *float64 {
    return core.PtrTo(val)
}

// getChainConfigValue gets the value of a field from ChainConfig.
// It returns the value from Chains[chainID] if non-zero,
// else from BaseChainConfig if non-zero,
// else from DefaultChainConfig.
func (c Config) getChainConfigValue(chainID int, fieldName string) (interface{}, error) {
    chainConfig, ok := c.Chains[chainID]
    if ok {
        value, err := getFieldValue(chainConfig, fieldName)
        if err != nil {
            return nil, err
        }
        if !isNilOrZero(value) {
            return derefPointer(value), nil
        }
    }

    baseValue, err := getFieldValue(c.BaseChainConfig, fieldName)
    if err != nil {
        return nil, err
    }
    if !isNilOrZero(baseValue) {
        return derefPointer(baseValue), nil
    }

    defaultValue, err := getFieldValue(DefaultChainConfig, fieldName)
    if err != nil {
        return nil, err
    }
    return derefPointer(defaultValue), nil
}

func getFieldValue(obj interface{}, fieldName string) (interface{}, error) {
    if !isChainConfigField(fieldName) {
        return nil, fmt.Errorf("invalid chain config field: %s", fieldName)
    }
    val := reflect.ValueOf(obj)
    fieldVal := val.FieldByName(fieldName)

    if !fieldVal.IsValid() {
        return nil, fmt.Errorf("invalid field: %s", fieldName)
    }

    return fieldVal.Interface(), nil
}

var chainConfigFields map[string]bool

// isChainConfigField returns true if the field is a valid field in ChainConfig.
// We use this to avoid unsafe-reflect-by-name.
func isChainConfigField(fieldName string) bool {
    // Cache the fields for faster lookups.
    if chainConfigFields == nil {
        chainConfigFields = make(map[string]bool)
        for i := 0; i < reflect.TypeOf(ChainConfig{}).NumField(); i++ {
            chainConfigFields[reflect.TypeOf(ChainConfig{}).Field(i).Name] = true
        }
    }

    _, ok := chainConfigFields[fieldName]
    return ok
}

func derefPointer(value interface{}) interface{} {
    val := reflect.ValueOf(value)
    if val.Kind() == reflect.Ptr && !val.IsNil() {
        return val.Elem().Interface()
    }
    return value
}

func isNilOrZero(value interface{}) bool {
    val := reflect.ValueOf(value)
    if val.Kind() == reflect.Ptr {
        return val.IsNil()
    }
    return reflect.DeepEqual(value, reflect.Zero(val.Type()).Interface())
}

// GetRFQAddress returns the RFQ address for the given chainID.
func (c Config) GetRFQAddress(chainID int) (value common.Address, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "RFQAddress")
    if err != nil {
        return value, err
    }

    strValue, ok := rawValue.(string)
    if !ok {
        return value, fmt.Errorf("failed to cast RFQAddress to string")
    }

    if strValue == "" {
        return value, fmt.Errorf("no RFQAddress for chain %d", chainID)
    }
    return common.HexToAddress(strValue), nil
}

// GetSynapseCCTPAddress returns the SynapseCCTP address for the given chainID.
func (c Config) GetSynapseCCTPAddress(chainID int) (value common.Address, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Synapse == nil {
        return value, fmt.Errorf("no synapse config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Synapse.SynapseCCTPAddress == "" {
        return value, fmt.Errorf("no SynapseCCTPAddress for chain %d", chainID)
    }
    return common.HexToAddress(chainCfg.RebalanceConfigs.Synapse.SynapseCCTPAddress), nil
}

// GetTokenMessengerAddress returns the TokenMessenger address for the given chainID.
func (c Config) GetTokenMessengerAddress(chainID int) (value common.Address, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Circle == nil {
        return value, fmt.Errorf("no token messenger address config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Circle.TokenMessengerAddress == "" {
        return value, fmt.Errorf("no TokenMessengerAddress for chain %d", chainID)
    }
    return common.HexToAddress(chainCfg.RebalanceConfigs.Circle.TokenMessengerAddress), nil
}

// GetL1GatewayAddress returns the L1Gateway address for the given chainID.
func (c Config) GetL1GatewayAddress(chainID int) (value common.Address, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll == nil {
        return value, fmt.Errorf("no scroll config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll.L1GatewayAddress == "" {
        return value, fmt.Errorf("no L1GatewayAddress for chain %d", chainID)
    }
    return common.HexToAddress(chainCfg.RebalanceConfigs.Scroll.L1GatewayAddress), nil
}

// GetL1ScrollMessengerAddress returns the L1ScrollMessenger address for the given chainID.
func (c Config) GetL1ScrollMessengerAddress(chainID int) (value common.Address, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll == nil {
        return value, fmt.Errorf("no scroll config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll.L1ScrollMessengerAddress == "" {
        return value, fmt.Errorf("no L1ScrollMessengerAddress for chain %d", chainID)
    }
    return common.HexToAddress(chainCfg.RebalanceConfigs.Scroll.L1ScrollMessengerAddress), nil
}

// GetL2GatewayAddress returns the L2Gateway address for the given chainID.
func (c Config) GetL2GatewayAddress(chainID int) (value common.Address, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll == nil {
        return value, fmt.Errorf("no scroll config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll.L2GatewayAddress == "" {
        return value, fmt.Errorf("no L2GatewayAddress for chain %d", chainID)
    }
    return common.HexToAddress(chainCfg.RebalanceConfigs.Scroll.L2GatewayAddress), nil
}

// GetConfirmations returns the Confirmations for the given chainID.
func (c Config) GetConfirmations(chainID int) (value uint64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "Confirmations")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(uint64)
    if !ok {
        return value, fmt.Errorf("failed to cast Confirmations to int")
    }
    return value, nil
}

// GetFinalityConfirmations returns the FinalityConfirmations for the given chainID.
func (c Config) GetFinalityConfirmations(chainID int) (value uint64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "FinalityConfirmations")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(uint64)
    if !ok {
        return value, fmt.Errorf("failed to cast FinalityConfirmations to int")
    }
    return value, nil
}

// GetNativeToken returns the NativeToken for the given chainID.
func (c Config) GetNativeToken(chainID int) (value string, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "NativeToken")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(string)
    if !ok {
        return value, fmt.Errorf("failed to cast NativeToken to string")
    }
    return value, nil
}

// GetDeadlineBuffer returns the DeadlineBuffer for the given chainID.
func (c Config) GetDeadlineBuffer(chainID int) (seconds time.Duration, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "DeadlineBufferSeconds")
    if err != nil {
        return seconds, err
    }

    value, ok := rawValue.(int)
    if !ok {
        return seconds, fmt.Errorf("failed to cast DeadlineBufferSeconds to int")
    }
    seconds = time.Duration(value) * time.Second
    return seconds, nil
}

// GetOriginGasEstimate returns the OriginGasEstimate for the given chainID.
func (c Config) GetOriginGasEstimate(chainID int) (value int, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "OriginGasEstimate")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(int)
    if !ok {
        return value, fmt.Errorf("failed to cast OriginGasEstimate to int")
    }
    return value, nil
}

// GetDestGasEstimate returns the DestGasEstimate for the given chainID.
func (c Config) GetDestGasEstimate(chainID int) (value int, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "DestGasEstimate")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(int)
    if !ok {
        return value, fmt.Errorf("failed to cast DestGasEstimate to int")
    }
    return value, nil
}

// GetL1FeeChainID returns the L1FeeChainID for the given chainID.
func (c Config) GetL1FeeChainID(chainID int) (value uint32, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "L1FeeChainID")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(uint32)
    if !ok {
        return value, fmt.Errorf("failed to cast L1FeeChainID to int")
    }
    return value, nil
}

// GetL1FeeOriginGasEstimate returns the L1FeeOriginGasEstimate for the given chainID.
func (c Config) GetL1FeeOriginGasEstimate(chainID int) (value int, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "L1FeeOriginGasEstimate")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(int)
    if !ok {
        return value, fmt.Errorf("failed to cast L1FeeOriginGasEstimate to int")
    }
    return value, nil
}

// GetL1FeeDestGasEstimate returns the L1FeeDestGasEstimate for the given chainID.
func (c Config) GetL1FeeDestGasEstimate(chainID int) (value int, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "L1FeeDestGasEstimate")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(int)
    if !ok {
        return value, fmt.Errorf("failed to cast L1FeeDestGasEstimate to int")
    }
    return value, nil
}

// GetMinGasToken returns the MinGasToken for the given chainID.
func (c Config) GetMinGasToken(chainID int) (value *big.Int, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "MinGasToken")
    if err != nil {
        return value, err
    }

    strValue, ok := rawValue.(string)
    if !ok {
        return value, fmt.Errorf("failed to cast MinGasToken to string")
    }

    value, ok = new(big.Int).SetString(strValue, 10)
    if !ok {
        return value, fmt.Errorf("failed to cast MinGasToken to bigint")
    }
    return value, nil
}

const defaultScrollMessageFee = 1e17

// GetScrollMessageFee returns the ScrollMessageFee for the given chainID.
func (c Config) GetScrollMessageFee(chainID int) (value *big.Int, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return value, fmt.Errorf("no chain config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll == nil {
        return value, fmt.Errorf("no scroll config for chain %d", chainID)
    }
    if chainCfg.RebalanceConfigs.Scroll.ScrollMessageFee == nil {
        return big.NewInt(defaultScrollMessageFee), nil
    }

    value, ok = new(big.Int).SetString(*chainCfg.RebalanceConfigs.Scroll.ScrollMessageFee, 10)
    if !ok {
        return value, fmt.Errorf("failed to cast ScrollMessageFee to bigint")
    }
    return value, nil
}

// GetQuotePct returns the QuotePct for the given chainID.
func (c Config) GetQuotePct(chainID int) (value float64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "QuotePct")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(float64)
    if !ok {
        return value, fmt.Errorf("failed to cast QuotePct to int")
    }
    return value, nil
}

// GetQuoteOffsetBps returns the QuoteOffsetBps for the given chainID and tokenAddr.
// If the chainID corresponds to the origin of a quote, we flip the sign.
func (c Config) GetQuoteOffsetBps(chainID int, tokenName string, isOrigin bool) (value float64, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return 0, fmt.Errorf("no chain config for chain %d", chainID)
    }

    tokenCfg, ok := chainCfg.Tokens[tokenName]
    if !ok {
        return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, tokenName)
    }

    offset := tokenCfg.QuoteOffsetBps
    if isOrigin {
        offset *= -1
    }

    return offset, nil
}

var defaultMaxBalance *big.Int // default to nil, signifies 'positive inf'

// GetMaxBalance returns the MaxBalance for the given chain and address.
// Note that this getter returns the value in native token decimals.
func (c Config) GetMaxBalance(chainID int, addr common.Address) *big.Int {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return defaultMaxBalance
    }

    var tokenCfg *TokenConfig
    for _, cfg := range chainCfg.Tokens {
        if common.HexToAddress(cfg.Address).Hex() == addr.Hex() {
            cfgCopy := cfg
            tokenCfg = &cfgCopy
            break
        }
    }
    if tokenCfg == nil || tokenCfg.MaxBalance == nil {
        return defaultMaxBalance
    }
    quoteAmountFlt, ok := new(big.Float).SetString(*tokenCfg.MaxBalance)
    if !ok {
        return defaultMaxBalance
    }
    if quoteAmountFlt.Cmp(big.NewFloat(0)) <= 0 {
        return defaultMaxBalance
    }

    // Scale the minBalance by the token decimals.
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
    quoteAmountScaled, _ := new(big.Float).Mul(quoteAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return quoteAmountScaled
}

// GetQuoteWidthBps returns the QuoteWidthBps for the given chainID.
func (c Config) GetQuoteWidthBps(chainID int, tokenName string) (value float64, err error) {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return 0, fmt.Errorf("no chain config for chain %d", chainID)
    }

    tokenCfg, ok := chainCfg.Tokens[tokenName]
    if !ok {
        return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, tokenName)
    }

    width := tokenCfg.QuoteWidthBps
    if width < 0 {
        return 0, fmt.Errorf("quote width bps must be positive: %f", width)
    }

    return width, nil
}

// GetQuoteFixedFeeMultiplier returns the QuoteFixedFeeMultiplier for the given chainID.
func (c Config) GetQuoteFixedFeeMultiplier(chainID int) (value float64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "QuoteFixedFeeMultiplier")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(float64)
    if !ok {
        return value, fmt.Errorf("failed to cast QuoteFixedFeeMultiplier to int")
    }
    if value <= 0 {
        value = *DefaultChainConfig.QuoteFixedFeeMultiplier
    }
    return value, nil
}

// GetRelayFixedFeeMultiplier returns the RelayFixedFeeMultiplier for the given chainID.
func (c Config) GetRelayFixedFeeMultiplier(chainID int) (value float64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "RelayFixedFeeMultiplier")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(float64)
    if !ok {
        return value, fmt.Errorf("failed to cast RelayFixedFeeMultiplier to int")
    }
    if value <= 0 {
        // If the value is not set, we default to the quote fixed fee multiplier.
        value, err = c.GetQuoteFixedFeeMultiplier(chainID)
        if err != nil {
            return value, err
        }
    }
    return value, nil
}

// GetRebalanceStartBlock returns the RebalanceStartBlock for the given chainID.
func (c Config) GetRebalanceStartBlock(chainID int) (value uint64, err error) {
    rawValue, err := c.getChainConfigValue(chainID, "RebalanceStartBlock")
    if err != nil {
        return value, err
    }

    value, ok := rawValue.(uint64)
    if !ok {
        return value, fmt.Errorf("failed to cast RebalanceStartBlock to int")
    }
    return value, nil
}

// GetL1FeeParams returns the L1 fee params for the given chain.
func (c Config) GetL1FeeParams(chainID uint32, origin bool) (uint32, int, bool) {
    var gasEstimate int
    var err error
    if origin {
        gasEstimate, err = c.GetL1FeeOriginGasEstimate(int(chainID))
        if err != nil {
            return 0, 0, false
        }
    } else {
        gasEstimate, err = c.GetL1FeeDestGasEstimate(int(chainID))
        if err != nil {
            return 0, 0, false
        }
    }

    l1FeeChainID, err := c.GetL1FeeChainID(int(chainID))
    if err != nil || l1FeeChainID <= 0 || gasEstimate <= 0 {
        return 0, 0, false
    }
    return l1FeeChainID, gasEstimate, true
}

// GetChains returns the chains config.
func (c Config) GetChains() map[int]ChainConfig {
    return c.Chains
}

// GetOmniRPCURL returns the OmniRPCURL.
func (c Config) GetOmniRPCURL() string {
    return c.OmniRPCURL
}

// GetRFQAPIURL returns the RFQ API URL.
func (c Config) GetRFQAPIURL() string {
    return c.RFQAPIURL
}

// GetDatabase returns the database config.
func (c Config) GetDatabase() DatabaseConfig {
    return c.Database
}

// GetSigner returns the signer config.
func (c Config) GetSigner() config.SignerConfig {
    return c.Signer
}

// GetFeePricer returns the fee pricer config.
func (c Config) GetFeePricer() FeePricerConfig {
    return c.FeePricer
}

const defaultHTTPTimeoutMs = 1000

// GetHTTPTimeout returns the HTTP timeout.
func (c Config) GetHTTPTimeout() time.Duration {
    feePricerCfg := c.GetFeePricer()
    timeoutMs := defaultHTTPTimeoutMs
    if feePricerCfg.HTTPTimeoutMs > 0 {
        timeoutMs = feePricerCfg.HTTPTimeoutMs
    }
    return time.Duration(timeoutMs) * time.Millisecond
}

func (c Config) getTokenConfigByAddr(chainID int, tokenAddr string) (cfg TokenConfig, err error) {
    chainConfig, ok := c.Chains[chainID]
    if !ok {
        return cfg, fmt.Errorf("no chain config for chain %d", chainID)
    }
    for _, tokenConfig := range chainConfig.Tokens {
        if common.HexToAddress(tokenConfig.Address).Hex() == common.HexToAddress(tokenAddr).Hex() {
            return tokenConfig, nil
        }
    }
    return cfg, fmt.Errorf("no token config for chain %d and address %s", chainID, tokenAddr)
}

// GetRebalanceMethods returns the rebalance method for the given chain path and token address.
// This method will error if there is a rebalance method mismatch, and neither methods correspond to
// RebalanceMethodsNone.
func (c Config) GetRebalanceMethods(chainID int, tokenAddr string) (methods []RebalanceMethod, err error) {
    tokenCfg, err := c.getTokenConfigByAddr(chainID, tokenAddr)
    if err != nil {
        return nil, err
    }

    methods = []RebalanceMethod{}
    for _, m := range tokenCfg.RebalanceMethods {
        method, err := RebalanceMethodFromString(m)
        if err != nil {
            return nil, err
        }
        if method != RebalanceMethodNone {
            methods = append(methods, method)
        }
    }
    return methods, nil
}

// GetAllRebalanceMethods returns all rebalance methods present in the config.
func (c Config) GetAllRebalanceMethods() (allMethods map[RebalanceMethod]bool, err error) {
    allMethods = make(map[RebalanceMethod]bool)
    for chainID, chainCfg := range c.Chains {
        for _, tokenCfg := range chainCfg.Tokens {
            methods, err := c.GetRebalanceMethods(chainID, tokenCfg.Address)
            if err != nil {
                return nil, err
            }
            for _, method := range methods {
                if method != RebalanceMethodNone {
                    allMethods[method] = true
                }
            }
        }
    }
    return allMethods, nil
}

// GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token address.
func (c Config) GetMaintenanceBalancePct(chainID int, tokenAddr string) (float64, error) {
    tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr)
    if err != nil {
        return 0, err
    }
    if tokenConfig.MaintenanceBalancePct <= 0 {
        return 0, fmt.Errorf("maintenance balance pct must be positive: %f", tokenConfig.MaintenanceBalancePct)
    }
    return tokenConfig.MaintenanceBalancePct, nil
}

// GetInitialBalancePct returns the initial balance percentage for the given chain and token address.
func (c Config) GetInitialBalancePct(chainID int, tokenAddr string) (float64, error) {
    tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr)
    if err != nil {
        return 0, err
    }
    if tokenConfig.InitialBalancePct <= 0 {
        return 0, fmt.Errorf("initial balance pct must be positive: %f", tokenConfig.InitialBalancePct)
    }
    return tokenConfig.InitialBalancePct, nil
}

// GetTokenID returns the tokenID for the given chain and address.
func (c Config) GetTokenID(chain int, addr string) (string, error) {
    chainConfig, ok := c.Chains[chain]
    if !ok {
        return "", fmt.Errorf("no chain config for chain %d", chain)
    }
    for tokenID, tokenConfig := range chainConfig.Tokens {
        if tokenConfig.Address == addr {
            return tokenID, nil
        }
    }
    return "", fmt.Errorf("no tokenID found for chain %d and address %s", chain, addr)
}

// GetQuotableTokens returns the quotable tokens for the given token.
func (c Config) GetQuotableTokens(token string) ([]string, error) {
    tokens, ok := c.QuotableTokens[token]
    if !ok {
        return nil, fmt.Errorf("no quotable tokens for token %s", token)
    }
    return tokens, nil
}

// GetTokenDecimals returns the token decimals for the given chain and token.
func (c Config) GetTokenDecimals(chainID uint32, token string) (uint8, error) {
    chainConfig, ok := c.Chains[int(chainID)]
    if !ok {
        return 0, fmt.Errorf("could not get chain config for chainID: %d", chainID)
    }
    for tokenName, tokenConfig := range chainConfig.Tokens {
        if token == tokenName {
            return tokenConfig.Decimals, nil
        }
    }
    return 0, fmt.Errorf("could not get token decimals for chain %d and token %s", chainID, token)
}

// GetTokens returns the tokens for the given chain.
func (c Config) GetTokens(chainID uint32) (map[string]TokenConfig, error) {
    chainConfig, ok := c.Chains[int(chainID)]
    if !ok {
        return nil, fmt.Errorf("could not get chain config for chainID: %d", chainID)
    }
    return chainConfig.Tokens, nil
}

// GetTokenName returns the token name for the given chain and address.
func (c Config) GetTokenName(chain uint32, addr string) (string, error) {
    chainConfig, ok := c.Chains[int(chain)]
    if !ok {
        return "", fmt.Errorf("no chain config for chain %d", chain)
    }
    for tokenName, tokenConfig := range chainConfig.Tokens {
        if common.HexToAddress(tokenConfig.Address).Hex() == common.HexToAddress(addr).Hex() {
            return tokenName, nil
        }
    }
    return "", fmt.Errorf("no tokenName found for chain %d and address %s", chain, addr)
}

const defaultMinQuoteAmount = 0

// GetMinQuoteAmount returns the quote amount for the given chain and address.
// Note that this getter returns the value in native token decimals.
func (c Config) GetMinQuoteAmount(chainID int, addr common.Address) *big.Int {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return big.NewInt(defaultMinQuoteAmount)
    }

    var tokenCfg *TokenConfig
    for _, cfg := range chainCfg.Tokens {
        if common.HexToAddress(cfg.Address).Hex() == addr.Hex() {
            cfgCopy := cfg
            tokenCfg = &cfgCopy
            break
        }
    }
    if tokenCfg == nil {
        return big.NewInt(defaultMinQuoteAmount)
    }
    quoteAmountFlt, ok := new(big.Float).SetString(tokenCfg.MinQuoteAmount)
    if !ok {
        return big.NewInt(defaultMinQuoteAmount)
    }
    if quoteAmountFlt.Cmp(big.NewFloat(0)) <= 0 {
        return big.NewInt(defaultMinQuoteAmount)
    }

    // Scale the minQuoteAmount by the token decimals.
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
    quoteAmountScaled, _ := new(big.Float).Mul(quoteAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return quoteAmountScaled
}

var defaultMaxRelayAmount *big.Int // nil

// GetMaxRelayAmount returns the quote amount for the given chain and address.
// Note that this getter returns the value in native token decimals.
func (c Config) GetMaxRelayAmount(chainID int, addr common.Address) *big.Int {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return defaultMaxRelayAmount
    }

    var tokenCfg *TokenConfig
    for _, cfg := range chainCfg.Tokens {
        if common.HexToAddress(cfg.Address).Hex() == addr.Hex() {
            cfgCopy := cfg
            tokenCfg = &cfgCopy
            break
        }
    }
    if tokenCfg == nil {
        return defaultMaxRelayAmount
    }
    quoteAmountFlt, ok := new(big.Float).SetString(tokenCfg.MaxRelayAmount)
    if !ok {
        return defaultMaxRelayAmount
    }
    if quoteAmountFlt.Cmp(big.NewFloat(0)) <= 0 {
        return defaultMaxRelayAmount
    }

    // Scale the minQuoteAmount by the token decimals.
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(tokenDecimalsBase), big.NewInt(int64(tokenCfg.Decimals)), nil)
    quoteAmountScaled, _ := new(big.Float).Mul(quoteAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return quoteAmountScaled
}

var defaultMinRebalanceAmount = big.NewInt(1000)

// GetMinRebalanceAmount returns the min rebalance amount for the given chain and address.
// Note that this getter returns the value in native token decimals.
//
//nolint:dupl
func (c Config) GetMinRebalanceAmount(chainID int, addr common.Address) *big.Int {
    tokenCfg, err := c.getTokenConfigByAddr(chainID, addr.Hex())
    if err != nil {
        return defaultMaxRebalanceAmount
    }
    rebalanceAmountFlt, ok := new(big.Float).SetString(tokenCfg.MinRebalanceAmount)
    if !ok || rebalanceAmountFlt == nil {
        return defaultMinRebalanceAmount
    }

    // Scale by the token decimals.
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
    minRebalanceAmountScaled, _ := new(big.Float).Mul(rebalanceAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return minRebalanceAmountScaled
}

var defaultMaxRebalanceAmount = abi.MaxInt256

// GetMaxRebalanceAmount returns the max rebalance amount for the given chain and address.
// Note that this getter returns the value in native token decimals.
//
//nolint:dupl
func (c Config) GetMaxRebalanceAmount(chainID int, addr common.Address) *big.Int {
    tokenCfg, err := c.getTokenConfigByAddr(chainID, addr.Hex())
    if err != nil {
        return defaultMaxRebalanceAmount
    }
    rebalanceAmountFlt, ok := new(big.Float).SetString(tokenCfg.MaxRebalanceAmount)
    if !ok || rebalanceAmountFlt == nil {
        return defaultMaxRebalanceAmount
    }

    // Scale by the token decimals.
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
    maxRebalanceAmountScaled, _ := new(big.Float).Mul(rebalanceAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return maxRebalanceAmountScaled
}

const defaultDBSelectorIntervalSeconds = 1

// GetDBSelectorInterval returns the interval for the DB selector.
func (c Config) GetDBSelectorInterval() time.Duration {
    interval := c.DBSelectorInterval
    if interval <= 0 {
        interval = time.Duration(defaultDBSelectorIntervalSeconds) * time.Second
    }
    return interval
}

const defaultRebalanceIntervalSeconds = 30

// GetRebalanceInterval returns the interval for rebalancing.
func (c Config) GetRebalanceInterval() time.Duration {
    interval := c.RebalanceInterval
    if interval == 0 {
        interval = time.Duration(defaultRebalanceIntervalSeconds) * time.Second
    }
    return interval
}

const defaultQuoteSubmissionTimeoutSeconds = 30

// GetQuoteSubmissionTimeout returns the timeout for submitting quotes.
func (c Config) GetQuoteSubmissionTimeout() time.Duration {
    timeout := c.QuoteSubmissionTimeout
    if timeout == 0 {
        timeout = time.Duration(defaultQuoteSubmissionTimeoutSeconds) * time.Second
    }
    return timeout
}

var defaultVolumeLimit = core.CopyBigInt(big.NewInt(-1))

// GetVolumeLimit returns the volume limit for the relayer.
func (c Config) GetVolumeLimit(chainID int, addr common.Address) *big.Int {
    chainCfg, ok := c.Chains[chainID]
    if !ok {
        return defaultVolumeLimit
    }

    var tokenCfg TokenConfig
    for _, token := range chainCfg.Tokens {
        if common.HexToAddress(token.Address).Hex() == addr.Hex() {
            tokenCfg = token
            break
        }
    }

    volumeLimitFlt := new(big.Float).SetFloat64(c.VolumeLimit)

    // Scale the minBalance by the token decimals.
    //nolint: mnd
    denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil)
    volumeLimitScaled, _ := new(big.Float).Mul(volumeLimitFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil)
    return volumeLimitScaled
}