sequences

package
v0.0.0-...-92c9f9d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 9, 2026 License: MIT Imports: 82 Imported by: 0

Documentation

Overview

Package sequences defines CLDF deployment sequences for Stellar CCIP.

Deploy chain contracts uses cldf_chain.BlockChains as the dependency type to match chainlink-ccip deployment/v2_0_0 adapter contracts. The sequence builds deployment/operations/stellardeps.StellarDeps and a CCIP devenv host from the CLDF Stellar chain entry, loads stashed offchain topology via TakeStellarDeployOffchainTopologyForSelector when CCV registered one (in PreDeployContractsForSelector), then runs RunStellarCCIPFullDeploy. A missing stash is tolerated: committee verifier signer quorums are applied at lane-configuration time, so the deploy itself does not need NOP topology data. RunStellarCCIPFullDeployForCCV converts CCV environment topology and is used by ccv/chain; it keeps its nil-topology check, and devenv always supplies a CCV topology. The sequence return type matches chainlink-ccip/deployment/v2_0_0/adapters.DeployChainContractsAdapter for the module version in go.mod. DeployStellarCCIPInnerInput carries ExistingAddresses from the CCIP changeset input so deploy can seed the in-memory datastore like EVM ExistingAddresses. AllSelectors lists every chain selector in the environment (derived from BlockChains in the adapter sequence).

Index

Constants

View Source
const DefaultCREForwarderWasmRelative = "target/wasm32v1-none/release/forwarder.wasm"

DefaultCREForwarderWasmRelative is the release artifact path relative to the chainlink-stellar repo root.

View Source
const StellarSetFeeAggregatorSequenceID = "stellar-set-fee-aggregator"

StellarSetFeeAggregatorSequenceID is the pipeline sequence ID for SetFeeAggregator on Stellar (OnRamp, VVR, Committee Verifier). Use [StellarFeeAggregatorAdapter.SetFeeAggregator], which wraps ApplyStellarFeeAggregator with the cldf.Environment needed for datastore lookups.

View Source
const StellarWithdrawFeeTokensSequenceID = "stellar-withdraw-fee-tokens"

StellarWithdrawFeeTokensSequenceID is the pipeline sequence ID for WithdrawFeeTokens on Stellar (OnRamp, VVR, Committee Verifier). Use [StellarFeeAggregatorAdapter.WithdrawFeeTokens], which wraps ApplyStellarWithdrawFeeTokens with the cldf.Environment needed for datastore lookups.

Variables

View Source
var DeployAdvancedPoolHooks = cldf_ops.NewSequence(
	"stellar-deploy-advanced-pool-hooks",
	SequenceVersion,
	"Deploys and initializes the Advanced Pool Hooks Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployAdvancedPoolHooksInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Advanced Pool Hooks"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, aphops.Deploy,
			stellarccip.AdvancedPoolHooksDatastoreRef(in.Qualifier), in.ChainSelector,
			componentSaltLabel("advanced-pool-hooks", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, aphops.Initialize, aphops.InitializeInput{
					ContractID:        contractID,
					Owner:             owner,
					Allowlist:         in.Allowlist,
					ThresholdAmount:   in.ThresholdAmount,
					AuthorizedCallers: in.AuthorizedCallers,
					PolicyEngine:      in.PolicyEngine,
				}, withComponentInstanceIdempotencyKey[aphops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployAdvancedPoolHooksExtractor = cldf_ops.NewSequence(
	"stellar-deploy-advanced-pool-hooks-extractor",
	SequenceVersion,
	"Deploys the stateless Advanced Pool Hooks Extractor Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployAdvancedPoolHooksExtractorInput) (ComponentDeployOutput, error) {
		if err := statReleaseWasm(in.WasmPath, "Advanced Pool Hooks Extractor"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployStatelessComponent(b.GetContext(), b, deps, aphxops.Deploy,
			stellarccip.AdvancedPoolHooksExtractorDatastoreRef(), in.ChainSelector,
			"advanced-pool-hooks-extractor", in.WasmPath, in.ExistingAddresses)
	},
)
View Source
var DeployBnmToken = cldf_ops.NewSequence(
	"stellar-deploy-bnm-token",
	SequenceVersion,
	"Deploys and initializes the BnM Soroban token with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployBnmTokenContractInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		name, symbol, decimals := bnmTokenDefaults(in.Name, in.Symbol, in.Decimals)
		if err := statReleaseWasm(in.WasmPath, "BnM token"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, bnmops.Deploy,
			stellarccip.BnmTokenDatastoreRef(), in.ChainSelector, "bnm-token", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, bnmops.Initialize, bnmops.InitializeInput{
					ContractID: contractID,
					Admin:      owner,
					Name:       name,
					Symbol:     symbol,
					Decimals:   decimals,
				}, withComponentIdempotencyKey[bnmops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployBurnMintPool = cldf_ops.NewSequence(
	"stellar-deploy-burn-mint-pool",
	SequenceVersion,
	"Deploys and initializes the burn-mint token pool with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployBurnMintPoolInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Burn-mint pool"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, bmpops.Deploy,
			stellarccip.BurnMintPoolDatastoreRef(in.Qualifier), in.ChainSelector,
			componentSaltLabel("burn-mint-pool", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, bmpops.Initialize, bmpops.InitializeInput{
					ContractID:    contractID,
					Owner:         owner,
					Token:         in.Token,
					TokenDecimals: in.TokenDecimals,
					Router:        in.Router,
					RampRegistry:  in.RampRegistry,
					RmnProxy:      in.RmnProxy,
				}, withComponentInstanceIdempotencyKey[bmpops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployCCIPReceiver = cldf_ops.NewSequence(
	"stellar-deploy-ccip-receiver",
	SequenceVersion,
	"Deploys and initializes the ccip_receiver_example Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployCCIPReceiverInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "ccip_receiver_example"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.Router == "" {
			return ComponentDeployOutput{}, fmt.Errorf("router is required: deploy Router first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, recvops.Deploy,
			stellarccip.CCIPReceiverDatastoreRefWithQualifier(in.Qualifier), in.ChainSelector,
			componentSaltLabel("ccip-receiver-example", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, recvops.Initialize, recvops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					Router:     in.Router,
				}, withComponentInstanceIdempotencyKey[recvops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployCommitteeVerifier = cldf_ops.NewSequence(
	"stellar-deploy-committee-verifier",
	SequenceVersion,
	"Deploys and initializes the CommitteeVerifier Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployCommitteeVerifierInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		allowlistAdmin := in.AllowlistAdmin
		if allowlistAdmin == "" {
			allowlistAdmin = owner
		}
		feeAggregator := in.FeeAggregator
		if feeAggregator == "" {
			feeAggregator = owner
		}
		versionTag := in.VersionTag
		if versionTag == [4]byte{} {
			versionTag = stellarutil.DefaultCommitteeVerifierVersionTag()
		}
		if err := statReleaseWasm(in.WasmPath, "Committee Verifier"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if len(in.StorageLocations) == 0 {
			return ComponentDeployOutput{}, fmt.Errorf("storageLocations is required: pass the committee storage locations")
		}
		if in.RmnProxy == "" {
			return ComponentDeployOutput{}, fmt.Errorf("rmnProxy is required: deploy RMN Proxy first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, cvops.Deploy,
			stellarccip.CommitteeVerifierDatastoreRefWithQualifier(in.Qualifier), in.ChainSelector, componentSaltLabel("committee-verifier", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, cvops.Initialize, cvops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					DynamicConfig: cvbindings.DynamicConfig{
						AllowlistAdmin: &allowlistAdmin,
						FeeAggregator:  &feeAggregator,
					},
					StorageLocations: in.StorageLocations,
					RmnProxy:         in.RmnProxy,
					VersionTag:       versionTag,
				}, withComponentInstanceIdempotencyKey[cvops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployExecutor = cldf_ops.NewSequence(
	"stellar-deploy-executor",
	SequenceVersion,
	"Deploys and initializes the Executor Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployExecutorInput) (ComponentDeployOutput, error) {
		qualifier := in.Qualifier
		if qualifier == "" {
			qualifier = stellarccip.DefaultExecutorQualifier
		}
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		maxCCVs := in.MaxCCVsPerMsg
		if maxCCVs == 0 {

			maxCCVs = 10
		}
		feeAggregator := in.FeeAggregator
		if feeAggregator == "" {
			feeAggregator = owner
		}
		if err := statReleaseWasm(in.WasmPath, "Executor"); err != nil {
			return ComponentDeployOutput{}, err
		}
		out, err := deployAndInitialize(b.GetContext(), b, deps, execops.Deploy,
			stellarccip.ExecutorDatastoreRefWithQualifier(in.Qualifier), in.ChainSelector, componentSaltLabel("executor", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, execops.Initialize, execops.InitializeInput{
					ContractID:    contractID,
					Owner:         owner,
					MaxCCVsPerMsg: maxCCVs,
					DynamicConfig: executorbindings.DynamicConfig{
						AllowedFinalityConfig: in.AllowedFinalityConfig,
						CcvAllowlistEnabled:   in.CcvAllowlistEnabled,
						FeeAggregator:         &feeAggregator,
					},
				}, withComponentInstanceIdempotencyKey[execops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
		if err != nil {
			return ComponentDeployOutput{}, err
		}

		proxyRef := stellarccip.ExecutorProxyDatastoreRef(qualifier)
		refs, err := componentRefs(proxyRef, in.ChainSelector, out.ContractID)
		if err != nil {
			return ComponentDeployOutput{}, err
		}
		proxyRow := refs[0]
		if existing := findExistingComponentRef(in.ExistingAddresses, proxyRef, in.ChainSelector); existing == nil || existing.Address != proxyRow.Address {
			out.Refs = append(out.Refs, proxyRow)
		}
		return out, nil
	},
)
View Source
var DeployFeeQuoter = cldf_ops.NewSequence(
	"stellar-deploy-fee-quoter",
	SequenceVersion,
	"Deploys and initializes the FeeQuoter Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployFeeQuoterInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "FeeQuoter"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.FeeToken == "" {
			return ComponentDeployOutput{}, fmt.Errorf("feeToken is required: pass the fee-token SAC strkey")
		}
		maxFee := in.MaxFeeJuelsPerMsg
		if maxFee == nil {
			maxFee = big.NewInt(1_000_000_000_000_000_000)
		}
		callers := in.AuthorizedCallers
		if len(callers) == 0 {
			callers = []string{owner}
		}
		return deployAndInitialize(b.GetContext(), b, deps, fqops.Deploy,
			stellarccip.FeeQuoterDatastoreRef(), in.ChainSelector, "fee-quoter", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, fqops.Initialize, fqops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					StaticConfig: fqbindings.StaticConfig{
						LinkToken:         in.FeeToken,
						MaxFeeJuelsPerMsg: maxFee,
					},
					AuthorizedCallers: callers,
				}, withComponentIdempotencyKey[fqops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployLinkToken = cldf_ops.NewSequence(
	"stellar-deploy-link-token",
	SequenceVersion,
	"Deploys and initializes the LINK Soroban token with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployLinkTokenInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		name, symbol, decimals := linkTokenDefaults(in.Name, in.Symbol, in.Decimals)
		if err := statReleaseWasm(in.WasmPath, "LINK token"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, lnkops.Deploy,
			stellarccip.LinkTokenDatastoreRef(), in.ChainSelector, "link-token", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, lnkops.Initialize, lnkops.InitializeInput{
					ContractID: contractID,
					Admin:      owner,
					Name:       name,
					Symbol:     symbol,
					Decimals:   decimals,
				}, withComponentIdempotencyKey[lnkops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployLockReleasePool = cldf_ops.NewSequence(
	"stellar-deploy-lock-release-pool",
	SequenceVersion,
	"Deploys and initializes the canonical lock-release token pool with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployLockReleasePoolInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Lock-release pool"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, lrops.Deploy,
			stellarccip.LockReleasePoolDatastoreRef(in.Qualifier), in.ChainSelector,
			componentSaltLabel("lock-release-pool", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, lrops.Initialize, lrops.InitializeInput{
					ContractID:    contractID,
					Owner:         owner,
					Token:         in.Token,
					TokenDecimals: in.TokenDecimals,
					Router:        in.Router,
					RampRegistry:  in.RampRegistry,
					RmnProxy:      in.RmnProxy,
					LockBox:       in.LockBox,
				}, withComponentInstanceIdempotencyKey[lrops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployOffRamp = cldf_ops.NewSequence(
	"stellar-deploy-offramp",
	SequenceVersion,
	"Deploys and initializes the OffRamp Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployOffRampInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "OffRamp"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.RmnProxy == "" {
			return ComponentDeployOutput{}, fmt.Errorf("rmnProxy is required: deploy RMN Proxy first")
		}
		if in.TokenAdminRegistry == "" {
			return ComponentDeployOutput{}, fmt.Errorf("tokenAdminRegistry is required: deploy TokenAdminRegistry first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, offrampops.Deploy,
			stellarccip.OffRampDatastoreRef(), in.ChainSelector, "offramp", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, offrampops.Initialize, offrampops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					Config: offrampbindings.StaticConfig{
						ChainSelector:      in.ChainSelector,
						RmnProxy:           in.RmnProxy,
						TokenAdminRegistry: in.TokenAdminRegistry,
					},
				}, withComponentIdempotencyKey[offrampops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployOnRamp = cldf_ops.NewSequence(
	"stellar-deploy-onramp",
	SequenceVersion,
	"Deploys and initializes the OnRamp Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployOnRampInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		feeAggregator := in.FeeAggregator
		if feeAggregator == "" {
			feeAggregator = owner
		}
		maxUsdCents := in.MaxUsdCentsPerMessage
		if maxUsdCents == 0 {

			maxUsdCents = 500_000
		}
		if err := statReleaseWasm(in.WasmPath, "OnRamp"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.TokenAdminRegistry == "" {
			return ComponentDeployOutput{}, fmt.Errorf("tokenAdminRegistry is required: deploy TokenAdminRegistry first")
		}
		if in.RmnProxy == "" {
			return ComponentDeployOutput{}, fmt.Errorf("rmnProxy is required: deploy RMN Proxy first")
		}
		if in.FeeQuoter == "" {
			return ComponentDeployOutput{}, fmt.Errorf("feeQuoter is required: deploy FeeQuoter first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, onrampops.Deploy,
			stellarccip.OnRampDatastoreRef(), in.ChainSelector, "onramp", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, onrampops.Initialize, onrampops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					StaticConfig: onrampbindings.StaticConfig{
						ChainSelector:         in.ChainSelector,
						TokenAdminRegistry:    in.TokenAdminRegistry,
						RmnProxy:              in.RmnProxy,
						MaxUsdCentsPerMessage: maxUsdCents,
					},
					DynamicConfig: onrampbindings.DynamicConfig{
						FeeQuoter:     in.FeeQuoter,
						FeeAggregator: feeAggregator,
					},
				}, withComponentIdempotencyKey[onrampops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployRMNProxy = cldf_ops.NewSequence(
	"stellar-deploy-rmn-proxy",
	SequenceVersion,
	"Deploys and initializes the RMN Proxy Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployRMNProxyInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "RMN Proxy"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.RmnRemote == "" {
			return ComponentDeployOutput{}, fmt.Errorf("rmnRemote is required: deploy RMN Remote first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, rmnproxyops.Deploy,
			stellarccip.RMNProxyDatastoreRef(), in.ChainSelector, "rmn-proxy", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, rmnproxyops.Initialize, rmnproxyops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					RmnRemote:  in.RmnRemote,
				}, withComponentIdempotencyKey[rmnproxyops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployRMNRemote = cldf_ops.NewSequence(
	"stellar-deploy-rmn-remote",
	SequenceVersion,
	"Deploys and initializes the RMN Remote Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployRMNRemoteInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "RMN Remote"); err != nil {
			return ComponentDeployOutput{}, err
		}
		curseAdmins := in.CurseAdmins
		if in.EnableFastCurse {
			fastQual := in.FastCurseQualifier
			if fastQual == "" {
				fastQual = cciputils.UltraFastCurseMCMSQualifier
			}
			fastTL, ok := mcmsutil.FindExistingStellarTimelock(in.ExistingAddresses, in.ChainSelector, fastQual)
			if !ok {
				return ComponentDeployOutput{}, fmt.Errorf(
					"enable fast curse: no RBACTimelock deployed for qualifier %q on chain %d; deploy the fast-curse MCMS stack first", fastQual, in.ChainSelector)
			}
			curseAdmins = append([]string{fastTL}, curseAdmins...)
		}
		return deployAndInitialize(b.GetContext(), b, deps, rmnremoteops.Deploy,
			stellarccip.RMNRemoteDatastoreRef(), in.ChainSelector, "rmn-remote", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, rmnremoteops.Initialize, rmnremoteops.InitializeInput{
					ContractID:  contractID,
					Owner:       owner,
					CurseAdmins: curseAdmins,
				}, withComponentIdempotencyKey[rmnremoteops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployRampRegistry = cldf_ops.NewSequence(
	"stellar-deploy-ramp-registry",
	SequenceVersion,
	"Deploys and initializes the RampRegistry Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployRampRegistryInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "RampRegistry"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, rrops.Deploy,
			stellarccip.RampRegistryDatastoreRef(), in.ChainSelector, "ramp-registry", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, rrops.Initialize, rrops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
				}, withComponentIdempotencyKey[rrops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployRouter = cldf_ops.NewSequence(
	"stellar-deploy-router",
	SequenceVersion,
	"Deploys and initializes the Router Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployRouterInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Router"); err != nil {
			return ComponentDeployOutput{}, err
		}
		if in.RmnProxy == "" {
			return ComponentDeployOutput{}, fmt.Errorf("rmnProxy is required: deploy RMN Proxy first")
		}
		return deployAndInitialize(b.GetContext(), b, deps, routerops.Deploy,
			stellarccip.RouterDatastoreRef(), in.ChainSelector, "router", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, routerops.Initialize, routerops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					RmnProxy:   in.RmnProxy,
				}, withComponentIdempotencyKey[routerops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeploySiloedLockReleasePool = cldf_ops.NewSequence(
	"stellar-deploy-siloed-lock-release-pool",
	SequenceVersion,
	"Deploys and initializes the siloed lock-release token pool with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeploySiloedLockReleasePoolInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Siloed lock-release pool"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, slrrops.Deploy,
			stellarccip.SiloedLockReleasePoolDatastoreRef(in.Qualifier), in.ChainSelector,
			componentSaltLabel("siloed-lock-release-pool", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, slrrops.Initialize, slrrops.InitializeInput{
					ContractID:    contractID,
					Owner:         owner,
					Token:         in.Token,
					TokenDecimals: in.TokenDecimals,
					Router:        in.Router,
					RampRegistry:  in.RampRegistry,
					RmnProxy:      in.RmnProxy,
				}, withComponentInstanceIdempotencyKey[slrrops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployStellarCREForwarder = cldfops.NewSequence(
	"stellar-deploy-cre-forwarder",
	stellarops.ContractDeploymentVersion,
	"Deploy and bootstrap the CRE forwarder Soroban contract (deployer-owned until governance handoff)",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in DeployStellarCREForwarderInput) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		qual := mcmsutil.QualifierStr(in.Qualifier)

		dep, err := stellarDeployerFromChain(ch)
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)

		if existing, found := FindExistingStellarCREForwarder(in.ExistingAddresses, in.ChainSelector, qual); found {
			return seqcore.OnChainOutput{Addresses: []datastore.AddressRef{StellarCREForwarderDatastoreRef(in.ChainSelector, qual, existing)}}, nil
		}

		wasm, err := ResolveCREForwarderWasmPath()
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		depOut, err := cldfops.ExecuteOperation(b, creforwarderops.Deploy, deps, stellarops.DeployInput{
			WasmPath: wasm,
			Salt:     CREForwarderDeploySalt(in.ChainSelector, qual),
		})
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf("cre forwarder deploy: %w", err)
		}
		cid := depOut.Output.ContractID

		_, err = cldfops.ExecuteOperation(b, creforwarderops.Initialize, deps, creforwarderops.InitializeInput{
			ContractID: cid,
			Owner:      dep.SignerAddress(),
		})
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf("cre forwarder initialize: %w", err)
		}

		if cfg := in.InitialDONConfig; cfg != nil {
			_, err = cldfops.ExecuteOperation(b, creforwarderops.SetConfig, deps, creforwarderops.SetConfigInput{
				ContractID:    cid,
				DonID:         cfg.DonID,
				ConfigVersion: cfg.ConfigVersion,
				F:             cfg.F,
				Signers:       cfg.Signers,
			})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("cre forwarder set_config: %w", err)
			}
		}
		for _, transmitter := range in.Transmitters {
			_, err = cldfops.ExecuteOperation(b, creforwarderops.AddForwarder, deps, creforwarderops.AddForwarderInput{
				ContractID: cid,
				Forwarder:  transmitter,
			})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("cre forwarder add_forwarder %s: %w", transmitter, err)
			}
		}

		return seqcore.OnChainOutput{Addresses: []datastore.AddressRef{StellarCREForwarderDatastoreRef(in.ChainSelector, qual, cid)}}, nil
	},
)

DeployStellarCREForwarder deploys and initializes the CRE forwarder owned by the deployer, then applies the optional bootstrap DON config and transmitter registry. Ownership handoff to the RBACTimelock is deliberately separate: reuse StellarTransferOwnershipViaMCMS and StellarAcceptOwnership with the emitted ref, exactly like the CCIP contracts. Reruns are idempotent: an already-deployed forwarder is left untouched (changes go through governance).

View Source
var DeployStellarMCMS = cldfops.NewSequence(
	"stellar-deploy-mcms",
	deploy.MCMSVersion,
	"Deploy three role-specific Soroban MCMS instances and a self-administered RBACTimelock",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in deploy.MCMSDeploymentConfigPerChainWithAddress) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		qual := mcmsutil.QualifierStr(in.Qualifier)
		label := mcmsutil.QualifierStr(in.Label)

		dep, err := stellarDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)

		roleConfig := map[mcmsutil.MCMSRole]mcmstypes.Config{
			mcmsutil.RoleProposer:  in.Proposer,
			mcmsutil.RoleCanceller: in.Canceller,
			mcmsutil.RoleBypasser:  in.Bypasser,
		}

		addrs := map[mcmsutil.MCMSRole]string{}
		fresh := map[mcmsutil.MCMSRole]bool{}
		var mcmsWasm string
		for _, role := range mcmsutil.AllMCMSRoles {
			existing, found, err := mcmsutil.FindExistingStellarMCMSByRole(in.ExistingAddresses, in.ChainSelector, qual, role)
			if err != nil {
				return seqcore.OnChainOutput{}, err
			}
			if found {
				addrs[role] = existing
				continue
			}
			if mcmsWasm == "" {
				if mcmsWasm, err = mcmsutil.ResolveMCMSWasmPath(); err != nil {
					return seqcore.OnChainOutput{}, err
				}
			}
			salt := mcmsutil.MCMSRoleDeploySalt(in.ChainSelector, qual, label, role)
			depOut, err := cldfops.ExecuteOperation(b, mcmsops.Deploy, deps, stellarops.DeployInput{WasmPath: mcmsWasm, Salt: salt})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("mcms deploy (%s): %w", role, err)
			}
			addrs[role] = depOut.Output.ContractID
			fresh[role] = true
		}

		tlID, haveTL := mcmsutil.FindExistingStellarTimelock(in.ExistingAddresses, in.ChainSelector, qual)
		if !haveTL {
			tlWasm, err := mcmsutil.ResolveTimelockWasmPath()
			if err != nil {
				return seqcore.OnChainOutput{}, err
			}
			tlSalt := mcmsutil.TimelockDeploySalt(in.ChainSelector, qual, label)
			tlOut, err := cldfops.ExecuteOperation(b, timelockops.Deploy, deps, stellarops.DeployInput{WasmPath: tlWasm, Salt: tlSalt})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("timelock deploy: %w", err)
			}
			tlID = tlOut.Output.ContractID

			minDelay, err := timelockMinDelay(in)
			if err != nil {
				return seqcore.OnChainOutput{}, err
			}

			_, err = cldfops.ExecuteOperation(b, timelockops.Initialize, deps, timelockops.InitializeInput{
				ContractID: tlID,
				MinDelay:   minDelay,
				Proposers:  []string{addrs[mcmsutil.RoleProposer]},
				Cancellers: []string{addrs[mcmsutil.RoleProposer], addrs[mcmsutil.RoleCanceller]},
				Bypassers:  []string{addrs[mcmsutil.RoleBypasser]},
			})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("timelock initialize: %w", err)
			}
		}

		chainNetID := mcmsutil.ChainNetworkID(ch.NetworkPassphrase)
		for _, role := range mcmsutil.AllMCMSRoles {
			if !fresh[role] {
				continue
			}
			cfg := roleConfig[role]
			signerAddrs, signerGroups, gq, gp, _, err := mcmsutil.ConfigToStellarSetConfig(&cfg, true)
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("mcms config (%s): %w", role, err)
			}
			_, err = cldfops.ExecuteOperation(b, mcmsops.Initialize, deps, mcmsops.InitializeInput{
				ContractID:      addrs[role],
				Owner:           tlID,
				ChainNetworkID:  chainNetID,
				SignerAddresses: signerAddrs,
				SignerGroups:    signerGroups,
				GroupQuorums:    gq,
				GroupParents:    gp,
				InstanceLabel:   role.InstanceLabel(),
			})
			if err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("mcms initialize (%s): %w", role, err)
			}
		}

		refs := make([]datastore.AddressRef, 0, len(mcmsutil.AllMCMSRoles)+1)
		for _, role := range mcmsutil.AllMCMSRoles {
			ref, err := mcmsutil.StellarMCMSRoleDatastoreRef(in.ChainSelector, qual, role, addrs[role])
			if err != nil {
				return seqcore.OnChainOutput{}, err
			}
			refs = append(refs, ref)
		}
		refs = append(refs, mcmsutil.StellarTimelockDatastoreRef(in.ChainSelector, qual, tlID))
		return seqcore.OnChainOutput{Addresses: refs}, nil
	},
)

DeployStellarMCMS deploys three role-specific Soroban MCMS instances (proposer, canceller, bypasser) plus one self-administered RBACTimelock. Each MCMS gets an independent signer config, its own deterministic salt/address, an immutable instance label, and is owned by the timelock from initialization (no deployer ownership ever exists). Timelock roles follow the matrix: proposer MCMS holds PROPOSER and CANCELLER, canceller MCMS holds CANCELLER bypasser MCMS holds BYPASSER; execution is permissionless. Reruns are idempotent: an already-deployed role instance is left untouched (config changes go through governance).

View Source
var DeployTokenAdminRegistry = cldf_ops.NewSequence(
	"stellar-deploy-token-admin-registry",
	SequenceVersion,
	"Deploys and initializes the TokenAdminRegistry Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployTokenAdminRegistryInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "TokenAdminRegistry"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, tarops.Deploy,
			stellarccip.TokenAdminRegistryDatastoreRef(), in.ChainSelector, "token-admin-registry", in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, tarops.Initialize, tarops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
				}, withComponentIdempotencyKey[tarops.InitializeInput](in.ChainSelector))
				return err
			})
	},
)
View Source
var DeployTokenLockBox = cldf_ops.NewSequence(
	"stellar-deploy-token-lock-box",
	SequenceVersion,
	"Deploys and initializes the TokenLockBox Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployTokenLockBoxInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		if err := statReleaseWasm(in.WasmPath, "Token lock box"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, tlbops.Deploy,
			stellarccip.TokenLockBoxDatastoreRef(in.Qualifier), in.ChainSelector,
			componentSaltLabel("token-lock-box", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, tlbops.Initialize, tlbops.InitializeInput{
					ContractID: contractID,
					Owner:      owner,
					Token:      in.Token,
				}, withComponentInstanceIdempotencyKey[tlbops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var DeployVVR = cldf_ops.NewSequence(
	"stellar-deploy-versioned-verifier-resolver",
	SequenceVersion,
	"Deploys and initializes the VersionedVerifierResolver Soroban contract with skip-if-exists",
	func(b cldf_ops.Bundle, deps ComponentDeps, in DeployVVRInput) (ComponentDeployOutput, error) {
		owner := defaultOwner(in.Owner, deps.DeployerAddress)
		feeAggregator := in.FeeAggregator
		if feeAggregator == "" {
			feeAggregator = owner
		}
		if err := statReleaseWasm(in.WasmPath, "VVR"); err != nil {
			return ComponentDeployOutput{}, err
		}
		return deployAndInitialize(b.GetContext(), b, deps, vvrops.Deploy,
			stellarccip.VVRDatastoreRefWithQualifier(in.Qualifier), in.ChainSelector, componentSaltLabel("versioned-verifier-resolver", in.Qualifier), in.WasmPath, owner, in.ExistingAddresses,
			func(contractID string) error {
				_, err := execStellarCCIPOp(b, deps.StellarDeps, vvrops.Initialize, vvrops.InitializeInput{
					ContractID:    contractID,
					Owner:         owner,
					FeeAggregator: feeAggregator,
				}, withComponentInstanceIdempotencyKey[vvrops.InitializeInput](in.ChainSelector, in.Qualifier))
				return err
			})
	},
)
View Source
var ErrBaseExecutionGasCostLowered = errors.New("baseExecutionGasCost would be lowered")

ErrBaseExecutionGasCostLowered guards the OnRamp BaseExecutionGasCost: the family default is one flat value, so a lane re-run would otherwise reset a destination whose gas cost was raised by hand and break execution there.

View Source
var ErrZeroAddressNotAllowed = errors.New("zero address not allowed")

ErrZeroAddressNotAllowed rejects zero-valued source OnRamp entries: the OffRamp matches an incoming message by hashing the onramp bytes it carries, so a zero entry can never match a real source and would leave the lane enabled but unusable. The Soroban OffRamp would accept it silently, so the guard lives here (EVM ZeroAddressNotAllowed ABI parity).

View Source
var FamilyDefaultAllowedFinality = func() uint32 {
	raw := finality.Config{WaitForFinality: true, WaitForSafe: true, BlockDepth: 1}.Raw()
	return binary.BigEndian.Uint32(raw[:])
}()

FamilyDefaultAllowedFinality is the raw allowed-finality value the stellar chain family declares (adapters.StellarChainFamilyAdapter.GetDefaultFinalityConfig: wait-for-finality | safe-flag | depth 1; keep in sync with it). EVM deploy defaults apply the same kind of family default to executors (ccv deploy_defaults.go defaultFinalityConfig), so the Executor deploys permissive of every requested-finality mode the lane flows send (0, block depth, safe flag). The default AllowedFinalityConfig of 0 (wait-for-finality only) makes the OnRamp's get_fee path — which cross-calls Executor::get_fee — reject depth/safe requests with InvalidRequestedFinality (#315).

View Source
var FinalizeStellarDeployMCMS = cldfops.NewSequence(
	"stellar-finalize-deploy-mcms",
	deploy.MCMSVersion,
	"Verify no residual deployer authority and timelock ownership of each MCMS after deploy",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in deploy.MCMSDeploymentConfigPerChainWithAddress) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellarDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		if err := VerifyStellarMCMSGovernance(b.GetContext(), stellardeps.FromDeployer(dep), in.ExistingAddresses, in.ChainSelector, mcmsutil.QualifierStr(in.Qualifier), dep.SignerAddress()); err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf("mcms deployment finalize: %w", err)
		}
		return seqcore.OnChainOutput{}, nil
	},
)

FinalizeStellarDeployMCMS verifies the post-deployment authority invariants and fails the deployment if any residual deployer authority remains or the timelock does not own each MCMS.

View Source
var GrantAdminRoleToTimelockStellar = cldfops.NewSequence(
	"stellar-grant-admin-role-to-timelock",
	deploy.MCMSVersion,
	"No-op: Stellar has no CallProxy executor grant step (EVM parity stub)",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in deploy.GrantAdminRoleToTimelockConfigPerChainWithSelector) (seqcore.OnChainOutput, error) {
		return seqcore.OnChainOutput{}, nil
	},
)

GrantAdminRoleToTimelockStellar is a no-op (no Stellar CallProxy; timelock executors can be added in a follow-up).

View Source
var SequenceVersion = semver.MustParse("2.0.0")

SequenceVersion is the semver for Stellar CCIP deployment sequences aligned with deployment/v2_0_0 adapters.

View Source
var StellarAcceptOwnership = cldfops.NewSequence(
	"stellar-seq-accept-ownership",
	deploy.MCMSVersion,
	"Accepts Soroban contract ownership via MCMS or deployer",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarTransferOwnershipInput) (output seqcore.OnChainOutput, err error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()
		ctx := b.GetContext()

		for _, ref := range in.ContractRef {

			cid, err := ownership.NormalizeContractAddress(ref.Address)
			if err != nil {
				return output, fmt.Errorf("accept ownership on chain %d: %w", in.ChainSelector, err)
			}
			ref.Address = cid
			owner, err := ownership.ContractOwner(ctx, deps, ref)
			if err != nil {
				return output, fmt.Errorf("read owner %s: %w", ref.Address, err)
			}
			if owner == in.ProposedOwner {
				continue
			}
			var wo evmcontract.WriteOutput
			switch {
			case in.ProposedOwner == deployerAddr:
				if err := ownership.ExecuteAcceptOwnership(b, deps, ref); err != nil {
					return output, fmt.Errorf("accept ownership %s: %w", ref.Address, err)
				}
				wo = evmcontract.WriteOutput{
					ChainSelector: in.ChainSelector,
					ExecInfo:      &evmcontract.ExecInfo{Hash: "stellar-direct-accept"},
				}
			case in.ProposedOwner == in.GovernanceAddr:
				data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("accept_ownership", nil)
				if err != nil {
					return output, err
				}
				wo = evmcontract.WriteOutput{
					ChainSelector: in.ChainSelector,
					Tx: mcmstypes.Transaction{
						OperationMetadata: mcmstypes.OperationMetadata{
							ContractType: string(ref.Type),
						},
						To:               ref.Address,
						Data:             data,
						AdditionalFields: mcmsutil.StellarMCMSTxAdditionalFields(),
					},
				}
			default:
				return output, fmt.Errorf(
					"accept routing: proposed owner %q must be deployer %q or governance %q for %s",
					in.ProposedOwner, deployerAddr, in.GovernanceAddr, ref.Address,
				)
			}
			batchOp, err := evmcontract.NewBatchOperationFromWrites([]evmcontract.WriteOutput{wo})
			if err != nil {
				return output, err
			}
			output.BatchOps = append(output.BatchOps, batchOp)
		}
		return output, nil
	},
)

StellarAcceptOwnership mirrors EVM OpAcceptOwnership: deployer accepts directly; when the pending owner is the timelock (governance), emit an MCMS batch op — no timelock==MCMS check.

View Source
var StellarActivateRMN = cldfops.NewSequence(
	"stellar-seq-activate-rmn",
	deploy.MCMSVersion,
	"Grants curse admins and transfers Stellar RMN Remote ownership to the governance timelock",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarActivateRMNInput) (output seqcore.OnChainOutput, err error) {
		govQual := in.GovernanceQualifier
		if govQual == "" {
			govQual = cciputils.RMNTimelockQualifier
		}
		fastQual := in.FastCurseQualifier
		if fastQual == "" {
			fastQual = cciputils.UltraFastCurseMCMSQualifier
		}

		govTL, ok := mcmsutil.FindExistingStellarTimelock(in.ExistingAddresses, in.ChainSelector, govQual)
		if !ok {
			return output, fmt.Errorf("no RBACTimelock deployed for qualifier %q on chain %d; deploy the %s MCMS stack first", govQual, in.ChainSelector, govQual)
		}
		fastTL, ok := mcmsutil.FindExistingStellarTimelock(in.ExistingAddresses, in.ChainSelector, fastQual)
		if !ok {
			return output, fmt.Errorf("no RBACTimelock deployed for qualifier %q on chain %d; deploy the fast-curse MCMS stack first", fastQual, in.ChainSelector)
		}

		rmnRecordedRef, opsRef, err := resolveRMNRemoteRefs(in)
		if err != nil {
			return output, err
		}

		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()
		ctx := b.GetContext()

		owner, err := ownership.ContractOwner(ctx, deps, opsRef)
		if err != nil {
			return output, fmt.Errorf("read RMN Remote owner: %w", err)
		}
		currentAdmins, err := ownership.CurseAdmins(ctx, deps, opsRef)
		if err != nil {
			return output, fmt.Errorf("read RMN Remote curse admins: %w", err)
		}

		added := dedupeStrkeys(append([]string{fastTL}, in.ExtraCurseAdmins...))
		if slices.Contains(added, owner) {
			b.Logger.Warnw("RMN Remote owner is being added to the curse-admin list; on chain nothing filters the owner out of the stored list", "owner", owner)
		}
		var toAdd []string
		for _, addr := range added {
			if !slices.Contains(currentAdmins, addr) {
				toAdd = append(toAdd, addr)
			}
		}

		var batchOps []mcmstypes.BatchOperation

		if len(toAdd) > 0 {
			route, err := routeCurseAdminGrant(owner, deployerAddr, govTL)
			if err != nil {
				return output, err
			}
			switch route {
			case curseAdminGrantDirect:
				if err := ownership.ExecuteApplyCurseAdminUpdates(b, deps, opsRef, toAdd, nil); err != nil {
					return output, fmt.Errorf("apply curse admin updates: %w", err)
				}
			case curseAdminGrantPropose:
				data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("apply_curse_admin_updates", []xdr.ScVal{
					scval.AddressSliceToScVal(toAdd),
					scval.AddressSliceToScVal(nil),
				})
				if err != nil {
					return output, err
				}
				batchOps = append(batchOps, mcmsutil.MCMSInvokeTx(opsRef, data))
			}
		}

		if len(in.SubjectsToMigrate) > 0 {
			if owner != deployerAddr {
				return output, fmt.Errorf(
					"cannot migrate curse subjects: RMN Remote owner %q is not the deployer %q; migration must happen before ownership transfer",
					owner, deployerAddr,
				)
			}
			for _, subject := range in.SubjectsToMigrate {
				if _, err := cldfops.ExecuteOperation(b, rmnremoteops.Curse, deps, rmnremoteops.CurseInput{
					ContractID: opsRef.Address,
					Caller:     deployerAddr,
					Subjects:   [][16]byte{subject},
				}); err != nil {
					return output, fmt.Errorf("migrate curse subject %x: %w", subject, err)
				}
			}
		}

		transferReport, err := cldfops.ExecuteSequence(b, StellarTransferOwnershipViaMCMS, chains, StellarTransferOwnershipInput{
			TransferOwnershipPerChainInput: deploy.TransferOwnershipPerChainInput{
				ChainSelector: in.ChainSelector,
				ContractRef:   []datastore.AddressRef{opsRef},
				ProposedOwner: govTL,
			},
			GovernanceAddr: govTL,
		})
		if err != nil {
			return output, fmt.Errorf("transfer RMN Remote ownership: %w", err)
		}
		batchOps = append(batchOps, transferReport.Output.BatchOps...)

		output.BatchOps = batchOps

		output.Addresses = append(output.Addresses, rmnRecordedRef)
		return output, nil
	},
)

StellarActivateRMN activates an existing RMN Remote for MCMS governance.

View Source
var StellarApplyDestChainConfig = cldfops.NewSequence(
	"stellar-apply-dest-chain-config",
	stellarops.ContractDeploymentVersion,
	"Apply dest chain config updates on Stellar FeeQuoter",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarApplyDestChainConfigInput) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.Selector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found", in.Selector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)

		var cfgArgs []fqbindings.DestChainConfigArgs
		for destSel, cfg := range in.Settings {
			cfgArgs = append(cfgArgs, fqbindings.DestChainConfigArgs{
				DestChainSelector: destSel,
				Config: fqbindings.DestChainConfig{
					IsEnabled:             cfg.IsEnabled,
					MaxDataBytes:          cfg.MaxDataBytes,
					MaxPerMsgGasLimit:     cfg.MaxPerMsgGasLimit,
					DestGasOverhead:       cfg.DestGasOverhead,
					DestGasPerPayloadByte: uint32(cfg.DestGasPerPayloadByteBase),
					DefaultTokenFeeUsd:    uint32(cfg.DefaultTokenFeeUSDCents),
					DefaultTokenDestGas:   cfg.DefaultTokenDestGasOverhead,
					DefaultTxGasLimit:     cfg.DefaultTxGasLimit,
					NetworkFeeUsdCents:    uint32(cfg.NetworkFeeUSDCents),
					LinkPremiumPercent:    uint32(cfg.V2Params.LinkFeeMultiplierPercent),
				},
			})
		}
		if len(cfgArgs) == 0 {
			return seqcore.OnChainOutput{}, nil
		}
		_, err = cldfops.ExecuteOperation(b, fqops.ApplyDestChainConfigs, deps, fqops.ApplyDestChainConfigsInput{
			ContractID: in.FQContractID,
			Configs:    cfgArgs,
		})
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf("apply dest chain configs on chain %d: %w", in.Selector, err)
		}
		return seqcore.OnChainOutput{}, nil
	},
)

StellarApplyDestChainConfig applies destination chain configs on the Stellar FeeQuoter.

View Source
var StellarConfigureChainForLanes = cldf_ops.NewSequence(
	"stellar-configure-chain-for-lanes",
	SequenceVersion,
	"Applies full lane wiring (OffRamp, OnRamp, Executor, FeeQuoter, Router) and committee verifier signature quorums on Stellar for each remote lane",
	func(b cldf_ops.Bundle, chains cldf_chain.BlockChains, input ccvadapters.ConfigureChainForLanesInput) (seq_core.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[input.ChainSelector]
		if !ok {
			return seq_core.OnChainOutput{}, fmt.Errorf("stellar chain not found for selector %d", input.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seq_core.OnChainOutput{}, fmt.Errorf("stellar deployer from chain: %w", err)
		}
		return runStellarConfigureChainForLanes(b, stellardeps.FromDeployer(dep), input)
	},
)

StellarConfigureChainForLanes is the Stellar leg of the shared ConfigureChainsForLanesFromTopology changeset. Per remote chain it wires the full lane, EVM-sequence style: OffRamp source chain config, OnRamp dest chain config, Executor dest chain config, FeeQuoter dest chain config, and Router ramp entries. Every write is guarded by a read of the current on-chain state, so re-runs are idempotent: zero/empty/nil input values keep the current on-chain value, and a remote whose desired state already matches emits no write at all. Writes are ordered infra-first (OffRamp → OnRamp → Executor → FeeQuoter) with the Router write last, so the Router never routes to a contract that is not yet configured.

Guards, EVM-parity wording where one exists:

  • AllowOnrampOverride=false refuses to replace a Router onRamp entry that already points at a different OnRamp (use a migration to swap OnRamps).
  • AllowLoweringBaseExecutionGasCost=false refuses to lower the OnRamp BaseExecutionGasCost below its current on-chain value (ErrBaseExecutionGasCostLowered).
  • A remote whose SkipExecutorConfig is set gets the no-execution sentinel (stellarutil.NoExecutionExecutorStrkey, = Rust NO_EXECUTION_STRKEY) as the OnRamp DefaultExecutor and no Executor write at all — EVM NoExecutionAddress parity.
  • FeeQuoter configs already enabled on chain are left alone unless the input's OverrideExistingConfig is set, protecting deploy-time pricing.
  • Source OnRamp entries that are empty or all-zero are rejected (ErrZeroAddressNotAllowed) instead of being written verbatim.

Deliberately NOT done here:

  • FeeQuoter gas/token price updates (UpdatePrices): deploy seeds them and the offchain price updater owns them afterwards (Solana precedent).
  • CommitteeVerifier remote chain configs/allowlists/resolver routing: coalesced defaults here would clobber deploy-time values. The committee verifier writes this sequence does make are the signature quorums (applyCommitteeVerifierSignatureQuorums) and the verifier-global allowed-finality config (applyCommitteeVerifierAllowedFinality, EVM ConfigureChainForLanes parity).
  • RampRegistry sync: not reachable from ConfigureChainForLanesInput; would need a FamilyExtras change in chainlink-ccip (noted as a follow-up).

Committee verifier signature quorums run at lane-configuration time, not deploy time, on purpose: since the chainlink-ccv 2026-06-26 signing-key-sync cutover, topology enrichment skips families whose bootstrappers push signing keys to JD on connect, so signer addresses are not available to RunStellarCCIPFullDeploy. The changeset resolves them from JD (falling back to topology signer addresses) and passes them in via CommitteeVerifierRemoteChainConfig.SignatureConfig.

Everything executes as plain EOA transactions; Soroban has no MCMS, so the output carries no BatchOps.

View Source
var StellarCurse = cldfops.NewSequence(
	"stellar-curse-rmn-remote",
	stellarops.ContractDeploymentVersion,
	"Curse subjects on Stellar RMN Remote",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarCurseInput) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()

		caller, effectiveQualifier, err := authorizeCurseCaller(in, deployerAddr)
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"curse on chain %d: %w; grant curse-admin access with apply_curse_admin_updates, or set the MCMS qualifier to a stack that is authorized",
				in.ChainSelector, err,
			)
		}

		if caller == deployerAddr {
			if _, err := cldfops.ExecuteOperation(b, rmnremoteops.Curse, deps, rmnremoteops.CurseInput{
				ContractID: in.RMNContractID,
				Caller:     caller,
				Subjects:   in.Subjects,
			}); err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("curse on chain %d: %w", in.ChainSelector, err)
			}
			return directExecOutput(b, in.ChainSelector, in.RMNContractID, "stellar-direct-curse")
		}

		if in.MCMSQualifier == "" {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"curse on chain %d cannot run directly (deployer %s is neither the owner %s nor a curse admin of RMN Remote %s) and no MCMS qualifier was supplied to route a proposal; set the qualifier %q in the MCMS config to propose via its timelock %s",
				in.ChainSelector, deployerAddr, in.Owner, in.RMNContractID, effectiveQualifier, caller,
			)
		}

		data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("curse", []xdr.ScVal{
			scval.AddressToScVal(caller),
			scval.Bytes16SliceToScVal(in.Subjects),
		})
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		b.Logger.Infow("proposing stellar curse via MCMS",
			"qualifier", in.MCMSQualifier, "timelock", caller, "chainSelector", in.ChainSelector)
		return seqcore.OnChainOutput{
			BatchOps: []mcmstypes.BatchOperation{{
				ChainSelector: mcmstypes.ChainSelector(in.ChainSelector),
				Transactions: []mcmstypes.Transaction{{
					OperationMetadata: mcmstypes.OperationMetadata{ContractType: rmnremoteops.ContractType},
					To:                in.RMNContractID,
					Data:              data,
					AdditionalFields:  mcmsutil.StellarMCMSTxAdditionalFields(),
				}},
			}},
		}, nil
	},
)

StellarCurse curses subjects on a Stellar RMN Remote contract: directly when the deployer is authorized, otherwise as an MCMS proposal whose caller is the timelock of the qualifier chosen for this run.

View Source
var StellarDeployChainContracts = cldf_ops.NewSequence(
	"stellar-deploy-chain-contracts",
	SequenceVersion,
	"Deploys Stellar Soroban CCIP contracts via CLDF Stellar chain (adapter path); uses pre-stashed offchain topology from CCV pre-deploy when present.",
	func(b cldf_ops.Bundle, chains cldf_chain.BlockChains, input ccvadapters.DeployChainContractsInput) (DeployChainContractsOutput, error) {
		ch, ok := chains.StellarChains()[input.ChainSelector]
		if !ok {
			return DeployChainContractsOutput{}, fmt.Errorf("stellar chain not found for selector %d", input.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return DeployChainContractsOutput{}, fmt.Errorf("stellar deployer from chain: %w", err)
		}
		deps := stellardeps.FromDeployer(dep)
		lg := stellarccip.DefaultStellarDeployZerolog()
		host, err := stellarccip.NewCLDFStellarCCIPDevenvHost(ch, lg, dep)
		if err != nil {
			return DeployChainContractsOutput{}, fmt.Errorf("stellar devenv host: %w", err)
		}
		offTopo, _ := TakeStellarDeployOffchainTopologyForSelector(input.ChainSelector)
		output, err := RunStellarCCIPFullDeploy(b.GetContext(), b, deps, host, offTopo, DeployStellarCCIPInnerInput{
			ChainSelector:     input.ChainSelector,
			AllSelectors:      allSelectorsFromBlockChains(chains),
			ExistingAddresses: input.ExistingAddresses,
		})
		if err != nil {
			return DeployChainContractsOutput{}, err
		}
		return DeployChainContractsOutput{OnChainOutput: output}, nil
	},
)

StellarDeployChainContracts is the adapter sequence: DEP is BlockChains. It builds StellarDeps and a stellarccip.CCIPDevenvHost from the CLDF Stellar chain entry. CCV stashes offchain topology in RegisterStellarDeployOffchainTopologyForSelector during PreDeployContractsForSelector; this sequence TakeStellarDeployOffchainTopologyForSelector before RunStellarCCIPFullDeploy. A missing stash entry is tolerated: the stash is optional here, and signer quorums are applied at lane-configuration time rather than during the deploy.

View Source
var StellarSetCommitteeVerifierFinality = cldfops.NewSequence(
	"stellar-seq-set-committee-verifier-finality",
	stellarops.ContractDeploymentVersion,
	"Sets the Stellar Committee Verifier allowed finality, directly or via MCMS",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarSetCommitteeVerifierFinalityInput) (output seqcore.OnChainOutput, err error) {
		if err := in.AllowedFinality.Validate(); err != nil {
			return output, err
		}
		raw := in.AllowedFinality.Raw()
		allowed := binary.BigEndian.Uint32(raw[:])

		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, fmt.Errorf("stellar deployer from chain: %w", err)
		}
		deps := stellardeps.FromDeployer(dep)

		owner, err := readContractOwner(b.GetContext(), deps, in.CommitteeVerifier)
		if err != nil {
			return output, fmt.Errorf("read owner of committee verifier %s: %w", in.CommitteeVerifier.Address, err)
		}
		switch owner {
		case dep.SignerAddress():
			_, err := cldfops.ExecuteOperation(b, cvops.SetAllowedFinalityConfig, deps, cvops.SetAllowedFinalityConfigInput{
				ContractID:            in.CommitteeVerifier.Address,
				AllowedFinalityConfig: allowed,
			})
			return output, err
		case in.Timelock:
			data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("set_allowed_finality_config", []xdr.ScVal{scval.Uint32ToScVal(allowed)})
			if err != nil {
				return output, fmt.Errorf("encode set_allowed_finality_config payload: %w", err)
			}
			output.BatchOps = []mcmstypes.BatchOperation{mcmsutil.MCMSInvokeTx(in.CommitteeVerifier, data)}
			return output, nil
		default:
			return output, fmt.Errorf("committee verifier %s owner %q is neither the deployer %q nor the timelock %q",
				in.CommitteeVerifier.Address, owner, dep.SignerAddress(), in.Timelock)
		}
	},
)

StellarSetCommitteeVerifierFinality calls set_allowed_finality_config directly when the deployer owns the verifier, and emits it as an MCMS batch op when the timelock does.

View Source
var StellarSetTokenTransferFee = cldfops.NewSequence(
	"stellar-set-token-transfer-fee",
	stellarops.ContractDeploymentVersion,
	"Set per-token transfer fees on Stellar FeeQuoter",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarSetTokenTransferFeeInput) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.Selector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found", in.Selector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)

		var cfgArgs []fqbindings.TokenFeeConfigArgs
		for destSel, tokenFees := range in.Settings {
			for token, feeArgs := range tokenFees {
				if feeArgs == nil {
					continue
				}
				cfgArgs = append(cfgArgs, fqbindings.TokenFeeConfigArgs{
					DestChainSelector: destSel,
					Token:             token,
					Config: fqbindings.TokenTransferFeeConfig{
						DestBytesOverhead: feeArgs.DestBytesOverhead,
						DestGasOverhead:   feeArgs.DestGasOverhead,
						FeeUsdCents:       feeArgs.MinFeeUSDCents,
						IsEnabled:         feeArgs.IsEnabled,
					},
				})
			}
		}
		if len(cfgArgs) == 0 {
			return seqcore.OnChainOutput{}, nil
		}
		_, err = cldfops.ExecuteOperation(b, fqops.ApplyTokenFeeConfigs, deps, fqops.ApplyTokenFeeConfigsInput{
			ContractID: in.FQContractID,
			AddConfigs: cfgArgs,
		})
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf("apply token fee configs on chain %d: %w", in.Selector, err)
		}
		return seqcore.OnChainOutput{}, nil
	},
)

StellarSetTokenTransferFee applies per-token transfer fee configs on the Stellar FeeQuoter.

View Source
var StellarTransferOwnershipViaMCMS = cldfops.NewSequence(
	"stellar-seq-transfer-ownership-via-mcms",
	deploy.MCMSVersion,
	"Transfers Soroban contract ownership via MCMS or deployer",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarTransferOwnershipInput) (output seqcore.OnChainOutput, err error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()
		ctx := b.GetContext()

		for _, ref := range in.ContractRef {

			cid, err := ownership.NormalizeContractAddress(ref.Address)
			if err != nil {
				return output, fmt.Errorf("transfer ownership on chain %d: %w", in.ChainSelector, err)
			}
			ref.Address = cid
			owner, err := ownership.ContractOwner(ctx, deps, ref)
			if err != nil {
				return output, fmt.Errorf("read owner %s: %w", ref.Address, err)
			}
			if owner == in.ProposedOwner {
				continue
			}
			var wo evmcontract.WriteOutput
			switch {
			case owner == deployerAddr:
				if err := ownership.ExecuteTransferOwnership(b, deps, ref, in.ProposedOwner); err != nil {
					return output, fmt.Errorf("transfer ownership %s: %w", ref.Address, err)
				}
				wo = evmcontract.WriteOutput{
					ChainSelector: in.ChainSelector,
					ExecInfo:      &evmcontract.ExecInfo{Hash: "stellar-direct-transfer"},
				}
			case owner == in.GovernanceAddr:
				data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("transfer_ownership", []xdr.ScVal{scval.AddressToScVal(in.ProposedOwner)})
				if err != nil {
					return output, err
				}
				wo = evmcontract.WriteOutput{
					ChainSelector: in.ChainSelector,
					Tx: mcmstypes.Transaction{
						OperationMetadata: mcmstypes.OperationMetadata{
							ContractType: string(ref.Type),
						},
						To:               ref.Address,
						Data:             data,
						AdditionalFields: mcmsutil.StellarMCMSTxAdditionalFields(),
					},
				}
			default:
				return output, fmt.Errorf(
					"contract %s owner %q is neither deployer %q nor governance %q",
					ref.Address, owner, deployerAddr, in.GovernanceAddr,
				)
			}
			batchOp, err := evmcontract.NewBatchOperationFromWrites([]evmcontract.WriteOutput{wo})
			if err != nil {
				return output, err
			}
			output.BatchOps = append(output.BatchOps, batchOp)
		}
		return output, nil
	},
)

StellarTransferOwnershipViaMCMS mirrors EVM chains/evm/.../operations/mcms OpTransferOwnership: deployer sends the tx directly; when the current owner is the timelock (governance), emit an MCMS batch op (MCMS is the transport, not compared to timelock).

View Source
var StellarUncurse = cldfops.NewSequence(
	"stellar-uncurse-rmn-remote",
	stellarops.ContractDeploymentVersion,
	"Uncurses subjects on Stellar RMN Remote",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarCurseInput) (seqcore.OnChainOutput, error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return seqcore.OnChainOutput{}, fmt.Errorf("stellar chain %d not found", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()

		if in.Owner == "" {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"uncurse on chain %d: RMN Remote owner is unknown and uncurse is owner-only, so it cannot be routed; "+
					"the curse adapter reads the owner (failing loudly on RPC errors), so an empty Owner means the input bypassed it — populate Owner in the input", in.ChainSelector)
		}

		if deployerAddr == in.Owner {
			if _, err := cldfops.ExecuteOperation(b, rmnremoteops.Uncurse, deps, rmnremoteops.UncurseInput{
				ContractID: in.RMNContractID,
				Subjects:   in.Subjects,
			}); err != nil {
				return seqcore.OnChainOutput{}, fmt.Errorf("uncurse on chain %d: %w", in.ChainSelector, err)
			}
			return directExecOutput(b, in.ChainSelector, in.RMNContractID, "stellar-direct-uncurse")
		}

		caller, effectiveQualifier, err := authorizeCurseCaller(in, deployerAddr)
		if err != nil {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"uncurse on chain %d: %w; uncurse is owner-only — curse-admin grants cannot authorize it; transfer RMN Remote ownership, or route via the owner timelock's qualifier",
				in.ChainSelector, err,
			)
		}
		if caller != in.Owner {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"uncurse on chain %d via qualifier %q is not possible: uncurse is owner-only and fast-uncurse is not supported; route it through the owner timelock %s",
				in.ChainSelector, in.MCMSQualifier, in.Owner)
		}

		if in.MCMSQualifier == "" {
			return seqcore.OnChainOutput{}, fmt.Errorf(
				"uncurse on chain %d is owner-only and the deployer %s is not the owner %s, so it must run as a proposal via the owner timelock %s; set the MCMS qualifier %q in the MCMS config",
				in.ChainSelector, deployerAddr, in.Owner, caller, effectiveQualifier,
			)
		}

		data, err := mcmsutil.EncodeSorobanMCMSInvokePayload("uncurse", []xdr.ScVal{
			scval.Bytes16SliceToScVal(in.Subjects),
		})
		if err != nil {
			return seqcore.OnChainOutput{}, err
		}
		b.Logger.Infow("proposing stellar uncurse via MCMS",
			"qualifier", in.MCMSQualifier, "timelock", caller, "chainSelector", in.ChainSelector)
		return seqcore.OnChainOutput{
			BatchOps: []mcmstypes.BatchOperation{{
				ChainSelector: mcmstypes.ChainSelector(in.ChainSelector),
				Transactions: []mcmstypes.Transaction{{
					OperationMetadata: mcmstypes.OperationMetadata{ContractType: rmnremoteops.ContractType},
					To:                in.RMNContractID,
					Data:              data,
					AdditionalFields:  mcmsutil.StellarMCMSTxAdditionalFields(),
				}},
			}},
		}, nil
	},
)

StellarUncurse uncurses subjects on a Stellar RMN Remote contract. Uncurse is owner-only on chain and takes no caller argument, so it always routes through the RMNMCMS stack; a non-owner qualifier (including UltraFastCurse) is a build-time error: fast-uncurse is not supported.

View Source
var StellarUpgradeContractsViaMCMS = cldfops.NewSequence(
	"stellar-seq-upgrade-contracts-via-mcms",
	deploy.MCMSVersion,
	"Upgrades Soroban CCIP contracts in place via MCMS or deployer",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in StellarUpgradeContractsInput) (output seqcore.OnChainOutput, err error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellardeployment.NewDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, err
		}
		deps := stellardeps.FromDeployer(dep)
		deployerAddr := dep.SignerAddress()
		ctx := b.GetContext()

		for _, ref := range in.ContractRef {
			wo, err := upgradeContractRef(ctx, b, deps, dep, deployerAddr, in.GovernanceAddr, in.ChainSelector, ref)
			if err != nil {
				return output, err
			}
			batchOp, err := evmcontract.NewBatchOperationFromWrites([]evmcontract.WriteOutput{wo})
			if err != nil {
				return output, err
			}
			output.BatchOps = append(output.BatchOps, batchOp)
		}
		return output, nil
	},
)

StellarUpgradeContractsViaMCMS upgrades Soroban CCIP contracts in place. For each ref it uploads the freshly-compiled release WASM for that contract type, reads the current owner, and either executes the upgrade directly (EOA owner) or emits an MCMS proposal transaction (governance/timelock owner). Mirrors StellarTransferOwnershipViaMCMS; there is no EVM OpUpgrade to mirror (EVM upgrades by deploy-new-and-repoint, while Soroban swaps the Wasm hash in place via update_current_contract_wasm).

View Source
var UpdateStellarMCMSConfig = cldfops.NewSequence(
	"stellar-update-mcms-config",
	deploy.MCMSVersion,
	"Updates signer config on Stellar MCMS directly or through its timelock",
	func(b cldfops.Bundle, chains cldfchain.BlockChains, in deploy.UpdateMCMSConfigInputPerChainWithSelector) (output seqcore.OnChainOutput, err error) {
		ch, ok := chains.StellarChains()[in.ChainSelector]
		if !ok {
			return output, fmt.Errorf("stellar chain %d not found in environment", in.ChainSelector)
		}
		dep, err := stellarDeployerFromChain(ch, stellardeployment.WithLogger(b.Logger))
		if err != nil {
			return output, err
		}
		deps := stellardeps.FromDeployer(dep)
		signerAddrs, signerGroups, gq, gp, _, err := mcmsutil.ConfigToStellarSetConfig(&in.MCMConfig, true)
		if err != nil {
			return output, err
		}
		cfg := mcmsops.SetConfigInput{
			SignerAddresses: signerAddrs,
			SignerGroups:    signerGroups,
			GroupQuorums:    gq,
			GroupParents:    gp,
			ClearRoot:       true,
		}
		for _, ref := range in.MCMContracts {
			if ref.Address == "" {
				continue
			}
			governanceAddr, _ := mcmsutil.FindExistingStellarTimelock(in.ExistingAddresses, in.ChainSelector, ref.Qualifier)
			wo, err := setStellarMCMSConfigForRef(b.GetContext(), b, deps, dep.SignerAddress(), governanceAddr, in.ChainSelector, ref, cfg)
			if err != nil {
				return output, err
			}
			if wo.Executed() {
				continue
			}
			batchOp, err := evmcontract.NewBatchOperationFromWrites([]evmcontract.WriteOutput{wo})
			if err != nil {
				return output, err
			}
			output.BatchOps = append(output.BatchOps, batchOp)
		}
		return output, nil
	},
)

UpdateStellarMCMSConfig sets the signer config on each listed MCMS. Each MCMS is updated directly when the deployer owns it, or through an MCMS batch op when the timelock of its own stack (same qualifier) owns it.

Functions

func ApplyStellarFeeAggregator

func ApplyStellarFeeAggregator(b cldfops.Bundle, chains cldfchain.BlockChains, env cldf.Environment, in fees.FeeAggregatorForChain) (seqcore.OnChainOutput, error)

ApplyStellarFeeAggregator updates the fee aggregator on Stellar contracts that hold CCIP fee funds. When in.Contracts is empty, OnRamp, Versioned Verifier Resolver, and Committee Verifier are updated.

func ApplyStellarWithdrawFeeTokens

func ApplyStellarWithdrawFeeTokens(b cldfops.Bundle, chains cldfchain.BlockChains, env cldf.Environment, in fees.WithdrawFeeTokensForChain) (output seqcore.OnChainOutput, err error)

ApplyStellarWithdrawFeeTokens sweeps accumulated fee token balances to the configured fee aggregator on the Stellar contracts that hold CCIP fee funds. When in.Contracts is empty, OnRamp, Versioned Verifier Resolver, and Committee Verifier are swept; otherwise only the referenced contracts are, resolved by full ref (Type + Version + Qualifier).

withdraw_fee_tokens is permissionless on chain (it only sends to the trusted fee aggregator), so MCMS adds no authorization here; each contract is instead routed the same way the EVM fee adapters route their allowed-caller check: when the deployer owns the contract the withdrawal executes directly and returns no batch ops, otherwise it is emitted as an unexecuted MCMS write so the shared changeset proposes it via Build(cfg.MCMS). Governed environments (the owner is a timelock) therefore get a proposal, dev environments (the deployer owns) execute immediately.

func CREForwarderDeploySalt

func CREForwarderDeploySalt(chainSelector uint64, qual string) [32]byte

CREForwarderDeploySalt derives a deterministic deploy salt for a CRE forwarder instance.

func ClearStellarDeployOffchainTopologyForSelector

func ClearStellarDeployOffchainTopologyForSelector(selector uint64)

ClearStellarDeployOffchainTopologyForSelector deletes a stashed topology without returning it. Call from post-deploy after Take may have already run (idempotent cleanup).

func CvGasForVerificationForRemote

func CvGasForVerificationForRemote(remoteSelector uint64) (uint32, error)

cvGasForVerificationForRemote returns the per-CCV dest verification gas for a remote chain, mirroring the chain-family adapter default the lane changeset coalesces to for that remote family: EVM 75_000 (chains/evm deployment/v2_0_0/adapters/chain_family.go GetDefaultCommitteeVerifierRemoteChainConfig), stellar 60_000 (deployment/ adapters/stellar_chain_family_adapter.go). The full deploy is the only writer of stellar CV remote configs, so the remote's scale must be honored here — a stellar-scale value for an EVM remote under-budgets the on-wire execution_gas_limit (see the remoteChainConfigs loop above).

func DeployStellarCCIPContracts

func DeployStellarCCIPContracts(
	ctx context.Context,
	opBundle cldfops.Bundle,
	host stellarccip.CCIPDevenvHost,
	allSelectors []uint64,
	selector uint64,
	topology *ccvdeployment.EnvironmentTopology,
	existingAddresses []datastore.AddressRef,
) (datastore.DataStore, error)

DeployStellarCCIPContracts deploys the full Stellar CCIP stack and returns a sealed in-memory datastore containing address refs for merge into the environment datastore. opBundle must be the CLDF bundle for this run (parent changeset bundle from sequences, or cldfops.NewBundle for tests). topology must be non-nil so committee verifier signature config can be applied (RunStellarCCIPFullDeployForCCV).

func FindExistingStellarCREForwarder

func FindExistingStellarCREForwarder(refs []datastore.AddressRef, chainSelector uint64, qual string) (string, bool)

FindExistingStellarCREForwarder returns the CRE forwarder contract id from refs when present.

func RegisterStellarDeployOffchainTopologyForSelector

func RegisterStellarDeployOffchainTopologyForSelector(selector uint64, topo *offchain.EnvironmentTopology)

RegisterStellarDeployOffchainTopologyForSelector records offchain topology for the Stellar chain selector. It is overwritten if already present. nil topo is a no-op.

func ResolveCREForwarderWasmPath

func ResolveCREForwarderWasmPath() (string, error)

ResolveCREForwarderWasmPath returns the path to forwarder.wasm. Order: STELLAR_CRE_FORWARDER_WASM (full path), then CHAINLINK_STELLAR_ROOT + DefaultCREForwarderWasmRelative, then cwd-relative.

func RunStellarCCIPFullDeploy

RunStellarCCIPFullDeploy deploys and configures the full Stellar CCIP Soroban stack for devenv using CLDF operations on the given bundle. It mirrors the phased devenv pipeline (foundation → verification/fees → ramps → receiver). Each component deploys and initializes through its own rerun-safe sequence; this orchestrator passes dependencies, applies the config ops and updates the host.

topology is optional: committee verifier signature quorums are applied at lane-configuration time, not during the deploy, so the deploy itself needs no NOP/committee data. It is kept as a parameter for callers that hold one (CCV's stash → StellarDeployChainContracts, or RunStellarCCIPFullDeployForCCV which converts CCV topology) and for future deploy-time needs.

Output addresses carry only the refs this run recorded (new deploys, adoptions, strkey→hex rewrites); refs seeded from ExistingAddresses are never re-reported, so a re-run where every contract already exists records no refs. Post-deploy configuration (executor dest chains, FeeQuoter configs and prices,

func RunStellarCCIPFullDeployForCCV

func RunStellarCCIPFullDeployForCCV(
	ctx context.Context,
	opBundle cldfops.Bundle,
	deps stellardeps.StellarDeps,
	host stellarccip.CCIPDevenvHost,
	allSelectors []uint64,
	selector uint64,
	topology *ccvdeployment.EnvironmentTopology,
	existingAddresses []datastore.AddressRef,
) (seq_core.OnChainOutput, error)

RunStellarCCIPFullDeployForCCV runs the full Stellar CCIP Soroban deploy with CCV environment topology (converted to offchain). Used by ccv/chain and by DeployStellarCCIPContracts. topology must be non-nil so committee verifier signature config can be derived after conversion.

func StellarCREForwarderDatastoreRef

func StellarCREForwarderDatastoreRef(chainSelector uint64, qual, contractID string) datastore.AddressRef

StellarCREForwarderDatastoreRef is the datastore ref emitted for a deployed CRE forwarder.

func TakeStellarDeployOffchainTopologyForSelector

func TakeStellarDeployOffchainTopologyForSelector(selector uint64) (*offchain.EnvironmentTopology, bool)

TakeStellarDeployOffchainTopologyForSelector loads and removes the topology for selector. The second return is false if nothing was registered.

func VerifyStellarMCMSGovernance

func VerifyStellarMCMSGovernance(ctx context.Context, deps stellardeps.StellarDeps, refs []datastore.AddressRef, chainSelector uint64, qual, deployerAddr string) error

VerifyStellarMCMSGovernance resolves the three role instances + timelock from refs and asserts the post-deployment authority invariants (distinct addresses, correct labels, timelock ownership, exact role matrix, no residual deployer authority). It fails closed on any missing ref.

func VerifyStellarMCMSGovernanceWithExpectations

func VerifyStellarMCMSGovernanceWithExpectations(ctx context.Context, deps stellardeps.StellarDeps, refs []datastore.AddressRef, chainSelector uint64, qual, deployerAddr string, exp governanceExpectations) error

VerifyStellarMCMSGovernanceWithExpectations additionally asserts the timelock min delay and that the timelock owns each supplied governed contract.

Types

type CREForwarderDONConfig

type CREForwarderDONConfig struct {
	DonID         uint32     `json:"don_id"`
	ConfigVersion uint32     `json:"config_version"`
	F             uint32     `json:"f"`
	Signers       [][32]byte `json:"signers"`
}

CREForwarderDONConfig is the initial DON signer configuration applied deployer-signed on a fresh deploy.

type ComponentDeployOutput

type ComponentDeployOutput struct {
	ContractID  string                 `json:"contract_id"`
	Deployed    bool                   `json:"deployed"`
	Initialized bool                   `json:"initialized"`
	Refs        []datastore.AddressRef `json:"refs,omitempty"`
}

ComponentDeployOutput is what every component sequence returns.

type ComponentDeps

type ComponentDeps struct {
	stellardeps.StellarDeps
	NetworkPassphrase string
	DeployerAddress   string
	Ledger            InstanceStateReader
}

ComponentDeps is the host-free chain I/O every component sequence needs: the plain StellarDeps plus the inputs of the skip-if-exists check that must NOT enter the sequence input hash (they are environment, not parameters).

type DeployAdvancedPoolHooksExtractorInput

type DeployAdvancedPoolHooksExtractorInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	WasmPath          string                 `json:"wasmPath"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployAdvancedPoolHooksExtractorInput deploys the Advanced Pool Hooks Extractor — the stateless policy extractor that projects the pool-hooks preflight/postflight payloads into the named Parameter array the policy engine evaluates. There is no initialize step (the contract exposes only the pure extract / type_and_version views), so the sequence runs skip layers 1-2 only. Wiring the extractor to a policy engine (set_extractor) is an operator action on the engine, not deployment tooling (EVM parity).

type DeployAdvancedPoolHooksInput

type DeployAdvancedPoolHooksInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Allowlist         []string               `json:"allowlist,omitempty"`
	ThresholdAmount   *big.Int               `json:"thresholdAmount,omitempty"` // nil = 0: the hook applies to every amount
	AuthorizedCallers []string               `json:"authorizedCallers,omitempty"`
	PolicyEngine      *string                `json:"policyEngine,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployAdvancedPoolHooksInput deploys and initializes the Advanced Pool Hooks contract — the per-token, issuer-owned hooks contract through which a token issuer / pool owner requires the CCV set for their token's transfers. The Qualifier identifies the instance on chains with more than one hooks contract (typically the token symbol); ThresholdAmount defaults to 0 (the hook applies to every amount) and PolicyEngine nil (policy checks dormant, EVM address(0)).

type DeployBnmTokenContractInput

type DeployBnmTokenContractInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Name              string                 `json:"name,omitempty"`
	Symbol            string                 `json:"symbol,omitempty"`
	Decimals          uint32                 `json:"decimals,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployBnmTokenContractInput deploys and initializes the BnM test token — the custom (non-SAC) Soroban token implementing the full token::StellarAssetInterface that burn-mint token pools mint/burn. Name/Symbol/Decimals default to "CCIP BnM" / "BnM" / 7. The deployer is the initial admin (and owner); the set_admin mint-authority handoff to the pool and the TokenAdminRegistry registration are operator steps (see RunDeployBnmToken for the one-shot onboarding). No initial supply is minted.

type DeployBnmTokenInput

type DeployBnmTokenInput struct {
	TarContractID string `json:"tar_contract_id"`
	Name          string `json:"name"`
	Symbol        string `json:"symbol"`
	Decimals      uint32 `json:"decimals"`
}

DeployBnmTokenInput configures a BnM token + burn-mint pool onboarding. Name/Symbol/Decimals default to "CCIP BnM" / "BnM" / 7 (Stellar SAC convention) when left zero. TarContractID is the already-deployed TokenAdminRegistry that will link the BnM token to its pool.

type DeployBnmTokenOutput

type DeployBnmTokenOutput struct {
	BnmTokenID string `json:"bnm_token_id"`
	PoolID     string `json:"pool_id"`
}

DeployBnmTokenOutput is the result of a BnM onboarding: the deployed BnM token contract ID and the burn-mint pool wired to it.

func RunDeployBnmToken

RunDeployBnmToken onboards a CCIP BnM test token as a remotely-issued burn-mint token: it deploys the custom BnM Soroban token (implementing the full `token::StellarAssetInterface`), hands mint authority to a burn-mint token pool via `set_admin`, initializes the pool against the core CCIP stack (Router / RampRegistry / RMN proxy already on the host), and registers the token↔pool link in the TokenAdminRegistry. No initial supply is minted — supply tracks bridge flow (mint on inbound, burn on outbound).

This mirrors the canonical lock-release onboarding (`deploySiloedLockReleaseTestTokenPool`) but for the burn-mint model: there is no lock-box / approve / deposit (a burn-mint pool holds only accrued fees), and the burn-mint-specific step is the `set_admin` mint-authority handoff (the deployer, initial BnM admin, transfers admin to the pool so the pool can mint on inbound messages).

Prerequisites on the host: Router, RampRegistry, RMN proxy, and the TokenAdminRegistry (TarContractID) must already be deployed (run RunStellarCCIPFullDeploy first). Per-remote-chain pool config (apply_chain_updates) is NOT applied here — it is added at lane-configuration time, matching the lock-release onboarding.

deps is the Stellar deploy/invoke surface (as in RunStellarCCIPFullDeploy) so the sequence is unit-testable with the operationstest recording harness without constructing a real framework Deployer; in production it is `stellardeps.FromDeployer(host.Deployer())`.

type DeployBurnMintPoolInput

type DeployBurnMintPoolInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Token             string                 `json:"token"`
	TokenDecimals     uint32                 `json:"tokenDecimals"`
	Router            string                 `json:"router"`
	RampRegistry      string                 `json:"rampRegistry"`
	RmnProxy          string                 `json:"rmnProxy"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployBurnMintPoolInput deploys and initializes a burn-mint token pool against the already-deployed core stack. The Qualifier identifies the pool instance on chains with more than one pool (typically the token symbol). Token/TokenDecimals come from config (the pool takes any custom token — BnM, LINK, or an issuer token); the Router/RampRegistry/RMN Proxy strkeys are resolved from the environment datastore by the changeset. The set_admin mint-authority handoff and the TokenAdminRegistry registration are operator steps (see RunDeployBnmToken for the one-shot onboarding); per-remote-chain pool config (apply_chain_updates) is applied at lane-configuration time.

type DeployCCIPReceiverInput

type DeployCCIPReceiverInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Router            string                 `json:"router"`
	Qualifier         string                 `json:"qualifier,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployCCIPReceiverInput deploys and initializes the ccip_receiver_example contract (the 12th component; devenv needs it). Router is a required input. Enabling remote chains is a config op and stays with the orchestrator. Qualifier deploys an additional receiver instance recorded under that qualifier (empty keeps the original instance); the deploy salt and idempotency key are suffixed with it so each instance lands at its own deterministic contract ID.

type DeployChainContractsOutput

type DeployChainContractsOutput = ccvadapters.DeployChainContractsOutput

DeployChainContractsOutput wraps seq_core.OnChainOutput with additional adapter-specific fields. This type alias ensures compatibility with ccvadapters.DeployChainContractsOutput.

type DeployCommitteeVerifierInput

type DeployCommitteeVerifierInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier,omitempty"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	StorageLocations  [][]byte               `json:"storageLocations"`
	RmnProxy          string                 `json:"rmnProxy"`
	AllowlistAdmin    string                 `json:"allowlistAdmin,omitempty"`
	FeeAggregator     string                 `json:"feeAggregator,omitempty"`
	VersionTag        [4]byte                `json:"versionTag"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployCommitteeVerifierInput deploys and initializes the CommitteeVerifier contract. StorageLocations is a required input (G4): the deploy never invents one. AllowlistAdmin and FeeAggregator default to the owner, VersionTag to the default committee-verifier tag, and RmnProxy is a required dependency. An empty Qualifier deploys the default instance; a non-empty one deploys a distinct instance (separate datastore ref, salt and op reports) for extra, non-default verifiers.

type DeployExecutorInput

type DeployExecutorInput struct {
	ChainSelector         uint64                 `json:"chainSelector"`
	Qualifier             string                 `json:"qualifier,omitempty"`
	Owner                 string                 `json:"owner,omitempty"`
	WasmPath              string                 `json:"wasmPath"`
	MaxCCVsPerMsg         uint32                 `json:"maxCCVsPerMsg,omitempty"`
	AllowedFinalityConfig uint32                 `json:"allowedFinalityConfig,omitempty"`
	CcvAllowlistEnabled   bool                   `json:"ccvAllowlistEnabled,omitempty"`
	FeeAggregator         string                 `json:"feeAggregator,omitempty"`
	ExistingAddresses     []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployExecutorInput deploys and initializes the source-side Executor contract. MaxCCVsPerMsg defaults to 10 (EVM parity: the ccv devenv deploys its executor with MaxCCVsPerMsg 10), AllowedFinalityConfig to 0, CcvAllowlistEnabled to false and FeeAggregator to the owner (the monolith's constants). The sequence records both the executor and executor-proxy rows: Soroban has no delegate proxy, so both point at the same contract. An empty Qualifier deploys the default instance; a non-empty one deploys a distinct instance (separate datastore refs, salt and op reports) for extra executor pools (e.g. the tcapi custom executor).

type DeployFeeQuoterInput

type DeployFeeQuoterInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	FeeToken          string                 `json:"feeToken"`
	MaxFeeJuelsPerMsg *big.Int               `json:"maxFeeJuelsPerMsg,omitempty"`
	AuthorizedCallers []string               `json:"authorizedCallers,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployFeeQuoterInput deploys and initializes the FeeQuoter contract. FeeToken is the strkey of the SAC used for fee payments, a required input (G9): the deploy never derives it. MaxFeeJuelsPerMsg defaults to 1e18 and AuthorizedCallers to [owner] (the monolith's constants).

type DeployLinkTokenInput

type DeployLinkTokenInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Name              string                 `json:"name,omitempty"`
	Symbol            string                 `json:"symbol,omitempty"`
	Decimals          uint32                 `json:"decimals,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployLinkTokenInput deploys and initializes the LINK token — the custom (non-SAC) Soroban LINK token used as the CCIP fee token. Name/Symbol/Decimals default to "ChainLink Token" / "LINK" / 7 (the deliberate Stellar divergence from EVM's 18 decimals). The deployer is the initial admin (and owner); the set_admin mint-authority handoff to the pool and the TokenAdminRegistry registration are operator steps (see RunDeployBnmToken for the onboarding order). No initial supply is minted — mint authority moves to the pool via set_admin, and the pre-handoff window is the only operator minting window.

type DeployLockReleasePoolInput

type DeployLockReleasePoolInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Token             string                 `json:"token"`
	TokenDecimals     uint32                 `json:"tokenDecimals"`
	LockBox           string                 `json:"lockBox"`
	Router            string                 `json:"router"`
	RampRegistry      string                 `json:"rampRegistry"`
	RmnProxy          string                 `json:"rmnProxy"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployLockReleasePoolInput deploys and initializes the canonical (non-siloed) lock-release token pool. The Qualifier identifies the pool instance on chains with more than one pool (typically the token symbol). Token / TokenDecimals / LockBox come from config; the Router/RampRegistry/RMN Proxy strkeys are resolved from the environment datastore by the changeset. The LockBox must be an initialized TokenLockBox for the same token (the pool validates token support on-chain).

type DeployOffRampInput

type DeployOffRampInput struct {
	ChainSelector      uint64                 `json:"chainSelector"`
	Owner              string                 `json:"owner,omitempty"`
	WasmPath           string                 `json:"wasmPath"`
	RmnProxy           string                 `json:"rmnProxy"`
	TokenAdminRegistry string                 `json:"tokenAdminRegistry"`
	ExistingAddresses  []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployOffRampInput deploys and initializes the OffRamp contract. RmnProxy and TokenAdminRegistry are the dependency strkeys, required inputs.

type DeployOnRampInput

type DeployOnRampInput struct {
	ChainSelector         uint64                 `json:"chainSelector"`
	Owner                 string                 `json:"owner,omitempty"`
	WasmPath              string                 `json:"wasmPath"`
	TokenAdminRegistry    string                 `json:"tokenAdminRegistry"`
	RmnProxy              string                 `json:"rmnProxy"`
	FeeQuoter             string                 `json:"feeQuoter"`
	MaxUsdCentsPerMessage uint32                 `json:"maxUsdCentsPerMessage,omitempty"`
	FeeAggregator         string                 `json:"feeAggregator,omitempty"`
	ExistingAddresses     []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployOnRampInput deploys and initializes the OnRamp contract in one step. TokenAdminRegistry, RmnProxy and FeeQuoter are the dependency strkeys, required inputs. MaxUsdCentsPerMessage defaults to 500000 ($5000) and FeeAggregator to the owner (the monolith's devenv values).

type DeployRMNProxyInput

type DeployRMNProxyInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	RmnRemote         string                 `json:"rmnRemote"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployRMNProxyInput deploys and initializes the RMN Proxy contract. RmnRemote is the RMN Remote contract strkey this proxy points at, a required input.

type DeployRMNRemoteInput

type DeployRMNRemoteInput struct {
	ChainSelector      uint64                 `json:"chainSelector"`
	Owner              string                 `json:"owner,omitempty"`
	WasmPath           string                 `json:"wasmPath"`
	CurseAdmins        []string               `json:"curseAdmins,omitempty"`
	EnableFastCurse    bool                   `json:"enableFastCurse,omitempty"`
	FastCurseQualifier string                 `json:"fastCurseQualifier,omitempty"`
	ExistingAddresses  []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployRMNRemoteInput deploys and initializes the RMN Remote contract. Owner defaults to the deployer address. CurseAdmins default to none; EnableFastCurse prepends the fast-curse timelock resolved from ExistingAddresses (qualifier FastCurseQualifier, default the Ultra Fast Curse qualifier), mirroring the EVM deploy.

type DeployRampRegistryInput

type DeployRampRegistryInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployRampRegistryInput deploys and initializes the RampRegistry contract. The ramp map updates are config ops and stay with the orchestrator.

type DeployRouterInput

type DeployRouterInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	RmnProxy          string                 `json:"rmnProxy"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployRouterInput deploys and initializes the Router contract. RmnProxy is a required input.

type DeploySiloedLockReleasePoolInput

type DeploySiloedLockReleasePoolInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Token             string                 `json:"token"`
	TokenDecimals     uint32                 `json:"tokenDecimals"`
	Router            string                 `json:"router"`
	RampRegistry      string                 `json:"rampRegistry"`
	RmnProxy          string                 `json:"rmnProxy"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeploySiloedLockReleasePoolInput deploys and initializes the siloed lock-release token pool — the lock-release variant whose per-remote-chain lock boxes are configured post-deploy (there is no immutable lock box at initialize). The Qualifier identifies the pool instance on chains with more than one pool (typically the token symbol). Token/TokenDecimals come from config; the Router/RampRegistry/RMN Proxy strkeys are resolved from the environment datastore by the changeset. Lock-box wiring (configure_lock_boxes) and per-remote-chain pool config are lane-configuration-time operator steps.

type DeployStellarCCIPInnerInput

type DeployStellarCCIPInnerInput struct {
	ChainSelector     uint64
	AllSelectors      []uint64
	ExistingAddresses []datastore.AddressRef
	// CurseAdmins seeds the RMN Remote curse-admin list at deploy time (strkeys).
	// Zero value keeps the previous behavior: no curse admins.
	CurseAdmins []string
	// EnableFastCurse prepends the fast-curse RBACTimelock (resolved by
	// FastCurseQualifier, default the UltraFastCurse qualifier) to CurseAdmins,
	// mirroring EVM where the Ultra Fast Curse timelock is always the curse admin
	// on a newly deployed RMN. Errors if that timelock is not deployed.
	EnableFastCurse bool
	// FastCurseQualifier overrides the qualifier used to resolve the fast-curse
	// timelock when EnableFastCurse is set. Defaults to the UltraFastCurse qualifier.
	FastCurseQualifier string
	// GovernanceQualifier is carried for downstream activation wiring; the deploy
	// itself does not use it. Defaults to the RMNMCMS qualifier.
	GovernanceQualifier string
}

DeployStellarCCIPInnerInput carries per-chain inputs for the Stellar full-stack deploy sequence. AllSelectors must list every chain selector in the environment (from BlockChains).

type DeployStellarCREForwarderInput

type DeployStellarCREForwarderInput struct {
	ChainSelector     uint64                 `json:"chain_selector"`
	Qualifier         *string                `json:"qualifier,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existing_addresses,omitempty"`
	// InitialDONConfig and Transmitters are optional deployer-signed bootstrap applied only on a
	// fresh deploy, before ownership handoff. Post-handoff changes go through MCMS/timelock.
	InitialDONConfig *CREForwarderDONConfig `json:"initial_don_config,omitempty"`
	Transmitters     []string               `json:"transmitters,omitempty"`
}

DeployStellarCREForwarderInput drives an idempotent CRE forwarder deploy on one chain.

type DeployTokenAdminRegistryInput

type DeployTokenAdminRegistryInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployTokenAdminRegistryInput deploys and initializes the TokenAdminRegistry contract. No params beyond owner and dependencies.

type DeployTokenLockBoxInput

type DeployTokenLockBoxInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	Token             string                 `json:"token"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployTokenLockBoxInput deploys and initializes a TokenLockBox — the box the canonical lock-release pool deposits user tokens into. The Qualifier identifies the box instance on chains with more than one (typically the token symbol, matching the pool it serves). Token comes from config; a lock-release pool takes the box strkey in its own config.

type DeployVVRInput

type DeployVVRInput struct {
	ChainSelector     uint64                 `json:"chainSelector"`
	Qualifier         string                 `json:"qualifier,omitempty"`
	Owner             string                 `json:"owner,omitempty"`
	WasmPath          string                 `json:"wasmPath"`
	FeeAggregator     string                 `json:"feeAggregator,omitempty"`
	ExistingAddresses []datastore.AddressRef `json:"existingAddresses,omitempty"`
}

DeployVVRInput deploys and initializes the VersionedVerifierResolver contract. FeeAggregator defaults to the owner (the monolith's devenv value). An empty Qualifier deploys the default instance; a non-empty one deploys a distinct instance (separate datastore ref, salt and op reports) for extra, non-default committees.

type InstanceStateReader

type InstanceStateReader interface {
	ContractInstanceState(ctx context.Context, contractID string) (bool, xdr.Hash, error)
}

InstanceStateReader reports whether a Soroban contract instance exists on chain and which WASM hash it runs. Satisfied by *deployment.Deployer.

type StellarActivateRMNInput

type StellarActivateRMNInput struct {
	ChainSelector uint64
	// ExistingAddresses carries the chain's recorded refs. The RMN Remote ref is
	// resolved from it (stored hex, converted to the strkey form the ownership
	// helpers require); the governance and fast-curse RBACTimelocks are resolved
	// by qualifier.
	ExistingAddresses []datastore.AddressRef
	// RMNRemoteRef optionally overrides the RMN Remote ref resolved from
	// ExistingAddresses. Address must be the contract strkey.
	RMNRemoteRef *datastore.AddressRef
	// GovernanceQualifier names the MCMS stack that will own the RMN after
	// activation. Defaults to the RMNMCMS qualifier.
	GovernanceQualifier string
	// FastCurseQualifier names the MCMS stack granted curse-admin. Defaults to
	// the UltraFastCurse qualifier.
	FastCurseQualifier string
	// ExtraCurseAdmins are granted curse-admin alongside the fast-curse timelock.
	// Addresses must be strkeys.
	ExtraCurseAdmins []string
	// SubjectsToMigrate are cursed directly while the deployer still owns the RMN
	// (idempotent on chain). Only valid pre-transfer: a non-empty list errors once
	// ownership has moved off the deployer.
	SubjectsToMigrate [][16]byte
	// Description labels emitted MCMS proposals.
	Description string
}

StellarActivateRMNInput configures activation of an existing RMN Remote: grant curse-admin to the fast-curse timelock (plus extras), migrate curse subjects while the deployer still owns, and transfer ownership to the governance (RMNMCMS) timelock. Mirrors EVM DeployAndActivateRMN: the UltraFastCurse timelock is granted curse-admin; the RMNMCMS timelock's curse authority comes from ownership and it is never listed in get_curse_admins.

type StellarApplyDestChainConfigInput

type StellarApplyDestChainConfigInput struct {
	fees.ApplyDestChainConfigSequenceInput
	FQContractID string
}

StellarApplyDestChainConfigInput is the sequence input for applying dest chain configs.

type StellarCCIPDeploySession

type StellarCCIPDeploySession struct {
	// contains filtered or unexported fields
}

StellarCCIPDeploySession runs the full Stellar CCIP Soroban deploy via DeployStellarCCIPContracts.

func NewStellarCCIPDeploySession

func NewStellarCCIPDeploySession(
	ctx context.Context,
	opBundle cldfops.Bundle,
	host stellarccip.CCIPDevenvHost,
	allSelectors []uint64,
	selector uint64,
	topology *ccvdeployment.EnvironmentTopology,
	existingAddresses []datastore.AddressRef,
) (*StellarCCIPDeploySession, error)

NewStellarCCIPDeploySession validates inputs for a Stellar CCIP deploy run. opBundle must be a valid CLDF bundle (e.g. the parent changeset bundle from chainlink-ccv DeployContractsForSelector, or cldfops.NewBundle for standalone tests); all Soroban operations run on this bundle.

func (*StellarCCIPDeploySession) RunAllPhasesAndSeal

func (s *StellarCCIPDeploySession) RunAllPhasesAndSeal() (datastore.DataStore, error)

RunAllPhasesAndSeal deploys and configures the full stack and returns a sealed datastore view.

type StellarCurseInput

type StellarCurseInput struct {
	api.CurseInput
	// RMNContractID is the RMN Remote contract the curse targets (strkey).
	RMNContractID string
	// Owner is the RMN Remote's current owner (the owner is implicitly
	// curse-authorized even though it is never listed in get_curse_admins).
	Owner string
	// CurseAdmins is the raw stored curse-admin list.
	CurseAdmins []string
	// Timelocks maps MCMS qualifier → RBACTimelock contract ID deployed for that
	// qualifier on the chain. An absent qualifier means that stack is not deployed.
	Timelocks map[string]string
}

StellarCurseInput extends the shared CurseInput with the resolved RMN Remote contract ID and the on-chain facts needed to route the curse to the timelock that will execute it. All addresses are strkeys.

type StellarSetCommitteeVerifierFinalityInput

type StellarSetCommitteeVerifierFinalityInput struct {
	ChainSelector uint64
	// CommitteeVerifier is the verifier ref; Address must be the contract strkey.
	CommitteeVerifier datastore.AddressRef
	// Timelock is the RBACTimelock strkey allowed to own the verifier.
	Timelock        string
	AllowedFinality finality.Config
}

StellarSetCommitteeVerifierFinalityInput sets the allowed finality of one Committee Verifier.

type StellarSetTokenTransferFeeInput

type StellarSetTokenTransferFeeInput struct {
	fees.SetTokenTransferFeeSequenceInput
	FQContractID string
}

StellarSetTokenTransferFeeInput is the sequence input for applying per-token transfer fees.

type StellarTransferOwnershipInput

type StellarTransferOwnershipInput struct {
	deploy.TransferOwnershipPerChainInput
	GovernanceAddr string
}

StellarTransferOwnershipInput extends the shared input with the RBAC timelock (governance) address from the datastore — same role as EVM OpTransferOwnershipInput.TimelockAddress.

type StellarUpgradeContractsInput

type StellarUpgradeContractsInput struct {
	ChainSelector  uint64                 `json:"chain_selector"`
	ContractRef    []datastore.AddressRef `json:"contract_ref"`
	GovernanceAddr string                 `json:"governance_addr"`
}

StellarUpgradeContractsInput drives an in-place upgrade of one or more deployed Soroban CCIP contracts on a single chain. Each ref is upgraded independently: the freshly-compiled release WASM for its contract type is uploaded, the contract's current owner is read, and the upgrade is routed either as a direct on-chain call (owner == deployer EOA) or as an MCMS proposal transaction (owner == governance/timelock) — the same two-path model as StellarTransferOwnershipViaMCMS.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL