This repository contains the Chainlink Deployments Framework, a comprehensive set of libraries that enables developers to build, manage, and execute(in future) deployment changesets. The framework includes the Operations API and Datastore API.
$ go get github.com/smartcontractkit/chainlink-deployments-framework
import (
"github.com/smartcontractkit/chainlink-deployments-framework/deployment" // for writing changesets (migrated from chainlink/deployments
"github.com/smartcontractkit/chainlink-deployments-framework/operations" // for operations API
"github.com/smartcontractkit/chainlink-deployments-framework/datastore" // for datastore API
)
Install the required tools using asdf:
asdf install
task lint
task test
To add a new chain to the framework, follow these steps:
-
Create the chain structure and implementation:
- Create a new directory under
chain/
with the name of your chain (e.g.,chain/newchain/
). - Create a new
Chain
struct that embeds theChainMetadata
struct - See the Sui or TON implementations as reference examples. EVM, Solana, and Aptos chains follow a different implementation pattern as they were added before CLDF.
- Create a new directory under
-
Implement chain providers:
- Create a
provider/
subdirectory under your chain directory (e.g.,chain/newchain/provider/
) - Implement one or more provider types that satisfy the
chain.Provider
interface:type Provider interface { Initialize(ctx context.Context) (BlockChain, error) Name() string ChainSelector() uint64 BlockChain() BlockChain }
- Common provider types to implement:
- RPC Provider: Connects to a live blockchain node via RPC
- Simulated Provider: Creates an in-memory simulated chain for testing (if needed)
- CTF Provider: Connects to Chainlink Testing Framework environments (if needed)
Example RPC provider implementation:
package provider import ( "context" "errors" "fmt" "github.com/smartcontractkit/chainlink-deployments-framework/chain" "github.com/smartcontractkit/chainlink-deployments-framework/chain/newchain" ) // RPCChainProviderConfig holds the configuration for the RPC provider type RPCChainProviderConfig struct { RPCURL string DeployerSignerGen SignerGenerator // Your chain-specific signer } func (c RPCChainProviderConfig) validate() error { if c.RPCURL == "" { return errors.New("rpc url is required") } if c.DeployerSignerGen == nil { return errors.New("deployer signer generator is required") } return nil } // Ensure provider implements the interface var _ chain.Provider = (*RPCChainProvider)(nil) type RPCChainProvider struct { selector uint64 config RPCChainProviderConfig chain *newchain.Chain } func NewRPCChainProvider(selector uint64, config RPCChainProviderConfig) *RPCChainProvider { return &RPCChainProvider{ selector: selector, config: config, } } func (p *RPCChainProvider) Initialize(ctx context.Context) (chain.BlockChain, error) { if p.chain != nil { return p.chain, nil // Already initialized } if err := p.config.validate(); err != nil { return nil, fmt.Errorf("failed to validate config: %w", err) } // Initialize your chain client client, err := newchain.NewClient(p.config.RPCURL) if err != nil { return nil, fmt.Errorf("failed to create client: %w", err) } // Generate deployer signer signer, err := p.config.DeployerSignerGen.Generate() if err != nil { return nil, fmt.Errorf("failed to generate signer: %w", err) } p.chain = &newchain.Chain{ Selector: p.selector, Client: client, Signer: signer, URL: p.config.RPCURL, } return *p.chain, nil } func (p *RPCChainProvider) Name() string { return "NewChain RPC Provider" } func (p *RPCChainProvider) ChainSelector() uint64 { return p.selector } func (p *RPCChainProvider) BlockChain() chain.BlockChain { return *p.chain }
- Create a
-
Add environment configuration bindings:
- Update
engine/cld/config/env/config.go
:- Add a new config struct for your chain (e.g.,
NewChainConfig
):// NewChainConfig is the configuration for the NewChain chains. // // WARNING: This data type contains sensitive fields and should not be logged or set in file // configuration. type NewChainConfig struct { DeployerKey string `mapstructure:"deployer_key" yaml:"deployer_key"` // Secret: The private key of the deployer account. // Add any other chain-specific configuration fields }
- Add the new config to the
OnchainConfig
struct:type OnchainConfig struct { // ... existing fields ... NewChain NewChainConfig `mapstructure:"newchain" yaml:"newchain"` }
- Add environment variable bindings in the
envBindings
map:envBindings = map[string][]string{ // ... existing bindings ... "onchain.newchain.deployer_key": {"ONCHAIN_NEWCHAIN_DEPLOYER_KEY"}, // Add other bindings as needed }
- Add a new config struct for your chain (e.g.,
- Update
-
Create chain loader in the CLD engine:
-
Update
engine/cld/chains/chains.go
:-
Add a new chain loader struct and methods:
// chainLoaderNewChain implements the ChainLoader interface for NewChain. type chainLoaderNewChain struct { *baseChainLoader } // newChainLoaderNewChain creates a new chain loader for NewChain. func newChainLoaderNewChain( networks *cfgnet.Config, cfg cfgenv.OnchainConfig, ) *chainLoaderNewChain { return &chainLoaderNewChain{ baseChainLoader: newBaseChainLoader(networks, cfg), } } // Load loads a NewChain Chain for a selector. func (l *chainLoaderNewChain) Load(ctx context.Context, selector uint64) (fchain.BlockChain, error) { network, err := l.getNetwork(selector) if err != nil { return nil, err } rpcURL := network.RPCs[0].HTTPURL c, err := newchainprov.NewRPCChainProvider(selector, newchainprov.RPCChainProviderConfig{ RPCURL: rpcURL, DeployerSignerGen: newchainprov.AccountGenPrivateKey(l.cfg.NewChain.DeployerKey), }, ).Initialize(ctx) if err != nil { return nil, fmt.Errorf("failed to initialize NewChain chain %d: %w", selector, err) } return c, nil }
-
Add the loader to the
newChainLoaders
function with appropriate configuration checks:func newChainLoaders(...) map[string]ChainLoader { // ... existing loaders ... if cfg.NewChain.DeployerKey != "" { loaders[chainsel.FamilyNewChain] = newChainLoaderNewChain(networks, cfg) } else { lggr.Warn("Skipping NewChain chains, no private key found in secrets") } return loaders }
-
Add interface compliance verification:
var ( // ... existing verifications ... _ ChainLoader = &chainLoaderNewChain{} )
-
-
-
Update chain registry:
- Update
chain/blockchain.go
:- Add
var _ BlockChain = newchain.Chain{}
at the top to verify interface compliance - Create a new getter method (e.g.,
NewChainChains()
) that returnsmap[uint64]newchain.Chain
(e.g.,NewSuiChains()
)
- Add
- Update
-
Write comprehensive tests:
- Test chain instantiation
- Test all interface methods
- Test the getter method in BlockChains
- Test provider initialization and chain creation
- Test provider interface compliance with
var _ chain.Provider = (*YourProvider)(nil)
- Test chain loader functionality
Using Providers: Once you've implemented a provider, users can create and initialize chains like this:
// Create provider with configuration
provider := provider.NewRPCChainProvider(chainSelector, provider.RPCChainProviderConfig{
RPCURL: "https://your-chain-rpc-url.com",
DeployerSignerGen: yourSignerGenerator,
})
// Initialize the chain
ctx := context.Background()
blockchain, err := provider.Initialize(ctx)
if err != nil {
return fmt.Errorf("failed to initialize chain: %w", err)
}
For instructions on how to contribute to chainlink-deployments-framework
and the release process,
see CONTRIBUTING.md
For instructions on how to release chainlink-deployments-framework
,
see RELEASE.md