diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go
index 02dff6a2c9c..16b67bbb36c 100644
--- a/core/capabilities/ccip/delegate.go
+++ b/core/capabilities/ccip/delegate.go
@@ -43,7 +43,6 @@ import (
 	"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2"
 	"github.com/smartcontractkit/chainlink/v2/core/services/pipeline"
-	"github.com/smartcontractkit/chainlink/v2/core/services/relay"
 	"github.com/smartcontractkit/chainlink/v2/core/services/telemetry"
 	"github.com/smartcontractkit/chainlink/v2/plugins"
 )
@@ -251,10 +250,6 @@ func (d *Delegate) OnDeleteJob(ctx context.Context, spec job.Job) error {
 func (d *Delegate) getOCRKeys(ocrKeyBundleIDs job.JSONConfig) (map[string]ocr2key.KeyBundle, error) {
 	ocrKeys := make(map[string]ocr2key.KeyBundle)
 	for networkType, bundleIDRaw := range ocrKeyBundleIDs {
-		if networkType != relay.NetworkEVM {
-			return nil, fmt.Errorf("unsupported chain type: %s", networkType)
-		}
-
 		bundleID, ok := bundleIDRaw.(string)
 		if !ok {
 			return nil, fmt.Errorf("OCRKeyBundleIDs must be a map of chain types to OCR key bundle IDs, got: %T", bundleIDRaw)
diff --git a/core/capabilities/ccip/oraclecreator/bootstrap.go b/core/capabilities/ccip/oraclecreator/bootstrap.go
index ca4cf9ec6cd..993cd46a672 100644
--- a/core/capabilities/ccip/oraclecreator/bootstrap.go
+++ b/core/capabilities/ccip/oraclecreator/bootstrap.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	mapset "github.com/deckarep/golang-set/v2"
+
 	logger2 "github.com/smartcontractkit/chainlink-common/pkg/logger"
 
 	"github.com/ethereum/go-ethereum/common/hexutil"
diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go
index 57502d9aac0..a56ccf856f5 100644
--- a/core/capabilities/ccip/oraclecreator/plugin.go
+++ b/core/capabilities/ccip/oraclecreator/plugin.go
@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"math/big"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -35,7 +34,6 @@ import (
 	evmconfig "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm"
 	"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ocrimpls"
 	cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"
-	"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml"
 	"github.com/smartcontractkit/chainlink/v2/core/logger"
 	"github.com/smartcontractkit/chainlink/v2/core/services/job"
 	"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key"
@@ -44,7 +42,6 @@ import (
 	evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
 	"github.com/smartcontractkit/chainlink/v2/core/services/synchronization"
 	"github.com/smartcontractkit/chainlink/v2/core/services/telemetry"
-	"github.com/smartcontractkit/chainlink/v2/evm/assets"
 )
 
 var _ cctypes.OracleCreator = &pluginOracleCreator{}
@@ -216,6 +213,37 @@ func (i *pluginOracleCreator) Create(ctx context.Context, donID uint32, config c
 	return newWrappedOracle(oracle, closers), nil
 }
 
+type plugin struct {
+	CommitPluginCodec   cciptypes.CommitPluginCodec
+	ExecutePluginCodec  cciptypes.ExecutePluginCodec
+	ExtraArgsCodec      cciptypes.ExtraDataCodec
+	MessageHasher       func(lggr logger.Logger) cciptypes.MessageHasher
+	TokenDataEncoder    cciptypes.TokenDataEncoder
+	GasEstimateProvider cciptypes.EstimateProvider
+	RMNCrypto           func(lggr logger.Logger) cciptypes.RMNCrypto
+}
+
+var plugins = map[string]plugin{
+	chainsel.FamilyEVM: {
+		CommitPluginCodec:   ccipevm.NewCommitPluginCodecV1(),
+		ExecutePluginCodec:  ccipevm.NewExecutePluginCodecV1(),
+		ExtraArgsCodec:      ccipevm.ExtraArgsCodec{},
+		MessageHasher:       func(lggr logger.Logger) cciptypes.MessageHasher { return ccipevm.NewMessageHasherV1(lggr) },
+		TokenDataEncoder:    ccipevm.NewEVMTokenDataEncoder(),
+		GasEstimateProvider: ccipevm.NewGasEstimateProvider(),
+		RMNCrypto:           func(lggr logger.Logger) cciptypes.RMNCrypto { return ccipevm.NewEVMRMNCrypto(lggr) },
+	},
+	chainsel.FamilySolana: {
+		CommitPluginCodec:   nil,
+		ExecutePluginCodec:  nil,
+		ExtraArgsCodec:      nil,
+		MessageHasher:       func(lggr logger.Logger) cciptypes.MessageHasher { return nil },
+		TokenDataEncoder:    nil,
+		GasEstimateProvider: nil,
+		RMNCrypto:           func(lggr logger.Logger) cciptypes.RMNCrypto { return nil },
+	},
+}
+
 func (i *pluginOracleCreator) createFactoryAndTransmitter(
 	donID uint32,
 	config cctypes.OCR3ConfigWithMeta,
@@ -234,6 +262,16 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
 		return nil, nil, fmt.Errorf("unsupported chain selector %d %w", config.Config.ChainSelector, err)
 	}
 
+	chainFamily, err := chainsel.GetSelectorFamily(uint64(config.Config.ChainSelector))
+	if err != nil {
+		return nil, nil, fmt.Errorf("unsupported chain selector %d %w", config.Config.ChainSelector, err)
+	}
+	plugin, exists := plugins[chainFamily]
+	if !exists {
+		return nil, nil, fmt.Errorf("unsupported chain %v", chainFamily)
+	}
+	messageHasher := plugin.MessageHasher(i.lggr.Named(chainFamily).Named("MessageHasherV1"))
+
 	if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) {
 		if !i.peerWrapper.IsStarted() {
 			return nil, nil, fmt.Errorf("peer wrapper is not started")
@@ -249,7 +287,7 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
 			publicConfig.DeltaRound,
 		)
 
-		rmnCrypto := ccipevm.NewEVMRMNCrypto(i.lggr.Named("EVMRMNCrypto"))
+		rmnCrypto := plugin.RMNCrypto(i.lggr.Named(chainFamily).Named("RMNCrypto"))
 
 		factory = commitocr3.NewPluginFactory(
 			i.lggr.
@@ -259,9 +297,9 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
 				Named(hexutil.Encode(config.Config.OfframpAddress)),
 			donID,
 			ccipreaderpkg.OCR3ConfigWithMeta(config),
-			ccipevm.NewCommitPluginCodecV1(),
-			ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")),
-			ccipevm.NewExtraArgsCodec(),
+			plugin.CommitPluginCodec,
+			messageHasher,
+			plugin.ExtraArgsCodec,
 			i.homeChainReader,
 			i.homeChainSelector,
 			contractReaders,
@@ -282,12 +320,12 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
 				Named(hexutil.Encode(config.Config.OfframpAddress)),
 			donID,
 			ccipreaderpkg.OCR3ConfigWithMeta(config),
-			ccipevm.NewExecutePluginCodecV1(),
-			ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")),
-			ccipevm.NewExtraArgsCodec(),
+			plugin.ExecutePluginCodec,
+			messageHasher,
+			plugin.ExtraArgsCodec,
 			i.homeChainReader,
-			ccipevm.NewEVMTokenDataEncoder(),
-			ccipevm.NewGasEstimateProvider(),
+			plugin.TokenDataEncoder,
+			plugin.GasEstimateProvider,
 			contractReaders,
 			chainWriters,
 		)
@@ -328,9 +366,9 @@ func (i *pluginOracleCreator) createReadersAndWriters(
 		execBatchGasLimit = defaultExecGasLimit
 	}
 
-	homeChainID, err := i.getChainID(i.homeChainSelector)
+	homeChainID, err := chainsel.GetChainIDFromSelector(uint64(i.homeChainSelector))
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, fmt.Errorf("failed to get chain ID from chain selector %d: %w", i.homeChainSelector, err)
 	}
 
 	contractReaders := make(map[cciptypes.ChainSelector]types.ContractReader)
@@ -338,7 +376,8 @@ func (i *pluginOracleCreator) createReadersAndWriters(
 	for relayID, relayer := range i.relayers {
 		chainID := relayID.ChainID
 		relayChainFamily := relayID.Network
-		chainSelector, err1 := i.getChainSelector(chainID, relayChainFamily)
+		chainDetails, err1 := chainsel.GetChainDetailsByChainIDAndFamily(chainID, relayChainFamily)
+		chainSelector := cciptypes.ChainSelector(chainDetails.ChainSelector)
 		if err1 != nil {
 			return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID, err1)
 		}
@@ -421,22 +460,6 @@ func decodeAndValidateOffchainConfig(
 	return ofc, nil
 }
 
-func (i *pluginOracleCreator) getChainSelector(chainID string, chainFamily string) (cciptypes.ChainSelector, error) {
-	chainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(chainID, chainFamily)
-	if err != nil {
-		return 0, fmt.Errorf("failed to get chain selector from chain ID %s and family %s", chainID, chainFamily)
-	}
-	return cciptypes.ChainSelector(chainDetails.ChainSelector), nil
-}
-
-func (i *pluginOracleCreator) getChainID(chainSelector cciptypes.ChainSelector) (string, error) {
-	chainID, err := chainsel.GetChainIDFromSelector(uint64(chainSelector))
-	if err != nil {
-		return "", fmt.Errorf("failed to get chain ID from chain selector %d: %w", chainSelector, err)
-	}
-	return chainID, nil
-}
-
 func getChainReaderConfig(
 	lggr logger.Logger,
 	chainID string,
@@ -520,33 +543,6 @@ func createChainWriter(
 	return cw, nil
 }
 
-func getKeySpecificMaxGasPrice(evmConfigs toml.EVMConfigs, chainID *big.Int, fromAddress common.Address) *assets.Wei {
-	var maxGasPrice *assets.Wei
-
-	// If a chain is enabled it should have some configuration in the TOML config
-	// of the chainlink node.
-	for _, config := range evmConfigs {
-		if config.ChainID.ToInt().Cmp(chainID) != 0 {
-			continue
-		}
-
-		// find the key-specific max gas price for the given fromAddress.
-		for _, keySpecific := range config.KeySpecific {
-			if keySpecific.Key.Address() == fromAddress {
-				maxGasPrice = keySpecific.GasEstimator.PriceMax
-			}
-		}
-
-		// if we didn't find a key-specific max gas price, use the one specified
-		// in the gas estimator config, which should have a default value.
-		if maxGasPrice == nil {
-			maxGasPrice = config.GasEstimator.PriceMax
-		}
-	}
-
-	return maxGasPrice
-}
-
 type offChainConfig struct {
 	commitOffchainConfig *pluginconfig.CommitOffchainConfig
 	execOffchainConfig   *pluginconfig.ExecuteOffchainConfig
diff --git a/core/services/keystore/keys/ocr2key/solana_keyring.go b/core/services/keystore/keys/ocr2key/solana_keyring.go
index a5fa2f77dc0..e9a017c3ce5 100644
--- a/core/services/keystore/keys/ocr2key/solana_keyring.go
+++ b/core/services/keystore/keys/ocr2key/solana_keyring.go
@@ -7,7 +7,6 @@ import (
 	"io"
 
 	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/pkg/errors"
 	"github.com/smartcontractkit/libocr/offchainreporting2/types"
 	"github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil"
 	ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
@@ -49,7 +48,15 @@ func (skr *solanaKeyring) Sign(reportCtx ocrtypes.ReportContext, report ocrtypes
 }
 
 func (skr *solanaKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) (signature []byte, err error) {
-	return nil, errors.New("not implemented")
+	return skr.signBlob(skr.reportToSigData3(digest, seqNr, r))
+}
+
+func (skr *solanaKeyring) reportToSigData3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) []byte {
+	rawReportContext := RawReportContext3(digest, seqNr)
+	sigData := crypto.Keccak256(r)
+	sigData = append(sigData, rawReportContext[0][:]...)
+	sigData = append(sigData, rawReportContext[1][:]...)
+	return crypto.Keccak256(sigData)
 }
 
 func (skr *solanaKeyring) signBlob(b []byte) (sig []byte, err error) {
@@ -62,7 +69,8 @@ func (skr *solanaKeyring) Verify(publicKey ocrtypes.OnchainPublicKey, reportCtx
 }
 
 func (skr *solanaKeyring) Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool {
-	return false
+	hash := skr.reportToSigData3(cd, seqNr, r)
+	return skr.verifyBlob(publicKey, hash, signature)
 }
 
 func (skr *solanaKeyring) verifyBlob(pubkey types.OnchainPublicKey, b, sig []byte) bool {
diff --git a/deployment/ccip/changeset/cs_jobspec.go b/deployment/ccip/changeset/cs_jobspec.go
index cc75ea31960..ffa331021c5 100644
--- a/deployment/ccip/changeset/cs_jobspec.go
+++ b/deployment/ccip/changeset/cs_jobspec.go
@@ -8,11 +8,12 @@ import (
 	"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
 	"github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job"
 
+	chainsel "github.com/smartcontractkit/chain-selectors"
+
 	"github.com/smartcontractkit/chainlink/deployment"
 	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
 	"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate"
 	corejob "github.com/smartcontractkit/chainlink/v2/core/services/job"
-	"github.com/smartcontractkit/chainlink/v2/core/services/relay"
 )
 
 var _ deployment.ChangeSet[any] = CCIPCapabilityJobspecChangeset
@@ -54,6 +55,24 @@ func CCIPCapabilityJobspecChangeset(env deployment.Environment, _ any) (deployme
 	// Find the bootstrap nodes
 	nodesToJobSpecs := make(map[string][]string)
 	for _, node := range nodes {
+		// pick first keybundle of each type (by default nodes will auto create one key of each type for each defined chain family)
+		keyBundles := map[string]string{}
+		for details, config := range node.SelToOCRConfig {
+			family, err := chainsel.GetSelectorFamily(details.ChainSelector)
+			if err != nil {
+				env.Logger.Warnf("skipping unknown/invalid chain family for selector %v", details.ChainSelector)
+				continue
+			}
+			_, exists := keyBundles[family]
+			if exists {
+				if keyBundles[family] != config.KeyBundleID {
+					return deployment.ChangesetOutput{}, fmt.Errorf("multiple different %v OCR keys found for node %v", family, node.PeerID)
+				}
+				continue
+			}
+			keyBundles[family] = config.KeyBundleID
+		}
+
 		var spec string
 		var err error
 		if !node.IsBootstrap {
@@ -61,13 +80,10 @@ func CCIPCapabilityJobspecChangeset(env deployment.Environment, _ any) (deployme
 				P2PV2Bootstrappers:     nodes.BootstrapLocators(),
 				CapabilityVersion:      internal.CapabilityVersion,
 				CapabilityLabelledName: internal.CapabilityLabelledName,
-				OCRKeyBundleIDs: map[string]string{
-					// TODO: Validate that that all EVM chains are using the same keybundle.
-					relay.NetworkEVM: node.FirstOCRKeybundle().KeyBundleID,
-				},
-				P2PKeyID:     node.PeerID.String(),
-				RelayConfigs: nil,
-				PluginConfig: map[string]any{},
+				OCRKeyBundleIDs:        keyBundles,
+				P2PKeyID:               node.PeerID.String(),
+				RelayConfigs:           nil,
+				PluginConfig:           map[string]any{},
 			})
 		} else {
 			spec, err = validate.NewCCIPSpecToml(validate.SpecArgs{
@@ -75,10 +91,9 @@ func CCIPCapabilityJobspecChangeset(env deployment.Environment, _ any) (deployme
 				CapabilityVersion:      internal.CapabilityVersion,
 				CapabilityLabelledName: internal.CapabilityLabelledName,
 				OCRKeyBundleIDs:        map[string]string{},
-				// TODO: validate that all EVM chains are using the same keybundle
-				P2PKeyID:     node.PeerID.String(),
-				RelayConfigs: nil,
-				PluginConfig: map[string]any{},
+				P2PKeyID:               node.PeerID.String(),
+				RelayConfigs:           nil,
+				PluginConfig:           map[string]any{},
 			})
 		}
 		if err != nil {
diff --git a/deployment/environment.go b/deployment/environment.go
index 3d9c6c5420a..fc14101f7dc 100644
--- a/deployment/environment.go
+++ b/deployment/environment.go
@@ -316,13 +316,6 @@ func (n Node) OCRConfigForChainSelector(chainSel uint64) (OCRConfig, bool) {
 	return c, ok
 }
 
-func (n Node) FirstOCRKeybundle() OCRConfig {
-	for _, ocrConfig := range n.SelToOCRConfig {
-		return ocrConfig
-	}
-	return OCRConfig{}
-}
-
 func MustPeerIDFromString(s string) p2pkey.PeerID {
 	p := p2pkey.PeerID{}
 	if err := p.UnmarshalString(s); err != nil {