Skip to content
1 change: 1 addition & 0 deletions core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ require (
github.com/smartcontractkit/chain-selectors v1.0.97 // indirect
github.com/smartcontractkit/chainlink-aptos v0.0.0-20260318173523-755cafb24200 // indirect
github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm v0.0.0-20260323224438-d819cb3228e1 // indirect
github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment v0.0.0-20260317185256-d5f7db87ae70 // indirect
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288 // indirect
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 // indirect
github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70 // indirect
Expand Down
4 changes: 2 additions & 2 deletions deployment/ccip/changeset/testhelpers/test_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,8 +848,8 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tEnv TestEnvironment) Depl
state, err := stateview.LoadOnchainState(e.Env, stateview.WithLoadLegacyContracts(true))
require.NoError(t, err)

err = state.ValidatePostDeploymentState(e.Env, !tEnv.TestConfigs().SkipDONConfiguration)
require.NoError(t, err)
chainErrs := state.ValidatePostDeploymentStateWithoutMCMSOwnership(e.Env, !tEnv.TestConfigs().SkipDONConfiguration)
require.Empty(t, chainErrs)

return e
}
Expand Down
40 changes: 6 additions & 34 deletions deployment/ccip/changeset/v1_6/cs_chain_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import (
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm"
cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"
)

Expand Down Expand Up @@ -1738,40 +1737,13 @@ func isOCR3ConfigSetOnOffRamp(

// DefaultFeeQuoterDestChainConfig returns the default FeeQuoterDestChainConfig
// with the config enabled/disabled based on the configEnabled flag.
// Fee values are set based on the destination chain type:
// - Any → Ethereum: NetworkFee=50, TokenFee=150
// - Any → Solana: NetworkFee=10, TokenFee=35
// - Any → other: NetworkFee=10, TokenFee=25
// - Ethereum -> any: NetworkFee=50, TokenFee=50 ( Source-chain-dependent override that must be applied by the caller)
func DefaultFeeQuoterDestChainConfig(configEnabled bool, destChainSelector ...uint64) fee_quoter.FeeQuoterDestChainConfig {
familySelector, _ := hex.DecodeString(EVMFamilySelector) // evm
if len(destChainSelector) > 0 {
destFamily, _ := chain_selectors.GetSelectorFamily(destChainSelector[0])
switch destFamily {
case chain_selectors.FamilySolana:
familySelector, _ = hex.DecodeString(SVMFamilySelector) // solana
case chain_selectors.FamilyAptos:
familySelector, _ = hex.DecodeString(AptosFamilySelector) // aptos
case chain_selectors.FamilyTon:
familySelector, _ = hex.DecodeString(TVMFamilySelector) // ton
case chain_selectors.FamilySui:
familySelector, _ = hex.DecodeString(SuiFamilySelector) // Sui
}
}
return fee_quoter.FeeQuoterDestChainConfig{
IsEnabled: configEnabled,
MaxNumberOfTokensPerMsg: 10,
MaxDataBytes: 30_000,
MaxPerMsgGasLimit: 3_000_000,
DestGasOverhead: ccipevm.DestGasOverhead,
DefaultTokenFeeUSDCents: 25,
DestGasPerPayloadByteBase: ccipevm.CalldataGasPerByteBase,
DestGasPerPayloadByteHigh: ccipevm.CalldataGasPerByteHigh,
DestGasPerPayloadByteThreshold: ccipevm.CalldataGasPerByteThreshold,
DestDataAvailabilityOverheadGas: 100,
DestGasPerDataAvailabilityByte: 16,
DestDataAvailabilityMultiplierBps: 1,
DefaultTokenDestGasOverhead: 90_000,
DefaultTxGasLimit: 200_000,
GasMultiplierWeiPerEth: 11e17, // Gas multiplier in wei per eth is scaled by 1e18, so 11e17 is 1.1 = 110%
NetworkFeeUSDCents: 10,
ChainFamilySelector: [4]byte(familySelector),
}
return ccipops.DefaultFeeQuoterDestChainConfig(configEnabled, destChainSelector...)
}

type ApplyFeeTokensUpdatesConfig struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,33 +446,5 @@ func resolveUpdateLanesFeeQuoterAddressAndVersion(
addresses []datastore.AddressRef,
chainSel uint64,
) (common.Address, semver.Version, error) {
// Find the FeeQuoter with the highest version for this chain
var bestRef datastore.AddressRef
var bestVersion *semver.Version

for _, ref := range addresses {
if ref.ChainSelector != chainSel {
continue
}
if ref.Type != datastore.ContractType(fqv2ops.ContractType) {
continue
}
if ref.Version == nil {
continue
}
if bestVersion == nil || ref.Version.GreaterThan(bestVersion) {
bestVersion = ref.Version
bestRef = ref
}
}

if bestVersion == nil {
return common.Address{}, semver.Version{}, fmt.Errorf("no fee quoter address found for chain %d", chainSel)
}

if !common.IsHexAddress(bestRef.Address) {
return common.Address{}, semver.Version{}, fmt.Errorf("invalid fee quoter address %q for chain %d", bestRef.Address, chainSel)
}

return common.HexToAddress(bestRef.Address), *bestVersion, nil
return shared.ResolveFeeQuoterAddressAndVersion(addresses, chainSel)
}
37 changes: 30 additions & 7 deletions deployment/ccip/operation/evm/v1_6/ops_fee_quoter.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,43 @@ const (
EVMFamilySelector = "2812d52c"
SVMFamilySelector = "1e10bdc4"
AptosFamilySelector = "ac77ffec"
TVMFamilySelector = "647e2ba9"
SuiFamilySelector = "c4e05953"
)

// DefaultFeeQuoterDestChainConfig returns the default FeeQuoter dest chain config.
// If destChainSelector is provided, family-specific values (ChainFamilySelector,
// NetworkFeeUSDCents, DefaultTokenFeeUSDCents) are set accordingly.
func DefaultFeeQuoterDestChainConfig(configEnabled bool, destChainSelector ...uint64) fee_quoter.FeeQuoterDestChainConfig {
familySelector, _ := hex.DecodeString(EVMFamilySelector) // evm
familySelector, _ := hex.DecodeString(EVMFamilySelector)
networkFeeUSDCents := uint32(10)
Comment on lines +210 to +211
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultFeeQuoterDestChainConfig accepts multiple destChainSelector and always consider the first one? why do we need multiple then?

Copy link
Copy Markdown
Contributor Author

@simsonraj simsonraj Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already functioning that way here (cs_chain_contracts)
https://github.com/smartcontractkit/chainlink/pull/21630/changes#diff-e8b395a3a7f5dab0bcc777f295eccf0b017ec8bfc4e7696bf55fa7f51c45dc93R1745
I just moved it here to be shared & this function before was not being used anywhere.
But this shouldnt have any impact as it is only referenced by tests and they dont usually classify a chain as Ethereum or not for these settings, but my tests does but its always just the 1 chain

defaultTokenFeeUSDCents := uint16(25)
if len(destChainSelector) > 0 {
destFamily, _ := chain_selectors.GetSelectorFamily(destChainSelector[0])
switch destFamily {
case chain_selectors.FamilySolana:
familySelector, _ = hex.DecodeString(SVMFamilySelector) // solana
familySelector, _ = hex.DecodeString(SVMFamilySelector)
defaultTokenFeeUSDCents = 35
case chain_selectors.FamilyAptos:
familySelector, _ = hex.DecodeString(AptosFamilySelector) // aptos
familySelector, _ = hex.DecodeString(AptosFamilySelector)
case chain_selectors.FamilyTon:
familySelector, _ = hex.DecodeString(TVMFamilySelector)
case chain_selectors.FamilySui:
familySelector, _ = hex.DecodeString(SuiFamilySelector)
case chain_selectors.FamilyEVM:
if isEthereumChain(destChainSelector[0]) {
networkFeeUSDCents = 50
defaultTokenFeeUSDCents = 150
}
}
}
return fee_quoter.FeeQuoterDestChainConfig{
IsEnabled: configEnabled,
MaxNumberOfTokensPerMsg: 10,
MaxDataBytes: 30_000,
MaxPerMsgGasLimit: 3_000_000, // TODO: this needs to be updated based on RMN sig verification per chain?! 220/250K
MaxPerMsgGasLimit: 3_000_000,
DestGasOverhead: ccipevm.DestGasOverhead,
DefaultTokenFeeUSDCents: 25,
DefaultTokenFeeUSDCents: defaultTokenFeeUSDCents,
DestGasPerPayloadByteBase: ccipevm.CalldataGasPerByteBase,
DestGasPerPayloadByteHigh: ccipevm.CalldataGasPerByteHigh,
DestGasPerPayloadByteThreshold: ccipevm.CalldataGasPerByteThreshold,
Expand All @@ -227,9 +244,15 @@ func DefaultFeeQuoterDestChainConfig(configEnabled bool, destChainSelector ...ui
DestDataAvailabilityMultiplierBps: 1,
DefaultTokenDestGasOverhead: 90_000,
DefaultTxGasLimit: 200_000,
GasMultiplierWeiPerEth: 11e17, // Gas multiplier in wei per eth is scaled by 1e18, so 11e17 is 1.1 = 110%
NetworkFeeUSDCents: 10,
GasMultiplierWeiPerEth: 11e17,
NetworkFeeUSDCents: networkFeeUSDCents,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no gasPriceStalenessthreashold?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous, I just moved it to the shared package & remove the unused method, but this is good point, I think this is the reason why all the integrations have been setting the staleness threshold to zero, so I added it now

ChainFamilySelector: [4]byte(familySelector),
GasPriceStalenessThreshold: 90000,
}
}

func isEthereumChain(selector uint64) bool {
return selector == chain_selectors.ETHEREUM_MAINNET.Selector ||
selector == chain_selectors.ETHEREUM_TESTNET_SEPOLIA.Selector ||
selector == chain_selectors.ETHEREUM_TESTNET_HOODI.Selector
}
37 changes: 37 additions & 0 deletions deployment/ccip/shared/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"fmt"

"github.com/Masterminds/semver/v3"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"

fqv2ops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/operations/fee_quoter"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
capabilities_registry "github.com/smartcontractkit/chainlink-evm/gethwrappers/keystone/generated/capabilities_registry_1_1_0"
Expand Down Expand Up @@ -115,3 +117,38 @@ func PopulateDataStore(addressBook deployment.AddressBook) (*datastore.MemoryDat

return ds, nil
}

// ResolveFeeQuoterAddressAndVersion returns the FeeQuoter with the highest semver for a chain.
func ResolveFeeQuoterAddressAndVersion(
addresses []datastore.AddressRef,
chainSel uint64,
) (common.Address, semver.Version, error) {
var bestRef datastore.AddressRef
var bestVersion *semver.Version

for _, ref := range addresses {
if ref.ChainSelector != chainSel {
continue
}
if ref.Type != datastore.ContractType(fqv2ops.ContractType) {
continue
}
if ref.Version == nil {
continue
}
if bestVersion == nil || ref.Version.GreaterThan(bestVersion) {
bestVersion = ref.Version
bestRef = ref
}
}

if bestVersion == nil {
return common.Address{}, semver.Version{}, fmt.Errorf("no fee quoter address found for chain %d", chainSel)
}

if !common.IsHexAddress(bestRef.Address) {
return common.Address{}, semver.Version{}, fmt.Errorf("invalid fee quoter address %q for chain %d", bestRef.Address, chainSel)
}

return common.HexToAddress(bestRef.Address), *bestVersion, nil
}
Loading
Loading