diff --git a/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go b/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go new file mode 100644 index 000000000..f1c499b96 --- /dev/null +++ b/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go @@ -0,0 +1,3154 @@ +package burnminttokenpool + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ccipcodec "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipcodec" + extensionapi "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/extensionapi" + chainlinkapi "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/chainlink/chainlinkapi" + api "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/mcms/api" + splice_api_token_holding_v1 "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_holding_v1" + splice_api_token_metadata_v1 "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_metadata_v1" + "github.com/smartcontractkit/go-daml/pkg/bind" + "github.com/smartcontractkit/go-daml/pkg/codec" + "github.com/smartcontractkit/go-daml/pkg/model" + "github.com/smartcontractkit/go-daml/pkg/types" +) + +var ( + _ = fmt.Sprintf + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = model.Command{} + _ bind.BoundTemplate +) + +const ( + PackageName = "ccip-registry-burn-mint-token-pool-v2" + PackageID = "35a93dc2c0d2b65ac5aeffc1ed4f45d72a649adce56919e0354ba75bc88fc170" + SDKVersion = "3.4.11" +) + +type Template interface { + CreateCommand() *model.CreateCommand + GetTemplateID() string +} + +const ( + BurnMintFactoryExtraArgsMetaValuesContextKey = types.TEXT("burn-mint-factory-extra-args-meta-values") + BurnMintFactoryExtraArgsContextValuesContextKey = types.TEXT("burn-mint-factory-extra-args-values") + BurnMintFactoryContextKey = types.TEXT("burn-mint-factory") + BpsDenominator = types.NUMERIC("10000.") +) + +func argsToMap(args any) map[string]any { + if args == nil { + return map[string]any{} + } + + if m, ok := args.(map[string]any); ok { + return m + } + + type mapper interface { + ToMap() map[string]any + } + if mapper, ok := args.(mapper); ok { + return mapper.ToMap() + } + + return map[string]any{"args": args} +} + +// AddPoolReceiveContextContractValue is a Record type +type AddPoolReceiveContextContractValue struct { + ContextKey types.TEXT `json:"contextKey"` + ReferredContract types.CONTRACT_ID `json:"referredContract"` +} + +// ToMap converts AddPoolReceiveContextContractValue to a map for DAML arguments +func (t AddPoolReceiveContextContractValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["referredContract"] = model.NestedToDAMLValue(t.ReferredContract) + + return m +} + +func (t AddPoolReceiveContextContractValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextContractValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextContractValue to hex string (Canton MCMS format) +func (t AddPoolReceiveContextContractValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextContractValue from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextContractValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextContractValueParams is a Record type +type AddPoolReceiveContextContractValueParams struct { + ContextKey types.TEXT `json:"contextKey"` + ReferentInstanceAddress chainlinkapi.RawInstanceAddress `json:"referentInstanceAddress"` +} + +// ToMap converts AddPoolReceiveContextContractValueParams to a map for DAML arguments +func (t AddPoolReceiveContextContractValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["referentInstanceAddress"] = model.NestedToDAMLValue(t.ReferentInstanceAddress) + + return m +} + +func (t AddPoolReceiveContextContractValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextContractValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextContractValueParams to hex string (Canton MCMS format) +func (t AddPoolReceiveContextContractValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextContractValueParams from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextContractValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextNonContractValue is a Record type +type AddPoolReceiveContextNonContractValue struct { + ContextKey types.TEXT `json:"contextKey"` + Value splice_api_token_metadata_v1.AnyValue `json:"value"` +} + +// ToMap converts AddPoolReceiveContextNonContractValue to a map for DAML arguments +func (t AddPoolReceiveContextNonContractValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["value"] = model.NestedToDAMLValue(t.Value) + + return m +} + +func (t AddPoolReceiveContextNonContractValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextNonContractValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextNonContractValue to hex string (Canton MCMS format) +func (t AddPoolReceiveContextNonContractValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextNonContractValue from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextNonContractValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextNonContractValueParams is a Record type +type AddPoolReceiveContextNonContractValueParams struct { + ContextKey types.TEXT `json:"contextKey"` + ValuePayload types.TEXT `json:"valuePayload"` +} + +// ToMap converts AddPoolReceiveContextNonContractValueParams to a map for DAML arguments +func (t AddPoolReceiveContextNonContractValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["valuePayload"] = string(t.ValuePayload) + + return m +} + +func (t AddPoolReceiveContextNonContractValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextNonContractValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextNonContractValueParams to hex string (Canton MCMS format) +func (t AddPoolReceiveContextNonContractValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextNonContractValueParams from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextNonContractValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyChainUpdates is a Record type +type ApplyChainUpdates struct { + RemoteChainSelectorsToRemove []types.NUMERIC `json:"remoteChainSelectorsToRemove"` + ChainsToAdd []ChainUpdate `json:"chainsToAdd"` +} + +// ToMap converts ApplyChainUpdates to a map for DAML arguments +func (t ApplyChainUpdates) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelectorsToRemove"] = func() []any { + res := make([]any, 0, len(t.RemoteChainSelectorsToRemove)) + for _, e := range t.RemoteChainSelectorsToRemove { + res = append(res, e) + } + return res + }() + + m["chainsToAdd"] = func() []any { + res := make([]any, 0, len(t.ChainsToAdd)) + for _, e := range t.ChainsToAdd { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t ApplyChainUpdates) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyChainUpdates) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyChainUpdates to hex string (Canton MCMS format) +func (t ApplyChainUpdates) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyChainUpdates from hex string (Canton MCMS format) +func (t *ApplyChainUpdates) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyChainUpdatesParams is a Record type +type ApplyChainUpdatesParams struct { + RemoteChainSelectorsToRemove []types.NUMERIC `json:"remoteChainSelectorsToRemove"` + ChainsToAdd []ChainUpdate `json:"chainsToAdd"` +} + +// ToMap converts ApplyChainUpdatesParams to a map for DAML arguments +func (t ApplyChainUpdatesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelectorsToRemove"] = func() []any { + res := make([]any, 0, len(t.RemoteChainSelectorsToRemove)) + for _, e := range t.RemoteChainSelectorsToRemove { + res = append(res, e) + } + return res + }() + + m["chainsToAdd"] = func() []any { + res := make([]any, 0, len(t.ChainsToAdd)) + for _, e := range t.ChainsToAdd { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t ApplyChainUpdatesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyChainUpdatesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyChainUpdatesParams to hex string (Canton MCMS format) +func (t ApplyChainUpdatesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyChainUpdatesParams from hex string (Canton MCMS format) +func (t *ApplyChainUpdatesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyTokenTransferFeeConfigUpdates is a Record type +type ApplyTokenTransferFeeConfigUpdates struct { + TokenTransferFeeConfigArgs []TokenTransferFeeConfigArgs `json:"tokenTransferFeeConfigArgs"` + DisableTokenTransferFeeConfigArgs []types.NUMERIC `json:"disableTokenTransferFeeConfigArgs"` +} + +// ToMap converts ApplyTokenTransferFeeConfigUpdates to a map for DAML arguments +func (t ApplyTokenTransferFeeConfigUpdates) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.TokenTransferFeeConfigArgs)) + for _, e := range t.TokenTransferFeeConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["disableTokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.DisableTokenTransferFeeConfigArgs)) + for _, e := range t.DisableTokenTransferFeeConfigArgs { + res = append(res, e) + } + return res + }() + + return m +} + +func (t ApplyTokenTransferFeeConfigUpdates) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyTokenTransferFeeConfigUpdates) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyTokenTransferFeeConfigUpdates to hex string (Canton MCMS format) +func (t ApplyTokenTransferFeeConfigUpdates) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyTokenTransferFeeConfigUpdates from hex string (Canton MCMS format) +func (t *ApplyTokenTransferFeeConfigUpdates) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyTokenTransferFeeConfigUpdatesParams is a Record type +type ApplyTokenTransferFeeConfigUpdatesParams struct { + TokenTransferFeeConfigArgs []TokenTransferFeeConfigArgs `json:"tokenTransferFeeConfigArgs"` + DisableTokenTransferFeeConfigArgs []types.NUMERIC `json:"disableTokenTransferFeeConfigArgs"` +} + +// ToMap converts ApplyTokenTransferFeeConfigUpdatesParams to a map for DAML arguments +func (t ApplyTokenTransferFeeConfigUpdatesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.TokenTransferFeeConfigArgs)) + for _, e := range t.TokenTransferFeeConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["disableTokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.DisableTokenTransferFeeConfigArgs)) + for _, e := range t.DisableTokenTransferFeeConfigArgs { + res = append(res, e) + } + return res + }() + + return m +} + +func (t ApplyTokenTransferFeeConfigUpdatesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyTokenTransferFeeConfigUpdatesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyTokenTransferFeeConfigUpdatesParams to hex string (Canton MCMS format) +func (t ApplyTokenTransferFeeConfigUpdatesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyTokenTransferFeeConfigUpdatesParams from hex string (Canton MCMS format) +func (t *ApplyTokenTransferFeeConfigUpdatesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// BurnMintTokenPool is a Template type +type BurnMintTokenPool struct { + InstanceId types.TEXT `json:"instanceId"` + PoolOwner types.PARTY `json:"poolOwner"` + CcipOwner types.PARTY `json:"ccipOwner"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Decimals types.INT64 `json:"decimals"` + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` + Observers []types.PARTY `json:"observers"` + RemoteChainConfigs map[types.NUMERIC]RemoteChainConfig `json:"remoteChainConfigs"` + TokenTransferFeeConfigs map[types.NUMERIC]TokenTransferFeeConfig2 `json:"tokenTransferFeeConfigs"` + PoolReceiveContext splice_api_token_metadata_v1.ChoiceContext `json:"poolReceiveContext"` + TransferTimeout TransferTimeout `json:"transferTimeout"` + Deps BurnMintTokenPoolDeps `json:"deps"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t BurnMintTokenPool) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t BurnMintTokenPool) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t BurnMintTokenPool) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["ccipOwner"] = t.CcipOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instrumentId"] = model.NestedToDAMLValue(t.InstrumentId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["decimals"] = int64(t.Decimals) + + if t.RateLimitAdmin != nil { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["remoteChainConfigs"] = func() any { + if t.RemoteChainConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.RemoteChainConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["tokenTransferFeeConfigs"] = func() any { + if t.TokenTransferFeeConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.TokenTransferFeeConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolReceiveContext"] = model.NestedToDAMLValue(t.PoolReceiveContext) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["deps"] = model.NestedToDAMLValue(t.Deps) + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t BurnMintTokenPool) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["ccipOwner"] = t.CcipOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instrumentId"] = model.NestedToDAMLValue(t.InstrumentId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["decimals"] = int64(t.Decimals) + + if t.RateLimitAdmin != nil { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["remoteChainConfigs"] = func() any { + if t.RemoteChainConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.RemoteChainConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["tokenTransferFeeConfigs"] = func() any { + if t.TokenTransferFeeConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.TokenTransferFeeConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolReceiveContext"] = model.NestedToDAMLValue(t.PoolReceiveContext) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["deps"] = model.NestedToDAMLValue(t.Deps) + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t BurnMintTokenPool) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *BurnMintTokenPool) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes BurnMintTokenPool to hex string (Canton MCMS format) +func (t BurnMintTokenPool) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes BurnMintTokenPool from hex string (Canton MCMS format) +func (t *BurnMintTokenPool) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for BurnMintTokenPool + +// LockOrBurn exercises the LockOrBurn choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) LockOrBurn(contractID string, args LockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "LockOrBurn", + Arguments: argsToMap(args), + } +} + +// LockOrBurnWithPackageID exercises the LockOrBurn choice using the provided package ID instead of package name +func (t BurnMintTokenPool) LockOrBurnWithPackageID(contractID string, packageID string, args LockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "LockOrBurn", + Arguments: argsToMap(args), + } +} + +// ReleaseFromTicket exercises the ReleaseFromTicket choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) ReleaseFromTicket(contractID string, args ReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// ReleaseFromTicketWithPackageID exercises the ReleaseFromTicket choice using the provided package ID instead of package name +func (t BurnMintTokenPool) ReleaseFromTicketWithPackageID(contractID string, packageID string, args ReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// SetRateLimitConfig exercises the SetRateLimitConfig choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) SetRateLimitConfig(contractID string, args SetRateLimitConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetRateLimitConfig", + Arguments: argsToMap(args), + } +} + +// SetRateLimitConfigWithPackageID exercises the SetRateLimitConfig choice using the provided package ID instead of package name +func (t BurnMintTokenPool) SetRateLimitConfigWithPackageID(contractID string, packageID string, args SetRateLimitConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetRateLimitConfig", + Arguments: argsToMap(args), + } +} + +// ApplyTokenTransferFeeConfigUpdates exercises the ApplyTokenTransferFeeConfigUpdates choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) ApplyTokenTransferFeeConfigUpdates(contractID string, args ApplyTokenTransferFeeConfigUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ApplyTokenTransferFeeConfigUpdates", + Arguments: argsToMap(args), + } +} + +// ApplyTokenTransferFeeConfigUpdatesWithPackageID exercises the ApplyTokenTransferFeeConfigUpdates choice using the provided package ID instead of package name +func (t BurnMintTokenPool) ApplyTokenTransferFeeConfigUpdatesWithPackageID(contractID string, packageID string, args ApplyTokenTransferFeeConfigUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ApplyTokenTransferFeeConfigUpdates", + Arguments: argsToMap(args), + } +} + +// SetRateLimiterReferences exercises the SetRateLimiterReferences choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) SetRateLimiterReferences(contractID string, args SetRateLimiterReferences) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetRateLimiterReferences", + Arguments: argsToMap(args), + } +} + +// SetRateLimiterReferencesWithPackageID exercises the SetRateLimiterReferences choice using the provided package ID instead of package name +func (t BurnMintTokenPool) SetRateLimiterReferencesWithPackageID(contractID string, packageID string, args SetRateLimiterReferences) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetRateLimiterReferences", + Arguments: argsToMap(args), + } +} + +// ApplyChainUpdates exercises the ApplyChainUpdates choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) ApplyChainUpdates(contractID string, args ApplyChainUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ApplyChainUpdates", + Arguments: argsToMap(args), + } +} + +// ApplyChainUpdatesWithPackageID exercises the ApplyChainUpdates choice using the provided package ID instead of package name +func (t BurnMintTokenPool) ApplyChainUpdatesWithPackageID(contractID string, packageID string, args ApplyChainUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ApplyChainUpdates", + Arguments: argsToMap(args), + } +} + +// Initialize exercises the Initialize choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) Initialize(contractID string, args Initialize) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "Initialize", + Arguments: argsToMap(args), + } +} + +// InitializeWithPackageID exercises the Initialize choice using the provided package ID instead of package name +func (t BurnMintTokenPool) InitializeWithPackageID(contractID string, packageID string, args Initialize) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "Initialize", + Arguments: argsToMap(args), + } +} + +// SetDynamicConfig exercises the SetDynamicConfig choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) SetDynamicConfig(contractID string, args SetDynamicConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetDynamicConfig", + Arguments: argsToMap(args), + } +} + +// SetDynamicConfigWithPackageID exercises the SetDynamicConfig choice using the provided package ID instead of package name +func (t BurnMintTokenPool) SetDynamicConfigWithPackageID(contractID string, packageID string, args SetDynamicConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetDynamicConfig", + Arguments: argsToMap(args), + } +} + +// SetObservers exercises the SetObservers choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) SetObservers(contractID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// SetObserversWithPackageID exercises the SetObservers choice using the provided package ID instead of package name +func (t BurnMintTokenPool) SetObserversWithPackageID(contractID string, packageID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextNonContractValue exercises the AddPoolReceiveContextNonContractValue choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) AddPoolReceiveContextNonContractValue(contractID string, args AddPoolReceiveContextNonContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextNonContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextNonContractValueWithPackageID exercises the AddPoolReceiveContextNonContractValue choice using the provided package ID instead of package name +func (t BurnMintTokenPool) AddPoolReceiveContextNonContractValueWithPackageID(contractID string, packageID string, args AddPoolReceiveContextNonContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextNonContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextContractValue exercises the AddPoolReceiveContextContractValue choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) AddPoolReceiveContextContractValue(contractID string, args AddPoolReceiveContextContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextContractValueWithPackageID exercises the AddPoolReceiveContextContractValue choice using the provided package ID instead of package name +func (t BurnMintTokenPool) AddPoolReceiveContextContractValueWithPackageID(contractID string, packageID string, args AddPoolReceiveContextContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextContractValue", + Arguments: argsToMap(args), + } +} + +// RemovePoolReceiveContextValue exercises the RemovePoolReceiveContextValue choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) RemovePoolReceiveContextValue(contractID string, args RemovePoolReceiveContextValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "RemovePoolReceiveContextValue", + Arguments: argsToMap(args), + } +} + +// RemovePoolReceiveContextValueWithPackageID exercises the RemovePoolReceiveContextValue choice using the provided package ID instead of package name +func (t BurnMintTokenPool) RemovePoolReceiveContextValueWithPackageID(contractID string, packageID string, args RemovePoolReceiveContextValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "RemovePoolReceiveContextValue", + Arguments: argsToMap(args), + } +} + +// ClearPoolReceiveContext exercises the ClearPoolReceiveContext choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) ClearPoolReceiveContext(contractID string, args ClearPoolReceiveContext) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ClearPoolReceiveContext", + Arguments: argsToMap(args), + } +} + +// ClearPoolReceiveContextWithPackageID exercises the ClearPoolReceiveContext choice using the provided package ID instead of package name +func (t BurnMintTokenPool) ClearPoolReceiveContextWithPackageID(contractID string, packageID string, args ClearPoolReceiveContext) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "ClearPoolReceiveContext", + Arguments: argsToMap(args), + } +} + +// SetTransferTimeout exercises the SetTransferTimeout choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) SetTransferTimeout(contractID string, args SetTransferTimeout) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetTransferTimeout", + Arguments: argsToMap(args), + } +} + +// SetTransferTimeoutWithPackageID exercises the SetTransferTimeout choice using the provided package ID instead of package name +func (t BurnMintTokenPool) SetTransferTimeoutWithPackageID(contractID string, packageID string, args SetTransferTimeout) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "SetTransferTimeout", + Arguments: argsToMap(args), + } +} + +// GetRequiredCCVs exercises the GetRequiredCCVs choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) GetRequiredCCVs(contractID string, args GetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// GetRequiredCCVsWithPackageID exercises the GetRequiredCCVs choice using the provided package ID instead of package name +func (t BurnMintTokenPool) GetRequiredCCVsWithPackageID(contractID string, packageID string, args GetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// VerifyInboundMessage exercises the VerifyInboundMessage choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) VerifyInboundMessage(contractID string, args VerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// VerifyInboundMessageWithPackageID exercises the VerifyInboundMessage choice using the provided package ID instead of package name +func (t BurnMintTokenPool) VerifyInboundMessageWithPackageID(contractID string, packageID string, args VerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// VerifyOutboundCCVs exercises the VerifyOutboundCCVs choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) VerifyOutboundCCVs(contractID string, args VerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// VerifyOutboundCCVsWithPackageID exercises the VerifyOutboundCCVs choice using the provided package ID instead of package name +func (t BurnMintTokenPool) VerifyOutboundCCVsWithPackageID(contractID string, packageID string, args VerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// CalculateFee exercises the CalculateFee choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) CalculateFee(contractID string, args CalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "CalculateFee", + Arguments: argsToMap(args), + } +} + +// CalculateFeeWithPackageID exercises the CalculateFee choice using the provided package ID instead of package name +func (t BurnMintTokenPool) CalculateFeeWithPackageID(contractID string, packageID string, args CalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "CalculateFee", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "TokenPool"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t BurnMintTokenPool) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "TokenPool"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// GetFee exercises the GetFee choice on this BurnMintTokenPool contract +// This method uses the package name in the template ID +func (t BurnMintTokenPool) GetFee(contractID string, args GetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "GetFee", + Arguments: argsToMap(args), + } +} + +// GetFeeWithPackageID exercises the GetFee choice using the provided package ID instead of package name +func (t BurnMintTokenPool) GetFeeWithPackageID(contractID string, packageID string, args GetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "BurnMintTokenPool"), + ContractID: contractID, + Choice: "GetFee", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypoint exercises the MCMSReceiver_Entrypoint choice on this BurnMintTokenPool contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) MCMSReceiverEntrypoint(contractID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypointWithPackageID exercises the MCMSReceiver_Entrypoint choice using the provided package ID instead of package name +func (t BurnMintTokenPool) MCMSReceiverEntrypointWithPackageID(contractID string, packageID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetRequiredCCVs exercises the TokenPool_GetRequiredCCVs choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolGetRequiredCCVs(contractID string, args extensionapi.TokenPoolGetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetRequiredCCVsWithPackageID exercises the TokenPool_GetRequiredCCVs choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolGetRequiredCCVsWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolGetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyInboundMessage exercises the TokenPool_VerifyInboundMessage choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolVerifyInboundMessage(contractID string, args extensionapi.TokenPoolVerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyInboundMessageWithPackageID exercises the TokenPool_VerifyInboundMessage choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolVerifyInboundMessageWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolVerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyOutboundCCVs exercises the TokenPool_VerifyOutboundCCVs choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolVerifyOutboundCCVs(contractID string, args extensionapi.TokenPoolVerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyOutboundCCVsWithPackageID exercises the TokenPool_VerifyOutboundCCVs choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolVerifyOutboundCCVsWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolVerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolReleaseFromTicket exercises the TokenPool_ReleaseFromTicket choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolReleaseFromTicket(contractID string, args extensionapi.TokenPoolReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// TokenPoolReleaseFromTicketWithPackageID exercises the TokenPool_ReleaseFromTicket choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolReleaseFromTicketWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// TokenPoolLockOrBurn exercises the TokenPool_LockOrBurn choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolLockOrBurn(contractID string, args extensionapi.TokenPoolLockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_LockOrBurn", + Arguments: argsToMap(args), + } +} + +// TokenPoolLockOrBurnWithPackageID exercises the TokenPool_LockOrBurn choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolLockOrBurnWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolLockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_LockOrBurn", + Arguments: argsToMap(args), + } +} + +// TokenPoolCalculateFee exercises the TokenPool_CalculateFee choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolCalculateFee(contractID string, args extensionapi.TokenPoolCalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_CalculateFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolCalculateFeeWithPackageID exercises the TokenPool_CalculateFee choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolCalculateFeeWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolCalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_CalculateFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetFee exercises the TokenPool_GetFee choice on this BurnMintTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t BurnMintTokenPool) TokenPoolGetFee(contractID string, args extensionapi.TokenPoolGetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetFeeWithPackageID exercises the TokenPool_GetFee choice using the provided package ID instead of package name +func (t BurnMintTokenPool) TokenPoolGetFeeWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolGetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.BurnMintTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetFee", + Arguments: argsToMap(args), + } +} + +// Verify interface implementations for BurnMintTokenPool + +var _ api.IMCMSReceiver = (*BurnMintTokenPool)(nil) + +var _ extensionapi.IITokenPool = (*BurnMintTokenPool)(nil) + +// BurnMintTokenPoolDeps is a Record type +type BurnMintTokenPoolDeps struct { + TokenAdminRegistry chainlinkapi.RawInstanceAddress `json:"tokenAdminRegistry"` + RmnRemote chainlinkapi.RawInstanceAddress `json:"rmnRemote"` + FeeQuoter chainlinkapi.RawInstanceAddress `json:"feeQuoter"` +} + +// ToMap converts BurnMintTokenPoolDeps to a map for DAML arguments +func (t BurnMintTokenPoolDeps) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistry"] = model.NestedToDAMLValue(t.TokenAdminRegistry) + + m["rmnRemote"] = model.NestedToDAMLValue(t.RmnRemote) + + m["feeQuoter"] = model.NestedToDAMLValue(t.FeeQuoter) + + return m +} + +func (t BurnMintTokenPoolDeps) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *BurnMintTokenPoolDeps) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes BurnMintTokenPoolDeps to hex string (Canton MCMS format) +func (t BurnMintTokenPoolDeps) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes BurnMintTokenPoolDeps from hex string (Canton MCMS format) +func (t *BurnMintTokenPoolDeps) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CalculateFee is a Record type +type CalculateFee struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + FeeQuoterCid types.CONTRACT_ID `json:"feeQuoterCid"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts CalculateFee to a map for DAML arguments +func (t CalculateFee) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["feeQuoterCid"] = model.NestedToDAMLValue(t.FeeQuoterCid) + + m["tokenInstrumentId"] = model.NestedToDAMLValue(t.TokenInstrumentId) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t CalculateFee) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CalculateFee) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CalculateFee to hex string (Canton MCMS format) +func (t CalculateFee) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CalculateFee from hex string (Canton MCMS format) +func (t *CalculateFee) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CalculateFeeMCMSParams is CalculateFee without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type CalculateFeeMCMSParams struct { + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes CalculateFeeMCMSParams to hex string for MCMS operationData. +func (t CalculateFeeMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CalculateFeeMCMSParams from hex string. +func (t *CalculateFeeMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ChainUpdate is a Record type +type ChainUpdate struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts ChainUpdate to a map for DAML arguments +func (t ChainUpdate) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t ChainUpdate) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ChainUpdate) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ChainUpdate to hex string (Canton MCMS format) +func (t ChainUpdate) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ChainUpdate from hex string (Canton MCMS format) +func (t *ChainUpdate) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ClearPoolReceiveContext is a Record type +type ClearPoolReceiveContext struct { +} + +// ToMap converts ClearPoolReceiveContext to a map for DAML arguments +func (t ClearPoolReceiveContext) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t ClearPoolReceiveContext) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ClearPoolReceiveContext) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ClearPoolReceiveContext to hex string (Canton MCMS format) +func (t ClearPoolReceiveContext) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ClearPoolReceiveContext from hex string (Canton MCMS format) +func (t *ClearPoolReceiveContext) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetFee is a Record type +type GetFee struct { + FeeQuoterCid types.CONTRACT_ID `json:"feeQuoterCid"` + DestChainSelector types.NUMERIC `json:"destChainSelector"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts GetFee to a map for DAML arguments +func (t GetFee) ToMap() map[string]any { + m := make(map[string]any) + + m["feeQuoterCid"] = model.NestedToDAMLValue(t.FeeQuoterCid) + + m["destChainSelector"] = t.DestChainSelector + + m["tokenInstrumentId"] = model.NestedToDAMLValue(t.TokenInstrumentId) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t GetFee) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *GetFee) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes GetFee to hex string (Canton MCMS format) +func (t GetFee) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetFee from hex string (Canton MCMS format) +func (t *GetFee) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetFeeMCMSParams is GetFee without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type GetFeeMCMSParams struct { + DestChainSelector types.NUMERIC `json:"destChainSelector"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes GetFeeMCMSParams to hex string for MCMS operationData. +func (t GetFeeMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetFeeMCMSParams from hex string. +func (t *GetFeeMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetRequiredCCVs is a Record type +type GetRequiredCCVs struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + SourceAmount types.TEXT `json:"sourceAmount"` + Finality ccipcodec.FinalityConfig `json:"finality"` + ExtraData types.TEXT `json:"extraData"` + Direction extensionapi.TransferDirection `json:"direction"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts GetRequiredCCVs to a map for DAML arguments +func (t GetRequiredCCVs) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["sourceAmount"] = string(t.SourceAmount) + + m["finality"] = model.NestedToDAMLValue(t.Finality) + + m["extraData"] = string(t.ExtraData) + + m["direction"] = model.NestedToDAMLValue(t.Direction) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t GetRequiredCCVs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *GetRequiredCCVs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes GetRequiredCCVs to hex string (Canton MCMS format) +func (t GetRequiredCCVs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetRequiredCCVs from hex string (Canton MCMS format) +func (t *GetRequiredCCVs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetRequiredCCVsMCMSParams is GetRequiredCCVs without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type GetRequiredCCVsMCMSParams struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + SourceAmount types.TEXT `json:"sourceAmount"` + Finality ccipcodec.FinalityConfig `json:"finality"` + ExtraData types.TEXT `json:"extraData"` + Direction extensionapi.TransferDirection `json:"direction"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes GetRequiredCCVsMCMSParams to hex string for MCMS operationData. +func (t GetRequiredCCVsMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetRequiredCCVsMCMSParams from hex string. +func (t *GetRequiredCCVsMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Initialize is a Record type +type Initialize struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + ExistingTokenConfigCid *types.CONTRACT_ID `json:"existingTokenConfigCid" hex:"optional"` + Admin types.PARTY `json:"admin"` + Lanes []LaneDeploySpec `json:"lanes"` +} + +// ToMap converts Initialize to a map for DAML arguments +func (t Initialize) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + if t.ExistingTokenConfigCid != nil { + m["existingTokenConfigCid"] = map[string]any{ + "_type": "optional", + "value": model.NestedToDAMLValue(*t.ExistingTokenConfigCid), + } + } else { + m["existingTokenConfigCid"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + m["admin"] = t.Admin.ToMap() + + m["lanes"] = func() []any { + res := make([]any, 0, len(t.Lanes)) + for _, e := range t.Lanes { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t Initialize) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *Initialize) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes Initialize to hex string (Canton MCMS format) +func (t Initialize) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes Initialize from hex string (Canton MCMS format) +func (t *Initialize) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// InitializeResult is a Record type +type InitializeResult struct { + RateLimiterCids []types.CONTRACT_ID `json:"rateLimiterCids"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` +} + +// ToMap converts InitializeResult to a map for DAML arguments +func (t InitializeResult) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimiterCids"] = func() []any { + res := make([]any, 0, len(t.RateLimiterCids)) + for _, e := range t.RateLimiterCids { + res = append(res, e) + } + return res + }() + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + return m +} + +func (t InitializeResult) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *InitializeResult) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes InitializeResult to hex string (Canton MCMS format) +func (t InitializeResult) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes InitializeResult from hex string (Canton MCMS format) +func (t *InitializeResult) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LaneDeploySpec is a Record type +type LaneDeploySpec struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + Inbound RateLimiterDeploySpec `json:"inbound"` + Outbound RateLimiterDeploySpec `json:"outbound"` + InboundCustomFinality RateLimiterDeploySpec `json:"inboundCustomFinality"` +} + +// ToMap converts LaneDeploySpec to a map for DAML arguments +func (t LaneDeploySpec) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inbound"] = model.NestedToDAMLValue(t.Inbound) + + m["outbound"] = model.NestedToDAMLValue(t.Outbound) + + m["inboundCustomFinality"] = model.NestedToDAMLValue(t.InboundCustomFinality) + + return m +} + +func (t LaneDeploySpec) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LaneDeploySpec) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LaneDeploySpec to hex string (Canton MCMS format) +func (t LaneDeploySpec) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LaneDeploySpec from hex string (Canton MCMS format) +func (t *LaneDeploySpec) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LockOrBurn is a Record type +type LockOrBurn struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts LockOrBurn to a map for DAML arguments +func (t LockOrBurn) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["rmnRemoteCid"] = model.NestedToDAMLValue(t.RmnRemoteCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["senderInputCids"] = func() []any { + res := make([]any, 0, len(t.SenderInputCids)) + for _, e := range t.SenderInputCids { + res = append(res, e) + } + return res + }() + + m["amount"] = t.Amount + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t LockOrBurn) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LockOrBurn) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LockOrBurn to hex string (Canton MCMS format) +func (t LockOrBurn) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockOrBurn from hex string (Canton MCMS format) +func (t *LockOrBurn) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LockOrBurnMCMSParams is LockOrBurn without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type LockOrBurnMCMSParams struct { + SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes LockOrBurnMCMSParams to hex string for MCMS operationData. +func (t LockOrBurnMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockOrBurnMCMSParams from hex string. +func (t *LockOrBurnMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RateLimitConfigArgs is a Record type +type RateLimitConfigArgs struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts RateLimitConfigArgs to a map for DAML arguments +func (t RateLimitConfigArgs) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t RateLimitConfigArgs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RateLimitConfigArgs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RateLimitConfigArgs to hex string (Canton MCMS format) +func (t RateLimitConfigArgs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RateLimitConfigArgs from hex string (Canton MCMS format) +func (t *RateLimitConfigArgs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RateLimiterDeploySpec is a Record type +type RateLimiterDeploySpec struct { + InstanceId types.TEXT `json:"instanceId"` + IsEnabled types.BOOL `json:"isEnabled"` + Capacity types.NUMERIC `json:"capacity"` + Rate types.NUMERIC `json:"rate"` +} + +// ToMap converts RateLimiterDeploySpec to a map for DAML arguments +func (t RateLimiterDeploySpec) ToMap() map[string]any { + m := make(map[string]any) + + m["instanceId"] = string(t.InstanceId) + + m["isEnabled"] = bool(t.IsEnabled) + + m["capacity"] = t.Capacity + + m["rate"] = t.Rate + + return m +} + +func (t RateLimiterDeploySpec) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RateLimiterDeploySpec) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RateLimiterDeploySpec to hex string (Canton MCMS format) +func (t RateLimiterDeploySpec) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RateLimiterDeploySpec from hex string (Canton MCMS format) +func (t *RateLimiterDeploySpec) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ReleaseFromTicket is a Record type +type ReleaseFromTicket struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` + TokenReceiveTicketCid types.CONTRACT_ID `json:"tokenReceiveTicketCid"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts ReleaseFromTicket to a map for DAML arguments +func (t ReleaseFromTicket) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["rmnRemoteCid"] = model.NestedToDAMLValue(t.RmnRemoteCid) + + m["tokenReceiveTicketCid"] = model.NestedToDAMLValue(t.TokenReceiveTicketCid) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t ReleaseFromTicket) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ReleaseFromTicket) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ReleaseFromTicket to hex string (Canton MCMS format) +func (t ReleaseFromTicket) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ReleaseFromTicket from hex string (Canton MCMS format) +func (t *ReleaseFromTicket) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ReleaseFromTicketMCMSParams is ReleaseFromTicket without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type ReleaseFromTicketMCMSParams struct { + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes ReleaseFromTicketMCMSParams to hex string for MCMS operationData. +func (t ReleaseFromTicketMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ReleaseFromTicketMCMSParams from hex string. +func (t *ReleaseFromTicketMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemoteChainConfig is a Record type +type RemoteChainConfig struct { + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts RemoteChainConfig to a map for DAML arguments +func (t RemoteChainConfig) ToMap() map[string]any { + m := make(map[string]any) + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t RemoteChainConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemoteChainConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemoteChainConfig to hex string (Canton MCMS format) +func (t RemoteChainConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemoteChainConfig from hex string (Canton MCMS format) +func (t *RemoteChainConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemovePoolReceiveContextValue is a Record type +type RemovePoolReceiveContextValue struct { + ContextKey types.TEXT `json:"contextKey"` +} + +// ToMap converts RemovePoolReceiveContextValue to a map for DAML arguments +func (t RemovePoolReceiveContextValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + return m +} + +func (t RemovePoolReceiveContextValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemovePoolReceiveContextValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemovePoolReceiveContextValue to hex string (Canton MCMS format) +func (t RemovePoolReceiveContextValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemovePoolReceiveContextValue from hex string (Canton MCMS format) +func (t *RemovePoolReceiveContextValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemovePoolReceiveContextValueParams is a Record type +type RemovePoolReceiveContextValueParams struct { + ContextKey types.TEXT `json:"contextKey"` +} + +// ToMap converts RemovePoolReceiveContextValueParams to a map for DAML arguments +func (t RemovePoolReceiveContextValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + return m +} + +func (t RemovePoolReceiveContextValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemovePoolReceiveContextValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemovePoolReceiveContextValueParams to hex string (Canton MCMS format) +func (t RemovePoolReceiveContextValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemovePoolReceiveContextValueParams from hex string (Canton MCMS format) +func (t *RemovePoolReceiveContextValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetDynamicConfig is a Record type +type SetDynamicConfig struct { + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` +} + +// ToMap converts SetDynamicConfig to a map for DAML arguments +func (t SetDynamicConfig) ToMap() map[string]any { + m := make(map[string]any) + + if t.RateLimitAdmin != nil { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + return m +} + +func (t SetDynamicConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetDynamicConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetDynamicConfig to hex string (Canton MCMS format) +func (t SetDynamicConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetDynamicConfig from hex string (Canton MCMS format) +func (t *SetDynamicConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetDynamicConfigParams is a Record type +type SetDynamicConfigParams struct { + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` +} + +// ToMap converts SetDynamicConfigParams to a map for DAML arguments +func (t SetDynamicConfigParams) ToMap() map[string]any { + m := make(map[string]any) + + if t.RateLimitAdmin != nil { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + return m +} + +func (t SetDynamicConfigParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetDynamicConfigParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetDynamicConfigParams to hex string (Canton MCMS format) +func (t SetDynamicConfigParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetDynamicConfigParams from hex string (Canton MCMS format) +func (t *SetDynamicConfigParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetObservers is a Record type +type SetObservers struct { + Observers []types.PARTY `json:"observers"` +} + +// ToMap converts SetObservers to a map for DAML arguments +func (t SetObservers) ToMap() map[string]any { + m := make(map[string]any) + + m["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return m +} + +func (t SetObservers) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetObservers) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetObservers to hex string (Canton MCMS format) +func (t SetObservers) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetObservers from hex string (Canton MCMS format) +func (t *SetObservers) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetObserversParams is a Record type +type SetObserversParams struct { + Observers []types.PARTY `json:"observers"` +} + +// ToMap converts SetObserversParams to a map for DAML arguments +func (t SetObserversParams) ToMap() map[string]any { + m := make(map[string]any) + + m["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return m +} + +func (t SetObserversParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetObserversParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetObserversParams to hex string (Canton MCMS format) +func (t SetObserversParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetObserversParams from hex string (Canton MCMS format) +func (t *SetObserversParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfig is a Record type +type SetRateLimitConfig struct { + Caller types.PARTY `json:"caller"` + RateLimiterCid types.CONTRACT_ID `json:"rateLimiterCid"` + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// ToMap converts SetRateLimitConfig to a map for DAML arguments +func (t SetRateLimitConfig) ToMap() map[string]any { + m := make(map[string]any) + + m["caller"] = t.Caller.ToMap() + + m["rateLimiterCid"] = model.NestedToDAMLValue(t.RateLimiterCid) + + m["newIsEnabled"] = bool(t.NewIsEnabled) + + m["newCapacity"] = t.NewCapacity + + m["newRate"] = t.NewRate + + return m +} + +func (t SetRateLimitConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimitConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimitConfig to hex string (Canton MCMS format) +func (t SetRateLimitConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfig from hex string (Canton MCMS format) +func (t *SetRateLimitConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfigMCMSParams is SetRateLimitConfig without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type SetRateLimitConfigMCMSParams struct { + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// MarshalHex encodes SetRateLimitConfigMCMSParams to hex string for MCMS operationData. +func (t SetRateLimitConfigMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfigMCMSParams from hex string. +func (t *SetRateLimitConfigMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfigParams is a Record type +type SetRateLimitConfigParams struct { + Caller types.PARTY `json:"caller"` + RateLimiterInstanceAddress chainlinkapi.RawInstanceAddress `json:"rateLimiterInstanceAddress"` + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// ToMap converts SetRateLimitConfigParams to a map for DAML arguments +func (t SetRateLimitConfigParams) ToMap() map[string]any { + m := make(map[string]any) + + m["caller"] = t.Caller.ToMap() + + m["rateLimiterInstanceAddress"] = model.NestedToDAMLValue(t.RateLimiterInstanceAddress) + + m["newIsEnabled"] = bool(t.NewIsEnabled) + + m["newCapacity"] = t.NewCapacity + + m["newRate"] = t.NewRate + + return m +} + +func (t SetRateLimitConfigParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimitConfigParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimitConfigParams to hex string (Canton MCMS format) +func (t SetRateLimitConfigParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfigParams from hex string (Canton MCMS format) +func (t *SetRateLimitConfigParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimiterReferences is a Record type +type SetRateLimiterReferences struct { + RateLimitConfigArgs []RateLimitConfigArgs `json:"rateLimitConfigArgs"` +} + +// ToMap converts SetRateLimiterReferences to a map for DAML arguments +func (t SetRateLimiterReferences) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimitConfigArgs"] = func() []any { + res := make([]any, 0, len(t.RateLimitConfigArgs)) + for _, e := range t.RateLimitConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t SetRateLimiterReferences) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimiterReferences) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimiterReferences to hex string (Canton MCMS format) +func (t SetRateLimiterReferences) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimiterReferences from hex string (Canton MCMS format) +func (t *SetRateLimiterReferences) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimiterReferencesParams is a Record type +type SetRateLimiterReferencesParams struct { + RateLimitConfigArgs []RateLimitConfigArgs `json:"rateLimitConfigArgs"` +} + +// ToMap converts SetRateLimiterReferencesParams to a map for DAML arguments +func (t SetRateLimiterReferencesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimitConfigArgs"] = func() []any { + res := make([]any, 0, len(t.RateLimitConfigArgs)) + for _, e := range t.RateLimitConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t SetRateLimiterReferencesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimiterReferencesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimiterReferencesParams to hex string (Canton MCMS format) +func (t SetRateLimiterReferencesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimiterReferencesParams from hex string (Canton MCMS format) +func (t *SetRateLimiterReferencesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetTransferTimeout is a Record type +type SetTransferTimeout struct { + NewTransferTimeout TransferTimeout `json:"newTransferTimeout"` +} + +// ToMap converts SetTransferTimeout to a map for DAML arguments +func (t SetTransferTimeout) ToMap() map[string]any { + m := make(map[string]any) + + m["newTransferTimeout"] = model.NestedToDAMLValue(t.NewTransferTimeout) + + return m +} + +func (t SetTransferTimeout) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetTransferTimeout) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetTransferTimeout to hex string (Canton MCMS format) +func (t SetTransferTimeout) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetTransferTimeout from hex string (Canton MCMS format) +func (t *SetTransferTimeout) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetTransferTimeoutParams is a Record type +type SetTransferTimeoutParams struct { + TransferTimeout TransferTimeout `json:"transferTimeout"` +} + +// ToMap converts SetTransferTimeoutParams to a map for DAML arguments +func (t SetTransferTimeoutParams) ToMap() map[string]any { + m := make(map[string]any) + + m["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + return m +} + +func (t SetTransferTimeoutParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetTransferTimeoutParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetTransferTimeoutParams to hex string (Canton MCMS format) +func (t SetTransferTimeoutParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetTransferTimeoutParams from hex string (Canton MCMS format) +func (t *SetTransferTimeoutParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TokenTransferFeeConfig2 is a Record type +type TokenTransferFeeConfig2 struct { + IsEnabled types.BOOL `json:"isEnabled"` + DestGasOverhead types.INT64 `json:"destGasOverhead"` + DestBytesOverhead types.INT64 `json:"destBytesOverhead"` + FeeUSDCents types.NUMERIC `json:"feeUSDCents"` + FeeBps types.NUMERIC `json:"feeBps"` +} + +// ToMap converts TokenTransferFeeConfig2 to a map for DAML arguments +func (t TokenTransferFeeConfig2) ToMap() map[string]any { + m := make(map[string]any) + + m["isEnabled"] = bool(t.IsEnabled) + + m["destGasOverhead"] = int64(t.DestGasOverhead) + + m["destBytesOverhead"] = int64(t.DestBytesOverhead) + + m["feeUSDCents"] = t.FeeUSDCents + + m["feeBps"] = t.FeeBps + + return m +} + +func (t TokenTransferFeeConfig2) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *TokenTransferFeeConfig2) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes TokenTransferFeeConfig2 to hex string (Canton MCMS format) +func (t TokenTransferFeeConfig2) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes TokenTransferFeeConfig2 from hex string (Canton MCMS format) +func (t *TokenTransferFeeConfig2) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TokenTransferFeeConfigArgs is a Record type +type TokenTransferFeeConfigArgs struct { + DestChainSelector types.NUMERIC `json:"destChainSelector"` + IsEnabled types.BOOL `json:"isEnabled"` + DestGasOverhead types.INT64 `json:"destGasOverhead"` + DestBytesOverhead types.INT64 `json:"destBytesOverhead"` + FeeUSDCents types.NUMERIC `json:"feeUSDCents"` + FeeBps types.NUMERIC `json:"feeBps"` +} + +// ToMap converts TokenTransferFeeConfigArgs to a map for DAML arguments +func (t TokenTransferFeeConfigArgs) ToMap() map[string]any { + m := make(map[string]any) + + m["destChainSelector"] = t.DestChainSelector + + m["isEnabled"] = bool(t.IsEnabled) + + m["destGasOverhead"] = int64(t.DestGasOverhead) + + m["destBytesOverhead"] = int64(t.DestBytesOverhead) + + m["feeUSDCents"] = t.FeeUSDCents + + m["feeBps"] = t.FeeBps + + return m +} + +func (t TokenTransferFeeConfigArgs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *TokenTransferFeeConfigArgs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes TokenTransferFeeConfigArgs to hex string (Canton MCMS format) +func (t TokenTransferFeeConfigArgs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes TokenTransferFeeConfigArgs from hex string (Canton MCMS format) +func (t *TokenTransferFeeConfigArgs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TransferTimeout is a variant/union type +type TransferTimeout struct { + Indefinite *types.UNIT `json:"Indefinite,omitempty"` + RelativeHours *types.INT64 `json:"RelativeHours,omitempty"` +} + +// MarshalJSON implements custom JSON marshaling for TransferTimeout +func (v TransferTimeout) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(v) +} + +// UnmarshalJSON implements custom JSON unmarshalling for TransferTimeout +func (v *TransferTimeout) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, v) +} + +// MarshalHex encodes TransferTimeout to hex string (Canton MCMS format) +func (v TransferTimeout) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(v) +} + +// UnmarshalHex decodes TransferTimeout from hex string (Canton MCMS format) +func (v *TransferTimeout) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, v) +} + +// GetVariantTag implements types.VARIANT interface +func (v TransferTimeout) GetVariantTag() string { + + if v.Indefinite != nil { + return "Indefinite" + } + + if v.RelativeHours != nil { + return "RelativeHours" + } + + return "" +} + +// GetVariantValue implements types.VARIANT interface +func (v TransferTimeout) GetVariantValue() any { + + if v.Indefinite != nil { + return v.Indefinite + } + + if v.RelativeHours != nil { + return v.RelativeHours + } + + return nil +} + +var _ types.VARIANT = (*TransferTimeout)(nil) + +// VerifyInboundMessage is a Record type +type VerifyInboundMessage struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + ExecutingMessageCid types.CONTRACT_ID `json:"executingMessageCid"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts VerifyInboundMessage to a map for DAML arguments +func (t VerifyInboundMessage) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["executingMessageCid"] = model.NestedToDAMLValue(t.ExecutingMessageCid) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t VerifyInboundMessage) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *VerifyInboundMessage) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes VerifyInboundMessage to hex string (Canton MCMS format) +func (t VerifyInboundMessage) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyInboundMessage from hex string (Canton MCMS format) +func (t *VerifyInboundMessage) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyInboundMessageMCMSParams is VerifyInboundMessage without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type VerifyInboundMessageMCMSParams struct { + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes VerifyInboundMessageMCMSParams to hex string for MCMS operationData. +func (t VerifyInboundMessageMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyInboundMessageMCMSParams from hex string. +func (t *VerifyInboundMessageMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyOutboundCCVs is a Record type +type VerifyOutboundCCVs struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts VerifyOutboundCCVs to a map for DAML arguments +func (t VerifyOutboundCCVs) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["amount"] = t.Amount + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t VerifyOutboundCCVs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *VerifyOutboundCCVs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes VerifyOutboundCCVs to hex string (Canton MCMS format) +func (t VerifyOutboundCCVs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyOutboundCCVs from hex string (Canton MCMS format) +func (t *VerifyOutboundCCVs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyOutboundCCVsMCMSParams is VerifyOutboundCCVs without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type VerifyOutboundCCVsMCMSParams struct { + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes VerifyOutboundCCVsMCMSParams to hex string for MCMS operationData. +func (t VerifyOutboundCCVsMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyOutboundCCVsMCMSParams from hex string. +func (t *VerifyOutboundCCVsMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// MCMSEncoder interface for typed encoding methods. +// Implemented by Encoder for method-based encoding. +type MCMSEncoder interface { + AddPoolReceiveContextContractValue(args AddPoolReceiveContextContractValue) (*bind.EncodedChoice, error) + AddPoolReceiveContextContractValueParams(args AddPoolReceiveContextContractValueParams) (*bind.EncodedChoice, error) + AddPoolReceiveContextNonContractValue(args AddPoolReceiveContextNonContractValue) (*bind.EncodedChoice, error) + AddPoolReceiveContextNonContractValueParams(args AddPoolReceiveContextNonContractValueParams) (*bind.EncodedChoice, error) + ApplyChainUpdates(args ApplyChainUpdates) (*bind.EncodedChoice, error) + ApplyChainUpdatesParams(args ApplyChainUpdatesParams) (*bind.EncodedChoice, error) + ApplyTokenTransferFeeConfigUpdates(args ApplyTokenTransferFeeConfigUpdates) (*bind.EncodedChoice, error) + ApplyTokenTransferFeeConfigUpdatesParams(args ApplyTokenTransferFeeConfigUpdatesParams) (*bind.EncodedChoice, error) + CalculateFee(args CalculateFee) (*bind.EncodedChoice, error) + CalculateFeeMCMSParams(args CalculateFeeMCMSParams) (*bind.EncodedChoice, error) + ClearPoolReceiveContext(args ClearPoolReceiveContext) (*bind.EncodedChoice, error) + GetFee(args GetFee) (*bind.EncodedChoice, error) + GetFeeMCMSParams(args GetFeeMCMSParams) (*bind.EncodedChoice, error) + GetRequiredCCVs(args GetRequiredCCVs) (*bind.EncodedChoice, error) + GetRequiredCCVsMCMSParams(args GetRequiredCCVsMCMSParams) (*bind.EncodedChoice, error) + Initialize(args Initialize) (*bind.EncodedChoice, error) + LockOrBurn(args LockOrBurn) (*bind.EncodedChoice, error) + LockOrBurnMCMSParams(args LockOrBurnMCMSParams) (*bind.EncodedChoice, error) + ReleaseFromTicket(args ReleaseFromTicket) (*bind.EncodedChoice, error) + ReleaseFromTicketMCMSParams(args ReleaseFromTicketMCMSParams) (*bind.EncodedChoice, error) + RemovePoolReceiveContextValue(args RemovePoolReceiveContextValue) (*bind.EncodedChoice, error) + RemovePoolReceiveContextValueParams(args RemovePoolReceiveContextValueParams) (*bind.EncodedChoice, error) + SetDynamicConfig(args SetDynamicConfig) (*bind.EncodedChoice, error) + SetDynamicConfigParams(args SetDynamicConfigParams) (*bind.EncodedChoice, error) + SetObservers(args SetObservers) (*bind.EncodedChoice, error) + SetObserversParams(args SetObserversParams) (*bind.EncodedChoice, error) + SetRateLimitConfig(args SetRateLimitConfig) (*bind.EncodedChoice, error) + SetRateLimitConfigMCMSParams(args SetRateLimitConfigMCMSParams) (*bind.EncodedChoice, error) + SetRateLimitConfigParams(args SetRateLimitConfigParams) (*bind.EncodedChoice, error) + SetRateLimiterReferences(args SetRateLimiterReferences) (*bind.EncodedChoice, error) + SetRateLimiterReferencesParams(args SetRateLimiterReferencesParams) (*bind.EncodedChoice, error) + SetTransferTimeout(args SetTransferTimeout) (*bind.EncodedChoice, error) + SetTransferTimeoutParams(args SetTransferTimeoutParams) (*bind.EncodedChoice, error) + VerifyInboundMessage(args VerifyInboundMessage) (*bind.EncodedChoice, error) + VerifyInboundMessageMCMSParams(args VerifyInboundMessageMCMSParams) (*bind.EncodedChoice, error) + VerifyOutboundCCVs(args VerifyOutboundCCVs) (*bind.EncodedChoice, error) + VerifyOutboundCCVsMCMSParams(args VerifyOutboundCCVsMCMSParams) (*bind.EncodedChoice, error) +} + +// encoder provides typed encoding methods for choice parameters (unexported). +// It wraps bind.BoundTemplate to encode parameters to hex-encoded operation data. +type encoder struct { + *bind.BoundTemplate +} + +// Contract wraps template operations with Sui-style API access. +// Use NewContract to create instances, then call Encoder() for encoding methods. +type Contract struct { + enc *encoder +} + +// NewContract creates a Contract with encoder for the given template. +// This provides Sui-style API: contract.Encoder().Method(args) +func NewContract(packageID, moduleName, templateName string) *Contract { + return &Contract{ + enc: &encoder{ + BoundTemplate: bind.NewBoundTemplate(packageID, moduleName, templateName), + }, + } +} + +// Encoder returns the encoder for Sui-style contract.Encoder().Method() usage. +func (c *Contract) Encoder() MCMSEncoder { + return c.enc +} + +// AddPoolReceiveContextContractValue encodes parameters for the AddPoolReceiveContextContractValue choice. +func (e *encoder) AddPoolReceiveContextContractValue(args AddPoolReceiveContextContractValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextContractValue", args) +} + +// AddPoolReceiveContextContractValueParams encodes parameters for the AddPoolReceiveContextContractValue choice. +func (e *encoder) AddPoolReceiveContextContractValueParams(args AddPoolReceiveContextContractValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextContractValue", args) +} + +// AddPoolReceiveContextNonContractValue encodes parameters for the AddPoolReceiveContextNonContractValue choice. +func (e *encoder) AddPoolReceiveContextNonContractValue(args AddPoolReceiveContextNonContractValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextNonContractValue", args) +} + +// AddPoolReceiveContextNonContractValueParams encodes parameters for the AddPoolReceiveContextNonContractValue choice. +func (e *encoder) AddPoolReceiveContextNonContractValueParams(args AddPoolReceiveContextNonContractValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextNonContractValue", args) +} + +// ApplyChainUpdates encodes parameters for the ApplyChainUpdates choice. +func (e *encoder) ApplyChainUpdates(args ApplyChainUpdates) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyChainUpdates", args) +} + +// ApplyChainUpdatesParams encodes parameters for the ApplyChainUpdates choice. +func (e *encoder) ApplyChainUpdatesParams(args ApplyChainUpdatesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyChainUpdates", args) +} + +// ApplyTokenTransferFeeConfigUpdates encodes parameters for the ApplyTokenTransferFeeConfigUpdates choice. +func (e *encoder) ApplyTokenTransferFeeConfigUpdates(args ApplyTokenTransferFeeConfigUpdates) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyTokenTransferFeeConfigUpdates", args) +} + +// ApplyTokenTransferFeeConfigUpdatesParams encodes parameters for the ApplyTokenTransferFeeConfigUpdates choice. +func (e *encoder) ApplyTokenTransferFeeConfigUpdatesParams(args ApplyTokenTransferFeeConfigUpdatesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyTokenTransferFeeConfigUpdates", args) +} + +// CalculateFee encodes parameters for the CalculateFee choice. +func (e *encoder) CalculateFee(args CalculateFee) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CalculateFee", args) +} + +// CalculateFeeMCMSParams encodes MCMS parameters (without Caller) for the CalculateFee choice. +func (e *encoder) CalculateFeeMCMSParams(args CalculateFeeMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CalculateFee", args) +} + +// ClearPoolReceiveContext encodes parameters for the ClearPoolReceiveContext choice. +func (e *encoder) ClearPoolReceiveContext(args ClearPoolReceiveContext) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ClearPoolReceiveContext", args) +} + +// GetFee encodes parameters for the GetFee choice. +func (e *encoder) GetFee(args GetFee) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetFee", args) +} + +// GetFeeMCMSParams encodes MCMS parameters (without Caller) for the GetFee choice. +func (e *encoder) GetFeeMCMSParams(args GetFeeMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetFee", args) +} + +// GetRequiredCCVs encodes parameters for the GetRequiredCCVs choice. +func (e *encoder) GetRequiredCCVs(args GetRequiredCCVs) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetRequiredCCVs", args) +} + +// GetRequiredCCVsMCMSParams encodes MCMS parameters (without Caller) for the GetRequiredCCVs choice. +func (e *encoder) GetRequiredCCVsMCMSParams(args GetRequiredCCVsMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetRequiredCCVs", args) +} + +// Initialize encodes parameters for the Initialize choice. +func (e *encoder) Initialize(args Initialize) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("Initialize", args) +} + +// LockOrBurn encodes parameters for the LockOrBurn choice. +func (e *encoder) LockOrBurn(args LockOrBurn) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("LockOrBurn", args) +} + +// LockOrBurnMCMSParams encodes MCMS parameters (without Caller) for the LockOrBurn choice. +func (e *encoder) LockOrBurnMCMSParams(args LockOrBurnMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("LockOrBurn", args) +} + +// ReleaseFromTicket encodes parameters for the ReleaseFromTicket choice. +func (e *encoder) ReleaseFromTicket(args ReleaseFromTicket) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ReleaseFromTicket", args) +} + +// ReleaseFromTicketMCMSParams encodes MCMS parameters (without Caller) for the ReleaseFromTicket choice. +func (e *encoder) ReleaseFromTicketMCMSParams(args ReleaseFromTicketMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ReleaseFromTicket", args) +} + +// RemovePoolReceiveContextValue encodes parameters for the RemovePoolReceiveContextValue choice. +func (e *encoder) RemovePoolReceiveContextValue(args RemovePoolReceiveContextValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("RemovePoolReceiveContextValue", args) +} + +// RemovePoolReceiveContextValueParams encodes parameters for the RemovePoolReceiveContextValue choice. +func (e *encoder) RemovePoolReceiveContextValueParams(args RemovePoolReceiveContextValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("RemovePoolReceiveContextValue", args) +} + +// SetDynamicConfig encodes parameters for the SetDynamicConfig choice. +func (e *encoder) SetDynamicConfig(args SetDynamicConfig) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetDynamicConfig", args) +} + +// SetDynamicConfigParams encodes parameters for the SetDynamicConfig choice. +func (e *encoder) SetDynamicConfigParams(args SetDynamicConfigParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetDynamicConfig", args) +} + +// SetObservers encodes parameters for the SetObservers choice. +func (e *encoder) SetObservers(args SetObservers) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetObservers", args) +} + +// SetObserversParams encodes parameters for the SetObservers choice. +func (e *encoder) SetObserversParams(args SetObserversParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetObservers", args) +} + +// SetRateLimitConfig encodes parameters for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfig(args SetRateLimitConfig) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimitConfigMCMSParams encodes MCMS parameters (without Caller) for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfigMCMSParams(args SetRateLimitConfigMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimitConfigParams encodes parameters for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfigParams(args SetRateLimitConfigParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimiterReferences encodes parameters for the SetRateLimiterReferences choice. +func (e *encoder) SetRateLimiterReferences(args SetRateLimiterReferences) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimiterReferences", args) +} + +// SetRateLimiterReferencesParams encodes parameters for the SetRateLimiterReferences choice. +func (e *encoder) SetRateLimiterReferencesParams(args SetRateLimiterReferencesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimiterReferences", args) +} + +// SetTransferTimeout encodes parameters for the SetTransferTimeout choice. +func (e *encoder) SetTransferTimeout(args SetTransferTimeout) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetTransferTimeout", args) +} + +// SetTransferTimeoutParams encodes parameters for the SetTransferTimeout choice. +func (e *encoder) SetTransferTimeoutParams(args SetTransferTimeoutParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetTransferTimeout", args) +} + +// VerifyInboundMessage encodes parameters for the VerifyInboundMessage choice. +func (e *encoder) VerifyInboundMessage(args VerifyInboundMessage) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyInboundMessage", args) +} + +// VerifyInboundMessageMCMSParams encodes MCMS parameters (without Caller) for the VerifyInboundMessage choice. +func (e *encoder) VerifyInboundMessageMCMSParams(args VerifyInboundMessageMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyInboundMessage", args) +} + +// VerifyOutboundCCVs encodes parameters for the VerifyOutboundCCVs choice. +func (e *encoder) VerifyOutboundCCVs(args VerifyOutboundCCVs) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyOutboundCCVs", args) +} + +// VerifyOutboundCCVsMCMSParams encodes MCMS parameters (without Caller) for the VerifyOutboundCCVs choice. +func (e *encoder) VerifyOutboundCCVsMCMSParams(args VerifyOutboundCCVsMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyOutboundCCVs", args) +} + +// Verify MCMSEncoder interface implementation +var _ MCMSEncoder = (*encoder)(nil) diff --git a/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go b/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go new file mode 100644 index 000000000..c0339a7a9 --- /dev/null +++ b/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go @@ -0,0 +1,3151 @@ +package lockreleasetokenpool + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ccipcodec "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipcodec" + extensionapi "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/extensionapi" + chainlinkapi "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/chainlink/chainlinkapi" + api "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/mcms/api" + splice_api_token_holding_v1 "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_holding_v1" + splice_api_token_metadata_v1 "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_metadata_v1" + "github.com/smartcontractkit/go-daml/pkg/bind" + "github.com/smartcontractkit/go-daml/pkg/codec" + "github.com/smartcontractkit/go-daml/pkg/model" + "github.com/smartcontractkit/go-daml/pkg/types" +) + +var ( + _ = fmt.Sprintf + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = model.Command{} + _ bind.BoundTemplate +) + +const ( + PackageName = "ccip-registry-lock-release-token-pool-v2" + PackageID = "42c39df6943667e9ee2aeee1055cf88ba76724a16046ba76ead3333f25b07207" + SDKVersion = "3.4.11" +) + +type Template interface { + CreateCommand() *model.CreateCommand + GetTemplateID() string +} + +const ( + TransferFactoryExtraArgsMetaValuesContextKey = types.TEXT("transfer-factory-extra-args-meta-values") + TransferFactoryExtraArgsContextValuesContextKey = types.TEXT("transfer-factory-extra-args-context-values") + TransferFactoryContextKey = types.TEXT("transfer-factory") + TokenPoolHoldingsContextKey = types.TEXT("token-pool-holdings") + BpsDenominator = types.NUMERIC("10000.") +) + +func argsToMap(args any) map[string]any { + if args == nil { + return map[string]any{} + } + + if m, ok := args.(map[string]any); ok { + return m + } + + type mapper interface { + ToMap() map[string]any + } + if mapper, ok := args.(mapper); ok { + return mapper.ToMap() + } + + return map[string]any{"args": args} +} + +// AddPoolReceiveContextContractValue is a Record type +type AddPoolReceiveContextContractValue struct { + ContextKey types.TEXT `json:"contextKey"` + ReferredContract types.CONTRACT_ID `json:"referredContract"` +} + +// ToMap converts AddPoolReceiveContextContractValue to a map for DAML arguments +func (t AddPoolReceiveContextContractValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["referredContract"] = model.NestedToDAMLValue(t.ReferredContract) + + return m +} + +func (t AddPoolReceiveContextContractValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextContractValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextContractValue to hex string (Canton MCMS format) +func (t AddPoolReceiveContextContractValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextContractValue from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextContractValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextContractValueParams is a Record type +type AddPoolReceiveContextContractValueParams struct { + ContextKey types.TEXT `json:"contextKey"` + ReferentInstanceAddress chainlinkapi.RawInstanceAddress `json:"referentInstanceAddress"` +} + +// ToMap converts AddPoolReceiveContextContractValueParams to a map for DAML arguments +func (t AddPoolReceiveContextContractValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["referentInstanceAddress"] = model.NestedToDAMLValue(t.ReferentInstanceAddress) + + return m +} + +func (t AddPoolReceiveContextContractValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextContractValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextContractValueParams to hex string (Canton MCMS format) +func (t AddPoolReceiveContextContractValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextContractValueParams from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextContractValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextNonContractValue is a Record type +type AddPoolReceiveContextNonContractValue struct { + ContextKey types.TEXT `json:"contextKey"` + Value splice_api_token_metadata_v1.AnyValue `json:"value"` +} + +// ToMap converts AddPoolReceiveContextNonContractValue to a map for DAML arguments +func (t AddPoolReceiveContextNonContractValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["value"] = model.NestedToDAMLValue(t.Value) + + return m +} + +func (t AddPoolReceiveContextNonContractValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextNonContractValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextNonContractValue to hex string (Canton MCMS format) +func (t AddPoolReceiveContextNonContractValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextNonContractValue from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextNonContractValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// AddPoolReceiveContextNonContractValueParams is a Record type +type AddPoolReceiveContextNonContractValueParams struct { + ContextKey types.TEXT `json:"contextKey"` + ValuePayload types.TEXT `json:"valuePayload"` +} + +// ToMap converts AddPoolReceiveContextNonContractValueParams to a map for DAML arguments +func (t AddPoolReceiveContextNonContractValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + m["valuePayload"] = string(t.ValuePayload) + + return m +} + +func (t AddPoolReceiveContextNonContractValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *AddPoolReceiveContextNonContractValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes AddPoolReceiveContextNonContractValueParams to hex string (Canton MCMS format) +func (t AddPoolReceiveContextNonContractValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes AddPoolReceiveContextNonContractValueParams from hex string (Canton MCMS format) +func (t *AddPoolReceiveContextNonContractValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyChainUpdates is a Record type +type ApplyChainUpdates struct { + RemoteChainSelectorsToRemove []types.NUMERIC `json:"remoteChainSelectorsToRemove"` + ChainsToAdd []ChainUpdate `json:"chainsToAdd"` +} + +// ToMap converts ApplyChainUpdates to a map for DAML arguments +func (t ApplyChainUpdates) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelectorsToRemove"] = func() []any { + res := make([]any, 0, len(t.RemoteChainSelectorsToRemove)) + for _, e := range t.RemoteChainSelectorsToRemove { + res = append(res, e) + } + return res + }() + + m["chainsToAdd"] = func() []any { + res := make([]any, 0, len(t.ChainsToAdd)) + for _, e := range t.ChainsToAdd { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t ApplyChainUpdates) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyChainUpdates) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyChainUpdates to hex string (Canton MCMS format) +func (t ApplyChainUpdates) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyChainUpdates from hex string (Canton MCMS format) +func (t *ApplyChainUpdates) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyChainUpdatesParams is a Record type +type ApplyChainUpdatesParams struct { + RemoteChainSelectorsToRemove []types.NUMERIC `json:"remoteChainSelectorsToRemove"` + ChainsToAdd []ChainUpdate `json:"chainsToAdd"` +} + +// ToMap converts ApplyChainUpdatesParams to a map for DAML arguments +func (t ApplyChainUpdatesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelectorsToRemove"] = func() []any { + res := make([]any, 0, len(t.RemoteChainSelectorsToRemove)) + for _, e := range t.RemoteChainSelectorsToRemove { + res = append(res, e) + } + return res + }() + + m["chainsToAdd"] = func() []any { + res := make([]any, 0, len(t.ChainsToAdd)) + for _, e := range t.ChainsToAdd { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t ApplyChainUpdatesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyChainUpdatesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyChainUpdatesParams to hex string (Canton MCMS format) +func (t ApplyChainUpdatesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyChainUpdatesParams from hex string (Canton MCMS format) +func (t *ApplyChainUpdatesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyTokenTransferFeeConfigUpdates is a Record type +type ApplyTokenTransferFeeConfigUpdates struct { + TokenTransferFeeConfigArgs []TokenTransferFeeConfigArgs `json:"tokenTransferFeeConfigArgs"` + DisableTokenTransferFeeConfigArgs []types.NUMERIC `json:"disableTokenTransferFeeConfigArgs"` +} + +// ToMap converts ApplyTokenTransferFeeConfigUpdates to a map for DAML arguments +func (t ApplyTokenTransferFeeConfigUpdates) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.TokenTransferFeeConfigArgs)) + for _, e := range t.TokenTransferFeeConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["disableTokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.DisableTokenTransferFeeConfigArgs)) + for _, e := range t.DisableTokenTransferFeeConfigArgs { + res = append(res, e) + } + return res + }() + + return m +} + +func (t ApplyTokenTransferFeeConfigUpdates) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyTokenTransferFeeConfigUpdates) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyTokenTransferFeeConfigUpdates to hex string (Canton MCMS format) +func (t ApplyTokenTransferFeeConfigUpdates) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyTokenTransferFeeConfigUpdates from hex string (Canton MCMS format) +func (t *ApplyTokenTransferFeeConfigUpdates) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApplyTokenTransferFeeConfigUpdatesParams is a Record type +type ApplyTokenTransferFeeConfigUpdatesParams struct { + TokenTransferFeeConfigArgs []TokenTransferFeeConfigArgs `json:"tokenTransferFeeConfigArgs"` + DisableTokenTransferFeeConfigArgs []types.NUMERIC `json:"disableTokenTransferFeeConfigArgs"` +} + +// ToMap converts ApplyTokenTransferFeeConfigUpdatesParams to a map for DAML arguments +func (t ApplyTokenTransferFeeConfigUpdatesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.TokenTransferFeeConfigArgs)) + for _, e := range t.TokenTransferFeeConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["disableTokenTransferFeeConfigArgs"] = func() []any { + res := make([]any, 0, len(t.DisableTokenTransferFeeConfigArgs)) + for _, e := range t.DisableTokenTransferFeeConfigArgs { + res = append(res, e) + } + return res + }() + + return m +} + +func (t ApplyTokenTransferFeeConfigUpdatesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApplyTokenTransferFeeConfigUpdatesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApplyTokenTransferFeeConfigUpdatesParams to hex string (Canton MCMS format) +func (t ApplyTokenTransferFeeConfigUpdatesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApplyTokenTransferFeeConfigUpdatesParams from hex string (Canton MCMS format) +func (t *ApplyTokenTransferFeeConfigUpdatesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CalculateFee is a Record type +type CalculateFee struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + FeeQuoterCid types.CONTRACT_ID `json:"feeQuoterCid"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts CalculateFee to a map for DAML arguments +func (t CalculateFee) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["feeQuoterCid"] = model.NestedToDAMLValue(t.FeeQuoterCid) + + m["tokenInstrumentId"] = model.NestedToDAMLValue(t.TokenInstrumentId) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t CalculateFee) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CalculateFee) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CalculateFee to hex string (Canton MCMS format) +func (t CalculateFee) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CalculateFee from hex string (Canton MCMS format) +func (t *CalculateFee) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CalculateFeeMCMSParams is CalculateFee without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type CalculateFeeMCMSParams struct { + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes CalculateFeeMCMSParams to hex string for MCMS operationData. +func (t CalculateFeeMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CalculateFeeMCMSParams from hex string. +func (t *CalculateFeeMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ChainUpdate is a Record type +type ChainUpdate struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts ChainUpdate to a map for DAML arguments +func (t ChainUpdate) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t ChainUpdate) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ChainUpdate) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ChainUpdate to hex string (Canton MCMS format) +func (t ChainUpdate) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ChainUpdate from hex string (Canton MCMS format) +func (t *ChainUpdate) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ClearPoolReceiveContext is a Record type +type ClearPoolReceiveContext struct { +} + +// ToMap converts ClearPoolReceiveContext to a map for DAML arguments +func (t ClearPoolReceiveContext) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t ClearPoolReceiveContext) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ClearPoolReceiveContext) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ClearPoolReceiveContext to hex string (Canton MCMS format) +func (t ClearPoolReceiveContext) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ClearPoolReceiveContext from hex string (Canton MCMS format) +func (t *ClearPoolReceiveContext) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetFee is a Record type +type GetFee struct { + FeeQuoterCid types.CONTRACT_ID `json:"feeQuoterCid"` + DestChainSelector types.NUMERIC `json:"destChainSelector"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts GetFee to a map for DAML arguments +func (t GetFee) ToMap() map[string]any { + m := make(map[string]any) + + m["feeQuoterCid"] = model.NestedToDAMLValue(t.FeeQuoterCid) + + m["destChainSelector"] = t.DestChainSelector + + m["tokenInstrumentId"] = model.NestedToDAMLValue(t.TokenInstrumentId) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t GetFee) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *GetFee) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes GetFee to hex string (Canton MCMS format) +func (t GetFee) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetFee from hex string (Canton MCMS format) +func (t *GetFee) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetFeeMCMSParams is GetFee without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type GetFeeMCMSParams struct { + DestChainSelector types.NUMERIC `json:"destChainSelector"` + TokenInstrumentId splice_api_token_holding_v1.InstrumentId `json:"tokenInstrumentId"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes GetFeeMCMSParams to hex string for MCMS operationData. +func (t GetFeeMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetFeeMCMSParams from hex string. +func (t *GetFeeMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetRequiredCCVs is a Record type +type GetRequiredCCVs struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + SourceAmount types.TEXT `json:"sourceAmount"` + Finality ccipcodec.FinalityConfig `json:"finality"` + ExtraData types.TEXT `json:"extraData"` + Direction extensionapi.TransferDirection `json:"direction"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts GetRequiredCCVs to a map for DAML arguments +func (t GetRequiredCCVs) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["sourceAmount"] = string(t.SourceAmount) + + m["finality"] = model.NestedToDAMLValue(t.Finality) + + m["extraData"] = string(t.ExtraData) + + m["direction"] = model.NestedToDAMLValue(t.Direction) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t GetRequiredCCVs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *GetRequiredCCVs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes GetRequiredCCVs to hex string (Canton MCMS format) +func (t GetRequiredCCVs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetRequiredCCVs from hex string (Canton MCMS format) +func (t *GetRequiredCCVs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// GetRequiredCCVsMCMSParams is GetRequiredCCVs without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type GetRequiredCCVsMCMSParams struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + SourceAmount types.TEXT `json:"sourceAmount"` + Finality ccipcodec.FinalityConfig `json:"finality"` + ExtraData types.TEXT `json:"extraData"` + Direction extensionapi.TransferDirection `json:"direction"` +} + +// MarshalHex encodes GetRequiredCCVsMCMSParams to hex string for MCMS operationData. +func (t GetRequiredCCVsMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes GetRequiredCCVsMCMSParams from hex string. +func (t *GetRequiredCCVsMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Initialize is a Record type +type Initialize struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + ExistingTokenConfigCid *types.CONTRACT_ID `json:"existingTokenConfigCid" hex:"optional"` + Admin types.PARTY `json:"admin"` + Lanes []LaneDeploySpec `json:"lanes"` +} + +// ToMap converts Initialize to a map for DAML arguments +func (t Initialize) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + if t.ExistingTokenConfigCid != nil { + m["existingTokenConfigCid"] = map[string]any{ + "_type": "optional", + "value": model.NestedToDAMLValue(*t.ExistingTokenConfigCid), + } + } else { + m["existingTokenConfigCid"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + m["admin"] = t.Admin.ToMap() + + m["lanes"] = func() []any { + res := make([]any, 0, len(t.Lanes)) + for _, e := range t.Lanes { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t Initialize) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *Initialize) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes Initialize to hex string (Canton MCMS format) +func (t Initialize) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes Initialize from hex string (Canton MCMS format) +func (t *Initialize) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// InitializeResult is a Record type +type InitializeResult struct { + RateLimiterCids []types.CONTRACT_ID `json:"rateLimiterCids"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` +} + +// ToMap converts InitializeResult to a map for DAML arguments +func (t InitializeResult) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimiterCids"] = func() []any { + res := make([]any, 0, len(t.RateLimiterCids)) + for _, e := range t.RateLimiterCids { + res = append(res, e) + } + return res + }() + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + return m +} + +func (t InitializeResult) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *InitializeResult) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes InitializeResult to hex string (Canton MCMS format) +func (t InitializeResult) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes InitializeResult from hex string (Canton MCMS format) +func (t *InitializeResult) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LaneDeploySpec is a Record type +type LaneDeploySpec struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + Inbound RateLimiterDeploySpec `json:"inbound"` + Outbound RateLimiterDeploySpec `json:"outbound"` + InboundCustomFinality RateLimiterDeploySpec `json:"inboundCustomFinality"` +} + +// ToMap converts LaneDeploySpec to a map for DAML arguments +func (t LaneDeploySpec) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inbound"] = model.NestedToDAMLValue(t.Inbound) + + m["outbound"] = model.NestedToDAMLValue(t.Outbound) + + m["inboundCustomFinality"] = model.NestedToDAMLValue(t.InboundCustomFinality) + + return m +} + +func (t LaneDeploySpec) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LaneDeploySpec) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LaneDeploySpec to hex string (Canton MCMS format) +func (t LaneDeploySpec) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LaneDeploySpec from hex string (Canton MCMS format) +func (t *LaneDeploySpec) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LockOrBurn is a Record type +type LockOrBurn struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts LockOrBurn to a map for DAML arguments +func (t LockOrBurn) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["rmnRemoteCid"] = model.NestedToDAMLValue(t.RmnRemoteCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["senderInputCids"] = func() []any { + res := make([]any, 0, len(t.SenderInputCids)) + for _, e := range t.SenderInputCids { + res = append(res, e) + } + return res + }() + + m["amount"] = t.Amount + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t LockOrBurn) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LockOrBurn) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LockOrBurn to hex string (Canton MCMS format) +func (t LockOrBurn) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockOrBurn from hex string (Canton MCMS format) +func (t *LockOrBurn) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LockOrBurnMCMSParams is LockOrBurn without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type LockOrBurnMCMSParams struct { + SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes LockOrBurnMCMSParams to hex string for MCMS operationData. +func (t LockOrBurnMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockOrBurnMCMSParams from hex string. +func (t *LockOrBurnMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LockReleaseTokenPool is a Template type +type LockReleaseTokenPool struct { + InstanceId types.TEXT `json:"instanceId"` + PoolOwner types.PARTY `json:"poolOwner"` + CcipOwner types.PARTY `json:"ccipOwner"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Decimals types.INT64 `json:"decimals"` + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` + Observers []types.PARTY `json:"observers"` + RemoteChainConfigs map[types.NUMERIC]RemoteChainConfig `json:"remoteChainConfigs"` + TokenTransferFeeConfigs map[types.NUMERIC]TokenTransferFeeConfig2 `json:"tokenTransferFeeConfigs"` + PoolReceiveContext splice_api_token_metadata_v1.ChoiceContext `json:"poolReceiveContext"` + TransferTimeout TransferTimeout `json:"transferTimeout"` + Deps LockReleaseTokenPoolDeps `json:"deps"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LockReleaseTokenPool) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LockReleaseTokenPool) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LockReleaseTokenPool) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["ccipOwner"] = t.CcipOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instrumentId"] = model.NestedToDAMLValue(t.InstrumentId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["decimals"] = int64(t.Decimals) + + if t.RateLimitAdmin != nil { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["remoteChainConfigs"] = func() any { + if t.RemoteChainConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.RemoteChainConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["tokenTransferFeeConfigs"] = func() any { + if t.TokenTransferFeeConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.TokenTransferFeeConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolReceiveContext"] = model.NestedToDAMLValue(t.PoolReceiveContext) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["deps"] = model.NestedToDAMLValue(t.Deps) + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t LockReleaseTokenPool) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["ccipOwner"] = t.CcipOwner.ToMap() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instrumentId"] = model.NestedToDAMLValue(t.InstrumentId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["decimals"] = int64(t.Decimals) + + if t.RateLimitAdmin != nil { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + args["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["remoteChainConfigs"] = func() any { + if t.RemoteChainConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.RemoteChainConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["tokenTransferFeeConfigs"] = func() any { + if t.TokenTransferFeeConfigs == nil { + return map[string]any{"_type": "genmap", "value": types.GENMAP{}} + } + return map[string]any{"_type": "genmap", "value": t.TokenTransferFeeConfigs} + }() + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolReceiveContext"] = model.NestedToDAMLValue(t.PoolReceiveContext) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["deps"] = model.NestedToDAMLValue(t.Deps) + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t LockReleaseTokenPool) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LockReleaseTokenPool) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LockReleaseTokenPool to hex string (Canton MCMS format) +func (t LockReleaseTokenPool) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockReleaseTokenPool from hex string (Canton MCMS format) +func (t *LockReleaseTokenPool) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LockReleaseTokenPool + +// LockOrBurn exercises the LockOrBurn choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) LockOrBurn(contractID string, args LockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "LockOrBurn", + Arguments: argsToMap(args), + } +} + +// LockOrBurnWithPackageID exercises the LockOrBurn choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) LockOrBurnWithPackageID(contractID string, packageID string, args LockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "LockOrBurn", + Arguments: argsToMap(args), + } +} + +// ReleaseFromTicket exercises the ReleaseFromTicket choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) ReleaseFromTicket(contractID string, args ReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// ReleaseFromTicketWithPackageID exercises the ReleaseFromTicket choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) ReleaseFromTicketWithPackageID(contractID string, packageID string, args ReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// SetRateLimitConfig exercises the SetRateLimitConfig choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) SetRateLimitConfig(contractID string, args SetRateLimitConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetRateLimitConfig", + Arguments: argsToMap(args), + } +} + +// SetRateLimitConfigWithPackageID exercises the SetRateLimitConfig choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) SetRateLimitConfigWithPackageID(contractID string, packageID string, args SetRateLimitConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetRateLimitConfig", + Arguments: argsToMap(args), + } +} + +// ApplyTokenTransferFeeConfigUpdates exercises the ApplyTokenTransferFeeConfigUpdates choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) ApplyTokenTransferFeeConfigUpdates(contractID string, args ApplyTokenTransferFeeConfigUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ApplyTokenTransferFeeConfigUpdates", + Arguments: argsToMap(args), + } +} + +// ApplyTokenTransferFeeConfigUpdatesWithPackageID exercises the ApplyTokenTransferFeeConfigUpdates choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) ApplyTokenTransferFeeConfigUpdatesWithPackageID(contractID string, packageID string, args ApplyTokenTransferFeeConfigUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ApplyTokenTransferFeeConfigUpdates", + Arguments: argsToMap(args), + } +} + +// SetRateLimiterReferences exercises the SetRateLimiterReferences choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) SetRateLimiterReferences(contractID string, args SetRateLimiterReferences) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetRateLimiterReferences", + Arguments: argsToMap(args), + } +} + +// SetRateLimiterReferencesWithPackageID exercises the SetRateLimiterReferences choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) SetRateLimiterReferencesWithPackageID(contractID string, packageID string, args SetRateLimiterReferences) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetRateLimiterReferences", + Arguments: argsToMap(args), + } +} + +// ApplyChainUpdates exercises the ApplyChainUpdates choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) ApplyChainUpdates(contractID string, args ApplyChainUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ApplyChainUpdates", + Arguments: argsToMap(args), + } +} + +// ApplyChainUpdatesWithPackageID exercises the ApplyChainUpdates choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) ApplyChainUpdatesWithPackageID(contractID string, packageID string, args ApplyChainUpdates) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ApplyChainUpdates", + Arguments: argsToMap(args), + } +} + +// Initialize exercises the Initialize choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) Initialize(contractID string, args Initialize) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "Initialize", + Arguments: argsToMap(args), + } +} + +// InitializeWithPackageID exercises the Initialize choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) InitializeWithPackageID(contractID string, packageID string, args Initialize) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "Initialize", + Arguments: argsToMap(args), + } +} + +// SetDynamicConfig exercises the SetDynamicConfig choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) SetDynamicConfig(contractID string, args SetDynamicConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetDynamicConfig", + Arguments: argsToMap(args), + } +} + +// SetDynamicConfigWithPackageID exercises the SetDynamicConfig choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) SetDynamicConfigWithPackageID(contractID string, packageID string, args SetDynamicConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetDynamicConfig", + Arguments: argsToMap(args), + } +} + +// SetObservers exercises the SetObservers choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) SetObservers(contractID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// SetObserversWithPackageID exercises the SetObservers choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) SetObserversWithPackageID(contractID string, packageID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextNonContractValue exercises the AddPoolReceiveContextNonContractValue choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) AddPoolReceiveContextNonContractValue(contractID string, args AddPoolReceiveContextNonContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextNonContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextNonContractValueWithPackageID exercises the AddPoolReceiveContextNonContractValue choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) AddPoolReceiveContextNonContractValueWithPackageID(contractID string, packageID string, args AddPoolReceiveContextNonContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextNonContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextContractValue exercises the AddPoolReceiveContextContractValue choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) AddPoolReceiveContextContractValue(contractID string, args AddPoolReceiveContextContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextContractValue", + Arguments: argsToMap(args), + } +} + +// AddPoolReceiveContextContractValueWithPackageID exercises the AddPoolReceiveContextContractValue choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) AddPoolReceiveContextContractValueWithPackageID(contractID string, packageID string, args AddPoolReceiveContextContractValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "AddPoolReceiveContextContractValue", + Arguments: argsToMap(args), + } +} + +// RemovePoolReceiveContextValue exercises the RemovePoolReceiveContextValue choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) RemovePoolReceiveContextValue(contractID string, args RemovePoolReceiveContextValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "RemovePoolReceiveContextValue", + Arguments: argsToMap(args), + } +} + +// RemovePoolReceiveContextValueWithPackageID exercises the RemovePoolReceiveContextValue choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) RemovePoolReceiveContextValueWithPackageID(contractID string, packageID string, args RemovePoolReceiveContextValue) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "RemovePoolReceiveContextValue", + Arguments: argsToMap(args), + } +} + +// ClearPoolReceiveContext exercises the ClearPoolReceiveContext choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) ClearPoolReceiveContext(contractID string, args ClearPoolReceiveContext) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ClearPoolReceiveContext", + Arguments: argsToMap(args), + } +} + +// ClearPoolReceiveContextWithPackageID exercises the ClearPoolReceiveContext choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) ClearPoolReceiveContextWithPackageID(contractID string, packageID string, args ClearPoolReceiveContext) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "ClearPoolReceiveContext", + Arguments: argsToMap(args), + } +} + +// SetTransferTimeout exercises the SetTransferTimeout choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) SetTransferTimeout(contractID string, args SetTransferTimeout) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetTransferTimeout", + Arguments: argsToMap(args), + } +} + +// SetTransferTimeoutWithPackageID exercises the SetTransferTimeout choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) SetTransferTimeoutWithPackageID(contractID string, packageID string, args SetTransferTimeout) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "SetTransferTimeout", + Arguments: argsToMap(args), + } +} + +// GetRequiredCCVs exercises the GetRequiredCCVs choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) GetRequiredCCVs(contractID string, args GetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// GetRequiredCCVsWithPackageID exercises the GetRequiredCCVs choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) GetRequiredCCVsWithPackageID(contractID string, packageID string, args GetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// VerifyInboundMessage exercises the VerifyInboundMessage choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) VerifyInboundMessage(contractID string, args VerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// VerifyInboundMessageWithPackageID exercises the VerifyInboundMessage choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) VerifyInboundMessageWithPackageID(contractID string, packageID string, args VerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// VerifyOutboundCCVs exercises the VerifyOutboundCCVs choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) VerifyOutboundCCVs(contractID string, args VerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// VerifyOutboundCCVsWithPackageID exercises the VerifyOutboundCCVs choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) VerifyOutboundCCVsWithPackageID(contractID string, packageID string, args VerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// CalculateFee exercises the CalculateFee choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) CalculateFee(contractID string, args CalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "CalculateFee", + Arguments: argsToMap(args), + } +} + +// CalculateFeeWithPackageID exercises the CalculateFee choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) CalculateFeeWithPackageID(contractID string, packageID string, args CalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "CalculateFee", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "TokenPool"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "TokenPool"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// GetFee exercises the GetFee choice on this LockReleaseTokenPool contract +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) GetFee(contractID string, args GetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "GetFee", + Arguments: argsToMap(args), + } +} + +// GetFeeWithPackageID exercises the GetFee choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) GetFeeWithPackageID(contractID string, packageID string, args GetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "LockReleaseTokenPool"), + ContractID: contractID, + Choice: "GetFee", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypoint exercises the MCMSReceiver_Entrypoint choice on this LockReleaseTokenPool contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) MCMSReceiverEntrypoint(contractID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypointWithPackageID exercises the MCMSReceiver_Entrypoint choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) MCMSReceiverEntrypointWithPackageID(contractID string, packageID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetRequiredCCVs exercises the TokenPool_GetRequiredCCVs choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolGetRequiredCCVs(contractID string, args extensionapi.TokenPoolGetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetRequiredCCVsWithPackageID exercises the TokenPool_GetRequiredCCVs choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolGetRequiredCCVsWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolGetRequiredCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetRequiredCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyInboundMessage exercises the TokenPool_VerifyInboundMessage choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolVerifyInboundMessage(contractID string, args extensionapi.TokenPoolVerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyInboundMessageWithPackageID exercises the TokenPool_VerifyInboundMessage choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolVerifyInboundMessageWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolVerifyInboundMessage) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyInboundMessage", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyOutboundCCVs exercises the TokenPool_VerifyOutboundCCVs choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolVerifyOutboundCCVs(contractID string, args extensionapi.TokenPoolVerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolVerifyOutboundCCVsWithPackageID exercises the TokenPool_VerifyOutboundCCVs choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolVerifyOutboundCCVsWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolVerifyOutboundCCVs) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_VerifyOutboundCCVs", + Arguments: argsToMap(args), + } +} + +// TokenPoolReleaseFromTicket exercises the TokenPool_ReleaseFromTicket choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolReleaseFromTicket(contractID string, args extensionapi.TokenPoolReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// TokenPoolReleaseFromTicketWithPackageID exercises the TokenPool_ReleaseFromTicket choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolReleaseFromTicketWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolReleaseFromTicket) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_ReleaseFromTicket", + Arguments: argsToMap(args), + } +} + +// TokenPoolLockOrBurn exercises the TokenPool_LockOrBurn choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolLockOrBurn(contractID string, args extensionapi.TokenPoolLockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_LockOrBurn", + Arguments: argsToMap(args), + } +} + +// TokenPoolLockOrBurnWithPackageID exercises the TokenPool_LockOrBurn choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolLockOrBurnWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolLockOrBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_LockOrBurn", + Arguments: argsToMap(args), + } +} + +// TokenPoolCalculateFee exercises the TokenPool_CalculateFee choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolCalculateFee(contractID string, args extensionapi.TokenPoolCalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_CalculateFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolCalculateFeeWithPackageID exercises the TokenPool_CalculateFee choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolCalculateFeeWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolCalculateFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_CalculateFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetFee exercises the TokenPool_GetFee choice on this LockReleaseTokenPool contract via the IITokenPool interface +// This method uses the package name in the template ID +func (t LockReleaseTokenPool) TokenPoolGetFee(contractID string, args extensionapi.TokenPoolGetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetFee", + Arguments: argsToMap(args), + } +} + +// TokenPoolGetFeeWithPackageID exercises the TokenPool_GetFee choice using the provided package ID instead of package name +func (t LockReleaseTokenPool) TokenPoolGetFeeWithPackageID(contractID string, packageID string, args extensionapi.TokenPoolGetFee) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.LockReleaseTokenPoolV2", "ITokenPool"), + ContractID: contractID, + Choice: "TokenPool_GetFee", + Arguments: argsToMap(args), + } +} + +// Verify interface implementations for LockReleaseTokenPool + +var _ api.IMCMSReceiver = (*LockReleaseTokenPool)(nil) + +var _ extensionapi.IITokenPool = (*LockReleaseTokenPool)(nil) + +// LockReleaseTokenPoolDeps is a Record type +type LockReleaseTokenPoolDeps struct { + TokenAdminRegistry chainlinkapi.RawInstanceAddress `json:"tokenAdminRegistry"` + RmnRemote chainlinkapi.RawInstanceAddress `json:"rmnRemote"` + FeeQuoter chainlinkapi.RawInstanceAddress `json:"feeQuoter"` +} + +// ToMap converts LockReleaseTokenPoolDeps to a map for DAML arguments +func (t LockReleaseTokenPoolDeps) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistry"] = model.NestedToDAMLValue(t.TokenAdminRegistry) + + m["rmnRemote"] = model.NestedToDAMLValue(t.RmnRemote) + + m["feeQuoter"] = model.NestedToDAMLValue(t.FeeQuoter) + + return m +} + +func (t LockReleaseTokenPoolDeps) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LockReleaseTokenPoolDeps) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LockReleaseTokenPoolDeps to hex string (Canton MCMS format) +func (t LockReleaseTokenPoolDeps) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LockReleaseTokenPoolDeps from hex string (Canton MCMS format) +func (t *LockReleaseTokenPoolDeps) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RateLimitConfigArgs is a Record type +type RateLimitConfigArgs struct { + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts RateLimitConfigArgs to a map for DAML arguments +func (t RateLimitConfigArgs) ToMap() map[string]any { + m := make(map[string]any) + + m["remoteChainSelector"] = t.RemoteChainSelector + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t RateLimitConfigArgs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RateLimitConfigArgs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RateLimitConfigArgs to hex string (Canton MCMS format) +func (t RateLimitConfigArgs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RateLimitConfigArgs from hex string (Canton MCMS format) +func (t *RateLimitConfigArgs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RateLimiterDeploySpec is a Record type +type RateLimiterDeploySpec struct { + InstanceId types.TEXT `json:"instanceId"` + IsEnabled types.BOOL `json:"isEnabled"` + Capacity types.NUMERIC `json:"capacity"` + Rate types.NUMERIC `json:"rate"` +} + +// ToMap converts RateLimiterDeploySpec to a map for DAML arguments +func (t RateLimiterDeploySpec) ToMap() map[string]any { + m := make(map[string]any) + + m["instanceId"] = string(t.InstanceId) + + m["isEnabled"] = bool(t.IsEnabled) + + m["capacity"] = t.Capacity + + m["rate"] = t.Rate + + return m +} + +func (t RateLimiterDeploySpec) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RateLimiterDeploySpec) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RateLimiterDeploySpec to hex string (Canton MCMS format) +func (t RateLimiterDeploySpec) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RateLimiterDeploySpec from hex string (Canton MCMS format) +func (t *RateLimiterDeploySpec) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ReleaseFromTicket is a Record type +type ReleaseFromTicket struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` + TokenReceiveTicketCid types.CONTRACT_ID `json:"tokenReceiveTicketCid"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts ReleaseFromTicket to a map for DAML arguments +func (t ReleaseFromTicket) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["rmnRemoteCid"] = model.NestedToDAMLValue(t.RmnRemoteCid) + + m["tokenReceiveTicketCid"] = model.NestedToDAMLValue(t.TokenReceiveTicketCid) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t ReleaseFromTicket) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ReleaseFromTicket) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ReleaseFromTicket to hex string (Canton MCMS format) +func (t ReleaseFromTicket) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ReleaseFromTicket from hex string (Canton MCMS format) +func (t *ReleaseFromTicket) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ReleaseFromTicketMCMSParams is ReleaseFromTicket without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type ReleaseFromTicketMCMSParams struct { + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes ReleaseFromTicketMCMSParams to hex string for MCMS operationData. +func (t ReleaseFromTicketMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ReleaseFromTicketMCMSParams from hex string. +func (t *ReleaseFromTicketMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemoteChainConfig is a Record type +type RemoteChainConfig struct { + RemotePools []types.TEXT `json:"remotePools" hex:"[]bytes"` + RemoteTokenAddress types.TEXT `json:"remoteTokenAddress" hex:"bytes"` + InboundCCVs []chainlinkapi.RawInstanceAddress `json:"inboundCCVs"` + OutboundCCVs []chainlinkapi.RawInstanceAddress `json:"outboundCCVs"` + FinalityConfig ccipcodec.FinalityConfig `json:"finalityConfig"` + InboundRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundRateLimiter"` + InboundCustomBlockConfirmationsRateLimiter chainlinkapi.RawInstanceAddress `json:"inboundCustomBlockConfirmationsRateLimiter"` + OutboundRateLimiter chainlinkapi.RawInstanceAddress `json:"outboundRateLimiter"` +} + +// ToMap converts RemoteChainConfig to a map for DAML arguments +func (t RemoteChainConfig) ToMap() map[string]any { + m := make(map[string]any) + + m["remotePools"] = func() []any { + res := make([]any, 0, len(t.RemotePools)) + for _, e := range t.RemotePools { + res = append(res, string(e)) + } + return res + }() + + m["remoteTokenAddress"] = string(t.RemoteTokenAddress) + + m["inboundCCVs"] = func() []any { + res := make([]any, 0, len(t.InboundCCVs)) + for _, e := range t.InboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["outboundCCVs"] = func() []any { + res := make([]any, 0, len(t.OutboundCCVs)) + for _, e := range t.OutboundCCVs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + m["finalityConfig"] = model.NestedToDAMLValue(t.FinalityConfig) + + m["inboundRateLimiter"] = model.NestedToDAMLValue(t.InboundRateLimiter) + + m["inboundCustomBlockConfirmationsRateLimiter"] = model.NestedToDAMLValue(t.InboundCustomBlockConfirmationsRateLimiter) + + m["outboundRateLimiter"] = model.NestedToDAMLValue(t.OutboundRateLimiter) + + return m +} + +func (t RemoteChainConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemoteChainConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemoteChainConfig to hex string (Canton MCMS format) +func (t RemoteChainConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemoteChainConfig from hex string (Canton MCMS format) +func (t *RemoteChainConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemovePoolReceiveContextValue is a Record type +type RemovePoolReceiveContextValue struct { + ContextKey types.TEXT `json:"contextKey"` +} + +// ToMap converts RemovePoolReceiveContextValue to a map for DAML arguments +func (t RemovePoolReceiveContextValue) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + return m +} + +func (t RemovePoolReceiveContextValue) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemovePoolReceiveContextValue) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemovePoolReceiveContextValue to hex string (Canton MCMS format) +func (t RemovePoolReceiveContextValue) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemovePoolReceiveContextValue from hex string (Canton MCMS format) +func (t *RemovePoolReceiveContextValue) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RemovePoolReceiveContextValueParams is a Record type +type RemovePoolReceiveContextValueParams struct { + ContextKey types.TEXT `json:"contextKey"` +} + +// ToMap converts RemovePoolReceiveContextValueParams to a map for DAML arguments +func (t RemovePoolReceiveContextValueParams) ToMap() map[string]any { + m := make(map[string]any) + + m["contextKey"] = string(t.ContextKey) + + return m +} + +func (t RemovePoolReceiveContextValueParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RemovePoolReceiveContextValueParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RemovePoolReceiveContextValueParams to hex string (Canton MCMS format) +func (t RemovePoolReceiveContextValueParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RemovePoolReceiveContextValueParams from hex string (Canton MCMS format) +func (t *RemovePoolReceiveContextValueParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetDynamicConfig is a Record type +type SetDynamicConfig struct { + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` +} + +// ToMap converts SetDynamicConfig to a map for DAML arguments +func (t SetDynamicConfig) ToMap() map[string]any { + m := make(map[string]any) + + if t.RateLimitAdmin != nil { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + return m +} + +func (t SetDynamicConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetDynamicConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetDynamicConfig to hex string (Canton MCMS format) +func (t SetDynamicConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetDynamicConfig from hex string (Canton MCMS format) +func (t *SetDynamicConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetDynamicConfigParams is a Record type +type SetDynamicConfigParams struct { + RateLimitAdmin *types.PARTY `json:"rateLimitAdmin" hex:"optional"` +} + +// ToMap converts SetDynamicConfigParams to a map for DAML arguments +func (t SetDynamicConfigParams) ToMap() map[string]any { + m := make(map[string]any) + + if t.RateLimitAdmin != nil { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": (*t.RateLimitAdmin).ToMap(), + } + } else { + m["rateLimitAdmin"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + return m +} + +func (t SetDynamicConfigParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetDynamicConfigParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetDynamicConfigParams to hex string (Canton MCMS format) +func (t SetDynamicConfigParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetDynamicConfigParams from hex string (Canton MCMS format) +func (t *SetDynamicConfigParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetObservers is a Record type +type SetObservers struct { + Observers []types.PARTY `json:"observers"` +} + +// ToMap converts SetObservers to a map for DAML arguments +func (t SetObservers) ToMap() map[string]any { + m := make(map[string]any) + + m["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return m +} + +func (t SetObservers) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetObservers) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetObservers to hex string (Canton MCMS format) +func (t SetObservers) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetObservers from hex string (Canton MCMS format) +func (t *SetObservers) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetObserversParams is a Record type +type SetObserversParams struct { + Observers []types.PARTY `json:"observers"` +} + +// ToMap converts SetObserversParams to a map for DAML arguments +func (t SetObserversParams) ToMap() map[string]any { + m := make(map[string]any) + + m["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return m +} + +func (t SetObserversParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetObserversParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetObserversParams to hex string (Canton MCMS format) +func (t SetObserversParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetObserversParams from hex string (Canton MCMS format) +func (t *SetObserversParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfig is a Record type +type SetRateLimitConfig struct { + Caller types.PARTY `json:"caller"` + RateLimiterCid types.CONTRACT_ID `json:"rateLimiterCid"` + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// ToMap converts SetRateLimitConfig to a map for DAML arguments +func (t SetRateLimitConfig) ToMap() map[string]any { + m := make(map[string]any) + + m["caller"] = t.Caller.ToMap() + + m["rateLimiterCid"] = model.NestedToDAMLValue(t.RateLimiterCid) + + m["newIsEnabled"] = bool(t.NewIsEnabled) + + m["newCapacity"] = t.NewCapacity + + m["newRate"] = t.NewRate + + return m +} + +func (t SetRateLimitConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimitConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimitConfig to hex string (Canton MCMS format) +func (t SetRateLimitConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfig from hex string (Canton MCMS format) +func (t *SetRateLimitConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfigMCMSParams is SetRateLimitConfig without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type SetRateLimitConfigMCMSParams struct { + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// MarshalHex encodes SetRateLimitConfigMCMSParams to hex string for MCMS operationData. +func (t SetRateLimitConfigMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfigMCMSParams from hex string. +func (t *SetRateLimitConfigMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimitConfigParams is a Record type +type SetRateLimitConfigParams struct { + Caller types.PARTY `json:"caller"` + RateLimiterInstanceAddress chainlinkapi.RawInstanceAddress `json:"rateLimiterInstanceAddress"` + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// ToMap converts SetRateLimitConfigParams to a map for DAML arguments +func (t SetRateLimitConfigParams) ToMap() map[string]any { + m := make(map[string]any) + + m["caller"] = t.Caller.ToMap() + + m["rateLimiterInstanceAddress"] = model.NestedToDAMLValue(t.RateLimiterInstanceAddress) + + m["newIsEnabled"] = bool(t.NewIsEnabled) + + m["newCapacity"] = t.NewCapacity + + m["newRate"] = t.NewRate + + return m +} + +func (t SetRateLimitConfigParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimitConfigParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimitConfigParams to hex string (Canton MCMS format) +func (t SetRateLimitConfigParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimitConfigParams from hex string (Canton MCMS format) +func (t *SetRateLimitConfigParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimiterReferences is a Record type +type SetRateLimiterReferences struct { + RateLimitConfigArgs []RateLimitConfigArgs `json:"rateLimitConfigArgs"` +} + +// ToMap converts SetRateLimiterReferences to a map for DAML arguments +func (t SetRateLimiterReferences) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimitConfigArgs"] = func() []any { + res := make([]any, 0, len(t.RateLimitConfigArgs)) + for _, e := range t.RateLimitConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t SetRateLimiterReferences) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimiterReferences) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimiterReferences to hex string (Canton MCMS format) +func (t SetRateLimiterReferences) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimiterReferences from hex string (Canton MCMS format) +func (t *SetRateLimiterReferences) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetRateLimiterReferencesParams is a Record type +type SetRateLimiterReferencesParams struct { + RateLimitConfigArgs []RateLimitConfigArgs `json:"rateLimitConfigArgs"` +} + +// ToMap converts SetRateLimiterReferencesParams to a map for DAML arguments +func (t SetRateLimiterReferencesParams) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimitConfigArgs"] = func() []any { + res := make([]any, 0, len(t.RateLimitConfigArgs)) + for _, e := range t.RateLimitConfigArgs { + res = append(res, model.NestedToDAMLValue(e)) + } + return res + }() + + return m +} + +func (t SetRateLimiterReferencesParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetRateLimiterReferencesParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetRateLimiterReferencesParams to hex string (Canton MCMS format) +func (t SetRateLimiterReferencesParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetRateLimiterReferencesParams from hex string (Canton MCMS format) +func (t *SetRateLimiterReferencesParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetTransferTimeout is a Record type +type SetTransferTimeout struct { + NewTransferTimeout TransferTimeout `json:"newTransferTimeout"` +} + +// ToMap converts SetTransferTimeout to a map for DAML arguments +func (t SetTransferTimeout) ToMap() map[string]any { + m := make(map[string]any) + + m["newTransferTimeout"] = model.NestedToDAMLValue(t.NewTransferTimeout) + + return m +} + +func (t SetTransferTimeout) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetTransferTimeout) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetTransferTimeout to hex string (Canton MCMS format) +func (t SetTransferTimeout) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetTransferTimeout from hex string (Canton MCMS format) +func (t *SetTransferTimeout) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetTransferTimeoutParams is a Record type +type SetTransferTimeoutParams struct { + TransferTimeout TransferTimeout `json:"transferTimeout"` +} + +// ToMap converts SetTransferTimeoutParams to a map for DAML arguments +func (t SetTransferTimeoutParams) ToMap() map[string]any { + m := make(map[string]any) + + m["transferTimeout"] = model.NestedToDAMLValue(t.TransferTimeout) + + return m +} + +func (t SetTransferTimeoutParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetTransferTimeoutParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetTransferTimeoutParams to hex string (Canton MCMS format) +func (t SetTransferTimeoutParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetTransferTimeoutParams from hex string (Canton MCMS format) +func (t *SetTransferTimeoutParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TokenTransferFeeConfig2 is a Record type +type TokenTransferFeeConfig2 struct { + IsEnabled types.BOOL `json:"isEnabled"` + DestGasOverhead types.INT64 `json:"destGasOverhead"` + DestBytesOverhead types.INT64 `json:"destBytesOverhead"` + FeeUSDCents types.NUMERIC `json:"feeUSDCents"` + FeeBps types.NUMERIC `json:"feeBps"` +} + +// ToMap converts TokenTransferFeeConfig2 to a map for DAML arguments +func (t TokenTransferFeeConfig2) ToMap() map[string]any { + m := make(map[string]any) + + m["isEnabled"] = bool(t.IsEnabled) + + m["destGasOverhead"] = int64(t.DestGasOverhead) + + m["destBytesOverhead"] = int64(t.DestBytesOverhead) + + m["feeUSDCents"] = t.FeeUSDCents + + m["feeBps"] = t.FeeBps + + return m +} + +func (t TokenTransferFeeConfig2) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *TokenTransferFeeConfig2) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes TokenTransferFeeConfig2 to hex string (Canton MCMS format) +func (t TokenTransferFeeConfig2) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes TokenTransferFeeConfig2 from hex string (Canton MCMS format) +func (t *TokenTransferFeeConfig2) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TokenTransferFeeConfigArgs is a Record type +type TokenTransferFeeConfigArgs struct { + DestChainSelector types.NUMERIC `json:"destChainSelector"` + IsEnabled types.BOOL `json:"isEnabled"` + DestGasOverhead types.INT64 `json:"destGasOverhead"` + DestBytesOverhead types.INT64 `json:"destBytesOverhead"` + FeeUSDCents types.NUMERIC `json:"feeUSDCents"` + FeeBps types.NUMERIC `json:"feeBps"` +} + +// ToMap converts TokenTransferFeeConfigArgs to a map for DAML arguments +func (t TokenTransferFeeConfigArgs) ToMap() map[string]any { + m := make(map[string]any) + + m["destChainSelector"] = t.DestChainSelector + + m["isEnabled"] = bool(t.IsEnabled) + + m["destGasOverhead"] = int64(t.DestGasOverhead) + + m["destBytesOverhead"] = int64(t.DestBytesOverhead) + + m["feeUSDCents"] = t.FeeUSDCents + + m["feeBps"] = t.FeeBps + + return m +} + +func (t TokenTransferFeeConfigArgs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *TokenTransferFeeConfigArgs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes TokenTransferFeeConfigArgs to hex string (Canton MCMS format) +func (t TokenTransferFeeConfigArgs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes TokenTransferFeeConfigArgs from hex string (Canton MCMS format) +func (t *TokenTransferFeeConfigArgs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// TransferTimeout is a variant/union type +type TransferTimeout struct { + Indefinite *types.UNIT `json:"Indefinite,omitempty"` + RelativeHours *types.INT64 `json:"RelativeHours,omitempty"` +} + +// MarshalJSON implements custom JSON marshaling for TransferTimeout +func (v TransferTimeout) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(v) +} + +// UnmarshalJSON implements custom JSON unmarshalling for TransferTimeout +func (v *TransferTimeout) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, v) +} + +// MarshalHex encodes TransferTimeout to hex string (Canton MCMS format) +func (v TransferTimeout) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(v) +} + +// UnmarshalHex decodes TransferTimeout from hex string (Canton MCMS format) +func (v *TransferTimeout) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, v) +} + +// GetVariantTag implements types.VARIANT interface +func (v TransferTimeout) GetVariantTag() string { + + if v.Indefinite != nil { + return "Indefinite" + } + + if v.RelativeHours != nil { + return "RelativeHours" + } + + return "" +} + +// GetVariantValue implements types.VARIANT interface +func (v TransferTimeout) GetVariantValue() any { + + if v.Indefinite != nil { + return v.Indefinite + } + + if v.RelativeHours != nil { + return v.RelativeHours + } + + return nil +} + +var _ types.VARIANT = (*TransferTimeout)(nil) + +// VerifyInboundMessage is a Record type +type VerifyInboundMessage struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + ExecutingMessageCid types.CONTRACT_ID `json:"executingMessageCid"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts VerifyInboundMessage to a map for DAML arguments +func (t VerifyInboundMessage) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["executingMessageCid"] = model.NestedToDAMLValue(t.ExecutingMessageCid) + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t VerifyInboundMessage) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *VerifyInboundMessage) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes VerifyInboundMessage to hex string (Canton MCMS format) +func (t VerifyInboundMessage) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyInboundMessage from hex string (Canton MCMS format) +func (t *VerifyInboundMessage) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyInboundMessageMCMSParams is VerifyInboundMessage without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type VerifyInboundMessageMCMSParams struct { + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes VerifyInboundMessageMCMSParams to hex string for MCMS operationData. +func (t VerifyInboundMessageMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyInboundMessageMCMSParams from hex string. +func (t *VerifyInboundMessageMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyOutboundCCVs is a Record type +type VerifyOutboundCCVs struct { + TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` + TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` + SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` + Caller types.PARTY `json:"caller"` +} + +// ToMap converts VerifyOutboundCCVs to a map for DAML arguments +func (t VerifyOutboundCCVs) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminRegistryCid"] = model.NestedToDAMLValue(t.TokenAdminRegistryCid) + + m["tokenConfigCid"] = model.NestedToDAMLValue(t.TokenConfigCid) + + m["sendingMessageCid"] = model.NestedToDAMLValue(t.SendingMessageCid) + + m["amount"] = t.Amount + + m["context"] = model.NestedToDAMLValue(t.Context) + + m["caller"] = t.Caller.ToMap() + + return m +} + +func (t VerifyOutboundCCVs) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *VerifyOutboundCCVs) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes VerifyOutboundCCVs to hex string (Canton MCMS format) +func (t VerifyOutboundCCVs) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyOutboundCCVs from hex string (Canton MCMS format) +func (t *VerifyOutboundCCVs) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// VerifyOutboundCCVsMCMSParams is VerifyOutboundCCVs without the Caller field for MCMS operationData encoding. +// ContractId fields are omitted; pass them via the MCMS targetCids map at execution time. +type VerifyOutboundCCVsMCMSParams struct { + Amount types.NUMERIC `json:"amount"` + Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` +} + +// MarshalHex encodes VerifyOutboundCCVsMCMSParams to hex string for MCMS operationData. +func (t VerifyOutboundCCVsMCMSParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes VerifyOutboundCCVsMCMSParams from hex string. +func (t *VerifyOutboundCCVsMCMSParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// MCMSEncoder interface for typed encoding methods. +// Implemented by Encoder for method-based encoding. +type MCMSEncoder interface { + AddPoolReceiveContextContractValue(args AddPoolReceiveContextContractValue) (*bind.EncodedChoice, error) + AddPoolReceiveContextContractValueParams(args AddPoolReceiveContextContractValueParams) (*bind.EncodedChoice, error) + AddPoolReceiveContextNonContractValue(args AddPoolReceiveContextNonContractValue) (*bind.EncodedChoice, error) + AddPoolReceiveContextNonContractValueParams(args AddPoolReceiveContextNonContractValueParams) (*bind.EncodedChoice, error) + ApplyChainUpdates(args ApplyChainUpdates) (*bind.EncodedChoice, error) + ApplyChainUpdatesParams(args ApplyChainUpdatesParams) (*bind.EncodedChoice, error) + ApplyTokenTransferFeeConfigUpdates(args ApplyTokenTransferFeeConfigUpdates) (*bind.EncodedChoice, error) + ApplyTokenTransferFeeConfigUpdatesParams(args ApplyTokenTransferFeeConfigUpdatesParams) (*bind.EncodedChoice, error) + CalculateFee(args CalculateFee) (*bind.EncodedChoice, error) + CalculateFeeMCMSParams(args CalculateFeeMCMSParams) (*bind.EncodedChoice, error) + ClearPoolReceiveContext(args ClearPoolReceiveContext) (*bind.EncodedChoice, error) + GetFee(args GetFee) (*bind.EncodedChoice, error) + GetFeeMCMSParams(args GetFeeMCMSParams) (*bind.EncodedChoice, error) + GetRequiredCCVs(args GetRequiredCCVs) (*bind.EncodedChoice, error) + GetRequiredCCVsMCMSParams(args GetRequiredCCVsMCMSParams) (*bind.EncodedChoice, error) + Initialize(args Initialize) (*bind.EncodedChoice, error) + LockOrBurn(args LockOrBurn) (*bind.EncodedChoice, error) + LockOrBurnMCMSParams(args LockOrBurnMCMSParams) (*bind.EncodedChoice, error) + ReleaseFromTicket(args ReleaseFromTicket) (*bind.EncodedChoice, error) + ReleaseFromTicketMCMSParams(args ReleaseFromTicketMCMSParams) (*bind.EncodedChoice, error) + RemovePoolReceiveContextValue(args RemovePoolReceiveContextValue) (*bind.EncodedChoice, error) + RemovePoolReceiveContextValueParams(args RemovePoolReceiveContextValueParams) (*bind.EncodedChoice, error) + SetDynamicConfig(args SetDynamicConfig) (*bind.EncodedChoice, error) + SetDynamicConfigParams(args SetDynamicConfigParams) (*bind.EncodedChoice, error) + SetObservers(args SetObservers) (*bind.EncodedChoice, error) + SetObserversParams(args SetObserversParams) (*bind.EncodedChoice, error) + SetRateLimitConfig(args SetRateLimitConfig) (*bind.EncodedChoice, error) + SetRateLimitConfigMCMSParams(args SetRateLimitConfigMCMSParams) (*bind.EncodedChoice, error) + SetRateLimitConfigParams(args SetRateLimitConfigParams) (*bind.EncodedChoice, error) + SetRateLimiterReferences(args SetRateLimiterReferences) (*bind.EncodedChoice, error) + SetRateLimiterReferencesParams(args SetRateLimiterReferencesParams) (*bind.EncodedChoice, error) + SetTransferTimeout(args SetTransferTimeout) (*bind.EncodedChoice, error) + SetTransferTimeoutParams(args SetTransferTimeoutParams) (*bind.EncodedChoice, error) + VerifyInboundMessage(args VerifyInboundMessage) (*bind.EncodedChoice, error) + VerifyInboundMessageMCMSParams(args VerifyInboundMessageMCMSParams) (*bind.EncodedChoice, error) + VerifyOutboundCCVs(args VerifyOutboundCCVs) (*bind.EncodedChoice, error) + VerifyOutboundCCVsMCMSParams(args VerifyOutboundCCVsMCMSParams) (*bind.EncodedChoice, error) +} + +// encoder provides typed encoding methods for choice parameters (unexported). +// It wraps bind.BoundTemplate to encode parameters to hex-encoded operation data. +type encoder struct { + *bind.BoundTemplate +} + +// Contract wraps template operations with Sui-style API access. +// Use NewContract to create instances, then call Encoder() for encoding methods. +type Contract struct { + enc *encoder +} + +// NewContract creates a Contract with encoder for the given template. +// This provides Sui-style API: contract.Encoder().Method(args) +func NewContract(packageID, moduleName, templateName string) *Contract { + return &Contract{ + enc: &encoder{ + BoundTemplate: bind.NewBoundTemplate(packageID, moduleName, templateName), + }, + } +} + +// Encoder returns the encoder for Sui-style contract.Encoder().Method() usage. +func (c *Contract) Encoder() MCMSEncoder { + return c.enc +} + +// AddPoolReceiveContextContractValue encodes parameters for the AddPoolReceiveContextContractValue choice. +func (e *encoder) AddPoolReceiveContextContractValue(args AddPoolReceiveContextContractValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextContractValue", args) +} + +// AddPoolReceiveContextContractValueParams encodes parameters for the AddPoolReceiveContextContractValue choice. +func (e *encoder) AddPoolReceiveContextContractValueParams(args AddPoolReceiveContextContractValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextContractValue", args) +} + +// AddPoolReceiveContextNonContractValue encodes parameters for the AddPoolReceiveContextNonContractValue choice. +func (e *encoder) AddPoolReceiveContextNonContractValue(args AddPoolReceiveContextNonContractValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextNonContractValue", args) +} + +// AddPoolReceiveContextNonContractValueParams encodes parameters for the AddPoolReceiveContextNonContractValue choice. +func (e *encoder) AddPoolReceiveContextNonContractValueParams(args AddPoolReceiveContextNonContractValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("AddPoolReceiveContextNonContractValue", args) +} + +// ApplyChainUpdates encodes parameters for the ApplyChainUpdates choice. +func (e *encoder) ApplyChainUpdates(args ApplyChainUpdates) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyChainUpdates", args) +} + +// ApplyChainUpdatesParams encodes parameters for the ApplyChainUpdates choice. +func (e *encoder) ApplyChainUpdatesParams(args ApplyChainUpdatesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyChainUpdates", args) +} + +// ApplyTokenTransferFeeConfigUpdates encodes parameters for the ApplyTokenTransferFeeConfigUpdates choice. +func (e *encoder) ApplyTokenTransferFeeConfigUpdates(args ApplyTokenTransferFeeConfigUpdates) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyTokenTransferFeeConfigUpdates", args) +} + +// ApplyTokenTransferFeeConfigUpdatesParams encodes parameters for the ApplyTokenTransferFeeConfigUpdates choice. +func (e *encoder) ApplyTokenTransferFeeConfigUpdatesParams(args ApplyTokenTransferFeeConfigUpdatesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApplyTokenTransferFeeConfigUpdates", args) +} + +// CalculateFee encodes parameters for the CalculateFee choice. +func (e *encoder) CalculateFee(args CalculateFee) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CalculateFee", args) +} + +// CalculateFeeMCMSParams encodes MCMS parameters (without Caller) for the CalculateFee choice. +func (e *encoder) CalculateFeeMCMSParams(args CalculateFeeMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CalculateFee", args) +} + +// ClearPoolReceiveContext encodes parameters for the ClearPoolReceiveContext choice. +func (e *encoder) ClearPoolReceiveContext(args ClearPoolReceiveContext) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ClearPoolReceiveContext", args) +} + +// GetFee encodes parameters for the GetFee choice. +func (e *encoder) GetFee(args GetFee) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetFee", args) +} + +// GetFeeMCMSParams encodes MCMS parameters (without Caller) for the GetFee choice. +func (e *encoder) GetFeeMCMSParams(args GetFeeMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetFee", args) +} + +// GetRequiredCCVs encodes parameters for the GetRequiredCCVs choice. +func (e *encoder) GetRequiredCCVs(args GetRequiredCCVs) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetRequiredCCVs", args) +} + +// GetRequiredCCVsMCMSParams encodes MCMS parameters (without Caller) for the GetRequiredCCVs choice. +func (e *encoder) GetRequiredCCVsMCMSParams(args GetRequiredCCVsMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("GetRequiredCCVs", args) +} + +// Initialize encodes parameters for the Initialize choice. +func (e *encoder) Initialize(args Initialize) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("Initialize", args) +} + +// LockOrBurn encodes parameters for the LockOrBurn choice. +func (e *encoder) LockOrBurn(args LockOrBurn) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("LockOrBurn", args) +} + +// LockOrBurnMCMSParams encodes MCMS parameters (without Caller) for the LockOrBurn choice. +func (e *encoder) LockOrBurnMCMSParams(args LockOrBurnMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("LockOrBurn", args) +} + +// ReleaseFromTicket encodes parameters for the ReleaseFromTicket choice. +func (e *encoder) ReleaseFromTicket(args ReleaseFromTicket) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ReleaseFromTicket", args) +} + +// ReleaseFromTicketMCMSParams encodes MCMS parameters (without Caller) for the ReleaseFromTicket choice. +func (e *encoder) ReleaseFromTicketMCMSParams(args ReleaseFromTicketMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ReleaseFromTicket", args) +} + +// RemovePoolReceiveContextValue encodes parameters for the RemovePoolReceiveContextValue choice. +func (e *encoder) RemovePoolReceiveContextValue(args RemovePoolReceiveContextValue) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("RemovePoolReceiveContextValue", args) +} + +// RemovePoolReceiveContextValueParams encodes parameters for the RemovePoolReceiveContextValue choice. +func (e *encoder) RemovePoolReceiveContextValueParams(args RemovePoolReceiveContextValueParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("RemovePoolReceiveContextValue", args) +} + +// SetDynamicConfig encodes parameters for the SetDynamicConfig choice. +func (e *encoder) SetDynamicConfig(args SetDynamicConfig) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetDynamicConfig", args) +} + +// SetDynamicConfigParams encodes parameters for the SetDynamicConfig choice. +func (e *encoder) SetDynamicConfigParams(args SetDynamicConfigParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetDynamicConfig", args) +} + +// SetObservers encodes parameters for the SetObservers choice. +func (e *encoder) SetObservers(args SetObservers) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetObservers", args) +} + +// SetObserversParams encodes parameters for the SetObservers choice. +func (e *encoder) SetObserversParams(args SetObserversParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetObservers", args) +} + +// SetRateLimitConfig encodes parameters for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfig(args SetRateLimitConfig) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimitConfigMCMSParams encodes MCMS parameters (without Caller) for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfigMCMSParams(args SetRateLimitConfigMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimitConfigParams encodes parameters for the SetRateLimitConfig choice. +func (e *encoder) SetRateLimitConfigParams(args SetRateLimitConfigParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimitConfig", args) +} + +// SetRateLimiterReferences encodes parameters for the SetRateLimiterReferences choice. +func (e *encoder) SetRateLimiterReferences(args SetRateLimiterReferences) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimiterReferences", args) +} + +// SetRateLimiterReferencesParams encodes parameters for the SetRateLimiterReferences choice. +func (e *encoder) SetRateLimiterReferencesParams(args SetRateLimiterReferencesParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetRateLimiterReferences", args) +} + +// SetTransferTimeout encodes parameters for the SetTransferTimeout choice. +func (e *encoder) SetTransferTimeout(args SetTransferTimeout) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetTransferTimeout", args) +} + +// SetTransferTimeoutParams encodes parameters for the SetTransferTimeout choice. +func (e *encoder) SetTransferTimeoutParams(args SetTransferTimeoutParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetTransferTimeout", args) +} + +// VerifyInboundMessage encodes parameters for the VerifyInboundMessage choice. +func (e *encoder) VerifyInboundMessage(args VerifyInboundMessage) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyInboundMessage", args) +} + +// VerifyInboundMessageMCMSParams encodes MCMS parameters (without Caller) for the VerifyInboundMessage choice. +func (e *encoder) VerifyInboundMessageMCMSParams(args VerifyInboundMessageMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyInboundMessage", args) +} + +// VerifyOutboundCCVs encodes parameters for the VerifyOutboundCCVs choice. +func (e *encoder) VerifyOutboundCCVs(args VerifyOutboundCCVs) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyOutboundCCVs", args) +} + +// VerifyOutboundCCVsMCMSParams encodes MCMS parameters (without Caller) for the VerifyOutboundCCVs choice. +func (e *encoder) VerifyOutboundCCVsMCMSParams(args VerifyOutboundCCVsMCMSParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("VerifyOutboundCCVs", args) +} + +// Verify MCMSEncoder interface implementation +var _ MCMSEncoder = (*encoder)(nil) diff --git a/contracts/bindings/generated/ccip/registry/ratelimiter/ratelimiter.go b/contracts/bindings/generated/ccip/registry/ratelimiter/ratelimiter.go new file mode 100644 index 000000000..be6020f12 --- /dev/null +++ b/contracts/bindings/generated/ccip/registry/ratelimiter/ratelimiter.go @@ -0,0 +1,635 @@ +package ratelimiter + +import ( + "errors" + "fmt" + "math/big" + "strings" + + api "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/mcms/api" + "github.com/smartcontractkit/go-daml/pkg/bind" + "github.com/smartcontractkit/go-daml/pkg/codec" + "github.com/smartcontractkit/go-daml/pkg/model" + "github.com/smartcontractkit/go-daml/pkg/types" +) + +var ( + _ = fmt.Sprintf + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = model.Command{} + _ bind.BoundTemplate +) + +const ( + PackageName = "ccip-registry-rate-limiter-v2" + PackageID = "6856206c569bf6c13704eb5cd3fedecb64245fce1af80898b4ddf6580f51fa92" + SDKVersion = "3.4.11" +) + +type Template interface { + CreateCommand() *model.CreateCommand + GetTemplateID() string +} + +const ( + RateLimiterContextKey = types.TEXT("rate-limiter") +) + +func argsToMap(args any) map[string]any { + if args == nil { + return map[string]any{} + } + + if m, ok := args.(map[string]any); ok { + return m + } + + type mapper interface { + ToMap() map[string]any + } + if mapper, ok := args.(mapper); ok { + return mapper.ToMap() + } + + return map[string]any{"args": args} +} + +// ConsumeCapacity is a Record type +type ConsumeCapacity struct { + Requested types.NUMERIC `json:"requested"` +} + +// ToMap converts ConsumeCapacity to a map for DAML arguments +func (t ConsumeCapacity) ToMap() map[string]any { + m := make(map[string]any) + + m["requested"] = t.Requested + + return m +} + +func (t ConsumeCapacity) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ConsumeCapacity) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ConsumeCapacity to hex string (Canton MCMS format) +func (t ConsumeCapacity) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ConsumeCapacity from hex string (Canton MCMS format) +func (t *ConsumeCapacity) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ConsumeCapacityResult is a Record type +type ConsumeCapacityResult struct { + RateLimiterCid types.CONTRACT_ID `json:"rateLimiterCid"` + AvailableBeforeConsume types.NUMERIC `json:"availableBeforeConsume"` + Consumed types.NUMERIC `json:"consumed"` +} + +// ToMap converts ConsumeCapacityResult to a map for DAML arguments +func (t ConsumeCapacityResult) ToMap() map[string]any { + m := make(map[string]any) + + m["rateLimiterCid"] = model.NestedToDAMLValue(t.RateLimiterCid) + + m["availableBeforeConsume"] = t.AvailableBeforeConsume + + m["consumed"] = t.Consumed + + return m +} + +func (t ConsumeCapacityResult) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ConsumeCapacityResult) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ConsumeCapacityResult to hex string (Canton MCMS format) +func (t ConsumeCapacityResult) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ConsumeCapacityResult from hex string (Canton MCMS format) +func (t *ConsumeCapacityResult) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// RateLimitDirection is an enum type +type RateLimitDirection string + +const ( + RateLimitDirectionRateLimitDirection_Outbound RateLimitDirection = "RateLimitDirection_Outbound" + + RateLimitDirectionRateLimitDirection_Inbound RateLimitDirection = "RateLimitDirection_Inbound" +) + +func (e RateLimitDirection) GetEnumConstructor() string { return string(e) } + +func (e RateLimitDirection) GetEnumTypeID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimitDirection") +} + +// GetEnumTypeIDWithPackageID returns the enum type ID using the provided package ID instead of package name +func (e RateLimitDirection) GetEnumTypeIDWithPackageID(packageID string) string { + return fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimitDirection") +} + +func (e RateLimitDirection) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(e) +} + +func (e *RateLimitDirection) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, e) +} + +// MarshalHex encodes RateLimitDirection to hex string (Canton MCMS format) +func (e RateLimitDirection) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(e) +} + +// UnmarshalHex decodes RateLimitDirection from hex string (Canton MCMS format) +func (e *RateLimitDirection) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, e) +} + +var _ types.ENUM = RateLimitDirection("") + +// RateLimitMode is an enum type +type RateLimitMode string + +const ( + RateLimitModeRateLimitMode_DefaultFinality RateLimitMode = "RateLimitMode_DefaultFinality" + + RateLimitModeRateLimitMode_CustomFinality RateLimitMode = "RateLimitMode_CustomFinality" +) + +func (e RateLimitMode) GetEnumConstructor() string { return string(e) } + +func (e RateLimitMode) GetEnumTypeID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimitMode") +} + +// GetEnumTypeIDWithPackageID returns the enum type ID using the provided package ID instead of package name +func (e RateLimitMode) GetEnumTypeIDWithPackageID(packageID string) string { + return fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimitMode") +} + +func (e RateLimitMode) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(e) +} + +func (e *RateLimitMode) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, e) +} + +// MarshalHex encodes RateLimitMode to hex string (Canton MCMS format) +func (e RateLimitMode) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(e) +} + +// UnmarshalHex decodes RateLimitMode from hex string (Canton MCMS format) +func (e *RateLimitMode) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, e) +} + +var _ types.ENUM = RateLimitMode("") + +// RateLimiter is a Template type +type RateLimiter struct { + InstanceId types.TEXT `json:"instanceId"` + PoolInstanceId types.TEXT `json:"poolInstanceId"` + PoolOwner types.PARTY `json:"poolOwner"` + RemoteChainSelector types.NUMERIC `json:"remoteChainSelector"` + Direction RateLimitDirection `json:"direction"` + Mode RateLimitMode `json:"mode"` + IsEnabled types.BOOL `json:"isEnabled"` + Capacity types.NUMERIC `json:"capacity"` + Rate types.NUMERIC `json:"rate"` + Tokens types.NUMERIC `json:"tokens"` + LastUpdated types.TIMESTAMP `json:"lastUpdated"` + Observers []types.PARTY `json:"observers"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t RateLimiter) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimiter") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t RateLimiter) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimiter") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t RateLimiter) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolInstanceId"] = string(t.PoolInstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + if t.RemoteChainSelector != "" { + args["remoteChainSelector"] = t.RemoteChainSelector + } + + if t.Direction != "" { + args["direction"] = model.NestedToDAMLValue(t.Direction) + } + + if t.Mode != "" { + args["mode"] = model.NestedToDAMLValue(t.Mode) + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["isEnabled"] = bool(t.IsEnabled) + + if t.Capacity != "" { + args["capacity"] = t.Capacity + } + + if t.Rate != "" { + args["rate"] = t.Rate + } + + if t.Tokens != "" { + args["tokens"] = t.Tokens + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["lastUpdated"] = t.LastUpdated + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t RateLimiter) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["instanceId"] = string(t.InstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolInstanceId"] = string(t.PoolInstanceId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["poolOwner"] = t.PoolOwner.ToMap() + + if t.RemoteChainSelector != "" { + args["remoteChainSelector"] = t.RemoteChainSelector + } + + if t.Direction != "" { + args["direction"] = model.NestedToDAMLValue(t.Direction) + } + + if t.Mode != "" { + args["mode"] = model.NestedToDAMLValue(t.Mode) + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["isEnabled"] = bool(t.IsEnabled) + + if t.Capacity != "" { + args["capacity"] = t.Capacity + } + + if t.Rate != "" { + args["rate"] = t.Rate + } + + if t.Tokens != "" { + args["tokens"] = t.Tokens + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["lastUpdated"] = t.LastUpdated + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t RateLimiter) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *RateLimiter) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes RateLimiter to hex string (Canton MCMS format) +func (t RateLimiter) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes RateLimiter from hex string (Canton MCMS format) +func (t *RateLimiter) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for RateLimiter + +// ConsumeCapacity exercises the ConsumeCapacity choice on this RateLimiter contract +// This method uses the package name in the template ID +func (t RateLimiter) ConsumeCapacity(contractID string, args ConsumeCapacity) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "ConsumeCapacity", + Arguments: argsToMap(args), + } +} + +// ConsumeCapacityWithPackageID exercises the ConsumeCapacity choice using the provided package ID instead of package name +func (t RateLimiter) ConsumeCapacityWithPackageID(contractID string, packageID string, args ConsumeCapacity) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "ConsumeCapacity", + Arguments: argsToMap(args), + } +} + +// SetConfig exercises the SetConfig choice on this RateLimiter contract +// This method uses the package name in the template ID +func (t RateLimiter) SetConfig(contractID string, args SetConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "SetConfig", + Arguments: argsToMap(args), + } +} + +// SetConfigWithPackageID exercises the SetConfig choice using the provided package ID instead of package name +func (t RateLimiter) SetConfigWithPackageID(contractID string, packageID string, args SetConfig) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "SetConfig", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this RateLimiter contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t RateLimiter) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t RateLimiter) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// SetObservers exercises the SetObservers choice on this RateLimiter contract +// This method uses the package name in the template ID +func (t RateLimiter) SetObservers(contractID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// SetObserversWithPackageID exercises the SetObservers choice using the provided package ID instead of package name +func (t RateLimiter) SetObserversWithPackageID(contractID string, packageID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "RateLimiter"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypoint exercises the MCMSReceiver_Entrypoint choice on this RateLimiter contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t RateLimiter) MCMSReceiverEntrypoint(contractID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "CCIP.Registry.RateLimiterV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// MCMSReceiverEntrypointWithPackageID exercises the MCMSReceiver_Entrypoint choice using the provided package ID instead of package name +func (t RateLimiter) MCMSReceiverEntrypointWithPackageID(contractID string, packageID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "CCIP.Registry.RateLimiterV2", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// Verify interface implementations for RateLimiter + +var _ api.IMCMSReceiver = (*RateLimiter)(nil) + +// SetConfig is a Record type +type SetConfig struct { + NewIsEnabled types.BOOL `json:"newIsEnabled"` + NewCapacity types.NUMERIC `json:"newCapacity"` + NewRate types.NUMERIC `json:"newRate"` +} + +// ToMap converts SetConfig to a map for DAML arguments +func (t SetConfig) ToMap() map[string]any { + m := make(map[string]any) + + m["newIsEnabled"] = bool(t.NewIsEnabled) + + m["newCapacity"] = t.NewCapacity + + m["newRate"] = t.NewRate + + return m +} + +func (t SetConfig) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetConfig) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetConfig to hex string (Canton MCMS format) +func (t SetConfig) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetConfig from hex string (Canton MCMS format) +func (t *SetConfig) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetObservers is a Record type +type SetObservers struct { + Observers []types.PARTY `json:"observers"` +} + +// ToMap converts SetObservers to a map for DAML arguments +func (t SetObservers) ToMap() map[string]any { + m := make(map[string]any) + + m["observers"] = func() []any { + res := make([]any, 0, len(t.Observers)) + for _, e := range t.Observers { + res = append(res, e.ToMap()) + } + return res + }() + + return m +} + +func (t SetObservers) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetObservers) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetObservers to hex string (Canton MCMS format) +func (t SetObservers) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetObservers from hex string (Canton MCMS format) +func (t *SetObservers) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// MCMSEncoder interface for typed encoding methods. +// Implemented by Encoder for method-based encoding. +type MCMSEncoder interface { + ConsumeCapacity(args ConsumeCapacity) (*bind.EncodedChoice, error) + SetConfig(args SetConfig) (*bind.EncodedChoice, error) + SetObservers(args SetObservers) (*bind.EncodedChoice, error) +} + +// encoder provides typed encoding methods for choice parameters (unexported). +// It wraps bind.BoundTemplate to encode parameters to hex-encoded operation data. +type encoder struct { + *bind.BoundTemplate +} + +// Contract wraps template operations with Sui-style API access. +// Use NewContract to create instances, then call Encoder() for encoding methods. +type Contract struct { + enc *encoder +} + +// NewContract creates a Contract with encoder for the given template. +// This provides Sui-style API: contract.Encoder().Method(args) +func NewContract(packageID, moduleName, templateName string) *Contract { + return &Contract{ + enc: &encoder{ + BoundTemplate: bind.NewBoundTemplate(packageID, moduleName, templateName), + }, + } +} + +// Encoder returns the encoder for Sui-style contract.Encoder().Method() usage. +func (c *Contract) Encoder() MCMSEncoder { + return c.enc +} + +// ConsumeCapacity encodes parameters for the ConsumeCapacity choice. +func (e *encoder) ConsumeCapacity(args ConsumeCapacity) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ConsumeCapacity", args) +} + +// SetConfig encodes parameters for the SetConfig choice. +func (e *encoder) SetConfig(args SetConfig) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetConfig", args) +} + +// SetObservers encodes parameters for the SetObservers choice. +func (e *encoder) SetObservers(args SetObservers) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetObservers", args) +} + +// Verify MCMSEncoder interface implementation +var _ MCMSEncoder = (*encoder)(nil) diff --git a/contracts/ccip/registry-pools/burn-mint-token-pool/daml.yaml b/contracts/ccip/registry-pools/burn-mint-token-pool/daml.yaml new file mode 100644 index 000000000..8eefab160 --- /dev/null +++ b/contracts/ccip/registry-pools/burn-mint-token-pool/daml.yaml @@ -0,0 +1,25 @@ +sdk-version: 3.4.11 +name: ccip-registry-burn-mint-token-pool-v2 +source: daml +version: 2.1.1 +build-options: + - "-Wno-crypto-text-is-alpha" + - "-Werror=unused-dependency" +data-dependencies: + - ../../../dependencies/splice/splice-api-token-metadata-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-holding-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-burn-mint-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-transfer-instruction-v1-1.0.0.dar + - ../../../mcms/api/.daml/dist/mcms-api-1.0.0.dar + - ../../../chainlink/api/.daml/dist/chainlink-api-2.0.0.dar + - ../../codec/.daml/dist/ccip-codec-v2-2.0.0.dar + - ../../api/.daml/dist/ccip-api-v2-2.0.0.dar + - ../../extension-api/.daml/dist/ccip-extension-api-v2-2.0.0.dar + - ../../events/.daml/dist/ccip-events-v2-2.0.0.dar + - ../../tickets/.daml/dist/ccip-tickets-v2-2.0.0.dar + - ../../core/.daml/dist/ccip-core-v2-2.1.1.dar + - ../../utils/.daml/dist/ccip-utils-v2-2.1.0.dar + - ../rate-limiter/.daml/dist/ccip-registry-rate-limiter-v2-2.0.1.dar +dependencies: + - daml-prim + - daml-stdlib diff --git a/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2.daml b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2.daml new file mode 100644 index 000000000..300fc1cea --- /dev/null +++ b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2.daml @@ -0,0 +1,1437 @@ +-- | Burn/mint token pool for CCIP token transfers. +-- +-- Supports Splice token standard patterns where TransferFactory implementations +-- may look up preapproval from extraArgs.context (e.g., Amulet's ExternalPartyAmuletRules +-- uses "transfer-preapproval" key). The pool stores a poolReceiveContext that gets +-- merged into extraArgs for inbound transfers, enabling direct transfers without +-- pending state when the pool has preapproval. +module CCIP.Registry.BurnMintTokenPoolV2 ( + module CCIP.Registry.BurnMintTokenPoolV2 + , module CCIP.Registry.BurnMintTokenPoolV2Types + ) where + +import DA.Action (foldlA) +import DA.Crypto.Text (BytesHex, byteCount) +import DA.Foldable qualified as Foldable +import DA.List (dedup) +import qualified DA.Map as Map +import DA.Optional (fromOptional, fromSomeNote) +import qualified DA.TextMap as TextMap +import DA.Foldable (forA_) + +import qualified Splice.Api.Token.BurnMintV1 +import qualified Splice.Api.Token.HoldingV1 +import qualified Splice.Api.Token.MetadataV1 + +import qualified Chainlink.InstanceAddress + +import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiverView(..)) + +import qualified CCIP.APIV2.ExecutingMessage +import qualified CCIP.APIV2.FeeQuoter +import qualified CCIP.APIV2.RMNRemote +import qualified CCIP.APIV2.SendingMessage +import qualified CCIP.APIV2.TokenAdminRegistry +import qualified CCIP.CodecV2.FinalityConfig +import qualified CCIP.CodecV2.MessageCodecV1 +import qualified CCIP.CodecV2.Math +import qualified CCIP.CodecV2.Uint256 +import qualified CCIP.CoreV2.TokenAdminRegistry as TAR +import CCIP.CoreV2.TokenAdminRegistryTypes (PoolRegistration(..)) +import qualified CCIP.EventsV2.Events +import qualified CCIP.InterfacesV2.TokenPool +import qualified CCIP.Registry.RateLimiterV2 +import qualified CCIP.TicketsV2 +import qualified CCIP.UtilsV2.Context + +import CCIP.Registry.BurnMintTokenPoolV2CodecGen ( + decodeSetDynamicConfigParamsAt, + decodeSetObserversParamsAt, + decodeApplyChainUpdatesParamsAt, + decodeApplyTokenTransferFeeConfigUpdatesParamsAt, + decodeSetRateLimiterReferencesParamsAt, + decodeSetRateLimitConfigParamsAt, + decodeSetTransferTimeoutParamsAt, + decodeAddPoolReceiveContextNonContractValueParamsAt, + decodeAddPoolReceiveContextContractValueParamsAt, + decodeRemovePoolReceiveContextValueParamsAt, + ) +import CCIP.Registry.BurnMintTokenPoolV2Types ( + TokenTransferFeeConfig(..), + RemoteChainConfig(..), + ChainUpdate(..), + TokenTransferFeeConfigArgs(..), + RateLimitConfigArgs(..), + TransferTimeout(..), + SetDynamicConfigParams(..), + SetObserversParams(..), + ApplyChainUpdatesParams(..), + ApplyTokenTransferFeeConfigUpdatesParams(..), + SetRateLimiterReferencesParams(..), + SetRateLimitConfigParams(..), + SetTransferTimeoutParams(..), + AddPoolReceiveContextNonContractValueParams(..), + AddPoolReceiveContextContractValueParams(..), + RemovePoolReceiveContextValueParams(..), + RateLimiterDeploySpec(..), + LaneDeploySpec(..), + ) + +burnMintFactoryContextKey : Text +burnMintFactoryContextKey = "burn-mint-factory" + +lookupBurnMintFactory : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (ContractId Splice.Api.Token.BurnMintV1.BurnMintFactory) +lookupBurnMintFactory ctx = CCIP.UtilsV2.Context.getFromContextU ctx burnMintFactoryContextKey + +burnMintFactoryExtraArgsContextValuesContextKey : Text +burnMintFactoryExtraArgsContextValuesContextKey = "burn-mint-factory-extra-args-values" + +lookupBurnMintFactoryExtraArgsContextValues : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (TextMap.TextMap Splice.Api.Token.MetadataV1.AnyValue) +lookupBurnMintFactoryExtraArgsContextValues ctx = fromOptional TextMap.empty <$> CCIP.UtilsV2.Context.lookupFromContextU ctx burnMintFactoryExtraArgsContextValuesContextKey + +burnMintFactoryExtraArgsMetaValuesContextKey : Text +burnMintFactoryExtraArgsMetaValuesContextKey = "burn-mint-factory-extra-args-meta-values" + +lookupBurnMintFactoryExtraArgsMetaValues : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (TextMap.TextMap Text) +lookupBurnMintFactoryExtraArgsMetaValues ctx = fromOptional TextMap.empty <$> CCIP.UtilsV2.Context.lookupFromContextU ctx burnMintFactoryExtraArgsMetaValuesContextKey + +data BurnMintTokenPoolDeps = BurnMintTokenPoolDeps + with + tokenAdminRegistry : Chainlink.InstanceAddress.RawInstanceAddress + rmnRemote : Chainlink.InstanceAddress.RawInstanceAddress + feeQuoter : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +parseSourcePoolDecimals : Int -> BytesHex -> Int +parseSourcePoolDecimals localDecimals sourcePoolData = + if sourcePoolData == "" + then localDecimals + else + let decoded = fromSomeNote + "ReleaseFromTicket: invalid source pool decimals" + (CCIP.CodecV2.MessageCodecV1.decodeUint256 sourcePoolData) + in + if decoded < 0.0 || decoded > 255.0 + then error "ReleaseFromTicket: invalid source pool decimals" + else truncate decoded + +calculateLocalAmount : Numeric 0 -> Int -> Int -> Numeric 0 +calculateLocalAmount remoteAmount remoteDecimals localDecimals = + CCIP.CodecV2.Math.calculateLocalAmountChecked + "BurnMintTokenPool.calculateLocalAmount" + remoteAmount + remoteDecimals + localDecimals + +assertHoldingMatches + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> ContractId Splice.Api.Token.HoldingV1.Holding + -> Update Splice.Api.Token.HoldingV1.HoldingView +assertHoldingMatches prefix expectedOwner expectedInstrumentId cid = do + holding <- fetch cid + let hv = view holding + assertMsg (prefix <> ": holding owner mismatch") + (hv.owner == expectedOwner) + assertMsg (prefix <> ": holding instrumentId mismatch") + (hv.instrumentId == expectedInstrumentId) + pure hv + +sumCheckedHoldingAmounts + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> [ContractId Splice.Api.Token.HoldingV1.Holding] + -> Update Decimal +sumCheckedHoldingAmounts prefix expectedOwner expectedInstrumentId = + foldlA + (\acc cid -> do + hv <- assertHoldingMatches prefix expectedOwner expectedInstrumentId cid + pure (acc + hv.amount)) + 0.0 + +assertSingleHolding + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> Decimal + -> [ContractId Splice.Api.Token.HoldingV1.Holding] + -> Update (ContractId Splice.Api.Token.HoldingV1.Holding) +assertSingleHolding prefix expectedOwner expectedInstrumentId expectedAmount cids = + case cids of + [cid] -> do + hv <- assertHoldingMatches prefix expectedOwner expectedInstrumentId cid + assertMsg (prefix <> ": holding amount mismatch") + (hv.amount == expectedAmount) + pure cid + _ -> abort (prefix <> ": expected exactly one holding") + +validateLockOrBurnOutputs + : Party + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> Decimal + -> Decimal + -> [ContractId Splice.Api.Token.HoldingV1.Holding] + -> Update + ( [ContractId Splice.Api.Token.HoldingV1.Holding] + , [ContractId Splice.Api.Token.HoldingV1.Holding] + ) +validateLockOrBurnOutputs caller poolOwner instrumentId senderChangeAmount feeHoldingAmount outputCids = + case (senderChangeAmount > 0.0, feeHoldingAmount > 0.0, outputCids) of + (False, False, []) -> + pure ([], []) + (True, False, [senderChangeCid]) -> do + _ <- assertSingleHolding + "LockOrBurn: sender change holding" + caller + instrumentId + senderChangeAmount + [senderChangeCid] + pure ([senderChangeCid], []) + (False, True, [poolFeeCid]) -> do + _ <- assertSingleHolding + "LockOrBurn: pool fee holding" + poolOwner + instrumentId + feeHoldingAmount + [poolFeeCid] + pure ([], [poolFeeCid]) + (True, True, [senderChangeCid, poolFeeCid]) -> do + _ <- assertSingleHolding + "LockOrBurn: sender change holding" + caller + instrumentId + senderChangeAmount + [senderChangeCid] + _ <- assertSingleHolding + "LockOrBurn: pool fee holding" + poolOwner + instrumentId + feeHoldingAmount + [poolFeeCid] + pure ([senderChangeCid], [poolFeeCid]) + _ -> abort "LockOrBurn: burn/mint output shape mismatch" + +remoteChainRateLimiterAddresses : Map.Map (Numeric 0) RemoteChainConfig -> [Chainlink.InstanceAddress.RawInstanceAddress] +remoteChainRateLimiterAddresses configs = + concatMap + (\(_, cfg) -> + [ cfg.inboundRateLimiter + , cfg.inboundCustomBlockConfirmationsRateLimiter + , cfg.outboundRateLimiter + ]) + (Map.toList configs) + +assertPoolConfiguredRateLimiter + : Text + -> Party + -> Map.Map (Numeric 0) RemoteChainConfig + -> ContractId CCIP.Registry.RateLimiterV2.RateLimiter + -> Update () +assertPoolConfiguredRateLimiter poolInstanceId poolOwner remoteChainConfigs rateLimiterCid = do + rl <- fetch rateLimiterCid + assertMsg "SetRateLimitConfig: rate limiter not configured on pool" + (CCIP.Registry.RateLimiterV2.rawInstanceAddress rl `elem` remoteChainRateLimiterAddresses remoteChainConfigs) + assertMsg "SetRateLimitConfig: pool instance mismatch" (rl.poolInstanceId == poolInstanceId) + assertMsg "SetRateLimitConfig: pool owner mismatch" (rl.poolOwner == poolOwner) + +assertRateLimiter + : Text + -> Text + -> Numeric 0 + -> CCIP.Registry.RateLimiterV2.RateLimitDirection + -> CCIP.Registry.RateLimiterV2.RateLimitMode + -> Chainlink.InstanceAddress.RawInstanceAddress + -> CCIP.Registry.RateLimiterV2.RateLimiter + -> Update () +assertRateLimiter prefix poolInstanceId remoteChainSelector direction mode expected limiter = do + Chainlink.InstanceAddress.assertEqual (prefix <> ": instance address mismatch") + limiter.instanceId + limiter.poolOwner + expected + assertMsg (prefix <> ": pool instance mismatch") + (limiter.poolInstanceId == poolInstanceId) + assertMsg (prefix <> ": remote chain mismatch") + (limiter.remoteChainSelector == remoteChainSelector) + assertMsg (prefix <> ": direction mismatch") + (limiter.direction == direction) + assertMsg (prefix <> ": rate limit mode mismatch") + (limiter.mode == mode) + +bpsDenominator : Numeric 0 +bpsDenominator = 10000.0 + +calculateFeeFromBps : Numeric 0 -> Numeric 0 -> Numeric 0 +calculateFeeFromBps amount feeBps = CCIP.CodecV2.Math.mulDivDown amount feeBps bpsDenominator + +customFinalityRateLimiterRequired : CCIP.CodecV2.FinalityConfig.FinalityConfig -> Bool +customFinalityRateLimiterRequired finalityConfig = + CCIP.CodecV2.FinalityConfig.customFinalityRateLimiterRequired finalityConfig + +deployRateLimiter + : Party + -> Text + -> [Party] + -> Numeric 0 + -> CCIP.Registry.RateLimiterV2.RateLimitDirection + -> CCIP.Registry.RateLimiterV2.RateLimitMode + -> Time + -> RateLimiterDeploySpec + -> Update (ContractId CCIP.Registry.RateLimiterV2.RateLimiter) +deployRateLimiter poolOwner poolInstanceId observers remoteChainSelector direction mode now spec = + create CCIP.Registry.RateLimiterV2.RateLimiter with + instanceId = spec.instanceId + poolInstanceId + poolOwner + remoteChainSelector + direction + mode + isEnabled = spec.isEnabled + capacity = spec.capacity + rate = spec.rate + tokens = spec.capacity + lastUpdated = now + observers + +-- | Result of Initialize: the rate limiters deployed and the TokenConfig registered with TAR. +data InitializeResult = InitializeResult + with + rateLimiterCids : [ContractId CCIP.Registry.RateLimiterV2.RateLimiter] + tokenConfigCid : ContractId TAR.TokenConfig + deriving (Eq, Show) + +-- | Burn/mint token pool with dynamic CCV requirements per chain. +template BurnMintTokenPool + with + instanceId : Text -- Unique identifier for this pool instance + poolOwner : Party + + ccipOwner : Party + instrumentId : Splice.Api.Token.HoldingV1.InstrumentId + -- | The (local) decimals that the token pool will assume. + -- Note: As the Token Standard uses Decimal with a fixed 10 decimals, setting anything but 10 decimals on the pool will result in the local amounts being scaled up/down. + -- That is, if the pool is set to 8 decimals, a remote amount of 1.0 will be scaled to Decimal("0.01") when minting/burning locally. + -- And vice versa, if the pool is set to 12 decimals, a remote amount of 1.0 will be scaled to Decimal("100.0") when minting/burning locally. + -- This is an advanced setting that should only be used if the asset requires additional precision beyond the 10 decimals of the Token Standard, for example with an remote asset that has 18 decimals and requires all of this precision to be preserved when transferring to Canton. + -- In general, it is recommended to set the pool decimals to 10, which is the default for the Token Standard and will lead to all amounts being represented correctly locally and in Wallets. + decimals : Int + + -- | Dynamic config + -- Party capable of updating rate limits for this pool. + rateLimitAdmin : Optional Party + -- Observer parties used by the External Disclosure Service (EDS) to auto-detect this + -- pool. Mandatory - must contain at least one party, enforced by the ensure clause + -- below. Set at creation or via SetObservers; cannot be added retroactively to a + -- pool deployed on a package version that predates this field. + observers : [Party] + + -- Remote chain config: remote token address, CCV requirements, and rate limiter requirements for each remote chain. + -- Keyed by remote chain selector. + remoteChainConfigs : Map.Map (Numeric 0) RemoteChainConfig + -- Optional token-transfer fee overrides keyed by destination chain selector. + tokenTransferFeeConfigs : Map.Map (Numeric 0) TokenTransferFeeConfig + + -- Context merged into extraArgs for transfers TO the pool (LockOrBurn). + -- For tokens following Splice patterns (e.g., Amulet), this should contain + -- the pool's preapproval CID under the key the TransferFactory expects + -- (e.g., "transfer-preapproval"). Converted to Splice ChoiceContext at the + -- TransferFactory boundary. + poolReceiveContext : Splice.Api.Token.MetadataV1.ChoiceContext + -- Timeout for pending TransferInstructions created by this pool. + transferTimeout : TransferTimeout + deps : BurnMintTokenPoolDeps + where + signatory poolOwner + observer optional [] pure rateLimitAdmin <> observers + + ensure Chainlink.InstanceAddress.assertValidInstanceId instanceId + && instrumentId.admin == poolOwner + && not (null observers) + && CCIP.CodecV2.Math.isValidTokenDecimals decimals + && all + (\cfg -> CCIP.CodecV2.FinalityConfig.isValidFinalityConfig cfg.finalityConfig) + (Map.values remoteChainConfigs) + && all + (\cfg -> + cfg.feeBps >= 0.0 + && cfg.feeBps < bpsDenominator + && cfg.destBytesOverhead >= 32 + ) + (Map.values tokenTransferFeeConfigs) + + interface instance CCIP.InterfacesV2.TokenPool.ITokenPool for BurnMintTokenPool where + view = CCIP.InterfacesV2.TokenPool.TokenPoolView with + owner = poolOwner + ccipOwner = ccipOwner + instrumentId = instrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + tokenPool_getRequiredCCVsImpl self arg = do + let finality = fromSomeNote "Invalid finality" (CCIP.CodecV2.FinalityConfig.decodeRequestedFinality arg.finality) + exercise (fromInterfaceContractId @BurnMintTokenPool self) GetRequiredCCVs + with + remoteChainSelector = arg.remoteChainSelector + sourceAmount = arg.sourceAmount + finality = finality.requested + extraData = arg.extraData + direction = arg.direction + context = arg.context + caller = arg.caller + + tokenPool_verifyInboundMessageImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) VerifyInboundMessage + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + executingMessageCid = arg.executingMessageCid + context = arg.context + caller = arg.caller + + tokenPool_verifyOutboundCCVsImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) VerifyOutboundCCVs + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + sendingMessageCid = arg.sendingMessageCid + amount = arg.amount + context = arg.context + caller = arg.caller + + tokenPool_releaseFromTicketImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) ReleaseFromTicket + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + rmnRemoteCid = arg.rmnRemoteCid + tokenReceiveTicketCid = arg.tokenReceiveTicketCid + context = arg.context + caller = arg.caller + + tokenPool_lockOrBurnImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) LockOrBurn + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + rmnRemoteCid = arg.rmnRemoteCid + sendingMessageCid = arg.sendingMessageCid + senderInputCids = arg.senderInputCids + amount = arg.amount + context = arg.context + caller = arg.caller + + tokenPool_calculateFeeImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) CalculateFee + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + sendingMessageCid = arg.sendingMessageCid + feeQuoterCid = arg.feeQuoterCid + tokenInstrumentId = arg.tokenInstrumentId + context = arg.context + caller = arg.caller + + tokenPool_getFeeImpl self arg = do + exercise (fromInterfaceContractId @BurnMintTokenPool self) GetFee + with + feeQuoterCid = arg.feeQuoterCid + destChainSelector = arg.destChainSelector + tokenInstrumentId = arg.tokenInstrumentId + context = arg.context + caller = arg.caller + + -- Sets the dynamic configuration for the pool. + -- + -- Params: + -- rateLimitAdmin: Optional party that can manage rate limits + -- + -- Callable by poolOwner + choice SetDynamicConfig : ContractId BurnMintTokenPool + with + rateLimitAdmin : Optional Party + controller poolOwner + do + create this with + rateLimitAdmin = rateLimitAdmin + + -- Sets the EDS observers for the pool. + -- + -- Params: + -- observers: parties used by EDS to auto-detect this pool + -- + -- Callable by poolOwner + choice SetObservers : ContractId BurnMintTokenPool + with + observers : [Party] + controller poolOwner + do + create this with + observers = observers + + -- Sets the remote chain configurations for the pool. + -- + -- Params: + -- remoteChainSelectorsToRemove: list of remote chain selectors to remove from the pool config + -- chainsToAdd: list of remote chain configs to add to the pool config. + -- If a remote chain selector already exists in the pool config, it must be removed first + -- before it can be added with updated config values. + -- + -- Callable by poolOwner + choice ApplyChainUpdates : ContractId BurnMintTokenPool + with + remoteChainSelectorsToRemove : [Numeric 0] + chainsToAdd : [ChainUpdate] + controller poolOwner + do + -- Validate + forA_ remoteChainSelectorsToRemove $ \remoteChainSelectorToRemove -> do + assertMsg "ApplyChainUpdates: cannot remove non-existent chain config" (Map.member remoteChainSelectorToRemove remoteChainConfigs) + -- Remove specified chain configs + let remoteChainConfigsRemoved = foldl + (\configs remoteChainSelectorToRemove -> Map.delete remoteChainSelectorToRemove configs) + remoteChainConfigs + remoteChainSelectorsToRemove + + -- If the chain already exists, revert + let chainSelectorsToAdd = map (\u -> u.remoteChainSelector) chainsToAdd + assertMsg "ChainAlreadyExists: duplicate remoteChainSelector in chainsToAdd" + (length chainSelectorsToAdd == length (dedup chainSelectorsToAdd)) + + -- Validate + forA_ chainsToAdd $ \ChainUpdate{remoteChainSelector, remotePools, remoteTokenAddress, inboundCCVs, outboundCCVs, finalityConfig, inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter} -> do + assertMsg "ApplyChainUpdates: invalid chain selector" (remoteChainSelector > 0.0) + assertMsg "ApplyChainUpdates: remote token address cannot be empty" (remoteTokenAddress /= "") + -- If the chain already exists, the update should first remove it. + assertMsg ("ApplyChainUpdates: chain already exists: " <> show remoteChainSelector) (not (Map.member remoteChainSelector remoteChainConfigsRemoved)) + assertMsg "ApplyChainUpdates: BlockDepth finality must be positive" (CCIP.CodecV2.FinalityConfig.isValidFinalityConfig finalityConfig) + -- Validate rate limiters + CCIP.Registry.RateLimiterV2.assertDistinctRateLimiters "ApplyChainUpdates" + inboundRateLimiter + outboundRateLimiter + (if customFinalityRateLimiterRequired finalityConfig + then Some inboundCustomBlockConfirmationsRateLimiter + else None) + -- Validate remote pools + assertMsg "ApplyChainUpdates: remotePools cannot contain empty address" ("" `notElem` remotePools) + assertMsg "ApplyChainUpdates: remotePools cannot contain duplicates" (length remotePools == length (dedup remotePools)) + assertMsg "ApplyChainUpdates: inbound and outbound CCV lists cannot contain duplicates" (length inboundCCVs == length (dedup inboundCCVs) && length outboundCCVs == length (dedup outboundCCVs)) + -- Add new chain configs + let remoteChainConfigsAdded = foldl + (\configs chainToAdd -> + Map.insert chainToAdd.remoteChainSelector RemoteChainConfig with + remotePools = chainToAdd.remotePools + remoteTokenAddress = chainToAdd.remoteTokenAddress + inboundCCVs = chainToAdd.inboundCCVs + outboundCCVs = chainToAdd.outboundCCVs + finalityConfig = chainToAdd.finalityConfig + inboundRateLimiter = chainToAdd.inboundRateLimiter + inboundCustomBlockConfirmationsRateLimiter = chainToAdd.inboundCustomBlockConfirmationsRateLimiter + outboundRateLimiter = chainToAdd.outboundRateLimiter + configs) + remoteChainConfigsRemoved + chainsToAdd + create this with + remoteChainConfigs = remoteChainConfigsAdded + + -- Runs the deploy+configure+register flow for this already-created pool: propose+accept + -- admin for the instrument if needed, deploy rate limiters and wire up lanes via + -- ApplyChainUpdates, and register with the shared TokenAdminRegistry - all as one atomic + -- operation, callable via a single CreateAndExercise ledger command right after create. + -- + -- Only supports the self-issued/self-serve admin path: instrumentId.admin must equal + -- admin, since ProposeAdministrator requires caller == instrumentId.admin, and this + -- choice calls it with caller = admin. For instruments where the registry owner must + -- co-sign admin assignment, propose/accept admin separately first, then call this with + -- existingTokenConfigCid = Some (the resulting cid). + nonconsuming choice Initialize : InitializeResult + with + tokenAdminRegistryCid : ContractId TAR.TokenAdminRegistry + existingTokenConfigCid : Optional (ContractId TAR.TokenConfig) + admin : Party + lanes : [LaneDeploySpec] + controller poolOwner, admin + do + proposeResult <- exercise tokenAdminRegistryCid TAR.ProposeAdministrator with + tokenConfigCid = existingTokenConfigCid + instrumentId + newAdmin = admin + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + acceptedTokenConfigCid <- exercise proposeResult.tokenAdminRegistryCid TAR.AcceptAdminRole with + tokenConfigCid = proposeResult.tokenConfigCid + instrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + + now <- getTime + chainUpdatesAndCids <- mapA (\lane -> do + inboundCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality now lane.inbound + outboundCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Outbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality now lane.outbound + inboundCustomFinalityCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + CCIP.Registry.RateLimiterV2.RateLimitMode_CustomFinality now lane.inboundCustomFinality + let chainUpdate = ChainUpdate with + remoteChainSelector = lane.remoteChainSelector + remotePools = lane.remotePools + remoteTokenAddress = lane.remoteTokenAddress + inboundCCVs = lane.inboundCCVs + outboundCCVs = lane.outboundCCVs + finalityConfig = lane.finalityConfig + inboundRateLimiter = Chainlink.InstanceAddress.make lane.inbound.instanceId poolOwner + inboundCustomBlockConfirmationsRateLimiter = Chainlink.InstanceAddress.make lane.inboundCustomFinality.instanceId poolOwner + outboundRateLimiter = Chainlink.InstanceAddress.make lane.outbound.instanceId poolOwner + pure (chainUpdate, [inboundCid, outboundCid, inboundCustomFinalityCid]) + ) lanes + + let chainUpdates = map fst chainUpdatesAndCids + rateLimiterCids = concat (map snd chainUpdatesAndCids) + + _ <- exercise self ApplyChainUpdates with + remoteChainSelectorsToRemove = [] + chainsToAdd = chainUpdates + + newTokenConfigCid <- exercise proposeResult.tokenAdminRegistryCid TAR.SetPool with + tokenConfigCid = acceptedTokenConfigCid + instrumentId + tokenPool = Some PoolRegistration with + poolOwner + poolInstanceId = instanceId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + + pure InitializeResult with + rateLimiterCids + tokenConfigCid = newTokenConfigCid + + -- Updates the token transfer fee configurations for specified destination chains. + -- + -- Params: + -- tokenTransferFeeConfigArgs: Array of structs containing destination chain selectors and their fee configs. + -- disableTokenTransferFeeConfigArgs: Array of destination chain selectors to disable custom fee configs for. + -- + -- Callable by poolOwner + choice ApplyTokenTransferFeeConfigUpdates : ContractId BurnMintTokenPool + with + tokenTransferFeeConfigArgs : [TokenTransferFeeConfigArgs] + disableTokenTransferFeeConfigArgs : [Numeric 0] + controller poolOwner + do + -- Validate + forA_ tokenTransferFeeConfigArgs $ \arg -> do + assertMsg "ApplyTokenTransferFeeConfigUpdates: invalid chain selector" (arg.destChainSelector > 0.0) + assertMsg ("ApplyTokenTransferFeeConfigUpdates: unsupported chain selector: " <> show arg.destChainSelector) (Map.member arg.destChainSelector remoteChainConfigs) + -- Reject configs with isEnabled: false - use disableTokenTransferFeeConfigs parameter instead. + assertMsg "ApplyTokenTransferFeeConfigUpdates: must be enabled" (arg.isEnabled) + assertMsg "ApplyTokenTransferFeeConfigUpdates: feeBps must be non-negative" (arg.feeBps >= 0.0) + assertMsg ("ApplyTokenTransferFeeConfigUpdates: feeUSDCents must be non-negative: " <> show arg.feeUSDCents) (arg.feeUSDCents >= 0.0) + assertMsg "ApplyTokenTransferFeeConfigUpdates: feeBps must be less than 10000" (arg.feeBps < bpsDenominator) + assertMsg "ApplyTokenTransferFeeConfigUpdates: destBytesOverhead must be at least 32" (arg.destBytesOverhead >= 32) + assertMsg "ApplyTokenTransferFeeConfigUpdates: destGasOverhead must be non-zero" (arg.destGasOverhead > 0) + + -- Update + let tokenTransferFeeConfigsUpdated = foldl + (\configs arg -> + Map.insert arg.destChainSelector TokenTransferFeeConfig with + isEnabled = arg.isEnabled + destGasOverhead = arg.destGasOverhead + destBytesOverhead = arg.destBytesOverhead + feeUSDCents = arg.feeUSDCents + feeBps = arg.feeBps + configs) + tokenTransferFeeConfigs + tokenTransferFeeConfigArgs + let tokenTransferFeeConfigsRemoved = foldr Map.delete tokenTransferFeeConfigsUpdated disableTokenTransferFeeConfigArgs + create this with + tokenTransferFeeConfigs = tokenTransferFeeConfigsRemoved + + -- Tunes capacity/rate/isEnabled on a rate limiter already referenced by this pool's remote chain config. + -- Callable by poolOwner or rateLimitAdmin directly; poolOwner signatory authority propagates to the nested RateLimiter.SetConfig exercise. + nonconsuming choice SetRateLimitConfig : ContractId CCIP.Registry.RateLimiterV2.RateLimiter + with + caller : Party + rateLimiterCid : ContractId CCIP.Registry.RateLimiterV2.RateLimiter + newIsEnabled : Bool + newCapacity : Numeric 0 + newRate : Numeric 0 + controller caller + do + assertMsg "SetRateLimitConfig: unauthorized caller" + (caller == poolOwner || rateLimitAdmin == Some caller) + assertPoolConfiguredRateLimiter instanceId poolOwner remoteChainConfigs rateLimiterCid + exercise rateLimiterCid CCIP.Registry.RateLimiterV2.SetConfig with + newIsEnabled + newCapacity + newRate + + -- Updates which RateLimiter contract identities the pool references per remote chain (Canton instance swap). + -- Callable by poolOwner only. + choice SetRateLimiterReferences : ContractId BurnMintTokenPool + with + rateLimitConfigArgs : [RateLimitConfigArgs] + controller poolOwner + do + -- Validate + forA_ rateLimitConfigArgs $ \arg -> do + assertMsg "SetRateLimiterReferences: invalid chain selector" (arg.remoteChainSelector > 0.0) + cfg <- case Map.lookup arg.remoteChainSelector remoteChainConfigs of + None -> abort ("SetRateLimiterReferences: unsupported chain selector: " <> show arg.remoteChainSelector) + Some cfg -> pure cfg + CCIP.Registry.RateLimiterV2.assertDistinctRateLimiters "SetRateLimitConfig" + arg.inboundRateLimiter + arg.outboundRateLimiter + (if customFinalityRateLimiterRequired cfg.finalityConfig + then Some arg.inboundCustomBlockConfirmationsRateLimiter + else None) + + -- Update + let remoteChainConfigsUpdated = foldl + (\configs RateLimitConfigArgs{remoteChainSelector, inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter} -> + case Map.lookup remoteChainSelector configs of + None -> configs + Some cfg -> + let updatedCfg = cfg with inboundRateLimiter = inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter = inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter = outboundRateLimiter + in Map.insert remoteChainSelector updatedCfg configs + ) + remoteChainConfigs + rateLimitConfigArgs + create this with + remoteChainConfigs = remoteChainConfigsUpdated + + -- Add or replace one receive-context entry with a non-contract AnyValue (for preapproval CIDs in Splice token patterns). + -- The context is merged into extraArgs for inbound transfers. + choice AddPoolReceiveContextNonContractValue : ContractId BurnMintTokenPool + with + contextKey : Text + value : Splice.Api.Token.MetadataV1.AnyValue + controller poolOwner + do + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.insert contextKey value poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + -- Add or replace one receive-context entry with AV_ContractId from an already-resolved ContractId (). + -- (for preapproval CIDs in Splice token patterns). + -- The context is merged into extraArgs for inbound transfers. + choice AddPoolReceiveContextContractValue : ContractId BurnMintTokenPool + with + contextKey : Text + referredContract : ContractId () + controller poolOwner + do + let av = Splice.Api.Token.MetadataV1.AV_ContractId (coerceContractId referredContract) + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.insert contextKey av poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + choice RemovePoolReceiveContextValue : ContractId BurnMintTokenPool + with + contextKey : Text + controller poolOwner + do + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.delete contextKey poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + choice ClearPoolReceiveContext : ContractId BurnMintTokenPool + controller poolOwner + do create this with poolReceiveContext = Splice.Api.Token.MetadataV1.emptyChoiceContext + + -- Set the transfer timeout for pending TransferInstructions. + -- Use Indefinite for tokens without expiry, RelativeHours for tokens with expiry constraints. + -- + -- Callable by poolOwner + choice SetTransferTimeout : ContractId BurnMintTokenPool + with + newTransferTimeout : TransferTimeout + controller poolOwner + do create this with transferTimeout = newTransferTimeout + + -- Get required CCV raw instance addresses based on chain and direction. + nonconsuming choice GetRequiredCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + with + remoteChainSelector : Numeric 0 + sourceAmount : BytesHex + finality : CCIP.CodecV2.FinalityConfig.FinalityConfig + extraData : BytesHex + direction : CCIP.InterfacesV2.TokenPool.TransferDirection + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + case Map.lookup remoteChainSelector remoteChainConfigs of + None -> abort "pool: unsupported remote chain selector" + Some config -> case direction of + CCIP.InterfacesV2.TokenPool.Inbound -> pure config.inboundCCVs + CCIP.InterfacesV2.TokenPool.Outbound -> pure config.outboundCCVs + + -- Validate inbound token transfer message and record pool CCV requirements. + -- Validates that the message has a token transfer targeting this pool's instrument, + -- validates the source pool address against the allowed remote pools for the source chain, + -- looks up per-chain CCV requirements, and records them on the executing message. + -- OffRamp performs the actual CCV coverage check. + nonconsuming choice VerifyInboundMessage : ContractId CCIP.APIV2.ExecutingMessage.IExecutingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + executingMessageCid : ContractId CCIP.APIV2.ExecutingMessage.IExecutingMessage + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + execMsg <- fetch executingMessageCid + let execMsgView = view execMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: executing message tokenAdminRegistry mismatch" + (execMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: executing message rmnRemote mismatch" + (execMsgView.rmnRemote == deps.rmnRemote) + let message = execMsgView.message + + tt <- case message.tokenTransfer of + None -> abort "pool: no token transfer in message" + Some t -> pure t + + let expectedAddr = CCIP.CodecV2.MessageCodecV1.encodeInstrumentId instrumentId + assertMsg "pool: destTokenAddress does not match pool instrumentId" + (tt.destTokenAddress == expectedAddr) + + remoteChainCfg <- case Map.lookup message.sourceChainSelector remoteChainConfigs of + None -> abort "pool: unsupported source chain" + Some cfg -> pure cfg + assertMsg "pool: message finality not allowed by pool finality config" + (CCIP.CodecV2.FinalityConfig.requestedFinalityAllowed message.finality.requested remoteChainCfg.finalityConfig) + + assertMsg "pool: invalid sourcePoolAddress" + (tt.sourcePoolAddress `elem` remoteChainCfg.remotePools) + + assertMsg "pool: sourceTokenAddress does not match remote token address for lane" + (tt.sourceTokenAddress == remoteChainCfg.remoteTokenAddress) + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_SetInboundPoolCCVs with + tokenConfigCid = tokenConfigCid + executingMessageCid + poolInstanceId = instanceId + poolCCVs = remoteChainCfg.inboundCCVs + context = context + caller = poolOwner + + -- Declare outbound CCV requirements on SendingMessageV1 via TAR. + -- Looks up per-chain CCV requirements and records them on the sending message. + nonconsuming choice VerifyOutboundCCVs : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + amount : Decimal + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: amount must be positive" (amount > 0.0) + + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "pool: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: sending message rmnRemote mismatch" + (sendingMsgView.rmnRemote == deps.rmnRemote) + + poolCCVs <- case Map.lookup sendingMsgView.destChainSelector remoteChainConfigs of + None -> abort "pool: unsupported dest chain" + Some cfg -> pure cfg.outboundCCVs + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_SetOutboundPoolCCVs with + tokenConfigCid = tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + poolCCVs = poolCCVs + context = context + caller = poolOwner + + -- Release tokens by consuming a TokenReceiveTicket via TokenAdminRegistry. + -- The TAR validates the ticket was issued by the correct registry instance, + -- the pool is the registered pool for the instrument, and archives the ticket + -- atomically. If any check fails, the entire transaction rolls back. + nonconsuming choice ReleaseFromTicket : CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + rmnRemoteCid : ContractId CCIP.APIV2.RMNRemote.IRMNRemote + tokenReceiveTicketCid : ContractId CCIP.APIV2.ExecutingMessage.ITokenReceiveTicket + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + let tokenReceiveTicketImplementation = coerceContractId @_ @CCIP.TicketsV2.TokenReceiveTicket tokenReceiveTicketCid + + ticket <- fetch tokenReceiveTicketImplementation + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "ReleaseFromTicket: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + _ <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_FetchTokenConfig with + tokenConfigCid + instrumentId = instrumentId + context = context + caller = poolOwner + assertMsg "ReleaseFromTicket: ticket tokenAdminRegistry instance mismatch" + (ticket.tokenAdminRegistryInstanceId == tokenAdminRegistryView.instanceId) + assertMsg "ReleaseFromTicket: ticket ccipOwner mismatch" + (ticket.ccipOwner == tokenAdminRegistryView.ccipOwner) + rmnRemoteView <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_PublicFetch with + expectedAddress = deps.rmnRemote + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "ReleaseFromTicket: rmn mismatch" + rmnRemoteView.instanceId rmnRemoteView.rmnOwner deps.rmnRemote + assertMsg "ReleaseFromTicket: ticket instrument must match pool instrument" + (ticket.instrumentId == instrumentId) + let poolAddress = Chainlink.InstanceAddress.make instanceId poolOwner + assertMsg "ReleaseFromTicket: ticket pool owner mismatch" + (ticket.poolOwner == poolOwner) + assertMsg "ReleaseFromTicket: ticket pool address mismatch" + (ticket.poolAddress == poolAddress) + + isCursed <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_IsCursedForChain with + chainSelector = ticket.sourceChainSelector + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + assertMsg "ReleaseFromTicket: source chain is cursed" (not isCursed) + + assertMsg "ReleaseFromTicket: caller must be tokenReceiver" + (caller == ticket.tokenReceiver) + + -- Defensive consistency check: every verified CCV instance must be + -- owned by a ticket signatory party. + let ticketSignatories = signatory ticket + Foldable.forA_ ticket.verifiedCCVs $ \verifiedCCV -> + assertMsg ("ReleaseFromTicket: verified CCV owner is not a ticket signatory " <> show verifiedCCV) + (Chainlink.InstanceAddress.ownerParty verifiedCCV `elem` ticketSignatories) + + -- Also check that the pool's owner is a signatory on the ticket + assertMsg "ReleaseFromTicket: pool owner is not a ticket signatory" + (poolOwner `elem` ticketSignatories) + + -- Validate the exact pool-required CCV instances are present on the ticket. + -- This preserves the configured instance-level trust model even when + -- multiple CCV instances share the same owner party. + remoteChainCfg <- case Map.lookup ticket.sourceChainSelector remoteChainConfigs of + None -> abort "ReleaseFromTicket: unsupported source chain" + Some cfg -> pure cfg + let requiredPoolCCVs = dedup ticket.requiredInboundPoolCCVs + Foldable.forA_ requiredPoolCCVs $ \requiredCCV -> + assertMsg ("ReleaseFromTicket: missing required pool CCV " <> show requiredCCV) + (requiredCCV `elem` ticket.verifiedCCVs) + + let useCustomFinalityRateLimiter = + ticket.finality /= CCIP.CodecV2.FinalityConfig.WaitForFinality + && CCIP.CodecV2.FinalityConfig.customFinalityRateLimiterRequired remoteChainCfg.finalityConfig + let expectedRateLimitMode = if useCustomFinalityRateLimiter + then CCIP.Registry.RateLimiterV2.RateLimitMode_CustomFinality + else CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality + let expectedRateLimiter = if useCustomFinalityRateLimiter + then remoteChainCfg.inboundCustomBlockConfirmationsRateLimiter + else remoteChainCfg.inboundRateLimiter + rateLimiterCid <- CCIP.UtilsV2.Context.getFromContextU context CCIP.Registry.RateLimiterV2.rateLimiterContextKey + rateLimiter <- fetch rateLimiterCid + assertRateLimiter + "ReleaseFromTicket: rateLimiter" + instanceId + ticket.sourceChainSelector + CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + expectedRateLimitMode + expectedRateLimiter + rateLimiter + let sourceDecimals = parseSourcePoolDecimals decimals ticket.sourcePoolData + conversion <- case CCIP.CodecV2.Uint256.calculateLocalAmountFromUint256Checked + "ReleaseFromTicket" + ticket.amount + sourceDecimals + decimals of + None -> abort "ReleaseFromTicket: source amount cannot be represented as local amount" + Some result -> pure result + assertMsg "ReleaseFromTicket: local amount rounds down to zero" + (conversion.localAmount > 0.0) + let localAmount = conversion.localAmount + _ <- exercise rateLimiterCid CCIP.Registry.RateLimiterV2.ConsumeCapacity with + requested = localAmount + + -- Consume ticket via TAR after validating the disclosed TAR identity. + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_ConsumeReceiveTicket with + tokenConfigCid = tokenConfigCid + tokenReceiveTicketCid = tokenReceiveTicketCid + instrumentId = instrumentId + poolInstanceId = instanceId + context = context + caller = poolOwner + + -- Get inputs from context + burnMintFactory <- lookupBurnMintFactory context + _ <- exercise tokenConfigCid CCIP.APIV2.TokenAdminRegistry.TokenConfig_AssertConfiguredBurnMintFactory with + suppliedFactory = burnMintFactory + context = context + caller = poolOwner + extraArgsContextValues <- lookupBurnMintFactoryExtraArgsContextValues context + extraArgsMetaValues <- lookupBurnMintFactoryExtraArgsMetaValues context + + let amountDecimal = CCIP.CodecV2.Math.numeric0ToDecimal localAmount + burnMintResult <- exercise burnMintFactory + Splice.Api.Token.BurnMintV1.BurnMintFactory_BurnMint with + expectedAdmin = instrumentId.admin + instrumentId = instrumentId + inputHoldingCids = [] + outputs = + [Splice.Api.Token.BurnMintV1.BurnMintOutput with + owner = ticket.tokenReceiver + amount = amountDecimal + context = Splice.Api.Token.MetadataV1.emptyChoiceContext] + extraActors = if ticket.tokenReceiver == instrumentId.admin then [] else [ticket.tokenReceiver] + extraArgs = Splice.Api.Token.MetadataV1.ExtraArgs with + context = Splice.Api.Token.MetadataV1.ChoiceContext with + values = extraArgsContextValues + meta = Splice.Api.Token.MetadataV1.Metadata with + values = extraArgsMetaValues + + receiverCid <- assertSingleHolding + "ReleaseFromTicket: receiver holding" + ticket.tokenReceiver + instrumentId + amountDecimal + burnMintResult.outputCids + let receiverCids = [receiverCid] + let output = CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult_Completed with + receiverHoldingCids = receiverCids + let claimedOutput = CCIP.EventsV2.Events.TokenReceiveTicketClaimed_Completed with + receiverHoldingCids = receiverCids + claimedEventCid <- create CCIP.EventsV2.Events.TokenReceiveTicketClaimed with + ccipOwner = ticket.ccipOwner + ccvOwners = ticket.ccvOwners + poolOwner = ticket.poolOwner + receiver = ticket.receiver + tokenReceiver = ticket.tokenReceiver + tokenReceiveTicketCid = coerceContractId tokenReceiveTicketImplementation + event = CCIP.EventsV2.Events.TokenReceiveTicketClaimedEvent with + verifiedCCVs = ticket.verifiedCCVs + tokenAdminRegistryInstanceId = ticket.tokenAdminRegistryInstanceId + poolAddress = ticket.poolAddress + instrumentId = ticket.instrumentId + sourceAmount = ticket.amount + localAmount = localAmount + sourcePoolData = ticket.sourcePoolData + messageId = ticket.messageId + sourceChainSelector = ticket.sourceChainSelector + finality = ticket.finality + output = claimedOutput + pure CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult with + output + poolChangeCids = [] + claimedEventCid = coerceContractId claimedEventCid + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + -- Calculate fee for outbound token transfer (USD cents only). + -- Reads destChainSelector from SendingMessageV1, looks up per-chain + -- fee config, appends fee directly to SendingMessageV1. + nonconsuming choice CalculateFee : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + feeQuoterCid : ContractId CCIP.APIV2.FeeQuoter.IFeeQuoter + tokenInstrumentId : Splice.Api.Token.HoldingV1.InstrumentId + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "pool: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: tokenInstrumentId mismatch" + (tokenInstrumentId == instrumentId) + feeQuoterView <- exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_PublicFetch with + expectedAddress = deps.feeQuoter + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: feeQuoter mismatch" + feeQuoterView.instanceId feeQuoterView.ccipOwner deps.feeQuoter + assertMsg "pool: sending message feeQuoter mismatch" + (sendingMsgView.feeQuoter == deps.feeQuoter) + -- Check if fee config is enabled for the destination chain. + -- If no config is set/it's disabled, use the FeeQuoter defaults. If enabled, use the specified fee parameters. + (feeUSDCents, destGasOverhead, destBytesOverhead) <- case Map.lookup sendingMsgView.destChainSelector tokenTransferFeeConfigs of + Some feeConfig | feeConfig.isEnabled -> + pure + ( feeConfig.feeUSDCents + , feeConfig.destGasOverhead + , feeConfig.destBytesOverhead + ) + _ -> + exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_GetTokenTransferFee with + destChainSelector = sendingMsgView.destChainSelector + token = tokenInstrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_AddTokenSendFee with + tokenConfigCid = tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + feeUSDCents = feeUSDCents + destGasOverhead = destGasOverhead + destBytesOverhead = destBytesOverhead + context = context + caller = poolOwner + + -- Quote fee for outbound token transfer (USD cents only). + nonconsuming choice GetFee + : CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote + with + feeQuoterCid : ContractId CCIP.APIV2.FeeQuoter.IFeeQuoter + destChainSelector : Numeric 0 + tokenInstrumentId : Splice.Api.Token.HoldingV1.InstrumentId + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: tokenInstrumentId mismatch" + (tokenInstrumentId == instrumentId) + + feeQuoterView <- exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_PublicFetch with + expectedAddress = deps.feeQuoter + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: feeQuoter mismatch" + feeQuoterView.instanceId feeQuoterView.ccipOwner deps.feeQuoter + + case Map.lookup destChainSelector tokenTransferFeeConfigs of + Some feeConfig | feeConfig.isEnabled -> + pure CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote with + poolInstanceId = instanceId + poolOwner = poolOwner + feeUSDCents = feeConfig.feeUSDCents + destGasOverhead = feeConfig.destGasOverhead + destBytesOverhead = feeConfig.destBytesOverhead + tokenFeeBps = feeConfig.feeBps + isEnabled = True + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + _ -> do + (feeUSDCents, destGasOverhead, destBytesOverhead) <- exercise feeQuoterCid + CCIP.APIV2.FeeQuoter.FeeQuoter_GetTokenTransferFee with + destChainSelector = destChainSelector + token = tokenInstrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + pure CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote with + poolInstanceId = instanceId + poolOwner = poolOwner + feeUSDCents = feeUSDCents + destGasOverhead = destGasOverhead + destBytesOverhead = destBytesOverhead + tokenFeeBps = 0.0 + isEnabled = False + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + -- Lock tokens for outbound transfer and append token send data. + -- Reads from SendingMessageV1 instead of taking separate params. + -- Requires ccvAndExecutorHash to be finalized by the executor step before this runs. + nonconsuming choice LockOrBurn : CCIP.InterfacesV2.TokenPool.LockOrBurnResult + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + rmnRemoteCid : ContractId CCIP.APIV2.RMNRemote.IRMNRemote + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + senderInputCids : [ContractId Splice.Api.Token.HoldingV1.Holding] + amount : Decimal + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: amount must be positive" (amount > 0.0) + let amountNumeric0 = CCIP.CodecV2.Math.decimalToNumeric0 amount + + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "LockOrBurn: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + _ <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_FetchTokenConfig with + tokenConfigCid + instrumentId + context = context + caller = poolOwner + assertMsg "LockOrBurn: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "LockOrBurn: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + rmnRemoteView <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_PublicFetch with + expectedAddress = deps.rmnRemote + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "LockOrBurn: rmn mismatch" + rmnRemoteView.instanceId rmnRemoteView.rmnOwner deps.rmnRemote + assertMsg "LockOrBurn: sending message rmn mismatch" + (sendingMsgView.rmnRemote == deps.rmnRemote) + + isCursed <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_IsCursedForChain with + chainSelector = sendingMsgView.destChainSelector + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + assertMsg "LockOrBurn: dest chain is cursed" (not isCursed) + + remoteChainCfg <- case Map.lookup sendingMsgView.destChainSelector remoteChainConfigs of + None -> abort "pool: unsupported dest chain" + Some cfg -> pure cfg + + let destTokenAddr = remoteChainCfg.remoteTokenAddress + + let destPoolData = CCIP.CodecV2.MessageCodecV1.encodeUint256 (intToNumeric decimals) + let tokenFeeBps = case Map.lookup sendingMsgView.destChainSelector tokenTransferFeeConfigs of + Some feeCfg | feeCfg.isEnabled -> feeCfg.feeBps + _ -> 0.0 + let feeAmount = calculateFeeFromBps amountNumeric0 tokenFeeBps + let amountAfterFeeBps = amountNumeric0 - feeAmount + + let expectedRateLimiter = remoteChainCfg.outboundRateLimiter + rateLimiterCid <- CCIP.UtilsV2.Context.getFromContextU context CCIP.Registry.RateLimiterV2.rateLimiterContextKey + rateLimiter <- fetch rateLimiterCid + assertRateLimiter + "LockOrBurn: rateLimiter" + instanceId + sendingMsgView.destChainSelector + CCIP.Registry.RateLimiterV2.RateLimitDirection_Outbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality + expectedRateLimiter + rateLimiter + _ <- exercise rateLimiterCid CCIP.Registry.RateLimiterV2.ConsumeCapacity with + requested = amountAfterFeeBps + + -- Get inputs from context + burnMintFactory <- lookupBurnMintFactory context + _ <- exercise tokenConfigCid CCIP.APIV2.TokenAdminRegistry.TokenConfig_AssertConfiguredBurnMintFactory with + suppliedFactory = burnMintFactory + context = context + caller = poolOwner + extraArgsContextValues <- lookupBurnMintFactoryExtraArgsContextValues context + extraArgsMetaValues <- lookupBurnMintFactoryExtraArgsMetaValues context + + inputTotal <- sumCheckedHoldingAmounts + "LockOrBurn: sender input holdings" + caller + instrumentId + senderInputCids + assertMsg "LockOrBurn: insufficient sender input amount" + (inputTotal >= amount) + let amountAfterFeeBpsBytes = fromSomeNote + "LockOrBurn: amountAfterFeeBps does not fit uint256" + (CCIP.CodecV2.Uint256.numeric0ToUint256Bytes amountAfterFeeBps) + let senderChangeAmount = inputTotal - amount + let feeHoldingAmount = CCIP.CodecV2.Math.numeric0ToDecimal feeAmount + let outputs = + (if senderChangeAmount > 0.0 then + [Splice.Api.Token.BurnMintV1.BurnMintOutput with + owner = caller + amount = senderChangeAmount + context = Splice.Api.Token.MetadataV1.emptyChoiceContext] + else []) + ++ + (if feeHoldingAmount > 0.0 then + [Splice.Api.Token.BurnMintV1.BurnMintOutput with + owner = poolOwner + amount = feeHoldingAmount + context = Splice.Api.Token.MetadataV1.emptyChoiceContext] + else []) + burnMintResult <- exercise burnMintFactory + Splice.Api.Token.BurnMintV1.BurnMintFactory_BurnMint with + expectedAdmin = instrumentId.admin + instrumentId = instrumentId + inputHoldingCids = senderInputCids + outputs = outputs + extraActors = if caller == instrumentId.admin then [] else [caller] + extraArgs = Splice.Api.Token.MetadataV1.ExtraArgs with + context = Splice.Api.Token.MetadataV1.ChoiceContext with + values = extraArgsContextValues + meta = Splice.Api.Token.MetadataV1.Metadata with + values = extraArgsMetaValues + + (senderChangeCids, poolChangeCids) <- validateLockOrBurnOutputs + caller + poolOwner + instrumentId + senderChangeAmount + feeHoldingAmount + burnMintResult.outputCids + let extraData = fromOptional "" destPoolData + newSendingMessageCid <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_AddTokenSend with + tokenConfigCid = tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + instrumentId = instrumentId + amount = amountAfterFeeBpsBytes + destTokenAddress = destTokenAddr + extraData = extraData + context = context + caller = poolOwner + + pure CCIP.InterfacesV2.TokenPool.LockOrBurnResult with + poolChangeCids = poolChangeCids + senderChangeCids = senderChangeCids + sendingMessageCid = coerceContractId newSendingMessageCid + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + interface instance MCMSReceiver for BurnMintTokenPool where + view = MCMSReceiverView with + mcmsController = poolOwner + instanceId + + mcmsEntrypoint functionName operationData contractIds = do + let instAddr = instanceId <> "@" <> partyToText poolOwner + selfCid <- case Map.lookup instAddr contractIds of + None -> abort $ "E_SELF_CID_NOT_IN_MAP: " <> instAddr + Some cid -> pure (coerceContractId cid : ContractId BurnMintTokenPool) + + case functionName of + "SetDynamicConfig" -> do + case decodeSetDynamicConfigParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetDynamicConfig" + Some (SetDynamicConfigParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetDynamicConfig" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetDynamicConfig with + rateLimitAdmin + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetObservers" -> do + case decodeSetObserversParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetObservers" + Some (SetObserversParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetObservers" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetObservers with + observers + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ApplyChainUpdates" -> do + case decodeApplyChainUpdatesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApplyChainUpdates" + Some (ApplyChainUpdatesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApplyChainUpdates" (finalOffset == byteCount operationData) + newCid <- exercise selfCid ApplyChainUpdates with + remoteChainSelectorsToRemove + chainsToAdd + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ApplyTokenTransferFeeConfigUpdates" -> do + case decodeApplyTokenTransferFeeConfigUpdatesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApplyTokenTransferFeeConfigUpdates" + Some (ApplyTokenTransferFeeConfigUpdatesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApplyTokenTransferFeeConfigUpdates" (finalOffset == byteCount operationData) + newCid <- exercise selfCid ApplyTokenTransferFeeConfigUpdates with + tokenTransferFeeConfigArgs + disableTokenTransferFeeConfigArgs + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetRateLimiterReferences" -> do + case decodeSetRateLimiterReferencesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetRateLimiterReferences" + Some (SetRateLimiterReferencesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetRateLimiterReferences" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetRateLimiterReferences with + rateLimitConfigArgs + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetRateLimitConfig" -> do + case decodeSetRateLimitConfigParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetRateLimitConfig" + Some (SetRateLimitConfigParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetRateLimitConfig" (finalOffset == byteCount operationData) + pool <- fetch selfCid + assertMsg "E_RATE_LIMITER_NOT_ON_POOL" + (rateLimiterInstanceAddress `elem` remoteChainRateLimiterAddresses pool.remoteChainConfigs) + let limiterAddr = Chainlink.InstanceAddress.toText rateLimiterInstanceAddress + rateLimiterCid <- case Map.lookup limiterAddr contractIds of + None -> abort ("E_RATE_LIMITER_NOT_IN_MAP: " <> limiterAddr) + Some c -> pure (coerceContractId c : ContractId CCIP.Registry.RateLimiterV2.RateLimiter) + resolvedLimiter <- fetch rateLimiterCid + assertMsg "E_RATE_LIMITER_ADDRESS_MISMATCH" + (CCIP.Registry.RateLimiterV2.rawInstanceAddress resolvedLimiter == rateLimiterInstanceAddress) + updatedLimiterCid <- exercise selfCid SetRateLimitConfig with + caller + rateLimiterCid + newIsEnabled + newCapacity + newRate + pure (Map.insert limiterAddr (coerceContractId updatedLimiterCid) contractIds) + "AddPoolReceiveContextNonContractValue" -> do + case decodeAddPoolReceiveContextNonContractValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextNonContractValue" + Some (AddPoolReceiveContextNonContractValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextNonContractValue" (finalOffset == byteCount operationData) + (value, innerOff) <- case CCIP.UtilsV2.Context.decodeAnyValueAt valuePayload 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextNonContractValue:valuePayload" + Some p -> pure p + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextNonContractValue:valuePayload" (innerOff == byteCount valuePayload) + newCid <- exercise selfCid AddPoolReceiveContextNonContractValue with contextKey, value + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "AddPoolReceiveContextContractValue" -> do + case decodeAddPoolReceiveContextContractValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextContractValue" + Some (AddPoolReceiveContextContractValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextContractValue" (finalOffset == byteCount operationData) + let refAddr = Chainlink.InstanceAddress.toText referentInstanceAddress + referred <- case Map.lookup refAddr contractIds of + None -> abort ("E_REFERENT_CONTRACT_NOT_IN_MAP: " <> refAddr) + Some c -> pure c + newCid <- exercise selfCid AddPoolReceiveContextContractValue with contextKey, referredContract = referred + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "RemovePoolReceiveContextValue" -> do + case decodeRemovePoolReceiveContextValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: RemovePoolReceiveContextValue" + Some (RemovePoolReceiveContextValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: RemovePoolReceiveContextValue" (finalOffset == byteCount operationData) + newCid <- exercise selfCid RemovePoolReceiveContextValue with contextKey + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ClearPoolReceiveContext" -> do + assertMsg "E_TRAILING_BYTES: ClearPoolReceiveContext" (byteCount operationData == 0) + newCid <- exercise selfCid ClearPoolReceiveContext + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetTransferTimeout" -> do + case decodeSetTransferTimeoutParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetTransferTimeout" + Some (SetTransferTimeoutParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetTransferTimeout" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetTransferTimeout with + newTransferTimeout = transferTimeout + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + _ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName diff --git a/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2CodecGen.daml b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2CodecGen.daml new file mode 100644 index 000000000..161899ec7 --- /dev/null +++ b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2CodecGen.daml @@ -0,0 +1,386 @@ +-- AUTO-GENERATED by go-daml codegen. DO NOT EDIT. +module CCIP.Registry.BurnMintTokenPoolV2CodecGen where + +import DA.Crypto.Text (BytesHex) +import DA.Optional (fromSome) + +import CCIP.Registry.BurnMintTokenPoolV2Types (ChainUpdate(..), RemoteChainConfig(..), TokenTransferFeeConfigArgs(..), TokenTransferFeeConfig(..), RateLimitConfigArgs(..), SetDynamicConfigParams(..), SetObserversParams(..), ApplyChainUpdatesParams(..), ApplyTokenTransferFeeConfigUpdatesParams(..), SetRateLimiterReferencesParams(..), SetRateLimitConfigParams(..), SetTransferTimeoutParams(..), AddPoolReceiveContextNonContractValueParams(..), AddPoolReceiveContextContractValueParams(..), RemovePoolReceiveContextValueParams(..), RateLimiterDeploySpec(..), LaneDeploySpec(..), TransferTimeout(..)) + +import MCMS.Codec ( + decodeBoolAt, + decodeBytesHexAt, + decodeBytesHexList, + decodeInt64At, + decodeList, + decodeNumeric0At, + decodeOptional, + decodePartyAt, + decodePartyList, + decodeTextAt, + encodeBool, + encodeBytesHex, + encodeBytesHexList, + encodeInt64, + encodeList, + encodeNumeric0, + encodeOptional, + encodeParty, + encodePartyList, + encodeText, + encodeUint8, + extractBytes + ) + +import CCIP.CodecV2 (decodeRawInstanceAddressAt, decodeRequestedFinalityAt, encodeRawInstanceAddress, encodeRequestedFinality) + + +-- =============================================== +-- | ChainUpdate +-- =============================================== + +encodeChainUpdate : ChainUpdate -> BytesHex +encodeChainUpdate params = + encodeNumeric0 params.remoteChainSelector + <> encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeChainUpdateAt : BytesHex -> Int -> Optional (ChainUpdate, Int) +decodeChainUpdateAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (ChainUpdate{..}, offset) + + +-- =============================================== +-- | RemoteChainConfig +-- =============================================== + +encodeRemoteChainConfig : RemoteChainConfig -> BytesHex +encodeRemoteChainConfig params = + encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeRemoteChainConfigAt : BytesHex -> Int -> Optional (RemoteChainConfig, Int) +decodeRemoteChainConfigAt encoded offset = do + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (RemoteChainConfig{..}, offset) + + +-- =============================================== +-- | TokenTransferFeeConfigArgs +-- =============================================== + +encodeTokenTransferFeeConfigArgs : TokenTransferFeeConfigArgs -> BytesHex +encodeTokenTransferFeeConfigArgs params = + encodeNumeric0 params.destChainSelector + <> encodeBool params.isEnabled + <> fromSome (encodeInt64 params.destGasOverhead) + <> fromSome (encodeInt64 params.destBytesOverhead) + <> encodeNumeric0 params.feeUSDCents + <> encodeNumeric0 params.feeBps + +decodeTokenTransferFeeConfigArgsAt : BytesHex -> Int -> Optional (TokenTransferFeeConfigArgs, Int) +decodeTokenTransferFeeConfigArgsAt encoded offset = do + (destChainSelector, offset) <- decodeNumeric0At encoded offset + (isEnabled, offset) <- decodeBoolAt encoded offset + (destGasOverhead, offset) <- decodeInt64At encoded offset + (destBytesOverhead, offset) <- decodeInt64At encoded offset + (feeUSDCents, offset) <- decodeNumeric0At encoded offset + (feeBps, offset) <- decodeNumeric0At encoded offset + Some (TokenTransferFeeConfigArgs{..}, offset) + + +-- =============================================== +-- | TokenTransferFeeConfig +-- =============================================== + +encodeTokenTransferFeeConfig : TokenTransferFeeConfig -> BytesHex +encodeTokenTransferFeeConfig params = + encodeBool params.isEnabled + <> fromSome (encodeInt64 params.destGasOverhead) + <> fromSome (encodeInt64 params.destBytesOverhead) + <> encodeNumeric0 params.feeUSDCents + <> encodeNumeric0 params.feeBps + +decodeTokenTransferFeeConfigAt : BytesHex -> Int -> Optional (TokenTransferFeeConfig, Int) +decodeTokenTransferFeeConfigAt encoded offset = do + (isEnabled, offset) <- decodeBoolAt encoded offset + (destGasOverhead, offset) <- decodeInt64At encoded offset + (destBytesOverhead, offset) <- decodeInt64At encoded offset + (feeUSDCents, offset) <- decodeNumeric0At encoded offset + (feeBps, offset) <- decodeNumeric0At encoded offset + Some (TokenTransferFeeConfig{..}, offset) + + +-- =============================================== +-- | RateLimitConfigArgs +-- =============================================== + +encodeRateLimitConfigArgs : RateLimitConfigArgs -> BytesHex +encodeRateLimitConfigArgs params = + encodeNumeric0 params.remoteChainSelector + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeRateLimitConfigArgsAt : BytesHex -> Int -> Optional (RateLimitConfigArgs, Int) +decodeRateLimitConfigArgsAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (RateLimitConfigArgs{..}, offset) + + +-- =============================================== +-- | SetDynamicConfigParams +-- =============================================== + +encodeSetDynamicConfigParams : SetDynamicConfigParams -> BytesHex +encodeSetDynamicConfigParams params = + encodeOptional params.rateLimitAdmin encodeParty + +decodeSetDynamicConfigParamsAt : BytesHex -> Int -> Optional (SetDynamicConfigParams, Int) +decodeSetDynamicConfigParamsAt encoded offset = do + (rateLimitAdmin, offset) <- decodeOptional encoded offset decodePartyAt + Some (SetDynamicConfigParams{..}, offset) + + +-- =============================================== +-- | SetObserversParams +-- =============================================== + +encodeSetObserversParams : SetObserversParams -> BytesHex +encodeSetObserversParams params = + encodePartyList params.observers + +decodeSetObserversParamsAt : BytesHex -> Int -> Optional (SetObserversParams, Int) +decodeSetObserversParamsAt encoded offset = do + (observers, offset) <- decodePartyList encoded offset + Some (SetObserversParams{..}, offset) + + +-- =============================================== +-- | ApplyChainUpdatesParams +-- =============================================== + +encodeApplyChainUpdatesParams : ApplyChainUpdatesParams -> BytesHex +encodeApplyChainUpdatesParams params = + encodeList params.remoteChainSelectorsToRemove encodeNumeric0 + <> encodeList params.chainsToAdd encodeChainUpdate + +decodeApplyChainUpdatesParamsAt : BytesHex -> Int -> Optional (ApplyChainUpdatesParams, Int) +decodeApplyChainUpdatesParamsAt encoded offset = do + (remoteChainSelectorsToRemove, offset) <- decodeList encoded offset decodeNumeric0At + (chainsToAdd, offset) <- decodeList encoded offset decodeChainUpdateAt + Some (ApplyChainUpdatesParams{..}, offset) + + +-- =============================================== +-- | ApplyTokenTransferFeeConfigUpdatesParams +-- =============================================== + +encodeApplyTokenTransferFeeConfigUpdatesParams : ApplyTokenTransferFeeConfigUpdatesParams -> BytesHex +encodeApplyTokenTransferFeeConfigUpdatesParams params = + encodeList params.tokenTransferFeeConfigArgs encodeTokenTransferFeeConfigArgs + <> encodeList params.disableTokenTransferFeeConfigArgs encodeNumeric0 + +decodeApplyTokenTransferFeeConfigUpdatesParamsAt : BytesHex -> Int -> Optional (ApplyTokenTransferFeeConfigUpdatesParams, Int) +decodeApplyTokenTransferFeeConfigUpdatesParamsAt encoded offset = do + (tokenTransferFeeConfigArgs, offset) <- decodeList encoded offset decodeTokenTransferFeeConfigArgsAt + (disableTokenTransferFeeConfigArgs, offset) <- decodeList encoded offset decodeNumeric0At + Some (ApplyTokenTransferFeeConfigUpdatesParams{..}, offset) + + +-- =============================================== +-- | SetRateLimiterReferencesParams +-- =============================================== + +encodeSetRateLimiterReferencesParams : SetRateLimiterReferencesParams -> BytesHex +encodeSetRateLimiterReferencesParams params = + encodeList params.rateLimitConfigArgs encodeRateLimitConfigArgs + +decodeSetRateLimiterReferencesParamsAt : BytesHex -> Int -> Optional (SetRateLimiterReferencesParams, Int) +decodeSetRateLimiterReferencesParamsAt encoded offset = do + (rateLimitConfigArgs, offset) <- decodeList encoded offset decodeRateLimitConfigArgsAt + Some (SetRateLimiterReferencesParams{..}, offset) + + +-- =============================================== +-- | SetRateLimitConfigParams +-- =============================================== + +encodeSetRateLimitConfigParams : SetRateLimitConfigParams -> BytesHex +encodeSetRateLimitConfigParams params = + encodeParty params.caller + <> encodeRawInstanceAddress params.rateLimiterInstanceAddress + <> encodeBool params.newIsEnabled + <> encodeNumeric0 params.newCapacity + <> encodeNumeric0 params.newRate + +decodeSetRateLimitConfigParamsAt : BytesHex -> Int -> Optional (SetRateLimitConfigParams, Int) +decodeSetRateLimitConfigParamsAt encoded offset = do + (caller, offset) <- decodePartyAt encoded offset + (rateLimiterInstanceAddress, offset) <- decodeRawInstanceAddressAt encoded offset + (newIsEnabled, offset) <- decodeBoolAt encoded offset + (newCapacity, offset) <- decodeNumeric0At encoded offset + (newRate, offset) <- decodeNumeric0At encoded offset + Some (SetRateLimitConfigParams{..}, offset) + + +-- =============================================== +-- | SetTransferTimeoutParams +-- =============================================== + +encodeSetTransferTimeoutParams : SetTransferTimeoutParams -> BytesHex +encodeSetTransferTimeoutParams params = + encodeTransferTimeout params.transferTimeout + +decodeSetTransferTimeoutParamsAt : BytesHex -> Int -> Optional (SetTransferTimeoutParams, Int) +decodeSetTransferTimeoutParamsAt encoded offset = do + (transferTimeout, offset) <- decodeTransferTimeoutAt encoded offset + Some (SetTransferTimeoutParams{..}, offset) + + +-- =============================================== +-- | AddPoolReceiveContextNonContractValueParams +-- =============================================== + +encodeAddPoolReceiveContextNonContractValueParams : AddPoolReceiveContextNonContractValueParams -> BytesHex +encodeAddPoolReceiveContextNonContractValueParams params = + encodeText params.contextKey + <> encodeBytesHex params.valuePayload + +decodeAddPoolReceiveContextNonContractValueParamsAt : BytesHex -> Int -> Optional (AddPoolReceiveContextNonContractValueParams, Int) +decodeAddPoolReceiveContextNonContractValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + (valuePayload, offset) <- decodeBytesHexAt encoded offset + Some (AddPoolReceiveContextNonContractValueParams{..}, offset) + + +-- =============================================== +-- | AddPoolReceiveContextContractValueParams +-- =============================================== + +encodeAddPoolReceiveContextContractValueParams : AddPoolReceiveContextContractValueParams -> BytesHex +encodeAddPoolReceiveContextContractValueParams params = + encodeText params.contextKey + <> encodeRawInstanceAddress params.referentInstanceAddress + +decodeAddPoolReceiveContextContractValueParamsAt : BytesHex -> Int -> Optional (AddPoolReceiveContextContractValueParams, Int) +decodeAddPoolReceiveContextContractValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + (referentInstanceAddress, offset) <- decodeRawInstanceAddressAt encoded offset + Some (AddPoolReceiveContextContractValueParams{..}, offset) + + +-- =============================================== +-- | RemovePoolReceiveContextValueParams +-- =============================================== + +encodeRemovePoolReceiveContextValueParams : RemovePoolReceiveContextValueParams -> BytesHex +encodeRemovePoolReceiveContextValueParams params = + encodeText params.contextKey + +decodeRemovePoolReceiveContextValueParamsAt : BytesHex -> Int -> Optional (RemovePoolReceiveContextValueParams, Int) +decodeRemovePoolReceiveContextValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + Some (RemovePoolReceiveContextValueParams{..}, offset) + + +-- =============================================== +-- | RateLimiterDeploySpec +-- =============================================== + +encodeRateLimiterDeploySpec : RateLimiterDeploySpec -> BytesHex +encodeRateLimiterDeploySpec params = + encodeText params.instanceId + <> encodeBool params.isEnabled + <> encodeNumeric0 params.capacity + <> encodeNumeric0 params.rate + +decodeRateLimiterDeploySpecAt : BytesHex -> Int -> Optional (RateLimiterDeploySpec, Int) +decodeRateLimiterDeploySpecAt encoded offset = do + (instanceId, offset) <- decodeTextAt encoded offset + (isEnabled, offset) <- decodeBoolAt encoded offset + (capacity, offset) <- decodeNumeric0At encoded offset + (rate, offset) <- decodeNumeric0At encoded offset + Some (RateLimiterDeploySpec{..}, offset) + + +-- =============================================== +-- | LaneDeploySpec +-- =============================================== + +encodeLaneDeploySpec : LaneDeploySpec -> BytesHex +encodeLaneDeploySpec params = + encodeNumeric0 params.remoteChainSelector + <> encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRateLimiterDeploySpec params.inbound + <> encodeRateLimiterDeploySpec params.outbound + <> encodeRateLimiterDeploySpec params.inboundCustomFinality + +decodeLaneDeploySpecAt : BytesHex -> Int -> Optional (LaneDeploySpec, Int) +decodeLaneDeploySpecAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inbound, offset) <- decodeRateLimiterDeploySpecAt encoded offset + (outbound, offset) <- decodeRateLimiterDeploySpecAt encoded offset + (inboundCustomFinality, offset) <- decodeRateLimiterDeploySpecAt encoded offset + Some (LaneDeploySpec{..}, offset) + + + +-- =============================================== +-- | TransferTimeout +-- =============================================== + +encodeTransferTimeout : TransferTimeout -> BytesHex +encodeTransferTimeout variant = case variant of + Indefinite -> fromSome (encodeUint8 0) + RelativeHours payload -> fromSome (encodeUint8 1) <> fromSome (encodeInt64 payload) + +decodeTransferTimeoutAt : BytesHex -> Int -> Optional (TransferTimeout, Int) +decodeTransferTimeoutAt encoded offset = do + tagHex <- extractBytes encoded offset 1 + if tagHex == "00" then Some (Indefinite, offset + 1) + else if tagHex == "01" then do + (payload, newOffset) <- decodeInt64At encoded (offset + 1) + Some (RelativeHours payload, newOffset) + else None + + diff --git a/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2Types.daml b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2Types.daml new file mode 100644 index 000000000..ddb94f8f0 --- /dev/null +++ b/contracts/ccip/registry-pools/burn-mint-token-pool/daml/CCIP/Registry/BurnMintTokenPoolV2Types.daml @@ -0,0 +1,170 @@ +-- | Types for BurnMintTokenPool, extracted to avoid circular dependencies +module CCIP.Registry.BurnMintTokenPoolV2Types where + +import DA.Crypto.Text (BytesHex) + +import qualified Chainlink.InstanceAddress + +import qualified CCIP.CodecV2.FinalityConfig + +data ChainUpdate = ChainUpdate + with + remoteChainSelector : Numeric 0 -- Chain selector for this config update + remotePools : [BytesHex] -- Allowed remote pool addresses for this chain + remoteTokenAddress : BytesHex -- The token address on the remote chain + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for incoming transfers + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for outgoing transfers + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig -- Allowed finality policy, "WaitForFinality" means finalized-only + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for incoming transfers + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for FTF incoming transfers + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for outgoing transfers + deriving (Eq, Show) + +data RemoteChainConfig = RemoteChainConfig + with + remotePools : [BytesHex] -- Allowed remote pool addresses for this chain + remoteTokenAddress : BytesHex -- The token address on the remote chain + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for incoming transfers + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for outgoing transfers + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig -- Allowed finality policy, "WaitForFinality" means finalized-only + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for incoming transfers + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for FTF incoming transfers + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for outgoing transfers + deriving (Eq, Show) + +data TokenTransferFeeConfigArgs = TokenTransferFeeConfigArgs + with + destChainSelector : Numeric 0 -- Chain selector for this config update + isEnabled : Bool -- Whether this config is enabled + destGasOverhead : Int -- Gas charged to execute the token transfer on the destination chain. + destBytesOverhead : Int -- Data availability bytes. + feeUSDCents : Numeric 0 -- Fee to charge for token transfers, multiples of 0.01 USD. + feeBps : Numeric 0 -- Proportional token fee in bps, applied by the pool during LockOrBurn. + deriving (Eq, Show) + +data TokenTransferFeeConfig = TokenTransferFeeConfig + with + isEnabled : Bool -- Whether this config is enabled + destGasOverhead : Int -- Gas charged to execute the token transfer on the destination chain. + destBytesOverhead : Int -- Data availability bytes. + feeUSDCents : Numeric 0 -- Fee to charge for token transfers, multiples of 0.01 USD. + feeBps : Numeric 0 -- Proportional token fee in bps, applied by the pool during LockOrBurn. + deriving (Eq, Show) + +data RateLimitConfigArgs = RateLimitConfigArgs + with + remoteChainSelector : Numeric 0 + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +-- | Transfer timeout configuration for pending TransferInstructions. +-- Splice 0.6.11 enforces tokenStandardMaxTTL (default 90 days) on transfer +-- lifetimes, so executeBefore cannot be unbounded. Use RelativeHours for tokens +-- with stricter expiry constraints. Indefinite caps at the Splice default max. +data TransferTimeout + = Indefinite -- Use now + 90 days - capped by Splice tokenStandardMaxTTL + | RelativeHours Int -- Use now + N hours - for tokens with expiry constraints + deriving (Eq, Show) + +-- =========================================================================== +-- MCMS Choice Params Types +-- =========================================================================== + +-- | Params for SetDynamicConfig choice +data SetDynamicConfigParams = SetDynamicConfigParams + with + rateLimitAdmin : Optional Party + deriving (Eq, Show) + +-- | Params for SetObservers choice +data SetObserversParams = SetObserversParams + with + observers : [Party] + deriving (Eq, Show) + +-- | Params for ApplyChainUpdates choice +data ApplyChainUpdatesParams = ApplyChainUpdatesParams + with + remoteChainSelectorsToRemove : [Numeric 0] + chainsToAdd : [ChainUpdate] + deriving (Eq, Show) + +-- | Params for ApplyTokenTransferFeeConfigUpdates choice +data ApplyTokenTransferFeeConfigUpdatesParams = ApplyTokenTransferFeeConfigUpdatesParams + with + tokenTransferFeeConfigArgs : [TokenTransferFeeConfigArgs] + disableTokenTransferFeeConfigArgs : [Numeric 0] + deriving (Eq, Show) + +-- | Params for SetRateLimiterReferences choice (poolOwner only; MCMS entrypoint). +-- | Updates which RateLimiter contract identities the pool references per remote chain. +data SetRateLimiterReferencesParams = SetRateLimiterReferencesParams + with + rateLimitConfigArgs : [RateLimitConfigArgs] + deriving (Eq, Show) + +-- | Params for SetRateLimitConfig (tune capacity/rate/isEnabled; MCMS entrypoint). +-- | rateLimiterInstanceAddress: full Chainlink.InstanceAddress.RawInstanceAddress (instanceId@owner), not bare instanceId. +-- | Resolved against MCMS contractIds map key; pool choice receives the resulting ContractId. +data SetRateLimitConfigParams = SetRateLimitConfigParams + with + caller : Party + rateLimiterInstanceAddress : Chainlink.InstanceAddress.RawInstanceAddress + newIsEnabled : Bool + newCapacity : Numeric 0 + newRate : Numeric 0 + deriving (Eq, Show) + +-- | Params for SetTransferTimeout choice +data SetTransferTimeoutParams = SetTransferTimeoutParams + with + transferTimeout : TransferTimeout + deriving (Eq, Show) + +-- | MCMS params: set one ChoiceContext entry from a serialized AnyValue payload. +-- | valuePayload must be exactly encodeAnyValue output (see CCIP.ContextCodec); AV_ContractId is not round-trippable in bytes—use AddPoolReceiveContextContractValueParams instead. +data AddPoolReceiveContextNonContractValueParams = AddPoolReceiveContextNonContractValueParams + with + contextKey : Text + valuePayload : BytesHex + deriving (Eq, Show) + +-- | MCMS params: set one ChoiceContext entry to AV_ContractId by resolving referentInstanceAddress in the MCMS contractIds map. +data AddPoolReceiveContextContractValueParams = AddPoolReceiveContextContractValueParams + with + contextKey : Text + referentInstanceAddress : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +-- | MCMS params: remove one ChoiceContext entry by key. +data RemovePoolReceiveContextValueParams = RemovePoolReceiveContextValueParams + with + contextKey : Text + deriving (Eq, Show) + +-- | Config for one RateLimiter to deploy alongside a lane, via Initialize. Direction/mode are +-- implied by which slot of LaneDeploySpec this occupies, not repeated here. +data RateLimiterDeploySpec = RateLimiterDeploySpec + with + instanceId : Text + isEnabled : Bool + capacity : Numeric 0 + rate : Numeric 0 + deriving (Eq, Show) + +-- | Config for one remote-chain lane to wire up via Initialize: routing/CCV/finality config, +-- plus the three rate limiters to deploy alongside it (matching ChainUpdate's shape). +data LaneDeploySpec = LaneDeploySpec + with + remoteChainSelector : Numeric 0 + remotePools : [BytesHex] + remoteTokenAddress : BytesHex + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig + inbound : RateLimiterDeploySpec + outbound : RateLimiterDeploySpec + inboundCustomFinality : RateLimiterDeploySpec + deriving (Eq, Show) diff --git a/contracts/ccip/registry-pools/lock-release-token-pool/daml.yaml b/contracts/ccip/registry-pools/lock-release-token-pool/daml.yaml new file mode 100644 index 000000000..e7f87f464 --- /dev/null +++ b/contracts/ccip/registry-pools/lock-release-token-pool/daml.yaml @@ -0,0 +1,25 @@ +sdk-version: 3.4.11 +name: ccip-registry-lock-release-token-pool-v2 +source: daml +version: 2.1.1 +build-options: + - "-Wno-crypto-text-is-alpha" + - "-Werror=unused-dependency" +data-dependencies: + - ../../../dependencies/splice/splice-api-token-metadata-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-holding-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-burn-mint-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-transfer-instruction-v1-1.0.0.dar + - ../../../mcms/api/.daml/dist/mcms-api-1.0.0.dar + - ../../../chainlink/api/.daml/dist/chainlink-api-2.0.0.dar + - ../../codec/.daml/dist/ccip-codec-v2-2.0.0.dar + - ../../api/.daml/dist/ccip-api-v2-2.0.0.dar + - ../../extension-api/.daml/dist/ccip-extension-api-v2-2.0.0.dar + - ../../events/.daml/dist/ccip-events-v2-2.0.0.dar + - ../../tickets/.daml/dist/ccip-tickets-v2-2.0.0.dar + - ../../core/.daml/dist/ccip-core-v2-2.1.1.dar + - ../../utils/.daml/dist/ccip-utils-v2-2.1.0.dar + - ../rate-limiter/.daml/dist/ccip-registry-rate-limiter-v2-2.0.1.dar +dependencies: + - daml-prim + - daml-stdlib diff --git a/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2.daml b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2.daml new file mode 100644 index 000000000..767df475d --- /dev/null +++ b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2.daml @@ -0,0 +1,1478 @@ +-- | Lock/release token pool for CCIP token transfers. +-- +-- Supports Splice token standard patterns where TransferFactory implementations +-- may look up preapproval from extraArgs.context (e.g., Amulet's ExternalPartyAmuletRules +-- uses "transfer-preapproval" key). The pool stores a poolReceiveContext that gets +-- merged into extraArgs for inbound transfers, enabling direct transfers without +-- pending state when the pool has preapproval. +module CCIP.Registry.LockReleaseTokenPoolV2 ( + module CCIP.Registry.LockReleaseTokenPoolV2 + , module CCIP.Registry.LockReleaseTokenPoolV2Types + ) where + +import DA.Action (foldlA) +import DA.Crypto.Text (BytesHex, byteCount) +import DA.Foldable qualified as Foldable +import DA.List (dedup) +import qualified DA.Map as Map +import DA.Optional (fromOptional, fromSomeNote) +import qualified DA.TextMap as TextMap +import DA.Time (addRelTime, hours) +import DA.Foldable (forA_) + +import qualified Splice.Api.Token.HoldingV1 +import qualified Splice.Api.Token.MetadataV1 +import qualified Splice.Api.Token.TransferInstructionV1 + +import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiverView(..)) + +import qualified Chainlink.InstanceAddress + +import qualified CCIP.APIV2.ExecutingMessage +import qualified CCIP.APIV2.FeeQuoter +import qualified CCIP.APIV2.RMNRemote +import qualified CCIP.APIV2.SendingMessage +import qualified CCIP.APIV2.TokenAdminRegistry +import qualified CCIP.CodecV2.FinalityConfig +import qualified CCIP.CodecV2.Math +import qualified CCIP.CodecV2.MessageCodecV1 +import qualified CCIP.CodecV2.Uint256 +import qualified CCIP.CoreV2.TokenAdminRegistry as TAR +import CCIP.CoreV2.TokenAdminRegistryTypes (PoolRegistration(..)) +import qualified CCIP.EventsV2.Events +import qualified CCIP.InterfacesV2.TokenPool +import qualified CCIP.Registry.RateLimiterV2 +import qualified CCIP.TicketsV2 +import qualified CCIP.UtilsV2.Context + +import CCIP.Registry.LockReleaseTokenPoolV2CodecGen ( + decodeSetDynamicConfigParamsAt, + decodeSetObserversParamsAt, + decodeApplyChainUpdatesParamsAt, + decodeApplyTokenTransferFeeConfigUpdatesParamsAt, + decodeSetRateLimiterReferencesParamsAt, + decodeSetRateLimitConfigParamsAt, + decodeSetTransferTimeoutParamsAt, + decodeAddPoolReceiveContextNonContractValueParamsAt, + decodeAddPoolReceiveContextContractValueParamsAt, + decodeRemovePoolReceiveContextValueParamsAt, + ) +import CCIP.Registry.LockReleaseTokenPoolV2Types ( + TokenTransferFeeConfig(..), + RemoteChainConfig(..), + ChainUpdate(..), + TokenTransferFeeConfigArgs(..), + RateLimitConfigArgs(..), + TransferTimeout(..), + SetDynamicConfigParams(..), + SetObserversParams(..), + ApplyChainUpdatesParams(..), + ApplyTokenTransferFeeConfigUpdatesParams(..), + SetRateLimiterReferencesParams(..), + SetRateLimitConfigParams(..), + SetTransferTimeoutParams(..), + AddPoolReceiveContextNonContractValueParams(..), + AddPoolReceiveContextContractValueParams(..), + RemovePoolReceiveContextValueParams(..), + RateLimiterDeploySpec(..), + LaneDeploySpec(..), + ) + +transferFactoryContextKey : Text +transferFactoryContextKey = "transfer-factory" + +lookupTransferFactory : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (ContractId Splice.Api.Token.TransferInstructionV1.TransferFactory) +lookupTransferFactory ctx = CCIP.UtilsV2.Context.getFromContextU ctx transferFactoryContextKey + +transferFactoryExtraArgsContextValuesContextKey : Text +transferFactoryExtraArgsContextValuesContextKey = "transfer-factory-extra-args-context-values" + +lookupTransferFactoryExtraArgsContextValues : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (TextMap.TextMap Splice.Api.Token.MetadataV1.AnyValue) +lookupTransferFactoryExtraArgsContextValues ctx = fromOptional TextMap.empty <$> CCIP.UtilsV2.Context.lookupFromContextU ctx transferFactoryExtraArgsContextValuesContextKey + +transferFactoryExtraArgsMetaValuesContextKey : Text +transferFactoryExtraArgsMetaValuesContextKey = "transfer-factory-extra-args-meta-values" + +lookupTransferFactoryExtraArgsMetaValues : Splice.Api.Token.MetadataV1.ChoiceContext -> Update (TextMap.TextMap Text) +lookupTransferFactoryExtraArgsMetaValues ctx = fromOptional TextMap.empty <$> CCIP.UtilsV2.Context.lookupFromContextU ctx transferFactoryExtraArgsMetaValuesContextKey + +tokenPoolHoldingsContextKey : Text +tokenPoolHoldingsContextKey = "token-pool-holdings" + +lookupTokenPoolHoldings : Splice.Api.Token.MetadataV1.ChoiceContext -> Update [ContractId Splice.Api.Token.HoldingV1.Holding] +lookupTokenPoolHoldings ctx = CCIP.UtilsV2.Context.getFromContextU ctx tokenPoolHoldingsContextKey + +data LockReleaseTokenPoolDeps = LockReleaseTokenPoolDeps + with + tokenAdminRegistry : Chainlink.InstanceAddress.RawInstanceAddress + rmnRemote : Chainlink.InstanceAddress.RawInstanceAddress + feeQuoter : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +parseSourcePoolDecimals : Int -> BytesHex -> Int +parseSourcePoolDecimals localDecimals sourcePoolData = + if sourcePoolData == "" + then localDecimals + else + let decoded = fromSomeNote + "ReleaseFromTicket: invalid source pool decimals" + (CCIP.CodecV2.MessageCodecV1.decodeUint256 sourcePoolData) + in + if decoded < 0.0 || decoded > 255.0 + then error "ReleaseFromTicket: invalid source pool decimals" + else truncate decoded + +calculateLocalAmount : Numeric 0 -> Int -> Int -> Numeric 0 +calculateLocalAmount remoteAmount remoteDecimals localDecimals = + CCIP.CodecV2.Math.calculateLocalAmountChecked + "LockReleaseTokenPool.calculateLocalAmount" + remoteAmount + remoteDecimals + localDecimals + +assertHoldingMatches + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> ContractId Splice.Api.Token.HoldingV1.Holding + -> Update Splice.Api.Token.HoldingV1.HoldingView +assertHoldingMatches prefix expectedOwner expectedInstrumentId cid = do + holding <- fetch cid + let hv = view holding + assertMsg (prefix <> ": holding owner mismatch") + (hv.owner == expectedOwner) + assertMsg (prefix <> ": holding instrumentId mismatch") + (hv.instrumentId == expectedInstrumentId) + pure hv + +sumCheckedHoldingAmounts + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> [ContractId Splice.Api.Token.HoldingV1.Holding] + -> Update Decimal +sumCheckedHoldingAmounts prefix expectedOwner expectedInstrumentId = + foldlA + (\acc cid -> do + hv <- assertHoldingMatches prefix expectedOwner expectedInstrumentId cid + pure (acc + hv.amount)) + 0.0 + +assertHoldingsMatchAndSum + : Text + -> Party + -> Splice.Api.Token.HoldingV1.InstrumentId + -> Decimal + -> [ContractId Splice.Api.Token.HoldingV1.Holding] + -> Update () +assertHoldingsMatchAndSum prefix expectedOwner expectedInstrumentId expectedAmount cids = do + actualAmount <- sumCheckedHoldingAmounts prefix expectedOwner expectedInstrumentId cids + assertMsg (prefix <> ": holding amount mismatch") + (actualAmount == expectedAmount) + +assertTransferInstructionMatches + : Text + -> Party + -> Party + -> Decimal + -> Splice.Api.Token.HoldingV1.InstrumentId + -> ContractId Splice.Api.Token.TransferInstructionV1.TransferInstruction + -> Update () +assertTransferInstructionMatches prefix expectedSender expectedReceiver expectedAmount expectedInstrumentId instrCid = do + instr <- fetch instrCid + let instrView = view instr + let transfer = instrView.transfer + assertMsg (prefix <> ": pending transfer sender mismatch") + (transfer.sender == expectedSender) + assertMsg (prefix <> ": pending transfer receiver mismatch") + (transfer.receiver == expectedReceiver) + assertMsg (prefix <> ": pending transfer amount mismatch") + (transfer.amount == expectedAmount) + assertMsg (prefix <> ": pending transfer instrumentId mismatch") + (transfer.instrumentId == expectedInstrumentId) + +remoteChainRateLimiterAddresses : Map.Map (Numeric 0) RemoteChainConfig -> [Chainlink.InstanceAddress.RawInstanceAddress] +remoteChainRateLimiterAddresses configs = + concatMap + (\(_, cfg) -> + [ cfg.inboundRateLimiter + , cfg.inboundCustomBlockConfirmationsRateLimiter + , cfg.outboundRateLimiter + ]) + (Map.toList configs) + +assertPoolConfiguredRateLimiter + : Text + -> Party + -> Map.Map (Numeric 0) RemoteChainConfig + -> ContractId CCIP.Registry.RateLimiterV2.RateLimiter + -> Update () +assertPoolConfiguredRateLimiter poolInstanceId poolOwner remoteChainConfigs rateLimiterCid = do + rl <- fetch rateLimiterCid + assertMsg "SetRateLimitConfig: rate limiter not configured on pool" + (CCIP.Registry.RateLimiterV2.rawInstanceAddress rl `elem` remoteChainRateLimiterAddresses remoteChainConfigs) + assertMsg "SetRateLimitConfig: pool instance mismatch" (rl.poolInstanceId == poolInstanceId) + assertMsg "SetRateLimitConfig: pool owner mismatch" (rl.poolOwner == poolOwner) + +assertRateLimiter + : Text + -> Text + -> Numeric 0 + -> CCIP.Registry.RateLimiterV2.RateLimitDirection + -> CCIP.Registry.RateLimiterV2.RateLimitMode + -> Chainlink.InstanceAddress.RawInstanceAddress + -> CCIP.Registry.RateLimiterV2.RateLimiter + -> Update () +assertRateLimiter prefix poolInstanceId remoteChainSelector direction mode expected limiter = do + Chainlink.InstanceAddress.assertEqual (prefix <> ": instance address mismatch") + limiter.instanceId + limiter.poolOwner + expected + assertMsg (prefix <> ": pool instance mismatch") + (limiter.poolInstanceId == poolInstanceId) + assertMsg (prefix <> ": remote chain mismatch") + (limiter.remoteChainSelector == remoteChainSelector) + assertMsg (prefix <> ": direction mismatch") + (limiter.direction == direction) + assertMsg (prefix <> ": rate limit mode mismatch") + (limiter.mode == mode) + +bpsDenominator : Numeric 0 +bpsDenominator = 10000.0 + +calculateFeeFromBps : Numeric 0 -> Numeric 0 -> Numeric 0 +calculateFeeFromBps amount feeBps = CCIP.CodecV2.Math.mulDivDown amount feeBps bpsDenominator + +customFinalityRateLimiterRequired : CCIP.CodecV2.FinalityConfig.FinalityConfig -> Bool +customFinalityRateLimiterRequired finalityConfig = + CCIP.CodecV2.FinalityConfig.customFinalityRateLimiterRequired finalityConfig + +deployRateLimiter + : Party + -> Text + -> [Party] + -> Numeric 0 + -> CCIP.Registry.RateLimiterV2.RateLimitDirection + -> CCIP.Registry.RateLimiterV2.RateLimitMode + -> Time + -> RateLimiterDeploySpec + -> Update (ContractId CCIP.Registry.RateLimiterV2.RateLimiter) +deployRateLimiter poolOwner poolInstanceId observers remoteChainSelector direction mode now spec = + create CCIP.Registry.RateLimiterV2.RateLimiter with + instanceId = spec.instanceId + poolInstanceId + poolOwner + remoteChainSelector + direction + mode + isEnabled = spec.isEnabled + capacity = spec.capacity + rate = spec.rate + tokens = spec.capacity + lastUpdated = now + observers + +-- | Result of Initialize: the rate limiters deployed and the TokenConfig registered with TAR. +data InitializeResult = InitializeResult + with + rateLimiterCids : [ContractId CCIP.Registry.RateLimiterV2.RateLimiter] + tokenConfigCid : ContractId TAR.TokenConfig + deriving (Eq, Show) + +-- | Lock/release token pool with dynamic CCV requirements per chain. +template LockReleaseTokenPool + with + instanceId : Text -- Unique identifier for this pool instance + poolOwner : Party + + ccipOwner : Party + instrumentId : Splice.Api.Token.HoldingV1.InstrumentId + -- | The (local) decimals that the token pool will assume. + -- Note: As the Token Standard uses Decimal with a fixed 10 decimals, setting anything but 10 decimals on the pool will result in the local amounts being scaled up/down. + -- That is, if the pool is set to 8 decimals, a remote amount of 1.0 will be scaled to Decimal("0.01") when minting/burning locally. + -- And vice versa, if the pool is set to 12 decimals, a remote amount of 1.0 will be scaled to Decimal("100.0") when minting/burning locally. + -- This is an advanced setting that should only be used if the asset requires additional precision beyond the 10 decimals of the Token Standard, for example with an remote asset that has 18 decimals and requires all of this precision to be preserved when transferring to Canton. + -- In general, it is recommended to set the pool decimals to 10, which is the default for the Token Standard and will lead to all amounts being represented correctly locally and in Wallets. + decimals : Int + + -- | Dynamic config + -- Party capable of updating rate limits for this pool. + rateLimitAdmin : Optional Party + -- Observer parties used by the External Disclosure Service (EDS) to auto-detect this + -- pool. Mandatory - must contain at least one party, enforced by the ensure clause + -- below. Set at creation or via SetObservers; cannot be added retroactively to a + -- pool deployed on a package version that predates this field. + observers : [Party] + + -- Remote chain config: remote token address, CCV requirements, and rate limiter requirements for each remote chain. + -- Keyed by remote chain selector. + remoteChainConfigs : Map.Map (Numeric 0) RemoteChainConfig + -- Optional token-transfer fee overrides keyed by destination chain selector. + tokenTransferFeeConfigs : Map.Map (Numeric 0) TokenTransferFeeConfig + + -- Context merged into extraArgs for transfers TO the pool (LockOrBurn). + -- For tokens following Splice patterns (e.g., Amulet), this should contain + -- the pool's preapproval CID under the key the TransferFactory expects + -- (e.g., "transfer-preapproval"). Converted to Splice ChoiceContext at the + -- TransferFactory boundary. + poolReceiveContext : Splice.Api.Token.MetadataV1.ChoiceContext + -- Timeout for pending TransferInstructions created by this pool. + transferTimeout : TransferTimeout + deps : LockReleaseTokenPoolDeps + where + signatory poolOwner + observer optional [] pure rateLimitAdmin <> observers + + ensure Chainlink.InstanceAddress.assertValidInstanceId instanceId + && not (null observers) + && CCIP.CodecV2.Math.isValidTokenDecimals decimals + && all + (\cfg -> CCIP.CodecV2.FinalityConfig.isValidFinalityConfig cfg.finalityConfig) + (Map.values remoteChainConfigs) + && all + (\cfg -> + cfg.feeBps >= 0.0 + && cfg.feeBps < bpsDenominator + && cfg.destBytesOverhead >= 32 + ) + (Map.values tokenTransferFeeConfigs) + + interface instance CCIP.InterfacesV2.TokenPool.ITokenPool for LockReleaseTokenPool where + view = CCIP.InterfacesV2.TokenPool.TokenPoolView with + owner = poolOwner + ccipOwner = ccipOwner + instrumentId = instrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + tokenPool_getRequiredCCVsImpl self arg = do + let finality = fromSomeNote "Invalid finality" (CCIP.CodecV2.FinalityConfig.decodeRequestedFinality arg.finality) + exercise (fromInterfaceContractId @LockReleaseTokenPool self) GetRequiredCCVs + with + remoteChainSelector = arg.remoteChainSelector + sourceAmount = arg.sourceAmount + finality = finality.requested + extraData = arg.extraData + direction = arg.direction + caller = arg.caller + + tokenPool_verifyInboundMessageImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) VerifyInboundMessage + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + executingMessageCid = arg.executingMessageCid + context = arg.context + caller = arg.caller + + tokenPool_verifyOutboundCCVsImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) VerifyOutboundCCVs + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + sendingMessageCid = arg.sendingMessageCid + amount = arg.amount + context = arg.context + caller = arg.caller + + tokenPool_releaseFromTicketImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) ReleaseFromTicket + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + rmnRemoteCid = arg.rmnRemoteCid + tokenReceiveTicketCid = arg.tokenReceiveTicketCid + context = arg.context + caller = arg.caller + + tokenPool_lockOrBurnImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) LockOrBurn + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + rmnRemoteCid = arg.rmnRemoteCid + sendingMessageCid = arg.sendingMessageCid + senderInputCids = arg.senderInputCids + amount = arg.amount + context = arg.context + caller = arg.caller + + tokenPool_calculateFeeImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) CalculateFee + with + tokenAdminRegistryCid = arg.tokenAdminRegistryCid + tokenConfigCid = arg.tokenConfigCid + sendingMessageCid = arg.sendingMessageCid + feeQuoterCid = arg.feeQuoterCid + tokenInstrumentId = arg.tokenInstrumentId + context = arg.context + caller = arg.caller + + tokenPool_getFeeImpl self arg = do + exercise (fromInterfaceContractId @LockReleaseTokenPool self) GetFee + with + feeQuoterCid = arg.feeQuoterCid + destChainSelector = arg.destChainSelector + tokenInstrumentId = arg.tokenInstrumentId + context = arg.context + caller = arg.caller + + -- Sets the dynamic configuration for the pool. + -- + -- Params: + -- rateLimitAdmin: Optional party that can manage rate limits + -- + -- Callable by poolOwner + choice SetDynamicConfig : ContractId LockReleaseTokenPool + with + rateLimitAdmin : Optional Party + controller poolOwner + do + create this with + rateLimitAdmin = rateLimitAdmin + + -- Sets the EDS observers for the pool. + -- + -- Params: + -- observers: parties used by EDS to auto-detect this pool + -- + -- Callable by poolOwner + choice SetObservers : ContractId LockReleaseTokenPool + with + observers : [Party] + controller poolOwner + do + create this with + observers = observers + + -- Sets the remote chain configurations for the pool. + -- + -- Params: + -- remoteChainSelectorsToRemove: list of remote chain selectors to remove from the pool config + -- chainsToAdd: list of remote chain configs to add to the pool config. + -- If a remote chain selector already exists in the pool config, it must be removed first + -- before it can be added with updated config values. + -- + -- Callable by poolOwner + choice ApplyChainUpdates : ContractId LockReleaseTokenPool + with + remoteChainSelectorsToRemove : [Numeric 0] + chainsToAdd : [ChainUpdate] + controller poolOwner + do + -- Validate + forA_ remoteChainSelectorsToRemove $ \remoteChainSelectorToRemove -> do + assertMsg "ApplyChainUpdates: cannot remove non-existent chain config" (Map.member remoteChainSelectorToRemove remoteChainConfigs) + -- Remove specified chain configs + let remoteChainConfigsRemoved = foldl + (\configs remoteChainSelectorToRemove -> Map.delete remoteChainSelectorToRemove configs) + remoteChainConfigs + remoteChainSelectorsToRemove + + -- If the chain already exists, revert + let chainSelectorsToAdd = map (\u -> u.remoteChainSelector) chainsToAdd + assertMsg "ChainAlreadyExists: duplicate remoteChainSelector in chainsToAdd" + (length chainSelectorsToAdd == length (dedup chainSelectorsToAdd)) + + -- Validate + forA_ chainsToAdd $ \ChainUpdate{remoteChainSelector, remotePools, remoteTokenAddress, inboundCCVs, outboundCCVs, finalityConfig, inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter} -> do + assertMsg "ApplyChainUpdates: invalid chain selector" (remoteChainSelector > 0.0) + assertMsg "ApplyChainUpdates: remote token address cannot be empty" (remoteTokenAddress /= "") + -- If the chain already exists, the update should first remove it. + assertMsg ("ApplyChainUpdates: chain already exists: " <> show remoteChainSelector) (not (Map.member remoteChainSelector remoteChainConfigsRemoved)) + assertMsg "ApplyChainUpdates: BlockDepth finality must be positive" (CCIP.CodecV2.FinalityConfig.isValidFinalityConfig finalityConfig) + -- Validate rate limiters + CCIP.Registry.RateLimiterV2.assertDistinctRateLimiters "ApplyChainUpdates" + inboundRateLimiter + outboundRateLimiter + (if customFinalityRateLimiterRequired finalityConfig + then Some inboundCustomBlockConfirmationsRateLimiter + else None) + -- Validate remote pools + assertMsg "ApplyChainUpdates: remotePools cannot contain empty address" ("" `notElem` remotePools) + assertMsg "ApplyChainUpdates: remotePools cannot contain duplicates" (length remotePools == length (dedup remotePools)) + assertMsg "ApplyChainUpdates: inbound and outbound CCV lists cannot contain duplicates" (length inboundCCVs == length (dedup inboundCCVs) && length outboundCCVs == length (dedup outboundCCVs)) + -- Add new chain configs + let remoteChainConfigsAdded = foldl + (\configs chainToAdd -> + Map.insert chainToAdd.remoteChainSelector RemoteChainConfig with + remotePools = chainToAdd.remotePools + remoteTokenAddress = chainToAdd.remoteTokenAddress + inboundCCVs = chainToAdd.inboundCCVs + outboundCCVs = chainToAdd.outboundCCVs + finalityConfig = chainToAdd.finalityConfig + inboundRateLimiter = chainToAdd.inboundRateLimiter + inboundCustomBlockConfirmationsRateLimiter = chainToAdd.inboundCustomBlockConfirmationsRateLimiter + outboundRateLimiter = chainToAdd.outboundRateLimiter + configs) + remoteChainConfigsRemoved + chainsToAdd + create this with + remoteChainConfigs = remoteChainConfigsAdded + + -- Runs the deploy+configure+register flow for this already-created pool: propose+accept + -- admin for the instrument if needed, deploy rate limiters and wire up lanes via + -- ApplyChainUpdates, and register with the shared TokenAdminRegistry - all as one atomic + -- operation, callable via a single CreateAndExercise ledger command right after create. + -- + -- Only supports the self-issued/self-serve admin path: instrumentId.admin must equal + -- admin, since ProposeAdministrator requires caller == instrumentId.admin, and this + -- choice calls it with caller = admin. For instruments where the registry owner must + -- co-sign admin assignment, propose/accept admin separately first, then call this with + -- existingTokenConfigCid = Some (the resulting cid). + nonconsuming choice Initialize : InitializeResult + with + tokenAdminRegistryCid : ContractId TAR.TokenAdminRegistry + existingTokenConfigCid : Optional (ContractId TAR.TokenConfig) + admin : Party + lanes : [LaneDeploySpec] + controller poolOwner, admin + do + proposeResult <- exercise tokenAdminRegistryCid TAR.ProposeAdministrator with + tokenConfigCid = existingTokenConfigCid + instrumentId + newAdmin = admin + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + acceptedTokenConfigCid <- exercise proposeResult.tokenAdminRegistryCid TAR.AcceptAdminRole with + tokenConfigCid = proposeResult.tokenConfigCid + instrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + + now <- getTime + chainUpdatesAndCids <- mapA (\lane -> do + inboundCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality now lane.inbound + outboundCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Outbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality now lane.outbound + inboundCustomFinalityCid <- deployRateLimiter poolOwner instanceId observers + lane.remoteChainSelector CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + CCIP.Registry.RateLimiterV2.RateLimitMode_CustomFinality now lane.inboundCustomFinality + let chainUpdate = ChainUpdate with + remoteChainSelector = lane.remoteChainSelector + remotePools = lane.remotePools + remoteTokenAddress = lane.remoteTokenAddress + inboundCCVs = lane.inboundCCVs + outboundCCVs = lane.outboundCCVs + finalityConfig = lane.finalityConfig + inboundRateLimiter = Chainlink.InstanceAddress.make lane.inbound.instanceId poolOwner + inboundCustomBlockConfirmationsRateLimiter = Chainlink.InstanceAddress.make lane.inboundCustomFinality.instanceId poolOwner + outboundRateLimiter = Chainlink.InstanceAddress.make lane.outbound.instanceId poolOwner + pure (chainUpdate, [inboundCid, outboundCid, inboundCustomFinalityCid]) + ) lanes + + let chainUpdates = map fst chainUpdatesAndCids + rateLimiterCids = concat (map snd chainUpdatesAndCids) + + _ <- exercise self ApplyChainUpdates with + remoteChainSelectorsToRemove = [] + chainsToAdd = chainUpdates + + newTokenConfigCid <- exercise proposeResult.tokenAdminRegistryCid TAR.SetPool with + tokenConfigCid = acceptedTokenConfigCid + instrumentId + tokenPool = Some PoolRegistration with + poolOwner + poolInstanceId = instanceId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = admin + + pure InitializeResult with + rateLimiterCids + tokenConfigCid = newTokenConfigCid + + -- Updates the token transfer fee configurations for specified destination chains. + -- + -- Params: + -- tokenTransferFeeConfigArgs: Array of structs containing destination chain selectors and their fee configs. + -- disableTokenTransferFeeConfigArgs: Array of destination chain selectors to disable custom fee configs for. + -- + -- Callable by poolOwner + choice ApplyTokenTransferFeeConfigUpdates : ContractId LockReleaseTokenPool + with + tokenTransferFeeConfigArgs : [TokenTransferFeeConfigArgs] + disableTokenTransferFeeConfigArgs : [Numeric 0] + controller poolOwner + do + -- Validate + forA_ tokenTransferFeeConfigArgs $ \arg -> do + assertMsg "ApplyTokenTransferFeeConfigUpdates: invalid chain selector" (arg.destChainSelector > 0.0) + assertMsg ("ApplyTokenTransferFeeConfigUpdates: unsupported chain selector: " <> show arg.destChainSelector) (Map.member arg.destChainSelector remoteChainConfigs) + -- Reject configs with isEnabled: false - use disableTokenTransferFeeConfigs parameter instead. + assertMsg "ApplyTokenTransferFeeConfigUpdates: must be enabled" (arg.isEnabled) + assertMsg "ApplyTokenTransferFeeConfigUpdates: feeBps must be non-negative" (arg.feeBps >= 0.0) + assertMsg ("ApplyTokenTransferFeeConfigUpdates: feeUSDCents must be non-negative: " <> show arg.feeUSDCents) (arg.feeUSDCents >= 0.0) + assertMsg "ApplyTokenTransferFeeConfigUpdates: feeBps must be less than 10000" (arg.feeBps < bpsDenominator) + assertMsg "ApplyTokenTransferFeeConfigUpdates: destBytesOverhead must be at least 32" (arg.destBytesOverhead >= 32) + assertMsg "ApplyTokenTransferFeeConfigUpdates: destGasOverhead must be non-zero" (arg.destGasOverhead > 0) + + -- Update + let tokenTransferFeeConfigsUpdated = foldl + (\configs arg -> + Map.insert arg.destChainSelector TokenTransferFeeConfig with + isEnabled = arg.isEnabled + destGasOverhead = arg.destGasOverhead + destBytesOverhead = arg.destBytesOverhead + feeUSDCents = arg.feeUSDCents + feeBps = arg.feeBps + configs) + tokenTransferFeeConfigs + tokenTransferFeeConfigArgs + let tokenTransferFeeConfigsRemoved = foldr Map.delete tokenTransferFeeConfigsUpdated disableTokenTransferFeeConfigArgs + create this with + tokenTransferFeeConfigs = tokenTransferFeeConfigsRemoved + + -- Tunes capacity/rate/isEnabled on a rate limiter already referenced by this pool's remote chain config. + -- Callable by poolOwner; caller must be poolOwner or rateLimitAdmin (admin does not sign—owner executes on their behalf). + nonconsuming choice SetRateLimitConfig : ContractId CCIP.Registry.RateLimiterV2.RateLimiter + with + caller : Party + rateLimiterCid : ContractId CCIP.Registry.RateLimiterV2.RateLimiter + newIsEnabled : Bool + newCapacity : Numeric 0 + newRate : Numeric 0 + controller poolOwner + do + assertMsg "SetRateLimitConfig: unauthorized caller" + (caller == poolOwner || rateLimitAdmin == Some caller) + assertPoolConfiguredRateLimiter instanceId poolOwner remoteChainConfigs rateLimiterCid + exercise rateLimiterCid CCIP.Registry.RateLimiterV2.SetConfig with + newIsEnabled + newCapacity + newRate + + -- Updates which RateLimiter contract identities the pool references per remote chain (Canton instance swap). + -- Callable by poolOwner only. + choice SetRateLimiterReferences : ContractId LockReleaseTokenPool + with + rateLimitConfigArgs : [RateLimitConfigArgs] + controller poolOwner + do + -- Validate + forA_ rateLimitConfigArgs $ \arg -> do + assertMsg "SetRateLimiterReferences: invalid chain selector" (arg.remoteChainSelector > 0.0) + cfg <- case Map.lookup arg.remoteChainSelector remoteChainConfigs of + None -> abort ("SetRateLimiterReferences: unsupported chain selector: " <> show arg.remoteChainSelector) + Some cfg -> pure cfg + CCIP.Registry.RateLimiterV2.assertDistinctRateLimiters "SetRateLimitConfig" + arg.inboundRateLimiter + arg.outboundRateLimiter + (if customFinalityRateLimiterRequired cfg.finalityConfig + then Some arg.inboundCustomBlockConfirmationsRateLimiter + else None) + + -- Update + let remoteChainConfigsUpdated = foldl + (\configs RateLimitConfigArgs{remoteChainSelector, inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter} -> + case Map.lookup remoteChainSelector configs of + None -> configs + Some cfg -> + let updatedCfg = cfg with inboundRateLimiter = inboundRateLimiter, inboundCustomBlockConfirmationsRateLimiter = inboundCustomBlockConfirmationsRateLimiter, outboundRateLimiter = outboundRateLimiter + in Map.insert remoteChainSelector updatedCfg configs + ) + remoteChainConfigs + rateLimitConfigArgs + create this with + remoteChainConfigs = remoteChainConfigsUpdated + + -- Add or replace one receive-context entry with a non-contract AnyValue (for preapproval CIDs in Splice token patterns). + -- The context is merged into extraArgs for inbound transfers. + choice AddPoolReceiveContextNonContractValue : ContractId LockReleaseTokenPool + with + contextKey : Text + value : Splice.Api.Token.MetadataV1.AnyValue + controller poolOwner + do + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.insert contextKey value poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + -- Add or replace one receive-context entry with AV_ContractId from an already-resolved ContractId (). + -- (for preapproval CIDs in Splice token patterns). + -- The context is merged into extraArgs for inbound transfers. + choice AddPoolReceiveContextContractValue : ContractId LockReleaseTokenPool + with + contextKey : Text + referredContract : ContractId () + controller poolOwner + do + let av = Splice.Api.Token.MetadataV1.AV_ContractId (coerceContractId referredContract) + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.insert contextKey av poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + choice RemovePoolReceiveContextValue : ContractId LockReleaseTokenPool + with + contextKey : Text + controller poolOwner + do + let newPoolReceiveContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.delete contextKey poolReceiveContext.values + create this with poolReceiveContext = newPoolReceiveContext + + choice ClearPoolReceiveContext : ContractId LockReleaseTokenPool + controller poolOwner + do create this with poolReceiveContext = Splice.Api.Token.MetadataV1.emptyChoiceContext + + -- Set the transfer timeout for pending TransferInstructions. + -- Use Indefinite for tokens without expiry, RelativeHours for tokens with expiry constraints. + -- + -- Callable by poolOwner + choice SetTransferTimeout : ContractId LockReleaseTokenPool + with + newTransferTimeout : TransferTimeout + controller poolOwner + do create this with transferTimeout = newTransferTimeout + + -- Get required CCV raw instance addresses based on chain and direction. + nonconsuming choice GetRequiredCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + with + remoteChainSelector : Numeric 0 + sourceAmount : BytesHex + finality : CCIP.CodecV2.FinalityConfig.FinalityConfig + extraData : BytesHex + direction : CCIP.InterfacesV2.TokenPool.TransferDirection + caller : Party + controller caller + do + case Map.lookup remoteChainSelector remoteChainConfigs of + None -> abort "pool: unsupported remote chain selector" + Some config -> case direction of + CCIP.InterfacesV2.TokenPool.Inbound -> pure config.inboundCCVs + CCIP.InterfacesV2.TokenPool.Outbound -> pure config.outboundCCVs + + -- Validate inbound token transfer message and record pool CCV requirements. + -- Validates that the message has a token transfer targeting this pool's instrument, + -- validates the source pool address against the allowed remote pools for the source chain, + -- looks up per-chain CCV requirements, and records them on the executing message. + -- OffRamp performs the actual CCV coverage check. + nonconsuming choice VerifyInboundMessage : ContractId CCIP.APIV2.ExecutingMessage.IExecutingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + executingMessageCid : ContractId CCIP.APIV2.ExecutingMessage.IExecutingMessage + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + execMsg <- fetch executingMessageCid + let execMsgView = view execMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: executing message tokenAdminRegistry mismatch" + (execMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: executing message rmnRemote mismatch" + (execMsgView.rmnRemote == deps.rmnRemote) + let message = execMsgView.message + + tt <- case message.tokenTransfer of + None -> abort "pool: no token transfer in message" + Some t -> pure t + + let expectedAddr = CCIP.CodecV2.MessageCodecV1.encodeInstrumentId instrumentId + assertMsg "pool: destTokenAddress does not match pool instrumentId" + (tt.destTokenAddress == expectedAddr) + + remoteChainCfg <- case Map.lookup message.sourceChainSelector remoteChainConfigs of + None -> abort "pool: unsupported source chain" + Some cfg -> pure cfg + assertMsg "pool: message finality not allowed by pool finality config" + (CCIP.CodecV2.FinalityConfig.requestedFinalityAllowed message.finality.requested remoteChainCfg.finalityConfig) + + assertMsg "pool: invalid sourcePoolAddress" + (tt.sourcePoolAddress `elem` remoteChainCfg.remotePools) + + assertMsg "pool: sourceTokenAddress does not match remote token address for lane" + (tt.sourceTokenAddress == remoteChainCfg.remoteTokenAddress) + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_SetInboundPoolCCVs with + tokenConfigCid + executingMessageCid + poolInstanceId = instanceId + poolCCVs = remoteChainCfg.inboundCCVs + context = context + caller = poolOwner + + -- Declare outbound CCV requirements on SendingMessageV1 via TAR. + -- Looks up per-chain CCV requirements and records them on the sending message. + nonconsuming choice VerifyOutboundCCVs : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + amount : Decimal + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: amount must be positive" (amount > 0.0) + + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "pool: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: sending message rmnRemote mismatch" + (sendingMsgView.rmnRemote == deps.rmnRemote) + + poolCCVs <- case Map.lookup sendingMsgView.destChainSelector remoteChainConfigs of + None -> abort "pool: unsupported dest chain" + Some cfg -> pure cfg.outboundCCVs + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_SetOutboundPoolCCVs with + tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + poolCCVs = poolCCVs + context = context + caller = poolOwner + + -- Release tokens by consuming a TokenReceiveTicket via TokenAdminRegistry. + -- The TAR validates the ticket was issued by the correct registry instance, + -- the pool is the registered pool for the instrument, and archives the ticket + -- atomically. If any check fails, the entire transaction rolls back. + nonconsuming choice ReleaseFromTicket : CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + rmnRemoteCid : ContractId CCIP.APIV2.RMNRemote.IRMNRemote + tokenReceiveTicketCid : ContractId CCIP.APIV2.ExecutingMessage.ITokenReceiveTicket + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + let tokenReceiveTicketImplementation = coerceContractId @_ @CCIP.TicketsV2.TokenReceiveTicket tokenReceiveTicketCid + + ticket <- fetch tokenReceiveTicketImplementation + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "ReleaseFromTicket: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + _ <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_FetchTokenConfig with + tokenConfigCid + instrumentId = instrumentId + context = context + caller = poolOwner + assertMsg "ReleaseFromTicket: ticket tokenAdminRegistry instance mismatch" + (ticket.tokenAdminRegistryInstanceId == tokenAdminRegistryView.instanceId) + assertMsg "ReleaseFromTicket: ticket ccipOwner mismatch" + (ticket.ccipOwner == tokenAdminRegistryView.ccipOwner) + rmnRemoteView <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_PublicFetch with + expectedAddress = deps.rmnRemote + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "ReleaseFromTicket: rmn mismatch" + rmnRemoteView.instanceId rmnRemoteView.rmnOwner deps.rmnRemote + assertMsg "ReleaseFromTicket: ticket instrument must match pool instrument" + (ticket.instrumentId == instrumentId) + let poolAddress = Chainlink.InstanceAddress.make instanceId poolOwner + assertMsg "ReleaseFromTicket: ticket pool owner mismatch" + (ticket.poolOwner == poolOwner) + assertMsg "ReleaseFromTicket: ticket pool address mismatch" + (ticket.poolAddress == poolAddress) + + isCursed <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_IsCursedForChain with + chainSelector = ticket.sourceChainSelector + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + assertMsg "ReleaseFromTicket: source chain is cursed" (not isCursed) + + assertMsg "ReleaseFromTicket: caller must be tokenReceiver" + (caller == ticket.tokenReceiver) + + -- Defensive consistency check: every verified CCV instance must be + -- owned by a ticket signatory party. + let ticketSignatories = signatory ticket + Foldable.forA_ ticket.verifiedCCVs $ \verifiedCCV -> + assertMsg ("ReleaseFromTicket: verified CCV owner is not a ticket signatory " <> show verifiedCCV) + (Chainlink.InstanceAddress.ownerParty verifiedCCV `elem` ticketSignatories) + + -- Also check that the pool's owner is a signatory on the ticket + assertMsg "ReleaseFromTicket: pool owner is not a ticket signatory" + (poolOwner `elem` ticketSignatories) + + -- Validate the exact pool-required CCV instances are present on the ticket. + -- This preserves the configured instance-level trust model even when + -- multiple CCV instances share the same owner party. + remoteChainCfg <- case Map.lookup ticket.sourceChainSelector remoteChainConfigs of + None -> abort "ReleaseFromTicket: unsupported source chain" + Some cfg -> pure cfg + let requiredPoolCCVs = dedup ticket.requiredInboundPoolCCVs + Foldable.forA_ requiredPoolCCVs $ \requiredCCV -> + assertMsg ("ReleaseFromTicket: missing required pool CCV " <> show requiredCCV) + (requiredCCV `elem` ticket.verifiedCCVs) + + let useCustomFinalityRateLimiter = + ticket.finality /= CCIP.CodecV2.FinalityConfig.WaitForFinality + && CCIP.CodecV2.FinalityConfig.customFinalityRateLimiterRequired remoteChainCfg.finalityConfig + let expectedRateLimitMode = if useCustomFinalityRateLimiter + then CCIP.Registry.RateLimiterV2.RateLimitMode_CustomFinality + else CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality + let expectedRateLimiter = if useCustomFinalityRateLimiter + then remoteChainCfg.inboundCustomBlockConfirmationsRateLimiter + else remoteChainCfg.inboundRateLimiter + rateLimiterCid <- CCIP.UtilsV2.Context.getFromContextU context CCIP.Registry.RateLimiterV2.rateLimiterContextKey + rateLimiter <- fetch rateLimiterCid + assertRateLimiter + "ReleaseFromTicket: rateLimiter" + instanceId + ticket.sourceChainSelector + CCIP.Registry.RateLimiterV2.RateLimitDirection_Inbound + expectedRateLimitMode + expectedRateLimiter + rateLimiter + let sourceDecimals = parseSourcePoolDecimals decimals ticket.sourcePoolData + conversion <- case CCIP.CodecV2.Uint256.calculateLocalAmountFromUint256Checked + "ReleaseFromTicket" + ticket.amount + sourceDecimals + decimals of + None -> abort "ReleaseFromTicket: source amount cannot be represented as local amount" + Some result -> pure result + assertMsg "ReleaseFromTicket: local amount rounds down to zero" + (conversion.localAmount > 0.0) + let localAmount = conversion.localAmount + _ <- exercise rateLimiterCid CCIP.Registry.RateLimiterV2.ConsumeCapacity with + requested = localAmount + + -- Consume ticket via TAR after validating the disclosed TAR identity. + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_ConsumeReceiveTicket with + tokenConfigCid = tokenConfigCid + tokenReceiveTicketCid = tokenReceiveTicketCid + instrumentId = instrumentId + poolInstanceId = instanceId + context = context + caller = poolOwner + + -- Get inputs from context + tokenPoolHoldings <- CCIP.UtilsV2.Context.getFromContextU context tokenPoolHoldingsContextKey + _ <- sumCheckedHoldingAmounts + "ReleaseFromTicket: tokenPoolHoldings" + poolOwner + instrumentId + tokenPoolHoldings + transferFactory <- lookupTransferFactory context + _ <- exercise tokenConfigCid CCIP.APIV2.TokenAdminRegistry.TokenConfig_AssertConfiguredTransferFactory with + suppliedFactory = transferFactory + context = context + caller = poolOwner + extraArgsContextValues <- lookupTransferFactoryExtraArgsContextValues context + extraArgsMetaValues <- lookupTransferFactoryExtraArgsMetaValues context + + -- Transfer tokens from pool to receiver via TransferFactory. + -- If receiver has preapproval, completes immediately. Otherwise creates pending + -- TransferInstruction that receiver must accept. + now <- getTime + let executeBefore = case transferTimeout of + -- Indefinite is bounded by Splice's tokenStandardMaxTTL default (90 days). + Indefinite -> addRelTime now (hours 2160) + RelativeHours h -> addRelTime now (hours h) + let amountDecimal = CCIP.CodecV2.Math.numeric0ToDecimal localAmount + let transfer = Splice.Api.Token.TransferInstructionV1.Transfer with + sender = poolOwner + receiver = ticket.tokenReceiver + amount = amountDecimal + instrumentId = instrumentId + requestedAt = now + executeBefore + inputHoldingCids = tokenPoolHoldings + meta = Splice.Api.Token.MetadataV1.emptyMetadata + + transferResult <- exercise transferFactory + Splice.Api.Token.TransferInstructionV1.TransferFactory_Transfer with + expectedAdmin = instrumentId.admin + transfer + extraArgs = Splice.Api.Token.MetadataV1.ExtraArgs with + context = Splice.Api.Token.MetadataV1.ChoiceContext with + values = extraArgsContextValues + meta = Splice.Api.Token.MetadataV1.Metadata with + values = extraArgsMetaValues + + case transferResult.output of + Splice.Api.Token.TransferInstructionV1.TransferInstructionResult_Completed receiverCids -> do + assertMsg "ReleaseFromTicket: transfer completed with no receiver holdings" (not (null receiverCids)) + assertHoldingsMatchAndSum + "ReleaseFromTicket: receiver holdings" + ticket.tokenReceiver + instrumentId + amountDecimal + receiverCids + let output = CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult_Completed with + receiverHoldingCids = receiverCids + let claimedOutput = CCIP.EventsV2.Events.TokenReceiveTicketClaimed_Completed with + receiverHoldingCids = receiverCids + claimedEventCid <- create CCIP.EventsV2.Events.TokenReceiveTicketClaimed with + ccipOwner = ticket.ccipOwner + ccvOwners = ticket.ccvOwners + poolOwner = ticket.poolOwner + receiver = ticket.receiver + tokenReceiver = ticket.tokenReceiver + tokenReceiveTicketCid = coerceContractId tokenReceiveTicketImplementation + event = CCIP.EventsV2.Events.TokenReceiveTicketClaimedEvent with + verifiedCCVs = ticket.verifiedCCVs + tokenAdminRegistryInstanceId = ticket.tokenAdminRegistryInstanceId + poolAddress = ticket.poolAddress + instrumentId = ticket.instrumentId + sourceAmount = ticket.amount + localAmount = localAmount + sourcePoolData = ticket.sourcePoolData + messageId = ticket.messageId + sourceChainSelector = ticket.sourceChainSelector + finality = ticket.finality + output = claimedOutput + pure CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult with + output + poolChangeCids = transferResult.senderChangeCids + claimedEventCid = coerceContractId claimedEventCid + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + Splice.Api.Token.TransferInstructionV1.TransferInstructionResult_Pending instrCid -> do + assertTransferInstructionMatches + "ReleaseFromTicket" + poolOwner + ticket.tokenReceiver + amountDecimal + instrumentId + instrCid + let output = CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult_Pending with + transferInstructionCid = instrCid + let claimedOutput = CCIP.EventsV2.Events.TokenReceiveTicketClaimed_Pending with + transferInstructionCid = instrCid + claimedEventCid <- create CCIP.EventsV2.Events.TokenReceiveTicketClaimed with + ccipOwner = ticket.ccipOwner + ccvOwners = ticket.ccvOwners + poolOwner = ticket.poolOwner + receiver = ticket.receiver + tokenReceiver = ticket.tokenReceiver + tokenReceiveTicketCid = coerceContractId tokenReceiveTicketImplementation + event = CCIP.EventsV2.Events.TokenReceiveTicketClaimedEvent with + verifiedCCVs = ticket.verifiedCCVs + tokenAdminRegistryInstanceId = ticket.tokenAdminRegistryInstanceId + poolAddress = ticket.poolAddress + instrumentId = ticket.instrumentId + sourceAmount = ticket.amount + localAmount = localAmount + sourcePoolData = ticket.sourcePoolData + messageId = ticket.messageId + sourceChainSelector = ticket.sourceChainSelector + finality = ticket.finality + output = claimedOutput + pure CCIP.InterfacesV2.TokenPool.ReleaseOrMintResult with + output + poolChangeCids = transferResult.senderChangeCids + claimedEventCid = coerceContractId claimedEventCid + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + _ -> abort "ReleaseFromTicket: unexpected failed transfer" + + -- Calculate fee for outbound token transfer (USD cents only). + -- Reads destChainSelector from SendingMessageV1, looks up per-chain + -- fee config, appends fee directly to SendingMessageV1. + nonconsuming choice CalculateFee : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + feeQuoterCid : ContractId CCIP.APIV2.FeeQuoter.IFeeQuoter + tokenInstrumentId : Splice.Api.Token.HoldingV1.InstrumentId + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller + Chainlink.InstanceAddress.assertEqual "pool: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + assertMsg "pool: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "pool: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + assertMsg "pool: tokenInstrumentId mismatch" + (tokenInstrumentId == instrumentId) + feeQuoterView <- exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_PublicFetch with + expectedAddress = deps.feeQuoter + context = context + caller + Chainlink.InstanceAddress.assertEqual "pool: feeQuoter mismatch" + feeQuoterView.instanceId feeQuoterView.ccipOwner deps.feeQuoter + assertMsg "pool: sending message feeQuoter mismatch" + (sendingMsgView.feeQuoter == deps.feeQuoter) + -- Check if fee config is enabled for the destination chain. + -- If no config is set/it's disabled, use the FeeQuoter defaults. If enabled, use the specified fee parameters. + (feeUSDCents, destGasOverhead, destBytesOverhead) <- case Map.lookup sendingMsgView.destChainSelector tokenTransferFeeConfigs of + Some feeConfig | feeConfig.isEnabled -> + pure + ( feeConfig.feeUSDCents + , feeConfig.destGasOverhead + , feeConfig.destBytesOverhead + ) + _ -> + exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_GetTokenTransferFee with + destChainSelector = sendingMsgView.destChainSelector + token = tokenInstrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + + exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_AddTokenSendFee with + tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + feeUSDCents = feeUSDCents + destGasOverhead = destGasOverhead + destBytesOverhead = destBytesOverhead + context = context + caller = poolOwner + + -- Quote fee for outbound token transfer (USD cents only). + nonconsuming choice GetFee + : CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote + with + feeQuoterCid : ContractId CCIP.APIV2.FeeQuoter.IFeeQuoter + destChainSelector : Numeric 0 + tokenInstrumentId : Splice.Api.Token.HoldingV1.InstrumentId + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: tokenInstrumentId mismatch" + (tokenInstrumentId == instrumentId) + + feeQuoterView <- exercise feeQuoterCid CCIP.APIV2.FeeQuoter.FeeQuoter_PublicFetch with + expectedAddress = deps.feeQuoter + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "pool: feeQuoter mismatch" + feeQuoterView.instanceId feeQuoterView.ccipOwner deps.feeQuoter + + case Map.lookup destChainSelector tokenTransferFeeConfigs of + Some feeConfig | feeConfig.isEnabled -> + pure CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote with + poolInstanceId = instanceId + poolOwner = poolOwner + feeUSDCents = feeConfig.feeUSDCents + destGasOverhead = feeConfig.destGasOverhead + destBytesOverhead = feeConfig.destBytesOverhead + tokenFeeBps = feeConfig.feeBps + isEnabled = True + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + _ -> do + (feeUSDCents, destGasOverhead, destBytesOverhead) <- exercise feeQuoterCid + CCIP.APIV2.FeeQuoter.FeeQuoter_GetTokenTransferFee with + destChainSelector = destChainSelector + token = tokenInstrumentId + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + pure CCIP.InterfacesV2.TokenPool.TokenPoolFeeQuote with + poolInstanceId = instanceId + poolOwner = poolOwner + feeUSDCents = feeUSDCents + destGasOverhead = destGasOverhead + destBytesOverhead = destBytesOverhead + tokenFeeBps = 0.0 + isEnabled = False + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + -- Lock tokens for outbound transfer and append token send data. + -- Reads from SendingMessageV1 instead of taking separate params. + -- Requires ccvAndExecutorHash to be finalized by the executor step before this runs. + nonconsuming choice LockOrBurn : CCIP.InterfacesV2.TokenPool.LockOrBurnResult + with + tokenAdminRegistryCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenAdminRegistry + tokenConfigCid : ContractId CCIP.APIV2.TokenAdminRegistry.ITokenConfig + rmnRemoteCid : ContractId CCIP.APIV2.RMNRemote.IRMNRemote + sendingMessageCid : ContractId CCIP.APIV2.SendingMessage.ISendingMessage + senderInputCids : [ContractId Splice.Api.Token.HoldingV1.Holding] + amount : Decimal + context : Splice.Api.Token.MetadataV1.ChoiceContext + caller : Party + controller caller + do + assertMsg "pool: amount must be positive" (amount > 0.0) + let amountNumeric0 = CCIP.CodecV2.Math.decimalToNumeric0 amount + + sendingMsg <- fetch sendingMessageCid + let sendingMsgView = view sendingMsg + tokenAdminRegistryView <- exercise tokenAdminRegistryCid CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_PublicFetch + with + expectedAddress = deps.tokenAdminRegistry + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "LockOrBurn: tokenAdminRegistry mismatch" + tokenAdminRegistryView.instanceId tokenAdminRegistryView.ccipOwner deps.tokenAdminRegistry + _ <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_FetchTokenConfig with + tokenConfigCid + instrumentId + context = context + caller = poolOwner + assertMsg "LockOrBurn: sending message ccipOwner mismatch" + (sendingMsgView.ccipOwner == tokenAdminRegistryView.ccipOwner) + assertMsg "LockOrBurn: sending message tokenAdminRegistry mismatch" + (sendingMsgView.tokenAdminRegistry == deps.tokenAdminRegistry) + rmnRemoteView <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_PublicFetch with + expectedAddress = deps.rmnRemote + context = context + caller = caller + Chainlink.InstanceAddress.assertEqual "LockOrBurn: rmn mismatch" + rmnRemoteView.instanceId rmnRemoteView.rmnOwner deps.rmnRemote + assertMsg "LockOrBurn: sending message rmn mismatch" + (sendingMsgView.rmnRemote == deps.rmnRemote) + + isCursed <- exercise rmnRemoteCid CCIP.APIV2.RMNRemote.RMNRemote_IsCursedForChain with + chainSelector = sendingMsgView.destChainSelector + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + caller = poolOwner + assertMsg "LockOrBurn: dest chain is cursed" (not isCursed) + + remoteChainCfg <- case Map.lookup sendingMsgView.destChainSelector remoteChainConfigs of + None -> abort "pool: unsupported dest chain" + Some cfg -> pure cfg + + let destTokenAddr = remoteChainCfg.remoteTokenAddress + + let destPoolData = CCIP.CodecV2.MessageCodecV1.encodeUint256 (intToNumeric decimals) + let tokenFeeBps = case Map.lookup sendingMsgView.destChainSelector tokenTransferFeeConfigs of + Some feeCfg | feeCfg.isEnabled -> feeCfg.feeBps + _ -> 0.0 + let amountAfterFeeBps = amountNumeric0 - calculateFeeFromBps amountNumeric0 tokenFeeBps + + let expectedRateLimiter = remoteChainCfg.outboundRateLimiter + rateLimiterCid <- CCIP.UtilsV2.Context.getFromContextU context CCIP.Registry.RateLimiterV2.rateLimiterContextKey + rateLimiter <- fetch rateLimiterCid + assertRateLimiter + "LockOrBurn: rateLimiter" + instanceId + sendingMsgView.destChainSelector + CCIP.Registry.RateLimiterV2.RateLimitDirection_Outbound + CCIP.Registry.RateLimiterV2.RateLimitMode_DefaultFinality + expectedRateLimiter + rateLimiter + _ <- exercise rateLimiterCid CCIP.Registry.RateLimiterV2.ConsumeCapacity with + requested = amountAfterFeeBps + + -- Get inputs from context + tokenPoolHoldings <- CCIP.UtilsV2.Context.getFromContextU context tokenPoolHoldingsContextKey + _ <- sumCheckedHoldingAmounts + "LockOrBurn: existing tokenPoolHoldings" + poolOwner + instrumentId + tokenPoolHoldings + _ <- sumCheckedHoldingAmounts + "LockOrBurn: sender input holdings" + caller + instrumentId + senderInputCids + transferFactory <- lookupTransferFactory context + _ <- exercise tokenConfigCid CCIP.APIV2.TokenAdminRegistry.TokenConfig_AssertConfiguredTransferFactory with + suppliedFactory = transferFactory + context = context + caller = poolOwner + extraArgsContextValues <- lookupTransferFactoryExtraArgsContextValues context + extraArgsMetaValues <- lookupTransferFactoryExtraArgsMetaValues context + + -- Merge the pool's receive context with the extra args context values + let mergedContext = Splice.Api.Token.MetadataV1.ChoiceContext with + values = TextMap.union poolReceiveContext.values extraArgsContextValues + let mergedExtraArgs = Splice.Api.Token.MetadataV1.ExtraArgs with + context = mergedContext + meta = Splice.Api.Token.MetadataV1.Metadata with + values = extraArgsMetaValues + now <- getTime + let executeBefore = case transferTimeout of + -- Indefinite is bounded by Splice's tokenStandardMaxTTL default (90 days). + Indefinite -> addRelTime now (hours 2160) + RelativeHours h -> addRelTime now (hours h) + let transfer = Splice.Api.Token.TransferInstructionV1.Transfer with + sender = caller + receiver = poolOwner + amount = amount + instrumentId = instrumentId + requestedAt = now + executeBefore + inputHoldingCids = senderInputCids + meta = Splice.Api.Token.MetadataV1.emptyMetadata + + transferResult <- exercise transferFactory + Splice.Api.Token.TransferInstructionV1.TransferFactory_Transfer with + expectedAdmin = instrumentId.admin + transfer + extraArgs = mergedExtraArgs + + receiverCids <- case transferResult.output of + Splice.Api.Token.TransferInstructionV1.TransferInstructionResult_Completed + completedReceiverCids -> do + assertHoldingsMatchAndSum + "LockOrBurn: receiver holdings" + poolOwner + instrumentId + amount + completedReceiverCids + pure completedReceiverCids + _ -> abort "LockOrBurn: transfer did not complete directly" + + let amountAfterFeeBpsBytes = fromSomeNote + "LockOrBurn: amountAfterFeeBps does not fit uint256" + (CCIP.CodecV2.Uint256.numeric0ToUint256Bytes amountAfterFeeBps) + let extraData = fromOptional "" destPoolData + newSendingMessageCid <- exercise tokenAdminRegistryCid + CCIP.APIV2.TokenAdminRegistry.TokenAdminRegistry_AddTokenSend with + tokenConfigCid + sendingMessageCid = sendingMessageCid + poolInstanceId = instanceId + instrumentId = instrumentId + amount = amountAfterFeeBpsBytes + destTokenAddress = destTokenAddr + extraData = extraData + context = context + caller = poolOwner + + pure CCIP.InterfacesV2.TokenPool.LockOrBurnResult with + poolChangeCids = receiverCids ++ tokenPoolHoldings + senderChangeCids = transferResult.senderChangeCids + sendingMessageCid = coerceContractId newSendingMessageCid + context = Splice.Api.Token.MetadataV1.emptyChoiceContext + + interface instance MCMSReceiver for LockReleaseTokenPool where + view = MCMSReceiverView with + mcmsController = poolOwner + instanceId + + mcmsEntrypoint functionName operationData contractIds = do + let instAddr = instanceId <> "@" <> partyToText poolOwner + selfCid <- case Map.lookup instAddr contractIds of + None -> abort $ "E_SELF_CID_NOT_IN_MAP: " <> instAddr + Some cid -> pure (coerceContractId cid : ContractId LockReleaseTokenPool) + + case functionName of + "SetDynamicConfig" -> do + case decodeSetDynamicConfigParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetDynamicConfig" + Some (SetDynamicConfigParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetDynamicConfig" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetDynamicConfig with + rateLimitAdmin + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetObservers" -> do + case decodeSetObserversParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetObservers" + Some (SetObserversParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetObservers" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetObservers with + observers + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ApplyChainUpdates" -> do + case decodeApplyChainUpdatesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApplyChainUpdates" + Some (ApplyChainUpdatesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApplyChainUpdates" (finalOffset == byteCount operationData) + newCid <- exercise selfCid ApplyChainUpdates with + remoteChainSelectorsToRemove + chainsToAdd + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ApplyTokenTransferFeeConfigUpdates" -> do + case decodeApplyTokenTransferFeeConfigUpdatesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApplyTokenTransferFeeConfigUpdates" + Some (ApplyTokenTransferFeeConfigUpdatesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApplyTokenTransferFeeConfigUpdates" (finalOffset == byteCount operationData) + newCid <- exercise selfCid ApplyTokenTransferFeeConfigUpdates with + tokenTransferFeeConfigArgs + disableTokenTransferFeeConfigArgs + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetRateLimiterReferences" -> do + case decodeSetRateLimiterReferencesParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetRateLimiterReferences" + Some (SetRateLimiterReferencesParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetRateLimiterReferences" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetRateLimiterReferences with + rateLimitConfigArgs + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetRateLimitConfig" -> do + case decodeSetRateLimitConfigParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetRateLimitConfig" + Some (SetRateLimitConfigParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetRateLimitConfig" (finalOffset == byteCount operationData) + pool <- fetch selfCid + assertMsg "E_RATE_LIMITER_NOT_ON_POOL" + (rateLimiterInstanceAddress `elem` remoteChainRateLimiterAddresses pool.remoteChainConfigs) + let limiterAddr = Chainlink.InstanceAddress.toText rateLimiterInstanceAddress + rateLimiterCid <- case Map.lookup limiterAddr contractIds of + None -> abort ("E_RATE_LIMITER_NOT_IN_MAP: " <> limiterAddr) + Some c -> pure (coerceContractId c : ContractId CCIP.Registry.RateLimiterV2.RateLimiter) + resolvedLimiter <- fetch rateLimiterCid + assertMsg "E_RATE_LIMITER_ADDRESS_MISMATCH" + (CCIP.Registry.RateLimiterV2.rawInstanceAddress resolvedLimiter == rateLimiterInstanceAddress) + updatedLimiterCid <- exercise selfCid SetRateLimitConfig with + caller + rateLimiterCid + newIsEnabled + newCapacity + newRate + pure (Map.insert limiterAddr (coerceContractId updatedLimiterCid) contractIds) + "AddPoolReceiveContextNonContractValue" -> do + case decodeAddPoolReceiveContextNonContractValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextNonContractValue" + Some (AddPoolReceiveContextNonContractValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextNonContractValue" (finalOffset == byteCount operationData) + (value, innerOff) <- case CCIP.UtilsV2.Context.decodeAnyValueAt valuePayload 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextNonContractValue:valuePayload" + Some p -> pure p + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextNonContractValue:valuePayload" (innerOff == byteCount valuePayload) + newCid <- exercise selfCid AddPoolReceiveContextNonContractValue with contextKey, value + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "AddPoolReceiveContextContractValue" -> do + case decodeAddPoolReceiveContextContractValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: AddPoolReceiveContextContractValue" + Some (AddPoolReceiveContextContractValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: AddPoolReceiveContextContractValue" (finalOffset == byteCount operationData) + let refAddr = Chainlink.InstanceAddress.toText referentInstanceAddress + referred <- case Map.lookup refAddr contractIds of + None -> abort ("E_REFERENT_CONTRACT_NOT_IN_MAP: " <> refAddr) + Some c -> pure c + newCid <- exercise selfCid AddPoolReceiveContextContractValue with contextKey, referredContract = referred + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "RemovePoolReceiveContextValue" -> do + case decodeRemovePoolReceiveContextValueParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: RemovePoolReceiveContextValue" + Some (RemovePoolReceiveContextValueParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: RemovePoolReceiveContextValue" (finalOffset == byteCount operationData) + newCid <- exercise selfCid RemovePoolReceiveContextValue with contextKey + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "ClearPoolReceiveContext" -> do + assertMsg "E_TRAILING_BYTES: ClearPoolReceiveContext" (byteCount operationData == 0) + newCid <- exercise selfCid ClearPoolReceiveContext + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + "SetTransferTimeout" -> do + case decodeSetTransferTimeoutParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetTransferTimeout" + Some (SetTransferTimeoutParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetTransferTimeout" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetTransferTimeout with + newTransferTimeout = transferTimeout + pure (Map.insert instAddr (coerceContractId newCid) contractIds) + _ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName diff --git a/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2CodecGen.daml b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2CodecGen.daml new file mode 100644 index 000000000..7fe5c057c --- /dev/null +++ b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2CodecGen.daml @@ -0,0 +1,386 @@ +-- AUTO-GENERATED by go-daml codegen. DO NOT EDIT. +module CCIP.Registry.LockReleaseTokenPoolV2CodecGen where + +import DA.Crypto.Text (BytesHex) +import DA.Optional (fromSome) + +import CCIP.Registry.LockReleaseTokenPoolV2Types (ChainUpdate(..), RemoteChainConfig(..), TokenTransferFeeConfigArgs(..), TokenTransferFeeConfig(..), RateLimitConfigArgs(..), SetDynamicConfigParams(..), SetObserversParams(..), ApplyChainUpdatesParams(..), ApplyTokenTransferFeeConfigUpdatesParams(..), SetRateLimiterReferencesParams(..), SetRateLimitConfigParams(..), SetTransferTimeoutParams(..), AddPoolReceiveContextNonContractValueParams(..), AddPoolReceiveContextContractValueParams(..), RemovePoolReceiveContextValueParams(..), RateLimiterDeploySpec(..), LaneDeploySpec(..), TransferTimeout(..)) + +import MCMS.Codec ( + decodeBoolAt, + decodeBytesHexAt, + decodeBytesHexList, + decodeInt64At, + decodeList, + decodeNumeric0At, + decodeOptional, + decodePartyAt, + decodePartyList, + decodeTextAt, + encodeBool, + encodeBytesHex, + encodeBytesHexList, + encodeInt64, + encodeList, + encodeNumeric0, + encodeOptional, + encodeParty, + encodePartyList, + encodeText, + encodeUint8, + extractBytes + ) + +import CCIP.CodecV2 (decodeRawInstanceAddressAt, decodeRequestedFinalityAt, encodeRawInstanceAddress, encodeRequestedFinality) + + +-- =============================================== +-- | ChainUpdate +-- =============================================== + +encodeChainUpdate : ChainUpdate -> BytesHex +encodeChainUpdate params = + encodeNumeric0 params.remoteChainSelector + <> encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeChainUpdateAt : BytesHex -> Int -> Optional (ChainUpdate, Int) +decodeChainUpdateAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (ChainUpdate{..}, offset) + + +-- =============================================== +-- | RemoteChainConfig +-- =============================================== + +encodeRemoteChainConfig : RemoteChainConfig -> BytesHex +encodeRemoteChainConfig params = + encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeRemoteChainConfigAt : BytesHex -> Int -> Optional (RemoteChainConfig, Int) +decodeRemoteChainConfigAt encoded offset = do + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (RemoteChainConfig{..}, offset) + + +-- =============================================== +-- | TokenTransferFeeConfigArgs +-- =============================================== + +encodeTokenTransferFeeConfigArgs : TokenTransferFeeConfigArgs -> BytesHex +encodeTokenTransferFeeConfigArgs params = + encodeNumeric0 params.destChainSelector + <> encodeBool params.isEnabled + <> fromSome (encodeInt64 params.destGasOverhead) + <> fromSome (encodeInt64 params.destBytesOverhead) + <> encodeNumeric0 params.feeUSDCents + <> encodeNumeric0 params.feeBps + +decodeTokenTransferFeeConfigArgsAt : BytesHex -> Int -> Optional (TokenTransferFeeConfigArgs, Int) +decodeTokenTransferFeeConfigArgsAt encoded offset = do + (destChainSelector, offset) <- decodeNumeric0At encoded offset + (isEnabled, offset) <- decodeBoolAt encoded offset + (destGasOverhead, offset) <- decodeInt64At encoded offset + (destBytesOverhead, offset) <- decodeInt64At encoded offset + (feeUSDCents, offset) <- decodeNumeric0At encoded offset + (feeBps, offset) <- decodeNumeric0At encoded offset + Some (TokenTransferFeeConfigArgs{..}, offset) + + +-- =============================================== +-- | TokenTransferFeeConfig +-- =============================================== + +encodeTokenTransferFeeConfig : TokenTransferFeeConfig -> BytesHex +encodeTokenTransferFeeConfig params = + encodeBool params.isEnabled + <> fromSome (encodeInt64 params.destGasOverhead) + <> fromSome (encodeInt64 params.destBytesOverhead) + <> encodeNumeric0 params.feeUSDCents + <> encodeNumeric0 params.feeBps + +decodeTokenTransferFeeConfigAt : BytesHex -> Int -> Optional (TokenTransferFeeConfig, Int) +decodeTokenTransferFeeConfigAt encoded offset = do + (isEnabled, offset) <- decodeBoolAt encoded offset + (destGasOverhead, offset) <- decodeInt64At encoded offset + (destBytesOverhead, offset) <- decodeInt64At encoded offset + (feeUSDCents, offset) <- decodeNumeric0At encoded offset + (feeBps, offset) <- decodeNumeric0At encoded offset + Some (TokenTransferFeeConfig{..}, offset) + + +-- =============================================== +-- | RateLimitConfigArgs +-- =============================================== + +encodeRateLimitConfigArgs : RateLimitConfigArgs -> BytesHex +encodeRateLimitConfigArgs params = + encodeNumeric0 params.remoteChainSelector + <> encodeRawInstanceAddress params.inboundRateLimiter + <> encodeRawInstanceAddress params.inboundCustomBlockConfirmationsRateLimiter + <> encodeRawInstanceAddress params.outboundRateLimiter + +decodeRateLimitConfigArgsAt : BytesHex -> Int -> Optional (RateLimitConfigArgs, Int) +decodeRateLimitConfigArgsAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (inboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (inboundCustomBlockConfirmationsRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + (outboundRateLimiter, offset) <- decodeRawInstanceAddressAt encoded offset + Some (RateLimitConfigArgs{..}, offset) + + +-- =============================================== +-- | SetDynamicConfigParams +-- =============================================== + +encodeSetDynamicConfigParams : SetDynamicConfigParams -> BytesHex +encodeSetDynamicConfigParams params = + encodeOptional params.rateLimitAdmin encodeParty + +decodeSetDynamicConfigParamsAt : BytesHex -> Int -> Optional (SetDynamicConfigParams, Int) +decodeSetDynamicConfigParamsAt encoded offset = do + (rateLimitAdmin, offset) <- decodeOptional encoded offset decodePartyAt + Some (SetDynamicConfigParams{..}, offset) + + +-- =============================================== +-- | SetObserversParams +-- =============================================== + +encodeSetObserversParams : SetObserversParams -> BytesHex +encodeSetObserversParams params = + encodePartyList params.observers + +decodeSetObserversParamsAt : BytesHex -> Int -> Optional (SetObserversParams, Int) +decodeSetObserversParamsAt encoded offset = do + (observers, offset) <- decodePartyList encoded offset + Some (SetObserversParams{..}, offset) + + +-- =============================================== +-- | ApplyChainUpdatesParams +-- =============================================== + +encodeApplyChainUpdatesParams : ApplyChainUpdatesParams -> BytesHex +encodeApplyChainUpdatesParams params = + encodeList params.remoteChainSelectorsToRemove encodeNumeric0 + <> encodeList params.chainsToAdd encodeChainUpdate + +decodeApplyChainUpdatesParamsAt : BytesHex -> Int -> Optional (ApplyChainUpdatesParams, Int) +decodeApplyChainUpdatesParamsAt encoded offset = do + (remoteChainSelectorsToRemove, offset) <- decodeList encoded offset decodeNumeric0At + (chainsToAdd, offset) <- decodeList encoded offset decodeChainUpdateAt + Some (ApplyChainUpdatesParams{..}, offset) + + +-- =============================================== +-- | ApplyTokenTransferFeeConfigUpdatesParams +-- =============================================== + +encodeApplyTokenTransferFeeConfigUpdatesParams : ApplyTokenTransferFeeConfigUpdatesParams -> BytesHex +encodeApplyTokenTransferFeeConfigUpdatesParams params = + encodeList params.tokenTransferFeeConfigArgs encodeTokenTransferFeeConfigArgs + <> encodeList params.disableTokenTransferFeeConfigArgs encodeNumeric0 + +decodeApplyTokenTransferFeeConfigUpdatesParamsAt : BytesHex -> Int -> Optional (ApplyTokenTransferFeeConfigUpdatesParams, Int) +decodeApplyTokenTransferFeeConfigUpdatesParamsAt encoded offset = do + (tokenTransferFeeConfigArgs, offset) <- decodeList encoded offset decodeTokenTransferFeeConfigArgsAt + (disableTokenTransferFeeConfigArgs, offset) <- decodeList encoded offset decodeNumeric0At + Some (ApplyTokenTransferFeeConfigUpdatesParams{..}, offset) + + +-- =============================================== +-- | SetRateLimiterReferencesParams +-- =============================================== + +encodeSetRateLimiterReferencesParams : SetRateLimiterReferencesParams -> BytesHex +encodeSetRateLimiterReferencesParams params = + encodeList params.rateLimitConfigArgs encodeRateLimitConfigArgs + +decodeSetRateLimiterReferencesParamsAt : BytesHex -> Int -> Optional (SetRateLimiterReferencesParams, Int) +decodeSetRateLimiterReferencesParamsAt encoded offset = do + (rateLimitConfigArgs, offset) <- decodeList encoded offset decodeRateLimitConfigArgsAt + Some (SetRateLimiterReferencesParams{..}, offset) + + +-- =============================================== +-- | SetRateLimitConfigParams +-- =============================================== + +encodeSetRateLimitConfigParams : SetRateLimitConfigParams -> BytesHex +encodeSetRateLimitConfigParams params = + encodeParty params.caller + <> encodeRawInstanceAddress params.rateLimiterInstanceAddress + <> encodeBool params.newIsEnabled + <> encodeNumeric0 params.newCapacity + <> encodeNumeric0 params.newRate + +decodeSetRateLimitConfigParamsAt : BytesHex -> Int -> Optional (SetRateLimitConfigParams, Int) +decodeSetRateLimitConfigParamsAt encoded offset = do + (caller, offset) <- decodePartyAt encoded offset + (rateLimiterInstanceAddress, offset) <- decodeRawInstanceAddressAt encoded offset + (newIsEnabled, offset) <- decodeBoolAt encoded offset + (newCapacity, offset) <- decodeNumeric0At encoded offset + (newRate, offset) <- decodeNumeric0At encoded offset + Some (SetRateLimitConfigParams{..}, offset) + + +-- =============================================== +-- | SetTransferTimeoutParams +-- =============================================== + +encodeSetTransferTimeoutParams : SetTransferTimeoutParams -> BytesHex +encodeSetTransferTimeoutParams params = + encodeTransferTimeout params.transferTimeout + +decodeSetTransferTimeoutParamsAt : BytesHex -> Int -> Optional (SetTransferTimeoutParams, Int) +decodeSetTransferTimeoutParamsAt encoded offset = do + (transferTimeout, offset) <- decodeTransferTimeoutAt encoded offset + Some (SetTransferTimeoutParams{..}, offset) + + +-- =============================================== +-- | AddPoolReceiveContextNonContractValueParams +-- =============================================== + +encodeAddPoolReceiveContextNonContractValueParams : AddPoolReceiveContextNonContractValueParams -> BytesHex +encodeAddPoolReceiveContextNonContractValueParams params = + encodeText params.contextKey + <> encodeBytesHex params.valuePayload + +decodeAddPoolReceiveContextNonContractValueParamsAt : BytesHex -> Int -> Optional (AddPoolReceiveContextNonContractValueParams, Int) +decodeAddPoolReceiveContextNonContractValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + (valuePayload, offset) <- decodeBytesHexAt encoded offset + Some (AddPoolReceiveContextNonContractValueParams{..}, offset) + + +-- =============================================== +-- | AddPoolReceiveContextContractValueParams +-- =============================================== + +encodeAddPoolReceiveContextContractValueParams : AddPoolReceiveContextContractValueParams -> BytesHex +encodeAddPoolReceiveContextContractValueParams params = + encodeText params.contextKey + <> encodeRawInstanceAddress params.referentInstanceAddress + +decodeAddPoolReceiveContextContractValueParamsAt : BytesHex -> Int -> Optional (AddPoolReceiveContextContractValueParams, Int) +decodeAddPoolReceiveContextContractValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + (referentInstanceAddress, offset) <- decodeRawInstanceAddressAt encoded offset + Some (AddPoolReceiveContextContractValueParams{..}, offset) + + +-- =============================================== +-- | RemovePoolReceiveContextValueParams +-- =============================================== + +encodeRemovePoolReceiveContextValueParams : RemovePoolReceiveContextValueParams -> BytesHex +encodeRemovePoolReceiveContextValueParams params = + encodeText params.contextKey + +decodeRemovePoolReceiveContextValueParamsAt : BytesHex -> Int -> Optional (RemovePoolReceiveContextValueParams, Int) +decodeRemovePoolReceiveContextValueParamsAt encoded offset = do + (contextKey, offset) <- decodeTextAt encoded offset + Some (RemovePoolReceiveContextValueParams{..}, offset) + + +-- =============================================== +-- | RateLimiterDeploySpec +-- =============================================== + +encodeRateLimiterDeploySpec : RateLimiterDeploySpec -> BytesHex +encodeRateLimiterDeploySpec params = + encodeText params.instanceId + <> encodeBool params.isEnabled + <> encodeNumeric0 params.capacity + <> encodeNumeric0 params.rate + +decodeRateLimiterDeploySpecAt : BytesHex -> Int -> Optional (RateLimiterDeploySpec, Int) +decodeRateLimiterDeploySpecAt encoded offset = do + (instanceId, offset) <- decodeTextAt encoded offset + (isEnabled, offset) <- decodeBoolAt encoded offset + (capacity, offset) <- decodeNumeric0At encoded offset + (rate, offset) <- decodeNumeric0At encoded offset + Some (RateLimiterDeploySpec{..}, offset) + + +-- =============================================== +-- | LaneDeploySpec +-- =============================================== + +encodeLaneDeploySpec : LaneDeploySpec -> BytesHex +encodeLaneDeploySpec params = + encodeNumeric0 params.remoteChainSelector + <> encodeBytesHexList params.remotePools + <> encodeBytesHex params.remoteTokenAddress + <> encodeList params.inboundCCVs encodeRawInstanceAddress + <> encodeList params.outboundCCVs encodeRawInstanceAddress + <> encodeRequestedFinality params.finalityConfig + <> encodeRateLimiterDeploySpec params.inbound + <> encodeRateLimiterDeploySpec params.outbound + <> encodeRateLimiterDeploySpec params.inboundCustomFinality + +decodeLaneDeploySpecAt : BytesHex -> Int -> Optional (LaneDeploySpec, Int) +decodeLaneDeploySpecAt encoded offset = do + (remoteChainSelector, offset) <- decodeNumeric0At encoded offset + (remotePools, offset) <- decodeBytesHexList encoded offset + (remoteTokenAddress, offset) <- decodeBytesHexAt encoded offset + (inboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (outboundCCVs, offset) <- decodeList encoded offset decodeRawInstanceAddressAt + (finalityConfig, offset) <- decodeRequestedFinalityAt encoded offset + (inbound, offset) <- decodeRateLimiterDeploySpecAt encoded offset + (outbound, offset) <- decodeRateLimiterDeploySpecAt encoded offset + (inboundCustomFinality, offset) <- decodeRateLimiterDeploySpecAt encoded offset + Some (LaneDeploySpec{..}, offset) + + + +-- =============================================== +-- | TransferTimeout +-- =============================================== + +encodeTransferTimeout : TransferTimeout -> BytesHex +encodeTransferTimeout variant = case variant of + Indefinite -> fromSome (encodeUint8 0) + RelativeHours payload -> fromSome (encodeUint8 1) <> fromSome (encodeInt64 payload) + +decodeTransferTimeoutAt : BytesHex -> Int -> Optional (TransferTimeout, Int) +decodeTransferTimeoutAt encoded offset = do + tagHex <- extractBytes encoded offset 1 + if tagHex == "00" then Some (Indefinite, offset + 1) + else if tagHex == "01" then do + (payload, newOffset) <- decodeInt64At encoded (offset + 1) + Some (RelativeHours payload, newOffset) + else None + + diff --git a/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2Types.daml b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2Types.daml new file mode 100644 index 000000000..cba33fe55 --- /dev/null +++ b/contracts/ccip/registry-pools/lock-release-token-pool/daml/CCIP/Registry/LockReleaseTokenPoolV2Types.daml @@ -0,0 +1,170 @@ +-- | Types for LockReleaseTokenPool, extracted to avoid circular dependencies +module CCIP.Registry.LockReleaseTokenPoolV2Types where + +import DA.Crypto.Text (BytesHex) + +import qualified Chainlink.InstanceAddress + +import qualified CCIP.CodecV2.FinalityConfig + +data ChainUpdate = ChainUpdate + with + remoteChainSelector : Numeric 0 -- Chain selector for this config update + remotePools : [BytesHex] -- Allowed remote pool addresses for this chain + remoteTokenAddress : BytesHex -- The token address on the remote chain + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for incoming transfers + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for outgoing transfers + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig -- Allowed finality policy, "WaitForFinality" means finalized-only + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for incoming transfers + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for FTF incoming transfers + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for outgoing transfers + deriving (Eq, Show) + +data RemoteChainConfig = RemoteChainConfig + with + remotePools : [BytesHex] -- Allowed remote pool addresses for this chain + remoteTokenAddress : BytesHex -- The token address on the remote chain + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for incoming transfers + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] -- Required CCVs for outgoing transfers + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig -- Allowed finality policy, "WaitForFinality" means finalized-only + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for incoming transfers + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for FTF incoming transfers + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress -- Required rate limiter for outgoing transfers + deriving (Eq, Show) + +data TokenTransferFeeConfigArgs = TokenTransferFeeConfigArgs + with + destChainSelector : Numeric 0 -- Chain selector for this config update + isEnabled : Bool -- Whether this config is enabled + destGasOverhead : Int -- Gas charged to execute the token transfer on the destination chain. + destBytesOverhead : Int -- Data availability bytes. + feeUSDCents : Numeric 0 -- Fee to charge for token transfers, multiples of 0.01 USD. + feeBps : Numeric 0 -- Proportional token fee in bps, applied by the pool during LockOrBurn. + deriving (Eq, Show) + +data TokenTransferFeeConfig = TokenTransferFeeConfig + with + isEnabled : Bool -- Whether this config is enabled + destGasOverhead : Int -- Gas charged to execute the token transfer on the destination chain. + destBytesOverhead : Int -- Data availability bytes. + feeUSDCents : Numeric 0 -- Fee to charge for token transfers, multiples of 0.01 USD. + feeBps : Numeric 0 -- Proportional token fee in bps, applied by the pool during LockOrBurn. + deriving (Eq, Show) + +data RateLimitConfigArgs = RateLimitConfigArgs + with + remoteChainSelector : Numeric 0 + inboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + inboundCustomBlockConfirmationsRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + outboundRateLimiter : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +-- | Transfer timeout configuration for pending TransferInstructions. +-- Splice 0.6.11 enforces tokenStandardMaxTTL (default 90 days) on transfer +-- lifetimes, so executeBefore cannot be unbounded. Use RelativeHours for tokens +-- with stricter expiry constraints. Indefinite caps at the Splice default max. +data TransferTimeout + = Indefinite -- Use now + 90 days - capped by Splice tokenStandardMaxTTL + | RelativeHours Int -- Use now + N hours - for tokens with expiry constraints + deriving (Eq, Show) + +-- =========================================================================== +-- MCMS Choice Params Types +-- =========================================================================== + +-- | Params for SetDynamicConfig choice +data SetDynamicConfigParams = SetDynamicConfigParams + with + rateLimitAdmin : Optional Party + deriving (Eq, Show) + +-- | Params for SetObservers choice +data SetObserversParams = SetObserversParams + with + observers : [Party] + deriving (Eq, Show) + +-- | Params for ApplyChainUpdates choice +data ApplyChainUpdatesParams = ApplyChainUpdatesParams + with + remoteChainSelectorsToRemove : [Numeric 0] + chainsToAdd : [ChainUpdate] + deriving (Eq, Show) + +-- | Params for ApplyTokenTransferFeeConfigUpdates choice +data ApplyTokenTransferFeeConfigUpdatesParams = ApplyTokenTransferFeeConfigUpdatesParams + with + tokenTransferFeeConfigArgs : [TokenTransferFeeConfigArgs] + disableTokenTransferFeeConfigArgs : [Numeric 0] + deriving (Eq, Show) + +-- | Params for SetRateLimiterReferences choice (poolOwner only; MCMS entrypoint). +-- | Updates which RateLimiter contract identities the pool references per remote chain. +data SetRateLimiterReferencesParams = SetRateLimiterReferencesParams + with + rateLimitConfigArgs : [RateLimitConfigArgs] + deriving (Eq, Show) + +-- | Params for SetRateLimitConfig (tune capacity/rate/isEnabled; MCMS entrypoint). +-- | rateLimiterInstanceAddress: full Chainlink.InstanceAddress.RawInstanceAddress (instanceId@owner), not bare instanceId. +-- | Resolved against MCMS contractIds map key; pool choice receives the resulting ContractId. +data SetRateLimitConfigParams = SetRateLimitConfigParams + with + caller : Party + rateLimiterInstanceAddress : Chainlink.InstanceAddress.RawInstanceAddress + newIsEnabled : Bool + newCapacity : Numeric 0 + newRate : Numeric 0 + deriving (Eq, Show) + +-- | Params for SetTransferTimeout choice +data SetTransferTimeoutParams = SetTransferTimeoutParams + with + transferTimeout : TransferTimeout + deriving (Eq, Show) + +-- | MCMS params: set one ChoiceContext entry from a serialized AnyValue payload. +-- | valuePayload must be exactly encodeAnyValue output (see CCIP.ContextCodec); AV_ContractId is not round-trippable in bytes—use AddPoolReceiveContextContractValueParams instead. +data AddPoolReceiveContextNonContractValueParams = AddPoolReceiveContextNonContractValueParams + with + contextKey : Text + valuePayload : BytesHex + deriving (Eq, Show) + +-- | MCMS params: set one ChoiceContext entry to AV_ContractId by resolving referentInstanceAddress in the MCMS contractIds map. +data AddPoolReceiveContextContractValueParams = AddPoolReceiveContextContractValueParams + with + contextKey : Text + referentInstanceAddress : Chainlink.InstanceAddress.RawInstanceAddress + deriving (Eq, Show) + +-- | MCMS params: remove one ChoiceContext entry by key. +data RemovePoolReceiveContextValueParams = RemovePoolReceiveContextValueParams + with + contextKey : Text + deriving (Eq, Show) + +-- | Config for one RateLimiter to deploy alongside a lane, via Initialize. Direction/mode are +-- implied by which slot of LaneDeploySpec this occupies, not repeated here. +data RateLimiterDeploySpec = RateLimiterDeploySpec + with + instanceId : Text + isEnabled : Bool + capacity : Numeric 0 + rate : Numeric 0 + deriving (Eq, Show) + +-- | Config for one remote-chain lane to wire up via Initialize: routing/CCV/finality config, +-- plus the three rate limiters to deploy alongside it (matching ChainUpdate's shape). +data LaneDeploySpec = LaneDeploySpec + with + remoteChainSelector : Numeric 0 + remotePools : [BytesHex] + remoteTokenAddress : BytesHex + inboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + outboundCCVs : [Chainlink.InstanceAddress.RawInstanceAddress] + finalityConfig : CCIP.CodecV2.FinalityConfig.FinalityConfig + inbound : RateLimiterDeploySpec + outbound : RateLimiterDeploySpec + inboundCustomFinality : RateLimiterDeploySpec + deriving (Eq, Show) diff --git a/contracts/ccip/registry-pools/rate-limiter/daml.yaml b/contracts/ccip/registry-pools/rate-limiter/daml.yaml new file mode 100644 index 000000000..59ea1c2f6 --- /dev/null +++ b/contracts/ccip/registry-pools/rate-limiter/daml.yaml @@ -0,0 +1,13 @@ +sdk-version: 3.4.11 +name: ccip-registry-rate-limiter-v2 +source: daml +version: 2.0.1 +build-options: + - "-Wno-crypto-text-is-alpha" + - "-Werror=unused-dependency" +data-dependencies: + - ../../../mcms/api/.daml/dist/mcms-api-1.0.0.dar + - ../../../chainlink/api/.daml/dist/chainlink-api-2.0.0.dar +dependencies: + - daml-prim + - daml-stdlib diff --git a/contracts/ccip/registry-pools/rate-limiter/daml/CCIP/Registry/RateLimiterV2.daml b/contracts/ccip/registry-pools/rate-limiter/daml/CCIP/Registry/RateLimiterV2.daml new file mode 100644 index 000000000..cf86152da --- /dev/null +++ b/contracts/ccip/registry-pools/rate-limiter/daml/CCIP/Registry/RateLimiterV2.daml @@ -0,0 +1,226 @@ +module CCIP.Registry.RateLimiterV2 where + +import DA.Map qualified as Map +import DA.Numeric qualified as Numeric +import DA.Time (convertRelTimeToMicroseconds, subTime) +import DA.Crypto.Text (BytesHex, byteCount) + +import Chainlink.InstanceAddress qualified as RawInstanceAddress +import Chainlink.InstanceAddress (assertValidInstanceId) +import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiverView(..)) +import MCMS.Codec (decodeBoolAt, decodeNumeric0At, decodePartyList) + +-- | Enforce distinct RateLimiter contract references per logical path. Sharing one RateLimiter +-- across paths merges quota. Pass Some for the custom-finality slot when it is required. +assertDistinctRateLimiters + : Text + -> RawInstanceAddress.RawInstanceAddress + -> RawInstanceAddress.RawInstanceAddress + -> Optional RawInstanceAddress.RawInstanceAddress + -> Update () +assertDistinctRateLimiters label inbound outbound maybeCustom = do + assertMsg (label <> ": inbound/outbound rate limiters cannot be empty") + (inbound /= RawInstanceAddress.empty && outbound /= RawInstanceAddress.empty) + assertMsg (label <> ": rate limiters cannot be the same for inbound and outbound") + (inbound /= outbound) + case maybeCustom of + None -> pure () + Some custom -> do + assertMsg (label <> ": custom finality inbound rate limiter cannot be empty when finalityConfig is faster than finality") + (custom /= RawInstanceAddress.empty) + assertMsg (label <> ": custom finality inbound rate limiter must be distinct from the default inbound limiter") + (custom /= inbound) + assertMsg (label <> ": custom finality inbound rate limiter must be distinct from the outbound limiter") + (custom /= outbound) + +rateLimiterContextKey : Text +rateLimiterContextKey = "rate-limiter" + +rawInstanceAddress : RateLimiter -> RawInstanceAddress.RawInstanceAddress +rawInstanceAddress rl = RawInstanceAddress.make rl.instanceId rl.poolOwner + +data RateLimitDirection + = RateLimitDirection_Outbound + | RateLimitDirection_Inbound + deriving (Eq, Show) + +data RateLimitMode + = RateLimitMode_DefaultFinality + | RateLimitMode_CustomFinality + deriving (Eq, Show) + +data ConsumeCapacityResult = ConsumeCapacityResult + with + rateLimiterCid : ContractId RateLimiter + availableBeforeConsume : Numeric 0 + consumed : Numeric 0 + deriving (Eq, Show) + +-- | Calculate elapsed time in microseconds (as a whole number). Handles negative intervals by returning 0. +elapsedMicroseconds : Time -> Time -> Numeric 0 +elapsedMicroseconds start end = + let micros = convertRelTimeToMicroseconds (subTime end start) + in intToNumeric (max 0 micros) + +-- | Calculate whole tokens refilled during the elapsed time at a given rate. +-- | Preserves fractional seconds: (rate * elapsed_micros / 1_000_000) rounded down to whole tokens. +refilledTokens : Time -> Time -> Numeric 0 -> Numeric 0 +refilledTokens start end rate = + let tokenMicros : Decimal = Numeric.mul rate (elapsedMicroseconds start end) + microsPerSecond : Decimal = 1000000.0 + accrued : Decimal = Numeric.div tokenMicros microsPerSecond + in intToNumeric (truncate accrued) + +-- | Calculate total available capacity at a given time, accounting for refills since last update. +-- | If disabled, returns capacity. Otherwise returns current tokens plus accrued refills, capped at capacity. +availableTokensAt : Time -> RateLimiter -> Numeric 0 +availableTokensAt now rl = + if not rl.isEnabled then + rl.capacity + else + min rl.capacity (rl.tokens + refilledTokens rl.lastUpdated now rl.rate) + +template RateLimiter + with + instanceId : Text + poolInstanceId : Text + poolOwner : Party + remoteChainSelector : Numeric 0 + direction : RateLimitDirection + mode : RateLimitMode + isEnabled : Bool + -- | Maximum token capacity for this rate limiter (in pool's local decimal representation). + -- Must be scaled according to the associated token pool's decimals setting. + capacity : Numeric 0 + -- | Token refill rate per second (in pool's local decimal representation). + -- Must be scaled according to the associated token pool's decimals setting. + rate : Numeric 0 + -- | Current available tokens in the bucket (in pool's local decimal representation). + -- Must be scaled according to the associated token pool's decimals setting. + tokens : Numeric 0 + lastUpdated : Time + -- Observer parties used by the External Disclosure Service (EDS) to auto-detect this + -- rate limiter. Mandatory - must contain at least one party, enforced by the ensure + -- clause below. Set at creation or via SetObservers; cannot be added retroactively + -- to a rate limiter deployed on a package version that predates this field. + observers : [Party] + where + signatory poolOwner + observer observers + ensure assertValidInstanceId instanceId + && not (null observers) + && capacity >= 0.0 + && rate >= 0.0 + && tokens >= 0.0 + + choice SetConfig : ContractId RateLimiter + with + newIsEnabled : Bool + newCapacity : Numeric 0 + newRate : Numeric 0 + controller poolOwner + do + assertMsg "RateLimiter: capacity must be non-negative" + (newCapacity >= 0.0) + assertMsg "RateLimiter: rate must be non-negative" + (newRate >= 0.0) + assertMsg "RateLimiter: enabled config requires rate <= capacity" + (not newIsEnabled || newRate <= newCapacity) + assertMsg "RateLimiter: disabled config must have zero capacity and zero rate" + (newIsEnabled || (newCapacity == 0.0 && newRate == 0.0)) + now <- getTime + create this with + isEnabled = newIsEnabled + capacity = newCapacity + rate = newRate + tokens = newCapacity + lastUpdated = now + + -- Sets the EDS observers for this rate limiter. + -- + -- Params: + -- observers: parties used by EDS to auto-detect this rate limiter + -- + -- Callable by poolOwner + choice SetObservers : ContractId RateLimiter + with + observers : [Party] + controller poolOwner + do + create this with + observers = observers + + nonconsuming choice ConsumeCapacity : ConsumeCapacityResult + with + requested : Numeric 0 + controller poolOwner + do + if not isEnabled || requested == 0.0 then + pure ConsumeCapacityResult with + rateLimiterCid = self + availableBeforeConsume = availableTokensAt lastUpdated this + consumed = requested + else do + now <- getTime + let available = availableTokensAt now this + + assertMsg "RateLimiter: requested amount must be non-negative" + (requested >= 0.0) + assertMsg "RateLimiter: request exceeds capacity" + (requested <= capacity) + assertMsg "RateLimiter: insufficient capacity" + (requested <= available) + + archive self + newCid <- create this with + tokens = available - requested + lastUpdated = now + + pure ConsumeCapacityResult with + rateLimiterCid = newCid + availableBeforeConsume = available + consumed = requested + + interface instance MCMSReceiver for RateLimiter where + view = MCMSReceiverView with + mcmsController = poolOwner + instanceId + + mcmsEntrypoint functionName operationData contractIds = do + let selfAddress = RawInstanceAddress.makeText instanceId poolOwner + selfCid <- case Map.lookup selfAddress contractIds of + None -> abort $ "E_SELF_CID_NOT_IN_MAP: " <> selfAddress + Some cid -> pure (coerceContractId cid : ContractId RateLimiter) + + case functionName of + "SetConfig" -> do + case decodeSetConfigParams operationData of + None -> abort "E_INVALID_PARAMS: SetConfig" + Some ((newIsEnabled, newCapacity, newRate), finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetConfig" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetConfig with + newIsEnabled + newCapacity + newRate + pure (Map.insert selfAddress (coerceContractId newCid) contractIds) + "SetObservers" -> do + case decodeSetObserversParams operationData of + None -> abort "E_INVALID_PARAMS: SetObservers" + Some (observers, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetObservers" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetObservers with + observers + pure (Map.insert selfAddress (coerceContractId newCid) contractIds) + _ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName + +-- | Decode SetConfig params: (Bool, Numeric 0, Numeric 0) +decodeSetConfigParams : BytesHex -> Optional ((Bool, Numeric 0, Numeric 0), Int) +decodeSetConfigParams encoded = do + (newIsEnabled, offset1) <- decodeBoolAt encoded 0 + (newCapacity, offset2) <- decodeNumeric0At encoded offset1 + (newRate, finalOffset) <- decodeNumeric0At encoded offset2 + Some ((newIsEnabled, newCapacity, newRate), finalOffset) + +-- | Decode SetObservers params: [Party] +decodeSetObserversParams : BytesHex -> Optional ([Party], Int) +decodeSetObserversParams encoded = decodePartyList encoded 0 diff --git a/contracts/ccip/registry-pools/test/daml.yaml b/contracts/ccip/registry-pools/test/daml.yaml new file mode 100644 index 000000000..7c0c3de35 --- /dev/null +++ b/contracts/ccip/registry-pools/test/daml.yaml @@ -0,0 +1,27 @@ +sdk-version: 3.4.11 +name: ccip-registry-pools-test +source: daml +version: 1.0.0 +build-options: + - "-Wno-crypto-text-is-alpha" + - "-Wno-template-interface-depends-on-daml-script" + - "-Werror=unused-dependency" +data-dependencies: + - ../../../dependencies/splice/splice-api-token-metadata-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-holding-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-burn-mint-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-transfer-instruction-v1-1.0.0.dar + + - ../../../mcms/api/.daml/dist/mcms-api-1.0.0.dar + - ../../../chainlink/api/.daml/dist/chainlink-api-2.0.0.dar + + - ../../codec/.daml/dist/ccip-codec-v2-2.0.0.dar + - ../../core/.daml/dist/ccip-core-v2-2.1.1.dar + + - ../rate-limiter/.daml/dist/ccip-registry-rate-limiter-v2-2.0.1.dar + - ../lock-release-token-pool/.daml/dist/ccip-registry-lock-release-token-pool-v2-2.1.1.dar + - ../burn-mint-token-pool/.daml/dist/ccip-registry-burn-mint-token-pool-v2-2.1.1.dar +dependencies: + - daml-prim + - daml-stdlib + - daml-script diff --git a/contracts/ccip/registry-pools/test/daml/TokenPoolInitializeTest.daml b/contracts/ccip/registry-pools/test/daml/TokenPoolInitializeTest.daml new file mode 100644 index 000000000..3c5da74fe --- /dev/null +++ b/contracts/ccip/registry-pools/test/daml/TokenPoolInitializeTest.daml @@ -0,0 +1,186 @@ +-- | Integration tests for the Initialize choice on CCIP.Registry.BurnMintTokenPool and +-- CCIP.Registry.LockReleaseTokenPool: exercises the full deploy + configure + register flow +-- end-to-end against real templates (Daml Script, no mocking), via a single +-- createAndExerciseCmd - propose admin, accept admin, deploy pool, deploy rate limiters, +-- ApplyChainUpdates, and SetPool, all atomically in one command. +module TokenPoolInitializeTest where + +import Daml.Script +import DA.Foldable (mapA_) +import DA.Assert ((===)) +import DA.Map qualified as Map + +import qualified Splice.Api.Token.HoldingV1 +import qualified Splice.Api.Token.MetadataV1 + +import qualified Chainlink.InstanceAddress + +import qualified CCIP.CodecV2.FinalityConfig +import qualified CCIP.Registry.RateLimiterV2 +import CCIP.Registry.BurnMintTokenPoolV2 +import CCIP.Registry.LockReleaseTokenPoolV2 +import qualified CCIP.CoreV2.TokenAdminRegistry as TAR + +testInitializeBurnMintPool_SelfIssued : Script () +testInitializeBurnMintPool_SelfIssued = script do + poolOwner <- allocateParty "pool_owner" + observerParty <- allocateParty "registry_observer" + + let instanceId = "e2e-burn-mint-pool" + -- Self-issued: instrumentId.admin must equal the admin party for + -- ProposeAdministrator's caller == instrumentId.admin check to pass. + instrumentId = Splice.Api.Token.HoldingV1.InstrumentId poolOwner "E2ETOKEN" + dummyAddr label = Chainlink.InstanceAddress.make label poolOwner + + tarCid <- submit poolOwner do + createCmd TAR.TokenAdminRegistry with + instanceId = "e2e-tar" + ccipOwner = poolOwner + entryCount = 0 + + let lane = CCIP.Registry.BurnMintTokenPoolV2.LaneDeploySpec with + remoteChainSelector = 1.0 + remotePools = ["1fb477ac89df394bead4f46ad754f8aec70cc0e4"] + remoteTokenAddress = "cbae4ea0c4c503a582af009bb2b30b75badc1e32" + inboundCCVs = [] + outboundCCVs = [] + finalityConfig = CCIP.CodecV2.FinalityConfig.WaitForFinality + inbound = CCIP.Registry.BurnMintTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lane-1-in" + isEnabled = True + capacity = 1000.0 + rate = 10.0 + outbound = CCIP.Registry.BurnMintTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lane-1-out" + isEnabled = True + capacity = 1000.0 + rate = 10.0 + inboundCustomFinality = CCIP.Registry.BurnMintTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lane-1-in-custom" + isEnabled = False + capacity = 0.0 + rate = 0.0 + + result <- submit poolOwner do + createAndExerciseCmd + CCIP.Registry.BurnMintTokenPoolV2.BurnMintTokenPool with + instanceId + poolOwner + ccipOwner = poolOwner + instrumentId + decimals = 18 + rateLimitAdmin = None + observers = [observerParty] + remoteChainConfigs = Map.empty + tokenTransferFeeConfigs = Map.empty + poolReceiveContext = Splice.Api.Token.MetadataV1.emptyChoiceContext + transferTimeout = CCIP.Registry.BurnMintTokenPoolV2.Indefinite + deps = CCIP.Registry.BurnMintTokenPoolV2.BurnMintTokenPoolDeps with + tokenAdminRegistry = dummyAddr "dummy-tar" + feeQuoter = dummyAddr "dummy-fq" + rmnRemote = dummyAddr "dummy-rmn" + CCIP.Registry.BurnMintTokenPoolV2.Initialize with + tokenAdminRegistryCid = tarCid + existingTokenConfigCid = None + admin = poolOwner + lanes = [lane] + + -- Registration landed: TokenConfig now has admin = poolOwner and points at the pool. + Some config <- queryContractId poolOwner result.tokenConfigCid + config.admin === Some poolOwner + config.pendingAdmin === None + config.tokenPool === Some (TAR.PoolRegistration poolOwner instanceId) + + -- The pool itself was deployed with the lane wired up and observer set. + pools <- query @CCIP.Registry.BurnMintTokenPoolV2.BurnMintTokenPool poolOwner + case filter (\(_, pool) -> pool.instanceId == instanceId) pools of + [(_, pool)] -> do + pool.observers === [observerParty] + Map.size pool.remoteChainConfigs === 1 + got -> abort ("expected exactly one deployed BurnMintTokenPool, got " <> show (length got)) + + -- All three rate limiters for the lane were deployed with observer set. + length result.rateLimiterCids === 3 + rateLimiters <- query @CCIP.Registry.RateLimiterV2.RateLimiter poolOwner + let laneRateLimiters = filter (\(_, rl) -> rl.poolInstanceId == instanceId) rateLimiters + length laneRateLimiters === 3 + mapA_ (\(_, rl) -> rl.observers === [observerParty]) laneRateLimiters + + pure () + +testInitializeLockReleasePool_SelfIssued : Script () +testInitializeLockReleasePool_SelfIssued = script do + poolOwner <- allocateParty "pool_owner" + observerParty <- allocateParty "registry_observer" + + let instanceId = "e2e-lock-release-pool" + instrumentId = Splice.Api.Token.HoldingV1.InstrumentId poolOwner "E2ELRTOKEN" + dummyAddr label = Chainlink.InstanceAddress.make label poolOwner + + tarCid <- submit poolOwner do + createCmd TAR.TokenAdminRegistry with + instanceId = "e2e-lr-tar" + ccipOwner = poolOwner + entryCount = 0 + + let lane = CCIP.Registry.LockReleaseTokenPoolV2.LaneDeploySpec with + remoteChainSelector = 2.0 + remotePools = ["1fb477ac89df394bead4f46ad754f8aec70cc0e4"] + remoteTokenAddress = "cbae4ea0c4c503a582af009bb2b30b75badc1e32" + inboundCCVs = [] + outboundCCVs = [] + finalityConfig = CCIP.CodecV2.FinalityConfig.WaitForFinality + inbound = CCIP.Registry.LockReleaseTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lr-lane-1-in" + isEnabled = True + capacity = 1000.0 + rate = 10.0 + outbound = CCIP.Registry.LockReleaseTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lr-lane-1-out" + isEnabled = True + capacity = 1000.0 + rate = 10.0 + inboundCustomFinality = CCIP.Registry.LockReleaseTokenPoolV2.RateLimiterDeploySpec with + instanceId = "e2e-lr-lane-1-in-custom" + isEnabled = False + capacity = 0.0 + rate = 0.0 + + result <- submit poolOwner do + createAndExerciseCmd + CCIP.Registry.LockReleaseTokenPoolV2.LockReleaseTokenPool with + instanceId + poolOwner + ccipOwner = poolOwner + instrumentId + decimals = 18 + rateLimitAdmin = None + observers = [observerParty] + remoteChainConfigs = Map.empty + tokenTransferFeeConfigs = Map.empty + poolReceiveContext = Splice.Api.Token.MetadataV1.emptyChoiceContext + transferTimeout = CCIP.Registry.LockReleaseTokenPoolV2.Indefinite + deps = CCIP.Registry.LockReleaseTokenPoolV2.LockReleaseTokenPoolDeps with + tokenAdminRegistry = dummyAddr "dummy-tar" + feeQuoter = dummyAddr "dummy-fq" + rmnRemote = dummyAddr "dummy-rmn" + CCIP.Registry.LockReleaseTokenPoolV2.Initialize with + tokenAdminRegistryCid = tarCid + existingTokenConfigCid = None + admin = poolOwner + lanes = [lane] + + Some config <- queryContractId poolOwner result.tokenConfigCid + config.admin === Some poolOwner + config.tokenPool === Some (TAR.PoolRegistration poolOwner instanceId) + + pools <- query @CCIP.Registry.LockReleaseTokenPoolV2.LockReleaseTokenPool poolOwner + case filter (\(_, pool) -> pool.instanceId == instanceId) pools of + [(_, pool)] -> do + pool.observers === [observerParty] + Map.size pool.remoteChainConfigs === 1 + got -> abort ("expected exactly one deployed LockReleaseTokenPool, got " <> show (length got)) + + length result.rateLimiterCids === 3 + + pure () diff --git a/contracts/contracts.go b/contracts/contracts.go index 711d50090..d15b1d5ab 100644 --- a/contracts/contracts.go +++ b/contracts/contracts.go @@ -52,6 +52,14 @@ const ( CCIPBurnMintTokenPoolV2 = Package("ccip-burn-mint-token-pool-v2") CCIPFactoryV2 = Package("ccip-factory-v2") + // CCIP - Registry pools (EDS auto-detection observer field; separate template + // family from the production/LINK pools above, kept isolated so a bug or spam on + // the registry-discovery side can never affect production pool serving). + + CCIPRegistryBurnMintTokenPoolV2 = Package("ccip-registry-burn-mint-token-pool-v2") + CCIPRegistryLockReleaseTokenPoolV2 = Package("ccip-registry-lock-release-token-pool-v2") + CCIPRegistryRateLimiterV2 = Package("ccip-registry-rate-limiter-v2") + // CCIP - Legacy CCIPCore = Package("ccip-core") @@ -173,6 +181,11 @@ var Versions map[Package][]string = map[Package][]string{ CCIPBurnMintTokenPoolV2: append(ReleasedVersions[CCIPBurnMintTokenPoolV2], DevVersion), CCIPFactoryV2: append(ReleasedVersions[CCIPFactoryV2], DevVersion), + // Registry pools have no released version yet - dev only. + CCIPRegistryBurnMintTokenPoolV2: []string{DevVersion}, + CCIPRegistryLockReleaseTokenPoolV2: []string{DevVersion}, + CCIPRegistryRateLimiterV2: []string{DevVersion}, + Coin: []string{DevVersion}, GlobalConfig: []string{DevVersion}, MCMSTest: []string{DevVersion}, @@ -372,6 +385,10 @@ var BindingsOutputDirs = map[Package][]string{ CCIPBurnMintTokenPoolV2: []string{"ccip", "burnminttokenpool"}, CCIPFactoryV2: []string{"ccip", "factory"}, + CCIPRegistryBurnMintTokenPoolV2: []string{"ccip", "registry", "burnminttokenpool"}, + CCIPRegistryLockReleaseTokenPoolV2: []string{"ccip", "registry", "lockreleasetokenpool"}, + CCIPRegistryRateLimiterV2: []string{"ccip", "registry", "ratelimiter"}, + SpliceApiFeaturedAppV1: []string{"splice", "splice_api_featured_app_v1"}, SpliceApiTokenAllocationV1: []string{"splice", "splice_api_token_allocation_v1"}, SpliceApiTokenAllocationInstructionV1: []string{"splice", "splice_api_token_allocation_instruction_v1"}, diff --git a/contracts/dars/dev/ccip-registry-burn-mint-token-pool-v2-dev.dar b/contracts/dars/dev/ccip-registry-burn-mint-token-pool-v2-dev.dar new file mode 100644 index 000000000..1c8372bb5 Binary files /dev/null and b/contracts/dars/dev/ccip-registry-burn-mint-token-pool-v2-dev.dar differ diff --git a/contracts/dars/dev/ccip-registry-lock-release-token-pool-v2-dev.dar b/contracts/dars/dev/ccip-registry-lock-release-token-pool-v2-dev.dar new file mode 100644 index 000000000..0ffe4a198 Binary files /dev/null and b/contracts/dars/dev/ccip-registry-lock-release-token-pool-v2-dev.dar differ diff --git a/contracts/dars/dev/ccip-registry-pools-test-dev.dar b/contracts/dars/dev/ccip-registry-pools-test-dev.dar new file mode 100644 index 000000000..a9b39ccab Binary files /dev/null and b/contracts/dars/dev/ccip-registry-pools-test-dev.dar differ diff --git a/contracts/dars/dev/ccip-registry-rate-limiter-v2-dev.dar b/contracts/dars/dev/ccip-registry-rate-limiter-v2-dev.dar new file mode 100644 index 000000000..5e694f2cb Binary files /dev/null and b/contracts/dars/dev/ccip-registry-rate-limiter-v2-dev.dar differ diff --git a/contracts/multi-package.yaml b/contracts/multi-package.yaml index ba1504250..021cf6a6e 100644 --- a/contracts/multi-package.yaml +++ b/contracts/multi-package.yaml @@ -30,6 +30,11 @@ packages: - ccip/tickets - ccip/utils + - ccip/registry-pools/burn-mint-token-pool + - ccip/registry-pools/lock-release-token-pool + - ccip/registry-pools/rate-limiter + - ccip/registry-pools/test + - test/interfaces - test/proxy - test/receiver diff --git a/integration-tests/ccip/ccip_send_with_token_registry_pool_bnm_test.go b/integration-tests/ccip/ccip_send_with_token_registry_pool_bnm_test.go new file mode 100644 index 000000000..cd33bf0f9 --- /dev/null +++ b/integration-tests/ccip/ccip_send_with_token_registry_pool_bnm_test.go @@ -0,0 +1,976 @@ +package tests + +import ( + "context" + "crypto/ecdsa" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math/big" + "os" + "slices" + "strconv" + "testing" + "time" + + apiv2 "github.com/digital-asset/dazl-client/v8/go/api/com/daml/ledger/api/v2" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + devenvcommon "github.com/smartcontractkit/chainlink-ccv/build/devenv/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-deployments-framework/chain" + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + "github.com/smartcontractkit/freeport" + damlledger "github.com/smartcontractkit/go-daml/pkg/service/ledger" + "github.com/smartcontractkit/go-daml/pkg/types" + + "github.com/smartcontractkit/chainlink-canton/commonconfig" + "github.com/smartcontractkit/chainlink-canton/contracts/v2" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipcodec" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipruntime" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/clientapi" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/committeeverifier" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/core" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/events" + executorBinding "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/executor" + registrybnm "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/registry/burnminttokenpool" + registryrl "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/registry/ratelimiter" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/sender" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/link" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_burn_mint_v1" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_holding_v1" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_metadata_v1" + "github.com/smartcontractkit/chainlink-canton/deployment/changesets" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/committee_verifier" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/executor" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/fee_quoter" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/global_config" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/offramp" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/onramp" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/per_party_router_factory" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/rmn_remote" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/token_admin_registry" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/linkregistry" + "github.com/smartcontractkit/chainlink-canton/deployment/sequences" + contractops "github.com/smartcontractkit/chainlink-canton/deployment/utils/operations/contract" + "github.com/smartcontractkit/chainlink-canton/eds/config" + "github.com/smartcontractkit/chainlink-canton/eds/service" + oapiCCIP "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/ccip" + oapiCCV "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/ccv" + oapiCommon "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/common" + oapiExecutor "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/executor" + oapiGlobal "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/global" + "github.com/smartcontractkit/chainlink-canton/testhelpers" + edsTesthelpers "github.com/smartcontractkit/chainlink-canton/testhelpers/eds" + + // Import to register adapters + _ "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/adapters" +) + +// TestRegistryBurnMintTokenPool_FullSendFlow proves that a registry-pools +// BurnMintTokenPool deployed via the atomic Initialize choice (CreateAndExercise, +// no factory, no separate register step) is fully usable in a real CCIPSender.Send +// - the same LockOrBurn/VerifyOutboundCCVs/CalculateFee path a production pool goes +// through, driven by exactly the rate limiter and TAR registration Initialize itself +// produced. +// +// EDS's TokenPoolAPI does not know about the registry-pools package (it is hardcoded +// to the production burnminttokenpool binding), so the pool-specific choice context +// and disclosures are hand-built here instead of fetched through EDS - mirroring +// exactly what eds/internal/api/tokenpool/tokenpool.go's burnMintTokenPoolSend +// computes for the production pool. Everything else (CCIP core deploy, lane config, +// EDS for the CCIP/CCV/Executor side, PerPartyRouter, CCIPSender, Send) is unchanged +// from TestBnMTokenPool_FullSendFlow. +// +//nolint:paralleltest // We can't run this test in parallel as that would mix up the holding calculations +func TestRegistryBurnMintTokenPool_FullSendFlow(t *testing.T) { + env := testhelpers.NewTestEnvironment(t, testhelpers.WithNumberOfParticipants(2)) + + ccipParticipant := env.Chain.Participants[0] + senderParticipant := env.Chain.Participants[1] + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + testhelpers.ContractCleanup(t, ctx, env.Chain.Participants) + }) + + // Upload DARs + runtimeDar, err := contracts.GetDar(contracts.CCIPRuntimeV2, contracts.DevVersion) + require.NoError(t, err) + coreDar, err := contracts.GetDar(contracts.CCIPCoreV2, contracts.DevVersion) + require.NoError(t, err) + committeeVerifierDar, err := contracts.GetDar(contracts.CCIPCommitteeVerifierV2, contracts.DevVersion) + require.NoError(t, err) + ccipSenderDar, err := contracts.GetDar(contracts.CCIPSenderV2, contracts.DevVersion) + require.NoError(t, err) + ccipExecutorDar, err := contracts.GetDar(contracts.CCIPExecutorV2, contracts.DevVersion) + require.NoError(t, err) + registryTokenPoolDar, err := contracts.GetDar(contracts.CCIPRegistryBurnMintTokenPoolV2, contracts.DevVersion) + require.NoError(t, err) + registryRateLimiterDar, err := contracts.GetDar(contracts.CCIPRegistryRateLimiterV2, contracts.DevVersion) + require.NoError(t, err) + linkTokenDar, err := contracts.GetDar(contracts.Link, contracts.DevVersion) + require.NoError(t, err) + + dars := [][]byte{runtimeDar, coreDar, committeeVerifierDar, ccipSenderDar, ccipExecutorDar, registryTokenPoolDar, registryRateLimiterDar, linkTokenDar} + packageIds, err := testhelpers.UploadDARstoMultipleParticipants(t.Context(), dars, ccipParticipant, senderParticipant) + require.NoError(t, err) + t.Logf("Uploaded DARs to all participants: %v", packageIds) + + // Allocate parties + partyCCIP := ccipParticipant.PartyID + partySender := senderParticipant.PartyID + t.Logf("Parties: CCIP=%s, Sender=%s", partyCCIP, partySender) + + // CCV Setup + ccvSignerKeys := make([]*ecdsa.PrivateKey, 0, 3) + ccvSignerPubKeys := make([]string, 0, 3) + for range 3 { + pk, err := crypto.GenerateKey() + require.NoError(t, err) + ccvSignerKeys = append(ccvSignerKeys, pk) + pubKeyHex := hex.EncodeToString(crypto.FromECDSAPub(&pk.PublicKey)) + ccvSignerPubKeys = append(ccvSignerPubKeys, pubKeyHex) + } + t.Logf("Generated %d CCV signer keys", len(ccvSignerKeys)) + + versionTag := "e9a05a20" + ccvQualifier := devenvcommon.DefaultCommitteeVerifierQualifier + remoteSelector := chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector + + // Create Scan and Registry API clients + // Using the scanProxy endpoint of the 0-th participant, all participants are able to forward requests using the BFT Scan Proxy, it doesn't matter which one we use + scanProxyClient, tokenMetadataClient, transferInstructionClient, err := testhelpers.NewValidatorAPIClients(ccipParticipant) + require.NoError(t, err, "Failed to create validator API clients") + + // Setup Amulet token as fee token + // Get registry admin for Amulet tokens + registryAdmin, err := testhelpers.GetRegistryAdmin(t.Context(), tokenMetadataClient) + require.NoError(t, err, "failed to get registry admin") + + // Native is Amulet + nativeInstrumentId := splice_api_token_holding_v1.InstrumentId{ + Admin: types.PARTY(registryAdmin), + Id: types.TEXT("Amulet"), + } + linkInstrumentId := splice_api_token_holding_v1.InstrumentId{ + Admin: types.PARTY(partySender), + Id: "ChainLink", + } + + reporter := cld_ops.NewMemoryReporter() + bundle := cld_ops.NewBundle( + t.Context, + logger.Test(t), + reporter, + ) + cldfEnv := cldf.Environment{ + Logger: logger.Test(t), + GetContext: t.Context, + DataStore: datastore.NewMemoryDataStore().Seal(), + BlockChains: chain.NewBlockChainsFromSlice([]chain.BlockChain{env.Chain}), + OperationsBundle: bundle, + } + + tokenPriceExponentUSDCents := uint64(1e6) // 6 decimals for USD cents + tokenPriceExponentUSD := 1e2 * tokenPriceExponentUSDCents // FeeQuoter usd8 scale: $1 == 1e8 + + // Deploy Chain contracts (GlobalConfig, RMNRemote, FeeQuoter, CommitteeVerifier, Executor, TAR) + out, err := changesets.DeployChainContracts{}.Apply(cldfEnv, changesets.CantonCSDeps[changesets.DeployChainContractsConfig]{ + ChainSelector: env.Chain.ChainSelector(), + Participant: 0, + Config: changesets.DeployChainContractsConfig{ + Params: sequences.DeployChainContractsParams{ + CCIPOwnerParty: partyCCIP, + RMNOwnerParty: partyCCIP, + CommitteeVerifiers: []sequences.CommitteeVerifierParams{ + { + Qualifier: ccvQualifier, + Template: committeeverifier.CommitteeVerifier{ + Owner: types.PARTY(partyCCIP), + CcipOwner: types.PARTY(partyCCIP), + VersionTag: types.TEXT(versionTag), + MessageSentObservers: nil, + StorageLocations: []types.TEXT{"ipfs://test-send"}, + StorageLocationsAdmin: types.PARTY(partyCCIP), + PendingStorageLocationsAdmin: types.PARTY(partyCCIP), + Deps: committeeverifier.CommitteeVerifierDeps{}, // Set by sequence + }, + }, + }, + Executors: []sequences.ExecutorParams{ + { + Qualifier: devenvcommon.DefaultExecutorQualifier, + Template: executorBinding.Executor{ + Owner: types.PARTY(partyCCIP), + MaxCCVsPerMsg: 10, + DynamicConfig: executorBinding.DynamicConfig{ + FeeAggregator: nil, + AllowedFinalityConfig: ccipcodec.FinalityConfig{WaitForFinality: &types.UNIT{}}, + CcvAllowlistEnabled: false, + }, + AllowedCCVs: nil, + }, + }, + }, + GlobalConfig: sequences.GlobalConfigParams{ + Template: core.GlobalConfig{ + CcipOwner: "", // Populated by the sequence + ChainSelector: types.NUMERIC(strconv.FormatUint(chainsel.CANTON_LOCALNET.Selector, 10)), + }, + }, + RMNRemote: sequences.RMNRemoteParams{ + Template: core.RMNRemote{ + CcipOwner: "", // Populated by the sequence + RmnOwner: types.PARTY(partyCCIP), + CursedSubjects: nil, + }, + }, + FeeQuoterConfig: sequences.FeeQuoterParams{ + Template: core.FeeQuoter{ + PriceUpdaters: []types.PARTY{types.PARTY(partyCCIP)}, + }, + + USDPerNative: big.NewInt(int64(1 * tokenPriceExponentUSD)), // $1 + }, + NativeInstrumentId: nativeInstrumentId, + }, + }, + }) + require.NoErrorf(t, err, "Failed to deploy CCIP contracts: %v", err) + err = out.DataStore.Merge(cldfEnv.DataStore) + require.NoError(t, err) + cldfEnv.DataStore = out.DataStore.Seal() + + t.Log("Deployed CCIP chain contracts:") + addresses := cldfEnv.DataStore.Addresses().Filter() + for i, address := range addresses { + t.Logf("Deployed Address %d: ChainSelector=%d, Type=%s, Version=%s, Address=%s, Qualifier=%s, Labels=%s\n", i, address.ChainSelector, address.Type, address.Version, address.Address, address.Qualifier, address.Labels.String()) + } + + // Resolve contracts + globalConfigRef, globalConfigAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), global_config.ContractType, global_config.Version, "") + require.NoError(t, err, "failed to get GlobalConfig address") + _, feeQuoterAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), fee_quoter.ContractType, fee_quoter.Version, "") + require.NoError(t, err, "failed to get FeeQuoter address") + _, onRampAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), onramp.ContractType, onramp.Version, "") + require.NoError(t, err, "failed to get OnRamp address") + _, offRampAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), offramp.ContractType, offramp.Version, "") + require.NoError(t, err, "failed to get OffRamp address") + committeeVerifierRef, committeeVerifierAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), committee_verifier.ContractType, committee_verifier.Version, ccvQualifier) + require.NoError(t, err, "failed to get CommitteeVerifier address") + _, tokenAdminRegistryAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), token_admin_registry.ContractType, token_admin_registry.Version, "") + require.NoError(t, err, "failed to get TokenAdminRegistry address") + _, rmnRemoteAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), rmn_remote.ContractType, rmn_remote.Version, "") + require.NoError(t, err, "failed to get RMNRemote address") + _, perPartyRouterFactoryAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), per_party_router_factory.ContractType, per_party_router_factory.Version, "") + require.NoError(t, err, "failed to get PerPartyRouterFactory address") + executorRef, executorAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), executor.ContractType, executor.Version, devenvcommon.DefaultExecutorQualifier) + require.NoError(t, err, "failed to get Executor address") + + // Deploy and configure lane for outbound sends + + // 8 cents outgoing CCV verification fee + ccvFeeUSDCents := 7 + + feeQuoterDestChainConfig := lanes.FeeQuoterDestChainConfig{ + IsEnabled: true, + MaxDataBytes: 30_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + ChainFamilySelector: binary.BigEndian.Uint32([]byte{0x28, 0x12, 0xd5, 0x2c}), + DefaultTokenFeeUSDCents: 10, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + NetworkFeeUSDCents: 25, + V2Params: &lanes.FeeQuoterV2Params{ + LinkFeeMultiplierPercent: 100, // Not used when paying in native + // Integer scaled by 1e10 in cantonFeeQuoterUSDPerUnitGas (38 -> 0.0000000038). + USDPerUnitGas: big.NewInt(38), + }, + } + + deployLaneLegReport, err := cld_ops.ExecuteSequence(cldfEnv.OperationsBundle, sequences.ConfigureLaneLegAsSourceWithInput, cldfEnv.BlockChains, sequences.ConfigureLaneLegInput{ + DataStore: cldfEnv.DataStore, + Lane: lanes.UpdateLanesInput{ + Source: &lanes.ChainDefinition{ + Selector: env.Chain.ChainSelector(), + CommitteeVerifiers: []lanes.CommitteeVerifierConfig[datastore.AddressRef]{ + { + CommitteeVerifier: []datastore.AddressRef{committeeVerifierRef}, + RemoteChains: map[uint64]lanes.CommitteeVerifierRemoteChainConfig{ + remoteSelector: { + AllowlistEnabled: false, + AddedAllowlistedSenders: nil, + RemovedAllowlistedSenders: nil, + FeeUSDCents: uint16(ccvFeeUSDCents), + GasForVerification: 50_000, + PayloadSizeBytes: 6*64 + 2*32, + SignatureConfig: lanes.CommitteeVerifierSignatureQuorumConfig{ + Signers: ccvSignerPubKeys, + Threshold: 2, + }, + }, + }, + }, + }, + LaneMandatedOutboundCCVs: []datastore.AddressRef{committeeVerifierRef}, + DefaultOutboundCCVs: nil, + CantonLaneConfig: &lanes.CantonLaneConfig{ + GlobalConfig: globalConfigRef, + }, + DefaultExecutor: executorRef, + FeeQuoter: feeQuoterAddress.InstanceAddress().Bytes(), + OnRamp: onRampAddress.InstanceAddress().Bytes(), + OffRamp: offRampAddress.InstanceAddress().Bytes(), + }, + Dest: &lanes.ChainDefinition{ + Selector: remoteSelector, + AddressBytesLength: 20, + FeeQuoterDestChainConfig: feeQuoterDestChainConfig, + ExecutorDestChainConfig: lanes.ExecutorDestChainConfig{ + USDCentsFee: 50, + Enabled: true, + }, + OnRamp: hexutil.MustDecode("0xf6eced5e96fff2de4f0ecd722beb57556fc443fd"), + OffRamp: hexutil.MustDecode("0xd8c9ec8cad3fb34aeca3ddbebfabe9f28a9bfaed"), + Router: hexutil.MustDecode("0xe3ddcb2fde5d27a33c450fddc54a3f9bb2ecaa9f"), + }, + IsDisabled: false, + TestRouter: false, + ExtraConfigs: lanes.ExtraConfigs{}, + }, + }) + require.NoErrorf(t, err, "Failed to configure chain for lanes") + runningDs := datastore.NewMemoryDataStore() + for _, address := range deployLaneLegReport.Output.Addresses { + err = runningDs.Addresses().Add(address) + require.NoErrorf(t, err, "Failed to add address %s", address.Address) + } + err = runningDs.Merge(cldfEnv.DataStore) + require.NoErrorf(t, err, "Failed to merge datastore") + cldfEnv.DataStore = runningDs.Seal() + t.Log("Configured chain for lanes") + + // Deploy Link Token + linkTokenOut, err := cld_ops.ExecuteOperation(bundle, linkregistry.Deploy, env.Chain, contractops.DeployInput[link.LinkRegistry]{ + ParticipantIndex: 1, + OwnerParty: types.PARTY(partySender), + Template: link.LinkRegistry{ + RegistryAdmin: types.PARTY(partySender), + RegistryInstrumentId: linkInstrumentId, + RegistryMeta: splice_api_token_metadata_v1.Metadata{}, + }, + }) + require.NoError(t, err) + linkRegistryAddress, err := contracts.RawInstanceAddressFromString(linkTokenOut.Output.Labels.List()[0]) + require.NoError(t, err) + linkRegistryCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, contracts.TemplateIDFromBinding(link.LinkRegistry{}).String(), linkRegistryAddress.InstanceAddress()) + require.NoError(t, err) + t.Logf("LinkRegistry ContractId: %v", linkRegistryCid) + + // ------------------------------------------------------------------------ + // Deploy the registry-pools BurnMintTokenPool via a single atomic + // CreateAndExercise + Initialize - deploy, propose+accept admin, deploy the + // lane's 3 rate limiters, wire the lane, and register with TAR all in one + // ledger command. This replaces the production pool's separate + // DeployBurnMintTokenPool + RegisterTokenPool changesets. + // ------------------------------------------------------------------------ + poolInstanceID := "test-registry-pool-send" + + // Pool transfer amounts use the token's smallest units; with 10 pool decimals, + // a transfer amount of 1.0 means 1e10 smallest units, and a 5% bps fee floors + // to 5e8. + tokenTransferFeeUSDCents := 10 + tokenTransferFeeBps := 500 // 5% + + remotePoolAddress := hexutil.MustDecode("0x7e3febbdaf80e7e96c1ae107508ec3fafc36d7f3") + remoteTokenAddress := hexutil.MustDecode("0xacdafefb07bff5b120b7afa6ea777cf7eabacc0d") + + pool := registrybnm.BurnMintTokenPool{ + InstanceId: types.TEXT(poolInstanceID), + PoolOwner: types.PARTY(partySender), + CcipOwner: types.PARTY(partyCCIP), + InstrumentId: linkInstrumentId, + Decimals: 10, + Observers: []types.PARTY{types.PARTY(partyCCIP)}, + // Set directly at creation - Initialize only manages remoteChainConfigs/rate + // limiters/TAR registration, not the token-transfer fee overrides. + TokenTransferFeeConfigs: map[types.NUMERIC]registrybnm.TokenTransferFeeConfig2{ + types.NUMERIC(strconv.FormatUint(remoteSelector, 10)): { + IsEnabled: true, + DestGasOverhead: 25_000, + DestBytesOverhead: 32, + FeeUSDCents: types.NUMERIC(strconv.Itoa(tokenTransferFeeUSDCents)), + FeeBps: types.NUMERIC(strconv.Itoa(tokenTransferFeeBps)), + }, + }, + PoolReceiveContext: splice_api_token_metadata_v1.ChoiceContext{}, + TransferTimeout: registrybnm.TransferTimeout{ + RelativeHours: func(v types.INT64) *types.INT64 { return &v }(24), + }, + Deps: registrybnm.BurnMintTokenPoolDeps{ + TokenAdminRegistry: tokenAdminRegistryAddress.Binding(), + RmnRemote: rmnRemoteAddress.Binding(), + FeeQuoter: feeQuoterAddress.Binding(), + }, + } + + lane := registrybnm.LaneDeploySpec{ + RemoteChainSelector: types.NUMERIC(strconv.FormatUint(remoteSelector, 10)), + RemotePools: []types.TEXT{types.TEXT(hex.EncodeToString(remotePoolAddress))}, + RemoteTokenAddress: types.TEXT(hex.EncodeToString(remoteTokenAddress)), + InboundCCVs: nil, + OutboundCCVs: nil, + FinalityConfig: ccipcodec.FinalityConfig{WaitForFinality: &types.UNIT{}}, + Inbound: registrybnm.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-send-in", + IsEnabled: true, + Capacity: "10000000000", + Rate: "10000000000", + }, + Outbound: registrybnm.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-send-out", + IsEnabled: true, + Capacity: "10000000000", + Rate: "10000000000", + }, + InboundCustomFinality: registrybnm.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-send-in-custom", + IsEnabled: false, + Capacity: "0", + Rate: "0", + }, + } + + // TAR lives on ccipParticipant (ccipOwner=partyCCIP), not senderParticipant. + // Initialize's authorization only needs partySender's signature (self-issued + // isAdmin path), but the submitting participant (senderParticipant) still needs + // to *see* the TAR contract to build the transaction - that's a cross-participant + // visibility gap, separate from authorization, so it must be disclosed explicitly. + tarCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), ccipParticipant.LedgerServices.State, []string{partyCCIP}, contracts.TemplateIDFromBinding(core.TokenAdminRegistry{}).String(), tokenAdminRegistryAddress.InstanceAddress()) + require.NoError(t, err, "find TAR contract id from ccip participant") + tarDisclosure, err := testhelpers.GetDisclosedContractById(t.Context(), ccipParticipant, tarCid) + require.NoError(t, err, "disclose TAR contract to sender's participant") + + initArgs := registrybnm.Initialize{ + TokenAdminRegistryCid: types.CONTRACT_ID(tarCid), + ExistingTokenConfigCid: nil, + Admin: types.PARTY(partySender), + Lanes: []registrybnm.LaneDeploySpec{lane}, + } + _, err = submitCreateAndExercise(t.Context(), senderParticipant, partySender, pool, "Initialize", initArgs, tarDisclosure) + require.NoError(t, err, "create registry pool and exercise Initialize atomically") + + tokenPoolAddress := contracts.NewRawInstanceAddress(contracts.InstanceID(poolInstanceID), types.PARTY(partySender)) + poolCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, pool.GetTemplateID(), tokenPoolAddress.InstanceAddress()) + require.NoError(t, err, "find registry pool contract id") + outboundRateLimiterCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, registryrl.RateLimiter{}.GetTemplateID(), contracts.NewRawInstanceAddress(contracts.InstanceID(lane.Outbound.InstanceId), types.PARTY(partySender)).InstanceAddress()) + require.NoError(t, err, "find outbound rate limiter contract id") + + // Run EDS for the CCIP-global/CCV/Executor side only. There's no pool-side EDS + // instance: TokenPoolAPI doesn't know the registry-pools template, and (per + // eds/service/service.go) TokenStandardAPI's supply computation only works when + // TokenPoolAPI is also enabled, since that's what starts the shared + // instrumentHoldingStore's backfill - so a pool-only EDS instance here would + // serve nothing. The pool's own send disclosure is hand-built below instead, + // mirroring what EDS's burnMintTokenPoolSend computes. + edsCCIPPort := freeport.GetOne(t) + + ccipEDSToken, _ := ccipParticipant.TokenSource.Token() + edsCCIPCtx, edsCCIPCancel := context.WithCancel(t.Context()) + t.Cleanup(edsCCIPCancel) + go func() { + log.Info().Int("port", edsCCIPPort).Msg("Running EDS-ccip...") + err := service.RunEDS(edsCCIPCtx, log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.TraceLevel), &config.Config{ + ChainSelector: strconv.FormatUint(env.Chain.ChainSelector(), 10), + Server: config.ServerConfig{ + Host: "0.0.0.0", + Port: uint16(edsCCIPPort), + }, + Node: config.NodeConfig{ + URL: ccipParticipant.Endpoints.GRPCLedgerAPIURL, + AuthConfig: commonconfig.AuthConfig{ + Type: commonconfig.AuthTypeInsecureStatic, + UserID: ccipParticipant.UserID, + JWT: ccipEDSToken.AccessToken, + }, + MaxRetries: 0, + }, + CCIPAPIConfig: config.CCIPAPIConfig{ + Enabled: true, + PerPartyRouterFactory: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: perPartyRouterFactoryAddress.InstanceAddress(), + }, + OnRamp: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: onRampAddress.InstanceAddress(), + }, + OffRamp: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: offRampAddress.InstanceAddress(), + }, + GlobalConfig: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: globalConfigAddress.InstanceAddress(), + }, + TokenAdminRegistry: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: tokenAdminRegistryAddress.InstanceAddress(), + }, + RMNRemote: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: rmnRemoteAddress.InstanceAddress(), + }, + FeeQuoter: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: feeQuoterAddress.InstanceAddress(), + }, + }, + CCVAPIConfig: config.CCVAPIConfig{ + Enabled: true, + CCVs: []config.CCV{ + { + ContractIdentifier: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: committeeVerifierAddress.InstanceAddress(), + }, + }, + }, + }, + ExecutorAPIConfig: config.ExecutorAPIConfig{ + Enabled: true, + Executors: []config.Executor{ + { + ContractIdentifier: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: executorAddress.InstanceAddress(), + }, + }, + }, + }, + TokenPoolAPIConfig: config.TokenPoolAPIConfig{ + Enabled: false, + }, + TokenStandardAPIConfig: config.TokenStandardAPIConfig{ + Enabled: false, + }, + }) + log.Info().Err(err).Msg("EDS-ccip terminated") + if !errors.Is(err, context.Canceled) { + t.Errorf("EDS-ccip server exited with error: %v", err) + } + }() + + // Create EDS clients — CCIP global/CCV/executor on P0. + globalAPIClient, err := oapiGlobal.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create GlobalConfig API client") + ccipAPIClient, err := oapiCCIP.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create CCIP API client") + ccvAPIClient, err := oapiCCV.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create CCV API client") + executorAPIClient, err := oapiExecutor.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create Executor API client") + + waitForEDSListening(t, edsCCIPPort) + + // Create PerPartyRouter for sender via EDS + perPartyRouterFactoryDisclosure, err := edsTesthelpers.GetPerPartyRouterFactoryDisclosure(t.Context(), ccipAPIClient, partySender) + require.NoError(t, err) + t.Logf("disclosed contracts: %v", perPartyRouterFactoryDisclosure.DisclosedContracts) + t.Logf("per party router factory cid: %s", perPartyRouterFactoryDisclosure.ContractId) + + res, err := senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: contracts.IdentifierFromBinding(ccipruntime.PerPartyRouterFactory{}), + ContractId: perPartyRouterFactoryDisclosure.ContractId, + Choice: "CreateRouter", + ChoiceArgument: &apiv2.Value{Sum: &apiv2.Value_Record{Record: &apiv2.Record{Fields: []*apiv2.RecordField{ + {Label: "partyOwner", Value: &apiv2.Value{Sum: &apiv2.Value_Party{Party: partySender}}}, + {Label: "instanceId", Value: &apiv2.Value{Sum: &apiv2.Value_Text{Text: "test-router-receiver"}}}, + {Label: "feeTransferLifetime", Value: &apiv2.Value{Sum: &apiv2.Value_Optional{Optional: &apiv2.Optional{Value: nil}}}}, + }}}}, + }}, + }}, + ActAs: []string{partySender}, + DisclosedContracts: perPartyRouterFactoryDisclosure.DisclosedContracts, + }, + }) + require.NoError(t, err) + routerCid := "" + for _, event := range res.GetTransaction().GetEvents() { + if e, ok := event.GetEvent().(*apiv2.Event_Created); ok { + if e.Created.GetTemplateId().GetEntityName() == "PerPartyRouter" { + routerCid = e.Created.ContractId + break + } + } + } + require.NotEmpty(t, routerCid) + t.Logf("Created PerPartyRouter for sender: %s", routerCid) + + // Build test payload + testPayload := []byte("Hello CCIP - this is a test send message!") + testPayloadHex := hex.EncodeToString(testPayload) + t.Logf("Test payload: %s", string(testPayload)) + + // Deploy CCIPSender for sender + res, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Create{Create: &apiv2.CreateCommand{ + TemplateId: contracts.IdentifierFromBinding(sender.CCIPSender{}), + CreateArguments: &apiv2.Record{Fields: []*apiv2.RecordField{ + {Label: "instanceId", Value: &apiv2.Value{Sum: &apiv2.Value_Text{Text: "test-ccipsender"}}}, + {Label: "owner", Value: &apiv2.Value{Sum: &apiv2.Value_Party{Party: partySender}}}, + }}, + }}, + }}, + ActAs: []string{partySender}, + }, + }) + require.NoError(t, err) + ccipSenderCid := extractCreatedContractId(res) + t.Logf("Deployed CCIPSender: %s", ccipSenderCid) + + // Prepare EVM receiver address + receiver := hexutil.MustDecode("0xcf8def9adfe3dd90b3dffe42c8eabbf7cd4ee6ca") + receiverHex := hex.EncodeToString(receiver) + + // Fund separate holdings for fee payment and token transfer input. + feeTokenHoldingCid, err := testhelpers.MintAMT(t.Context(), senderParticipant, tokenMetadataClient, transferInstructionClient, scanProxyClient, partySender, "1000.0") + require.NoError(t, err, "failed to mint Amulet tokens to sender") + t.Logf("Minted fee-token Amulet holding to sender, Holding CID: %s", feeTokenHoldingCid) + + // Mint LINK to sender (pool owner / token admin) + _, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: &apiv2.Identifier{PackageId: "#splice-api-token-burn-mint-v1", ModuleName: "Splice.Api.Token.BurnMintV1", EntityName: "BurnMintFactory"}, + ContractId: linkRegistryCid, + Choice: "BurnMintFactory_BurnMint", + ChoiceArgument: damlledger.MapToValue(splice_api_token_burn_mint_v1.BurnMintFactoryBurnMint{ + ExpectedAdmin: types.PARTY(partySender), + InstrumentId: linkInstrumentId, + InputHoldingCids: nil, + Outputs: []splice_api_token_burn_mint_v1.BurnMintOutput{ + { + Owner: types.PARTY(partySender), + Amount: "50.0", + Context: splice_api_token_metadata_v1.ChoiceContext{}, + }, + }, + ExtraArgs: splice_api_token_metadata_v1.ExtraArgs{}, + }), + }}, + }}, + ActAs: []string{partySender}, + }, + }) + require.NoError(t, err) + var senderLinkHoldings []*apiv2.ActiveContract + require.Eventually(t, func() bool { + var listErr error + senderLinkHoldings, listErr = testhelpers.ListActiveContractsByTemplateId(t.Context(), senderParticipant, contracts.IdentifierFromBinding(link.LinkHolding{})) + if listErr != nil { + return false + } + + return len(senderLinkHoldings) > 0 + }, 15*time.Second, 200*time.Millisecond, "expected at least one active LinkHolding after minting") + t.Logf("Minted 50 LINK to sender. Current holdings: %v", len(senderLinkHoldings)) + senderHoldingCids := make([]types.CONTRACT_ID, len(senderLinkHoldings)) + for i, holding := range senderLinkHoldings { + t.Logf("- Holding CID: %s", holding.GetCreatedEvent().GetContractId()) + senderHoldingCids[i] = types.CONTRACT_ID(holding.GetCreatedEvent().GetContractId()) + } + + // Get transfer factory for Amulet tokens (sender to CCIP owner) + transferFactoryCid, transferFactoryDisclosures, choiceContextRaw, err := testhelpers.GetTransferFactory(t.Context(), transferInstructionClient, registryAdmin, partySender, partyCCIP) + require.NoError(t, err, "failed to get transfer factory") + require.NotNil(t, choiceContextRaw, "choiceContext should not be nil") + + choiceContext, err := testhelpers.ChoiceContextFromData(choiceContextRaw) + require.NoError(t, err, "failed to parse choice context") + transferFactoryContextValues := testhelpers.ExtractChoiceContextValues(choiceContext) + + const tokenTransferAmountDecimal = "1.0" + + executorRawOrHashedAddress := oapiCommon.RawOrHashedAddress{} + _ = executorRawOrHashedAddress.FromRawInstanceAddress(executorAddress.String()) + msg := oapiCommon.Message{ + DestinationChainSelector: strconv.FormatUint(remoteSelector, 10), + Executor: struct { + Address *oapiCommon.RawOrHashedAddress `json:"address,omitempty"` + Type oapiCommon.MessageExecutorType `json:"type"` + }{ + Type: oapiCommon.WithAddress, + Address: &executorRawOrHashedAddress, + }, + FeeToken: oapiCommon.InstrumentId{ + Admin: oapiCommon.PartyId(nativeInstrumentId.Admin), + Id: string(nativeInstrumentId.Id), + }, + Payload: testPayloadHex, + Sender: partySender, + Receiver: receiverHex, + TokenTransfer: &oapiCommon.TokenTransfer{ + Amount: tokenTransferAmountDecimal, + Token: oapiCommon.InstrumentId{ + Admin: oapiCommon.PartyId(linkInstrumentId.Admin), + Id: string(linkInstrumentId.Id), + }, + }, + } + + // Hand-built equivalent of edsTesthelpers.GetTokenPoolSendDisclosure, since EDS's + // TokenPoolAPI is hardcoded to the production burnminttokenpool package and does + // not know about the registry-pools template. Mirrors exactly what + // eds/internal/api/tokenpool/tokenpool.go's burnMintTokenPoolSend computes: the + // outbound rate limiter under "rate-limiter" and the BurnMintFactory (here, + // LinkRegistry itself) under "burn-mint-factory". Our lane's OutboundCCVs is + // empty, so RequiredCCVs is empty too. + tokenPoolChoiceContext := splice_api_token_metadata_v1.ChoiceContext{ + Values: map[string]splice_api_token_metadata_v1.AnyValue{ + string(registryrl.RateLimiterContextKey): {AVContractId: func(v types.CONTRACT_ID) *types.CONTRACT_ID { return &v }(types.CONTRACT_ID(outboundRateLimiterCid))}, + string(registrybnm.BurnMintFactoryContextKey): {AVContractId: func(v types.CONTRACT_ID) *types.CONTRACT_ID { return &v }(types.CONTRACT_ID(linkRegistryCid))}, + }, + } + var requiredCCVs []string + + ccipSendDisclosure, err := edsTesthelpers.GetCCIPSendDisclosure(t.Context(), ccipAPIClient, msg, nil, requiredCCVs) + require.NoError(t, err) + ccvAddressEDS, err := contracts.RawInstanceAddressFromString(ccipSendDisclosure.CCVs[0]) + require.NoError(t, err) + executorAddressEDS, err := contracts.RawInstanceAddressFromString(*ccipSendDisclosure.Executor) + require.NoError(t, err) + ccvSendDisclosure, err := edsTesthelpers.GetCCVSendDisclosure(t.Context(), ccvAPIClient, msg, ccvAddressEDS.InstanceAddress()) + require.NoError(t, err) + executorSendDisclosure, err := edsTesthelpers.GetExecutorSendDisclosure(t.Context(), executorAPIClient, msg, executorAddressEDS.InstanceAddress(), ccipSendDisclosure.CCVs) + require.NoError(t, err) + + // Sanity check - CCIP global batch covers P0 contracts only (LinkRegistry/pool live on senderParticipant). + disclosedContracts, err := edsTesthelpers.GetGlobalDisclosureBatch(t.Context(), globalAPIClient, []contracts.InstanceAddress{ + perPartyRouterFactoryAddress.InstanceAddress(), + globalConfigAddress.InstanceAddress(), + feeQuoterAddress.InstanceAddress(), + onRampAddress.InstanceAddress(), + offRampAddress.InstanceAddress(), + tokenAdminRegistryAddress.InstanceAddress(), + rmnRemoteAddress.InstanceAddress(), + committeeVerifierAddress.InstanceAddress(), + executorAddress.InstanceAddress(), + }) + require.NoError(t, err) + require.Lenf(t, disclosedContracts, 9, "expected to retrieve disclosures for all CCIP global addresses") + + // Pool takes a token amount cut at LockOrBurn: feeBps = 500 (5%). + // Message uses Decimal token amount 0.0000010000 → 10,000 smallest units; + // after 5% pool fee the bridged amount should be 9,500. + sendArgs := sender.Send{ + Context: ccipSendDisclosure.ChoiceContext, + RouterCid: types.CONTRACT_ID(routerCid), + DestinationChainSelector: types.NUMERIC(strconv.FormatUint(remoteSelector, 10)), + Message: clientapi.Canton2AnyMessage{ + Receiver: types.TEXT(receiverHex), + Payload: types.TEXT(testPayloadHex), + TokenTransfer: &clientapi.TokenTransfer{ + Token: linkInstrumentId, + Amount: types.NUMERIC(tokenTransferAmountDecimal), + }, + FeeToken: nativeInstrumentId, + ExtraArgs: clientapi.ExtraArgs{ + V3: &clientapi.GenericExtraArgsV3{ + GasLimit: 0, + Ccvs: []clientapi.CCVExtraArg{ + { + CcvAddress: committeeVerifierAddress.Binding(), + CcvArgs: types.TEXT(""), + }, + }, + Executor: clientapi.ExecutorExtraArg{ + ExecutorWithAddress: &clientapi.ExecutorWithAddress{ + ExecutorAddress: executorAddress.Binding(), + ExecutorArgs: types.TEXT(""), + }, + }, + TokenReceiver: types.TEXT(""), + TokenArgs: types.TEXT(""), + }, + }, + }, + FeeTokenInput: sender.FeeTokenInput{ + SenderInputCids: []types.CONTRACT_ID{types.CONTRACT_ID(feeTokenHoldingCid)}, + FeeTokenConfigCid: types.CONTRACT_ID(ccipSendDisclosure.FeeTokenConfigCid), + FeeTokenTransferFactory: types.CONTRACT_ID(transferFactoryCid), + FeeTokenExtraArgs: splice_api_token_metadata_v1.ExtraArgs{ + Context: splice_api_token_metadata_v1.ChoiceContext{ + Values: transferFactoryContextValues, + }, + Meta: splice_api_token_metadata_v1.Metadata{ + Values: map[string]types.TEXT{}, + }, + }, + }, + CcvSendInputs: []sender.CCVSendInput{ + { + CcvAddress: ccvSendDisclosure.Address.Binding(), + CcvCid: types.CONTRACT_ID(ccvSendDisclosure.ContractId), + Context: ccvSendDisclosure.ChoiceContext, + }, + }, + TokenTransferInput: &sender.TokenTransferInput{ + SenderInputCids: senderHoldingCids, + TokenPoolCid: types.CONTRACT_ID(poolCid), + Context: tokenPoolChoiceContext, + }, + ExecutorInput: &sender.ExecutorInput{ + ExecutorCid: types.CONTRACT_ID(executorSendDisclosure.ContractId), + Context: executorSendDisclosure.ChoiceContext, + }, + } + + sendDisclosures := testhelpers.DeduplicateDisclosedContracts(slices.Concat( + transferFactoryDisclosures, + ccipSendDisclosure.DisclosedContracts, + ccvSendDisclosure.DisclosedContracts, + executorSendDisclosure.DisclosedContracts, + )...) + quotedFee := quoteCCIPSenderFee(t, senderParticipant, partySender, ccipSenderCid, sendArgs, sendDisclosures) + feeStr := string(quotedFee.FeeTokenAmount) + poolFeeStr := string(quotedFee.PoolFeeTokenAmount) + t.Logf("GetFee: feeTokenAmount=%s poolFeeTokenAmount=%s", feeStr, poolFeeStr) + require.NotEqual(t, "0", feeStr, "GetFee should return a positive fee") + require.NotEqual(t, "0", poolFeeStr, "GetFee should return a positive pool fee") + + // quoteCCIPSenderFee uses TRANSACTION_SHAPE_LEDGER_EFFECTS; refresh EDS disclosures so the + // follow-up Send exercises current contract witnesses (avoids LOCAL_VERDICT_INACTIVE_CONTRACTS). + ccipSendDisclosure, err = edsTesthelpers.GetCCIPSendDisclosure(t.Context(), ccipAPIClient, msg, nil, requiredCCVs) + require.NoError(t, err) + ccvAddressEDS, err = contracts.RawInstanceAddressFromString(ccipSendDisclosure.CCVs[0]) + require.NoError(t, err) + executorAddressEDS, err = contracts.RawInstanceAddressFromString(*ccipSendDisclosure.Executor) + require.NoError(t, err) + ccvSendDisclosure, err = edsTesthelpers.GetCCVSendDisclosure(t.Context(), ccvAPIClient, msg, ccvAddressEDS.InstanceAddress()) + require.NoError(t, err) + executorSendDisclosure, err = edsTesthelpers.GetExecutorSendDisclosure(t.Context(), executorAPIClient, msg, executorAddressEDS.InstanceAddress(), ccipSendDisclosure.CCVs) + require.NoError(t, err) + sendArgs.Context = ccipSendDisclosure.ChoiceContext + sendArgs.FeeTokenInput.FeeTokenConfigCid = types.CONTRACT_ID(ccipSendDisclosure.FeeTokenConfigCid) + sendArgs.CcvSendInputs[0].CcvCid = types.CONTRACT_ID(ccvSendDisclosure.ContractId) + sendArgs.CcvSendInputs[0].Context = ccvSendDisclosure.ChoiceContext + sendArgs.ExecutorInput.ExecutorCid = types.CONTRACT_ID(executorSendDisclosure.ContractId) + sendArgs.ExecutorInput.Context = executorSendDisclosure.ChoiceContext + sendDisclosures = testhelpers.DeduplicateDisclosedContracts(slices.Concat( + transferFactoryDisclosures, + ccipSendDisclosure.DisclosedContracts, + ccvSendDisclosure.DisclosedContracts, + executorSendDisclosure.DisclosedContracts, + )...) + + senderBalanceBefore, err := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &nativeInstrumentId) + require.NoError(t, err) + senderLinkBalanceBefore, err := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &linkInstrumentId) + require.NoError(t, err) + + // CCIPSender.Send: PrepareSend + CCV tickets + Send in one transaction. + res, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: contracts.IdentifierFromBinding(sender.CCIPSender{}), + ContractId: ccipSenderCid, + Choice: "Send", + ChoiceArgument: damlledger.MapToValue(sendArgs), + }}, + }}, + ActAs: []string{partySender}, + DisclosedContracts: sendDisclosures, + }, + }) + require.NoError(t, err) + + // Extract CCIPMessageSent event to verify success + eventTemplateId := events.CCIPMessageSent{}.GetTemplateID() + var ccipMessageSent *events.CCIPMessageSent + for _, event := range res.GetTransaction().GetEvents() { + if createdEvent := event.GetCreated(); createdEvent != nil { + if templateId := createdEvent.GetTemplateId(); templateId != nil { + gotTemplateId := fmt.Sprintf("#%s:%s:%s", createdEvent.GetPackageName(), templateId.GetModuleName(), templateId.GetEntityName()) + if gotTemplateId == eventTemplateId { + ccipMessageSent, err = bindings.UnmarshalCreatedEvent[events.CCIPMessageSent](createdEvent) + require.NoError(t, err) + break + } + } + } + } + require.NotNil(t, ccipMessageSent, "CCIPMessageSent event not found") + require.NotEmpty(t, ccipMessageSent.Event.MessageId, "CCIPMessageSent should be created") + require.NotEmpty(t, ccipMessageSent.Event.EncodedMessage, "CCIPMessageSent should contain encoded message") + require.Equal(t, nativeInstrumentId, ccipMessageSent.Event.FeeToken, "CCIPMessageSent should contain feeToken InstrumentId") + + // Verify pool feeBps haircut: 1e10 LINK, minus 5% feeBps => 0.95e10 bridged. + require.Equal(t, int64(9500000000), extractTokenTransferAmountFromEncodedMessageHex(t, ccipMessageSent.Event.EncodedMessage), "encoded token amount should be net after 5% feeBps") + + wantAmount, ok := new(big.Rat).SetString(tokenTransferAmountDecimal) + require.True(t, ok, "token transfer amount should parse as a decimal value") + gotAmount, ok := new(big.Rat).SetString(string(ccipMessageSent.Event.TokenAmountBeforeTokenPoolFees)) + require.True(t, ok, "token amount before token pool fees should parse as a decimal value") + require.Equal(t, 0, wantAmount.Cmp(gotAmount), "token amount before token pool fees should match original transfer amount") + + quotedFeeAmount, ok := new(big.Rat).SetString(feeStr) + require.True(t, ok, "quoted fee should parse as a decimal value") + poolFeeAmountRat, ok := new(big.Rat).SetString(poolFeeStr) + require.True(t, ok, "pool fee should parse as a decimal value") + tokenTransferAmountRat, ok := new(big.Rat).SetString(tokenTransferAmountDecimal) + require.True(t, ok, "token transfer amount should parse as a decimal value") + // When poolOwner == sender, pool feeBps is retained by the sender's pool; only net LINK leaves total balance. + netLinkTransferAmountRat := new(big.Rat).Sub(tokenTransferAmountRat, poolFeeAmountRat) + + require.Eventually(t, func() bool { + senderBalanceAfter, balanceErr := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &nativeInstrumentId) + if balanceErr != nil { + return false + } + senderDelta := new(big.Rat).Sub(senderBalanceBefore, senderBalanceAfter) + + return senderDelta.Cmp(quotedFeeAmount) == 0 + }, 15*time.Second, 200*time.Millisecond, "sender fee-token deduction should equal GetFee feeTokenAmount") + + require.Eventually(t, func() bool { + senderLinkBalanceAfter, balanceErr := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &linkInstrumentId) + if balanceErr != nil { + return false + } + linkDelta := new(big.Rat).Sub(senderLinkBalanceBefore, senderLinkBalanceAfter) + + return linkDelta.Cmp(netLinkTransferAmountRat) == 0 + }, 15*time.Second, 200*time.Millisecond, "sender LINK deduction should equal net token transfer amount after pool feeBps") + + t.Logf("Send completed") + t.Logf(" Message ID: %s", ccipMessageSent.Event.MessageId) + t.Logf(" Original payload: %s", string(testPayload)) + t.Logf("Success") +} diff --git a/integration-tests/ccip/ccip_send_with_token_registry_pool_lnr_test.go b/integration-tests/ccip/ccip_send_with_token_registry_pool_lnr_test.go new file mode 100644 index 000000000..0d73566c8 --- /dev/null +++ b/integration-tests/ccip/ccip_send_with_token_registry_pool_lnr_test.go @@ -0,0 +1,979 @@ +package tests + +import ( + "context" + "crypto/ecdsa" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math/big" + "os" + "slices" + "strconv" + "testing" + "time" + + apiv2 "github.com/digital-asset/dazl-client/v8/go/api/com/daml/ledger/api/v2" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + devenvcommon "github.com/smartcontractkit/chainlink-ccv/build/devenv/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-deployments-framework/chain" + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + "github.com/smartcontractkit/freeport" + damlledger "github.com/smartcontractkit/go-daml/pkg/service/ledger" + "github.com/smartcontractkit/go-daml/pkg/types" + + "github.com/smartcontractkit/chainlink-canton/commonconfig" + "github.com/smartcontractkit/chainlink-canton/contracts/v2" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipcodec" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/ccipruntime" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/clientapi" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/committeeverifier" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/core" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/events" + executorBinding "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/executor" + registrylnr "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/registry/lockreleasetokenpool" + registryrl "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/registry/ratelimiter" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/ccip/sender" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/link" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_burn_mint_v1" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_holding_v1" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_metadata_v1" + "github.com/smartcontractkit/chainlink-canton/deployment/changesets" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/committee_verifier" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/executor" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/fee_quoter" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/global_config" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/offramp" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/onramp" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/per_party_router_factory" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/rmn_remote" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/ccip/token_admin_registry" + "github.com/smartcontractkit/chainlink-canton/deployment/operations/linkregistry" + "github.com/smartcontractkit/chainlink-canton/deployment/sequences" + contractops "github.com/smartcontractkit/chainlink-canton/deployment/utils/operations/contract" + "github.com/smartcontractkit/chainlink-canton/eds/config" + "github.com/smartcontractkit/chainlink-canton/eds/service" + oapiCCIP "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/ccip" + oapiCCV "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/ccv" + oapiCommon "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/common" + oapiExecutor "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/executor" + oapiGlobal "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/global" + "github.com/smartcontractkit/chainlink-canton/testhelpers" + edsTesthelpers "github.com/smartcontractkit/chainlink-canton/testhelpers/eds" + + // Import to register adapters + _ "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/adapters" +) + +// TestRegistryLockReleaseTokenPool_FullSendFlow proves that a registry-pools +// LockReleaseTokenPool deployed via the atomic Initialize choice (CreateAndExercise, +// no factory, no separate register step) is fully usable in a real CCIPSender.Send. +// +// Self-issued LINK is used instead of Amulet: LockReleaseTokenPool's ensure clause +// has no instrumentId.admin == poolOwner restriction (unlike BurnMintTokenPool), so +// unlike the production TestLnRTokenPool_FullSendFlow it *could* wrap a +// third-party-administered instrument. But TokenAdminRegistry.ProposeAdministrator +// only accepts a caller that is either the registry's ccipOwner or the instrument's +// own admin - Amulet's admin is a foreign Splice registry party we don't control, so +// Initialize's simple self-issued path (controller poolOwner, admin - a single actAs +// party) can't authorize it without a separate propose/accept step or cross-participant +// actAs. Using self-issued LINK (admin == poolOwner == partySender) sidesteps that +// entirely, exactly like the BurnMint variant. LinkRegistry implements +// Splice.Api.Token.TransferInstructionV1.TransferFactory in addition to +// BurnMintFactory, and its TransferFactory_Transfer takes the direct-transfer path +// whenever sender == receiver, so no TransferPreapproval is needed for this +// self-locking setup either. +// +// EDS's TokenPoolAPI does not know about the registry-pools package, so the +// pool-specific choice context and disclosures are hand-built here instead of +// fetched through EDS - mirroring exactly what +// eds/internal/api/tokenpool/tokenpool.go's lockReleaseTokenPoolSend computes for +// the production pool. Everything else (CCIP core deploy, lane config, EDS for the +// CCIP/CCV/Executor side, PerPartyRouter, CCIPSender, Send) is unchanged from +// TestLnRTokenPool_FullSendFlow. +// +//nolint:paralleltest // We can't run this test in parallel as that would mix up the holding calculations +func TestRegistryLockReleaseTokenPool_FullSendFlow(t *testing.T) { + env := testhelpers.NewTestEnvironment(t, testhelpers.WithNumberOfParticipants(2)) + + ccipParticipant := env.Chain.Participants[0] + senderParticipant := env.Chain.Participants[1] + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + testhelpers.ContractCleanup(t, ctx, env.Chain.Participants) + }) + + // Upload DARs + runtimeDar, err := contracts.GetDar(contracts.CCIPRuntimeV2, contracts.DevVersion) + require.NoError(t, err) + coreDar, err := contracts.GetDar(contracts.CCIPCoreV2, contracts.DevVersion) + require.NoError(t, err) + committeeVerifierDar, err := contracts.GetDar(contracts.CCIPCommitteeVerifierV2, contracts.DevVersion) + require.NoError(t, err) + ccipSenderDar, err := contracts.GetDar(contracts.CCIPSenderV2, contracts.DevVersion) + require.NoError(t, err) + ccipExecutorDar, err := contracts.GetDar(contracts.CCIPExecutorV2, contracts.DevVersion) + require.NoError(t, err) + registryTokenPoolDar, err := contracts.GetDar(contracts.CCIPRegistryLockReleaseTokenPoolV2, contracts.DevVersion) + require.NoError(t, err) + registryRateLimiterDar, err := contracts.GetDar(contracts.CCIPRegistryRateLimiterV2, contracts.DevVersion) + require.NoError(t, err) + linkTokenDar, err := contracts.GetDar(contracts.Link, contracts.DevVersion) + require.NoError(t, err) + + dars := [][]byte{runtimeDar, coreDar, committeeVerifierDar, ccipSenderDar, ccipExecutorDar, registryTokenPoolDar, registryRateLimiterDar, linkTokenDar} + packageIds, err := testhelpers.UploadDARstoMultipleParticipants(t.Context(), dars, ccipParticipant, senderParticipant) + require.NoError(t, err) + t.Logf("Uploaded DARs to all participants: %v", packageIds) + + // Allocate parties + partyCCIP := ccipParticipant.PartyID + partySender := senderParticipant.PartyID + t.Logf("Parties: CCIP=%s, Sender=%s", partyCCIP, partySender) + + // CCV Setup + ccvSignerKeys := make([]*ecdsa.PrivateKey, 0, 3) + ccvSignerPubKeys := make([]string, 0, 3) + for range 3 { + pk, err := crypto.GenerateKey() + require.NoError(t, err) + ccvSignerKeys = append(ccvSignerKeys, pk) + pubKeyHex := hex.EncodeToString(crypto.FromECDSAPub(&pk.PublicKey)) + ccvSignerPubKeys = append(ccvSignerPubKeys, pubKeyHex) + } + t.Logf("Generated %d CCV signer keys", len(ccvSignerKeys)) + + versionTag := "e9a05a20" + ccvQualifier := devenvcommon.DefaultCommitteeVerifierQualifier + remoteSelector := chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector + + // Create Scan and Registry API clients + scanProxyClient, tokenMetadataClient, transferInstructionClient, err := testhelpers.NewValidatorAPIClients(ccipParticipant) + require.NoError(t, err, "Failed to create validator API clients") + + // Setup Amulet token as fee token + registryAdmin, err := testhelpers.GetRegistryAdmin(t.Context(), tokenMetadataClient) + require.NoError(t, err, "failed to get registry admin") + + // Native is Amulet + nativeInstrumentId := splice_api_token_holding_v1.InstrumentId{ + Admin: types.PARTY(registryAdmin), + Id: types.TEXT("Amulet"), + } + // Self-issued LINK is the locked/released asset - see the doc comment above for why. + linkInstrumentId := splice_api_token_holding_v1.InstrumentId{ + Admin: types.PARTY(partySender), + Id: "ChainLink", + } + + reporter := cld_ops.NewMemoryReporter() + bundle := cld_ops.NewBundle( + t.Context, + logger.Test(t), + reporter, + ) + cldfEnv := cldf.Environment{ + Logger: logger.Test(t), + GetContext: t.Context, + DataStore: datastore.NewMemoryDataStore().Seal(), + BlockChains: chain.NewBlockChainsFromSlice([]chain.BlockChain{env.Chain}), + OperationsBundle: bundle, + } + + tokenPriceExponentUSDCents := uint64(1e6) // 6 decimals for USD cents + tokenPriceExponentUSD := 1e2 * tokenPriceExponentUSDCents // FeeQuoter usd8 scale: $1 == 1e8 + + // Deploy Chain contracts (GlobalConfig, RMNRemote, FeeQuoter, CommitteeVerifier, Executor, TAR) + out, err := changesets.DeployChainContracts{}.Apply(cldfEnv, changesets.CantonCSDeps[changesets.DeployChainContractsConfig]{ + ChainSelector: env.Chain.ChainSelector(), + Participant: 0, + Config: changesets.DeployChainContractsConfig{ + Params: sequences.DeployChainContractsParams{ + CCIPOwnerParty: partyCCIP, + RMNOwnerParty: partyCCIP, + CommitteeVerifiers: []sequences.CommitteeVerifierParams{ + { + Qualifier: ccvQualifier, + Template: committeeverifier.CommitteeVerifier{ + Owner: types.PARTY(partyCCIP), + CcipOwner: types.PARTY(partyCCIP), + VersionTag: types.TEXT(versionTag), + MessageSentObservers: nil, + StorageLocations: []types.TEXT{"ipfs://test-send"}, + StorageLocationsAdmin: types.PARTY(partyCCIP), + PendingStorageLocationsAdmin: types.PARTY(partyCCIP), + Deps: committeeverifier.CommitteeVerifierDeps{}, // Set by sequence + }, + }, + }, + Executors: []sequences.ExecutorParams{ + { + Qualifier: devenvcommon.DefaultExecutorQualifier, + Template: executorBinding.Executor{ + Owner: types.PARTY(partyCCIP), + MaxCCVsPerMsg: 10, + DynamicConfig: executorBinding.DynamicConfig{ + FeeAggregator: nil, + AllowedFinalityConfig: ccipcodec.FinalityConfig{WaitForFinality: &types.UNIT{}}, + CcvAllowlistEnabled: false, + }, + AllowedCCVs: nil, + }, + }, + }, + GlobalConfig: sequences.GlobalConfigParams{ + Template: core.GlobalConfig{ + CcipOwner: "", // Populated by the sequence + ChainSelector: types.NUMERIC(strconv.FormatUint(chainsel.CANTON_LOCALNET.Selector, 10)), + }, + }, + RMNRemote: sequences.RMNRemoteParams{ + Template: core.RMNRemote{ + CcipOwner: "", // Populated by the sequence + RmnOwner: types.PARTY(partyCCIP), + CursedSubjects: nil, + }, + }, + FeeQuoterConfig: sequences.FeeQuoterParams{ + Template: core.FeeQuoter{ + PriceUpdaters: []types.PARTY{types.PARTY(partyCCIP)}, + }, + + USDPerNative: big.NewInt(int64(1 * tokenPriceExponentUSD)), // $1 + }, + NativeInstrumentId: nativeInstrumentId, + }, + }, + }) + require.NoErrorf(t, err, "Failed to deploy CCIP contracts: %v", err) + err = out.DataStore.Merge(cldfEnv.DataStore) + require.NoError(t, err) + cldfEnv.DataStore = out.DataStore.Seal() + + t.Log("Deployed CCIP chain contracts:") + addresses := cldfEnv.DataStore.Addresses().Filter() + for i, address := range addresses { + t.Logf("Deployed Address %d: ChainSelector=%d, Type=%s, Version=%s, Address=%s, Qualifier=%s, Labels=%s\n", i, address.ChainSelector, address.Type, address.Version, address.Address, address.Qualifier, address.Labels.String()) + } + + // Resolve contracts + globalConfigRef, globalConfigAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), global_config.ContractType, global_config.Version, "") + require.NoError(t, err, "failed to get GlobalConfig address") + _, feeQuoterAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), fee_quoter.ContractType, fee_quoter.Version, "") + require.NoError(t, err, "failed to get FeeQuoter address") + _, onRampAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), onramp.ContractType, onramp.Version, "") + require.NoError(t, err, "failed to get OnRamp address") + _, offRampAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), offramp.ContractType, offramp.Version, "") + require.NoError(t, err, "failed to get OffRamp address") + committeeVerifierRef, committeeVerifierAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), committee_verifier.ContractType, committee_verifier.Version, ccvQualifier) + require.NoError(t, err, "failed to get CommitteeVerifier address") + _, tokenAdminRegistryAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), token_admin_registry.ContractType, token_admin_registry.Version, "") + require.NoError(t, err, "failed to get TokenAdminRegistry address") + _, rmnRemoteAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), rmn_remote.ContractType, rmn_remote.Version, "") + require.NoError(t, err, "failed to get RMNRemote address") + _, perPartyRouterFactoryAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), per_party_router_factory.ContractType, per_party_router_factory.Version, "") + require.NoError(t, err, "failed to get PerPartyRouterFactory address") + executorRef, executorAddress, err := testhelpers.ResolveAddressFromDatastore(cldfEnv.DataStore, env.Chain.ChainSelector(), executor.ContractType, executor.Version, devenvcommon.DefaultExecutorQualifier) + require.NoError(t, err, "failed to get Executor address") + + // Deploy and configure lane for outbound sends + + // 8 cents outgoing CCV verification fee + ccvFeeUSDCents := 7 + + feeQuoterDestChainConfig := lanes.FeeQuoterDestChainConfig{ + IsEnabled: true, + MaxDataBytes: 30_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + ChainFamilySelector: binary.BigEndian.Uint32([]byte{0x28, 0x12, 0xd5, 0x2c}), + DefaultTokenFeeUSDCents: 10, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + NetworkFeeUSDCents: 25, + V2Params: &lanes.FeeQuoterV2Params{ + LinkFeeMultiplierPercent: 100, // Not used when paying in native + USDPerUnitGas: big.NewInt(38), + }, + } + + deployLaneLegReport, err := cld_ops.ExecuteSequence(cldfEnv.OperationsBundle, sequences.ConfigureLaneLegAsSourceWithInput, cldfEnv.BlockChains, sequences.ConfigureLaneLegInput{ + DataStore: cldfEnv.DataStore, + Lane: lanes.UpdateLanesInput{ + Source: &lanes.ChainDefinition{ + Selector: env.Chain.ChainSelector(), + CommitteeVerifiers: []lanes.CommitteeVerifierConfig[datastore.AddressRef]{ + { + CommitteeVerifier: []datastore.AddressRef{committeeVerifierRef}, + RemoteChains: map[uint64]lanes.CommitteeVerifierRemoteChainConfig{ + remoteSelector: { + AllowlistEnabled: false, + AddedAllowlistedSenders: nil, + RemovedAllowlistedSenders: nil, + FeeUSDCents: uint16(ccvFeeUSDCents), + GasForVerification: 50_000, + PayloadSizeBytes: 6*64 + 2*32, + SignatureConfig: lanes.CommitteeVerifierSignatureQuorumConfig{ + Signers: ccvSignerPubKeys, + Threshold: 2, + }, + }, + }, + }, + }, + LaneMandatedOutboundCCVs: []datastore.AddressRef{committeeVerifierRef}, + DefaultOutboundCCVs: nil, + CantonLaneConfig: &lanes.CantonLaneConfig{ + GlobalConfig: globalConfigRef, + }, + DefaultExecutor: executorRef, + FeeQuoter: feeQuoterAddress.InstanceAddress().Bytes(), + OnRamp: onRampAddress.InstanceAddress().Bytes(), + OffRamp: offRampAddress.InstanceAddress().Bytes(), + }, + Dest: &lanes.ChainDefinition{ + Selector: remoteSelector, + AddressBytesLength: 20, + FeeQuoterDestChainConfig: feeQuoterDestChainConfig, + ExecutorDestChainConfig: lanes.ExecutorDestChainConfig{ + USDCentsFee: 50, + Enabled: true, + }, + OnRamp: hexutil.MustDecode("0xf6eced5e96fff2de4f0ecd722beb57556fc443fd"), + OffRamp: hexutil.MustDecode("0xd8c9ec8cad3fb34aeca3ddbebfabe9f28a9bfaed"), + Router: hexutil.MustDecode("0xe3ddcb2fde5d27a33c450fddc54a3f9bb2ecaa9f"), + }, + IsDisabled: false, + TestRouter: false, + ExtraConfigs: lanes.ExtraConfigs{}, + }, + }) + require.NoErrorf(t, err, "Failed to configure chain for lanes") + runningDs := datastore.NewMemoryDataStore() + for _, address := range deployLaneLegReport.Output.Addresses { + err = runningDs.Addresses().Add(address) + require.NoErrorf(t, err, "Failed to add address %s", address.Address) + } + err = runningDs.Merge(cldfEnv.DataStore) + require.NoErrorf(t, err, "Failed to merge datastore") + cldfEnv.DataStore = runningDs.Seal() + t.Log("Configured chain for lanes") + + // Deploy Link Token - self-issued, doubles as the pool's TransferFactory + linkTokenOut, err := cld_ops.ExecuteOperation(bundle, linkregistry.Deploy, env.Chain, contractops.DeployInput[link.LinkRegistry]{ + ParticipantIndex: 1, + OwnerParty: types.PARTY(partySender), + Template: link.LinkRegistry{ + RegistryAdmin: types.PARTY(partySender), + RegistryInstrumentId: linkInstrumentId, + RegistryMeta: splice_api_token_metadata_v1.Metadata{}, + }, + }) + require.NoError(t, err) + linkRegistryAddress, err := contracts.RawInstanceAddressFromString(linkTokenOut.Output.Labels.List()[0]) + require.NoError(t, err) + linkRegistryCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, contracts.TemplateIDFromBinding(link.LinkRegistry{}).String(), linkRegistryAddress.InstanceAddress()) + require.NoError(t, err) + t.Logf("LinkRegistry ContractId: %v", linkRegistryCid) + + // ------------------------------------------------------------------------ + // Deploy the registry-pools LockReleaseTokenPool via a single atomic + // CreateAndExercise + Initialize. Self-issued LINK (admin == poolOwner == + // partySender) keeps Initialize's self-issued path valid - see the doc + // comment at the top of this test for why Amulet can't be used here. + // ------------------------------------------------------------------------ + poolInstanceID := "test-registry-pool-lnr-send" + + tokenTransferFeeUSDCents := 10 + tokenTransferFeeBps := 500 // 5% + + remotePoolAddress := hexutil.MustDecode("0x7e3febbdaf80e7e96c1ae107508ec3fafc36d7f3") + remoteTokenAddress := hexutil.MustDecode("0xacdafefb07bff5b120b7afa6ea777cf7eabacc0d") + + pool := registrylnr.LockReleaseTokenPool{ + InstanceId: types.TEXT(poolInstanceID), + PoolOwner: types.PARTY(partySender), + CcipOwner: types.PARTY(partyCCIP), + InstrumentId: linkInstrumentId, + Decimals: 10, + Observers: []types.PARTY{types.PARTY(partyCCIP)}, + TokenTransferFeeConfigs: map[types.NUMERIC]registrylnr.TokenTransferFeeConfig2{ + types.NUMERIC(strconv.FormatUint(remoteSelector, 10)): { + IsEnabled: true, + DestGasOverhead: 25_000, + DestBytesOverhead: 32, + FeeUSDCents: types.NUMERIC(strconv.Itoa(tokenTransferFeeUSDCents)), + FeeBps: types.NUMERIC(strconv.Itoa(tokenTransferFeeBps)), + }, + }, + PoolReceiveContext: splice_api_token_metadata_v1.ChoiceContext{}, + TransferTimeout: registrylnr.TransferTimeout{ + RelativeHours: func(v types.INT64) *types.INT64 { return &v }(24), + }, + Deps: registrylnr.LockReleaseTokenPoolDeps{ + TokenAdminRegistry: tokenAdminRegistryAddress.Binding(), + RmnRemote: rmnRemoteAddress.Binding(), + FeeQuoter: feeQuoterAddress.Binding(), + }, + } + + lane := registrylnr.LaneDeploySpec{ + RemoteChainSelector: types.NUMERIC(strconv.FormatUint(remoteSelector, 10)), + RemotePools: []types.TEXT{types.TEXT(hex.EncodeToString(remotePoolAddress))}, + RemoteTokenAddress: types.TEXT(hex.EncodeToString(remoteTokenAddress)), + InboundCCVs: nil, + OutboundCCVs: nil, + FinalityConfig: ccipcodec.FinalityConfig{WaitForFinality: &types.UNIT{}}, + Inbound: registrylnr.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-lnr-send-in", + IsEnabled: true, + Capacity: "10000000000", + Rate: "10000000000", + }, + Outbound: registrylnr.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-lnr-send-out", + IsEnabled: true, + Capacity: "10000000000", + Rate: "10000000000", + }, + InboundCustomFinality: registrylnr.RateLimiterDeploySpec{ + InstanceId: "test-registry-pool-lnr-send-in-custom", + IsEnabled: false, + Capacity: "0", + Rate: "0", + }, + } + + // TAR lives on ccipParticipant (ccipOwner=partyCCIP), not senderParticipant. + // Initialize's authorization only needs partySender's signature (self-issued + // isAdmin path), but the submitting participant (senderParticipant) still needs + // to *see* the TAR contract to build the transaction - that's a cross-participant + // visibility gap, separate from authorization, so it must be disclosed explicitly. + tarCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), ccipParticipant.LedgerServices.State, []string{partyCCIP}, contracts.TemplateIDFromBinding(core.TokenAdminRegistry{}).String(), tokenAdminRegistryAddress.InstanceAddress()) + require.NoError(t, err, "find TAR contract id from ccip participant") + tarDisclosure, err := testhelpers.GetDisclosedContractById(t.Context(), ccipParticipant, tarCid) + require.NoError(t, err, "disclose TAR contract to sender's participant") + + initArgs := registrylnr.Initialize{ + TokenAdminRegistryCid: types.CONTRACT_ID(tarCid), + ExistingTokenConfigCid: nil, + Admin: types.PARTY(partySender), + Lanes: []registrylnr.LaneDeploySpec{lane}, + } + _, err = submitCreateAndExercise(t.Context(), senderParticipant, partySender, pool, "Initialize", initArgs, tarDisclosure) + require.NoError(t, err, "create registry pool and exercise Initialize atomically") + + tokenPoolAddress := contracts.NewRawInstanceAddress(contracts.InstanceID(poolInstanceID), types.PARTY(partySender)) + poolCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, pool.GetTemplateID(), tokenPoolAddress.InstanceAddress()) + require.NoError(t, err, "find registry pool contract id") + outboundRateLimiterCid, err := contractops.FindActiveContractIDByInstanceAddress(t.Context(), senderParticipant.LedgerServices.State, []string{partySender}, registryrl.RateLimiter{}.GetTemplateID(), contracts.NewRawInstanceAddress(contracts.InstanceID(lane.Outbound.InstanceId), types.PARTY(partySender)).InstanceAddress()) + require.NoError(t, err, "find outbound rate limiter contract id") + + // Run EDS for the CCIP-global/CCV/Executor side only. There's no pool-side EDS + // instance: TokenPoolAPI doesn't know the registry-pools template, and this test + // doesn't use TokenStandardAPI, so a pool-only instance would serve nothing. + edsCCIPPort := freeport.GetOne(t) + + ccipEDSToken, _ := ccipParticipant.TokenSource.Token() + edsCCIPCtx, edsCCIPCancel := context.WithCancel(t.Context()) + t.Cleanup(edsCCIPCancel) + go func() { + log.Info().Int("port", edsCCIPPort).Msg("Running EDS-ccip...") + err := service.RunEDS(edsCCIPCtx, log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.TraceLevel), &config.Config{ + ChainSelector: strconv.FormatUint(env.Chain.ChainSelector(), 10), + Server: config.ServerConfig{ + Host: "0.0.0.0", + Port: uint16(edsCCIPPort), + }, + Node: config.NodeConfig{ + URL: ccipParticipant.Endpoints.GRPCLedgerAPIURL, + AuthConfig: commonconfig.AuthConfig{ + Type: commonconfig.AuthTypeInsecureStatic, + UserID: ccipParticipant.UserID, + JWT: ccipEDSToken.AccessToken, + }, + MaxRetries: 0, + }, + CCIPAPIConfig: config.CCIPAPIConfig{ + Enabled: true, + PerPartyRouterFactory: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: perPartyRouterFactoryAddress.InstanceAddress(), + }, + OnRamp: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: onRampAddress.InstanceAddress(), + }, + OffRamp: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: offRampAddress.InstanceAddress(), + }, + GlobalConfig: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: globalConfigAddress.InstanceAddress(), + }, + TokenAdminRegistry: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: tokenAdminRegistryAddress.InstanceAddress(), + }, + RMNRemote: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: rmnRemoteAddress.InstanceAddress(), + }, + FeeQuoter: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: feeQuoterAddress.InstanceAddress(), + }, + }, + CCVAPIConfig: config.CCVAPIConfig{ + Enabled: true, + CCVs: []config.CCV{ + { + ContractIdentifier: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: committeeVerifierAddress.InstanceAddress(), + }, + }, + }, + }, + ExecutorAPIConfig: config.ExecutorAPIConfig{ + Enabled: true, + Executors: []config.Executor{ + { + ContractIdentifier: config.ContractIdentifier{ + PartyID: partyCCIP, + InstanceAddress: executorAddress.InstanceAddress(), + }, + }, + }, + }, + TokenPoolAPIConfig: config.TokenPoolAPIConfig{ + Enabled: false, + }, + TokenStandardAPIConfig: config.TokenStandardAPIConfig{ + Enabled: false, + }, + }) + log.Info().Err(err).Msg("EDS-ccip terminated") + if !errors.Is(err, context.Canceled) { + t.Errorf("EDS-ccip server exited with error: %v", err) + } + }() + + // Create EDS clients — CCIP global/CCV/executor on P0. + globalAPIClient, err := oapiGlobal.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create GlobalConfig API client") + ccipAPIClient, err := oapiCCIP.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create CCIP API client") + ccvAPIClient, err := oapiCCV.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create CCV API client") + executorAPIClient, err := oapiExecutor.NewClientWithResponses(fmt.Sprintf("http://localhost:%d", edsCCIPPort)) + require.NoError(t, err, "Failed to create Executor API client") + + waitForEDSListening(t, edsCCIPPort) + + // Create PerPartyRouter for sender via EDS + perPartyRouterFactoryDisclosure, err := edsTesthelpers.GetPerPartyRouterFactoryDisclosure(t.Context(), ccipAPIClient, partySender) + require.NoError(t, err) + t.Logf("disclosed contracts: %v", perPartyRouterFactoryDisclosure.DisclosedContracts) + t.Logf("per party router factory cid: %s", perPartyRouterFactoryDisclosure.ContractId) + + res, err := senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: contracts.IdentifierFromBinding(ccipruntime.PerPartyRouterFactory{}), + ContractId: perPartyRouterFactoryDisclosure.ContractId, + Choice: "CreateRouter", + ChoiceArgument: &apiv2.Value{Sum: &apiv2.Value_Record{Record: &apiv2.Record{Fields: []*apiv2.RecordField{ + {Label: "partyOwner", Value: &apiv2.Value{Sum: &apiv2.Value_Party{Party: partySender}}}, + {Label: "instanceId", Value: &apiv2.Value{Sum: &apiv2.Value_Text{Text: "test-router-receiver"}}}, + {Label: "feeTransferLifetime", Value: &apiv2.Value{Sum: &apiv2.Value_Optional{Optional: &apiv2.Optional{Value: nil}}}}, + }}}}, + }}, + }}, + ActAs: []string{partySender}, + DisclosedContracts: perPartyRouterFactoryDisclosure.DisclosedContracts, + }, + }) + require.NoError(t, err) + routerCid := "" + for _, event := range res.GetTransaction().GetEvents() { + if e, ok := event.GetEvent().(*apiv2.Event_Created); ok { + if e.Created.GetTemplateId().GetEntityName() == "PerPartyRouter" { + routerCid = e.Created.ContractId + break + } + } + } + require.NotEmpty(t, routerCid) + t.Logf("Created PerPartyRouter for sender: %s", routerCid) + + // Build test payload + testPayload := []byte("Hello CCIP - this is a test send message!") + testPayloadHex := hex.EncodeToString(testPayload) + t.Logf("Test payload: %s", string(testPayload)) + + // Deploy CCIPSender for sender + res, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Create{Create: &apiv2.CreateCommand{ + TemplateId: contracts.IdentifierFromBinding(sender.CCIPSender{}), + CreateArguments: &apiv2.Record{Fields: []*apiv2.RecordField{ + {Label: "instanceId", Value: &apiv2.Value{Sum: &apiv2.Value_Text{Text: "test-ccipsender"}}}, + {Label: "owner", Value: &apiv2.Value{Sum: &apiv2.Value_Party{Party: partySender}}}, + }}, + }}, + }}, + ActAs: []string{partySender}, + }, + }) + require.NoError(t, err) + ccipSenderCid := extractCreatedContractId(res) + t.Logf("Deployed CCIPSender: %s", ccipSenderCid) + + // Prepare EVM receiver address + receiver := hexutil.MustDecode("0xcf8def9adfe3dd90b3dffe42c8eabbf7cd4ee6ca") + receiverHex := hex.EncodeToString(receiver) + + // Fund separate holdings for fee payment and token transfer input. + feeTokenHoldingCid, err := testhelpers.MintAMT(t.Context(), senderParticipant, tokenMetadataClient, transferInstructionClient, scanProxyClient, partySender, "1000.0") + require.NoError(t, err, "failed to mint Amulet tokens to sender") + t.Logf("Minted fee-token Amulet holding to sender, Holding CID: %s", feeTokenHoldingCid) + + // Mint LINK to sender - this is what gets locked into the pool. + _, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: &apiv2.Identifier{PackageId: "#splice-api-token-burn-mint-v1", ModuleName: "Splice.Api.Token.BurnMintV1", EntityName: "BurnMintFactory"}, + ContractId: linkRegistryCid, + Choice: "BurnMintFactory_BurnMint", + ChoiceArgument: damlledger.MapToValue(splice_api_token_burn_mint_v1.BurnMintFactoryBurnMint{ + ExpectedAdmin: types.PARTY(partySender), + InstrumentId: linkInstrumentId, + InputHoldingCids: nil, + Outputs: []splice_api_token_burn_mint_v1.BurnMintOutput{ + { + Owner: types.PARTY(partySender), + Amount: "50.0", + Context: splice_api_token_metadata_v1.ChoiceContext{}, + }, + }, + ExtraArgs: splice_api_token_metadata_v1.ExtraArgs{}, + }), + }}, + }}, + ActAs: []string{partySender}, + }, + }) + require.NoError(t, err) + var senderLinkHoldings []*apiv2.ActiveContract + require.Eventually(t, func() bool { + var listErr error + senderLinkHoldings, listErr = testhelpers.ListActiveContractsByTemplateId(t.Context(), senderParticipant, contracts.IdentifierFromBinding(link.LinkHolding{})) + if listErr != nil { + return false + } + + return len(senderLinkHoldings) > 0 + }, 15*time.Second, 200*time.Millisecond, "expected at least one active LinkHolding after minting") + t.Logf("Minted 50 LINK to sender. Current holdings: %v", len(senderLinkHoldings)) + tokenTransferHoldingCids := make([]types.CONTRACT_ID, len(senderLinkHoldings)) + for i, holding := range senderLinkHoldings { + t.Logf("- Holding CID: %s", holding.GetCreatedEvent().GetContractId()) + tokenTransferHoldingCids[i] = types.CONTRACT_ID(holding.GetCreatedEvent().GetContractId()) + } + + // Get transfer factory for Amulet tokens (sender to CCIP owner) - fee-token leg only. + transferFactoryCid, transferFactoryDisclosures, choiceContextRaw, err := testhelpers.GetTransferFactory(t.Context(), transferInstructionClient, registryAdmin, partySender, partyCCIP) + require.NoError(t, err, "failed to get transfer factory") + require.NotNil(t, choiceContextRaw, "choiceContext should not be nil") + + choiceContext, err := testhelpers.ChoiceContextFromData(choiceContextRaw) + require.NoError(t, err, "failed to parse choice context") + transferFactoryContextValues := testhelpers.ExtractChoiceContextValues(choiceContext) + + const tokenTransferAmountDecimal = "1.0" + + executorRawOrHashedAddress := oapiCommon.RawOrHashedAddress{} + _ = executorRawOrHashedAddress.FromRawInstanceAddress(executorAddress.String()) + msg := oapiCommon.Message{ + DestinationChainSelector: strconv.FormatUint(remoteSelector, 10), + Executor: struct { + Address *oapiCommon.RawOrHashedAddress `json:"address,omitempty"` + Type oapiCommon.MessageExecutorType `json:"type"` + }{ + Type: oapiCommon.WithAddress, + Address: &executorRawOrHashedAddress, + }, + FeeToken: oapiCommon.InstrumentId{ + Admin: oapiCommon.PartyId(nativeInstrumentId.Admin), + Id: string(nativeInstrumentId.Id), + }, + Payload: testPayloadHex, + Sender: partySender, + Receiver: receiverHex, + TokenTransfer: &oapiCommon.TokenTransfer{ + Amount: tokenTransferAmountDecimal, + Token: oapiCommon.InstrumentId{ + Admin: oapiCommon.PartyId(linkInstrumentId.Admin), + Id: string(linkInstrumentId.Id), + }, + }, + } + + // Hand-built equivalent of edsTesthelpers.GetTokenPoolSendDisclosure, since EDS's + // TokenPoolAPI is hardcoded to the production lockreleasetokenpool package and + // does not know about the registry-pools template. Mirrors what + // eds/internal/api/tokenpool/tokenpool.go's lockReleaseTokenPoolSend computes: + // the outbound rate limiter under "rate-limiter", the pool's own existing + // holdings under "token-pool-holdings" (empty - nothing locked yet), and + // LinkRegistry (which also implements TransferFactory) under "transfer-factory". + // No transfer-preapproval entry is needed: sender == receiver == partySender + // takes LinkRegistry's direct-transfer path, which never looks at preapproval. + tokenPoolChoiceContext := splice_api_token_metadata_v1.ChoiceContext{ + Values: map[string]splice_api_token_metadata_v1.AnyValue{ + string(registryrl.RateLimiterContextKey): {AVContractId: func(v types.CONTRACT_ID) *types.CONTRACT_ID { return &v }(types.CONTRACT_ID(outboundRateLimiterCid))}, + string(registrylnr.TokenPoolHoldingsContextKey): {AVList: &[]splice_api_token_metadata_v1.AnyValue{}}, + string(registrylnr.TransferFactoryContextKey): {AVContractId: func(v types.CONTRACT_ID) *types.CONTRACT_ID { return &v }(types.CONTRACT_ID(linkRegistryCid))}, + }, + } + var requiredCCVs []string + + ccipSendDisclosure, err := edsTesthelpers.GetCCIPSendDisclosure(t.Context(), ccipAPIClient, msg, nil, requiredCCVs) + require.NoError(t, err) + ccvAddressEDS, err := contracts.RawInstanceAddressFromString(ccipSendDisclosure.CCVs[0]) + require.NoError(t, err) + executorAddressEDS, err := contracts.RawInstanceAddressFromString(*ccipSendDisclosure.Executor) + require.NoError(t, err) + ccvSendDisclosure, err := edsTesthelpers.GetCCVSendDisclosure(t.Context(), ccvAPIClient, msg, ccvAddressEDS.InstanceAddress()) + require.NoError(t, err) + executorSendDisclosure, err := edsTesthelpers.GetExecutorSendDisclosure(t.Context(), executorAPIClient, msg, executorAddressEDS.InstanceAddress(), ccipSendDisclosure.CCVs) + require.NoError(t, err) + + // Sanity check - CCIP global batch covers P0 contracts only. + disclosedContracts, err := edsTesthelpers.GetGlobalDisclosureBatch(t.Context(), globalAPIClient, []contracts.InstanceAddress{ + perPartyRouterFactoryAddress.InstanceAddress(), + globalConfigAddress.InstanceAddress(), + feeQuoterAddress.InstanceAddress(), + onRampAddress.InstanceAddress(), + offRampAddress.InstanceAddress(), + tokenAdminRegistryAddress.InstanceAddress(), + rmnRemoteAddress.InstanceAddress(), + committeeVerifierAddress.InstanceAddress(), + executorAddress.InstanceAddress(), + }) + require.NoError(t, err) + require.Lenf(t, disclosedContracts, 9, "expected to retrieve disclosures for all CCIP global addresses") + + // Pool takes a token amount cut at LockOrBurn: feeBps = 500 (5%). + sendArgs := sender.Send{ + Context: ccipSendDisclosure.ChoiceContext, + RouterCid: types.CONTRACT_ID(routerCid), + DestinationChainSelector: types.NUMERIC(strconv.FormatUint(remoteSelector, 10)), + Message: clientapi.Canton2AnyMessage{ + Receiver: types.TEXT(receiverHex), + Payload: types.TEXT(testPayloadHex), + TokenTransfer: &clientapi.TokenTransfer{ + Token: linkInstrumentId, + Amount: types.NUMERIC(tokenTransferAmountDecimal), + }, + FeeToken: nativeInstrumentId, + ExtraArgs: clientapi.ExtraArgs{ + V3: &clientapi.GenericExtraArgsV3{ + GasLimit: 0, + Ccvs: []clientapi.CCVExtraArg{ + { + CcvAddress: committeeVerifierAddress.Binding(), + CcvArgs: types.TEXT(""), + }, + }, + Executor: clientapi.ExecutorExtraArg{ + ExecutorWithAddress: &clientapi.ExecutorWithAddress{ + ExecutorAddress: executorAddress.Binding(), + ExecutorArgs: types.TEXT(""), + }, + }, + TokenReceiver: types.TEXT(""), + TokenArgs: types.TEXT(""), + }, + }, + }, + FeeTokenInput: sender.FeeTokenInput{ + SenderInputCids: []types.CONTRACT_ID{types.CONTRACT_ID(feeTokenHoldingCid)}, + FeeTokenConfigCid: types.CONTRACT_ID(ccipSendDisclosure.FeeTokenConfigCid), + FeeTokenTransferFactory: types.CONTRACT_ID(transferFactoryCid), + FeeTokenExtraArgs: splice_api_token_metadata_v1.ExtraArgs{ + Context: splice_api_token_metadata_v1.ChoiceContext{ + Values: transferFactoryContextValues, + }, + Meta: splice_api_token_metadata_v1.Metadata{ + Values: map[string]types.TEXT{}, + }, + }, + }, + CcvSendInputs: []sender.CCVSendInput{ + { + CcvAddress: ccvSendDisclosure.Address.Binding(), + CcvCid: types.CONTRACT_ID(ccvSendDisclosure.ContractId), + Context: ccvSendDisclosure.ChoiceContext, + }, + }, + TokenTransferInput: &sender.TokenTransferInput{ + SenderInputCids: tokenTransferHoldingCids, + TokenPoolCid: types.CONTRACT_ID(poolCid), + Context: tokenPoolChoiceContext, + }, + ExecutorInput: &sender.ExecutorInput{ + ExecutorCid: types.CONTRACT_ID(executorSendDisclosure.ContractId), + Context: executorSendDisclosure.ChoiceContext, + }, + } + + sendDisclosures := testhelpers.DeduplicateDisclosedContracts(slices.Concat( + transferFactoryDisclosures, + ccipSendDisclosure.DisclosedContracts, + ccvSendDisclosure.DisclosedContracts, + executorSendDisclosure.DisclosedContracts, + )...) + quotedFee := quoteCCIPSenderFee(t, senderParticipant, partySender, ccipSenderCid, sendArgs, sendDisclosures) + feeStr := string(quotedFee.FeeTokenAmount) + poolFeeStr := string(quotedFee.PoolFeeTokenAmount) + t.Logf("GetFee: feeTokenAmount=%s poolFeeTokenAmount=%s", feeStr, poolFeeStr) + require.NotEqual(t, "0", feeStr, "GetFee should return a positive fee") + require.NotEqual(t, "0", poolFeeStr, "GetFee should return a positive pool fee") + + // quoteCCIPSenderFee uses TRANSACTION_SHAPE_LEDGER_EFFECTS; refresh EDS disclosures so the + // follow-up Send exercises current contract witnesses (avoids LOCAL_VERDICT_INACTIVE_CONTRACTS). + ccipSendDisclosure, err = edsTesthelpers.GetCCIPSendDisclosure(t.Context(), ccipAPIClient, msg, nil, requiredCCVs) + require.NoError(t, err) + ccvAddressEDS, err = contracts.RawInstanceAddressFromString(ccipSendDisclosure.CCVs[0]) + require.NoError(t, err) + executorAddressEDS, err = contracts.RawInstanceAddressFromString(*ccipSendDisclosure.Executor) + require.NoError(t, err) + ccvSendDisclosure, err = edsTesthelpers.GetCCVSendDisclosure(t.Context(), ccvAPIClient, msg, ccvAddressEDS.InstanceAddress()) + require.NoError(t, err) + executorSendDisclosure, err = edsTesthelpers.GetExecutorSendDisclosure(t.Context(), executorAPIClient, msg, executorAddressEDS.InstanceAddress(), ccipSendDisclosure.CCVs) + require.NoError(t, err) + sendArgs.Context = ccipSendDisclosure.ChoiceContext + sendArgs.FeeTokenInput.FeeTokenConfigCid = types.CONTRACT_ID(ccipSendDisclosure.FeeTokenConfigCid) + sendArgs.CcvSendInputs[0].CcvCid = types.CONTRACT_ID(ccvSendDisclosure.ContractId) + sendArgs.CcvSendInputs[0].Context = ccvSendDisclosure.ChoiceContext + sendArgs.ExecutorInput.ExecutorCid = types.CONTRACT_ID(executorSendDisclosure.ContractId) + sendArgs.ExecutorInput.Context = executorSendDisclosure.ChoiceContext + sendDisclosures = testhelpers.DeduplicateDisclosedContracts(slices.Concat( + transferFactoryDisclosures, + ccipSendDisclosure.DisclosedContracts, + ccvSendDisclosure.DisclosedContracts, + executorSendDisclosure.DisclosedContracts, + )...) + + senderBalanceBefore, err := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &nativeInstrumentId) + require.NoError(t, err) + senderLinkBalanceBefore, err := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &linkInstrumentId) + require.NoError(t, err) + + // CCIPSender.Send: PrepareSend + CCV tickets + Send in one transaction. + res, err = senderParticipant.LedgerServices.Command.SubmitAndWaitForTransaction(t.Context(), &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_Exercise{Exercise: &apiv2.ExerciseCommand{ + TemplateId: contracts.IdentifierFromBinding(sender.CCIPSender{}), + ContractId: ccipSenderCid, + Choice: "Send", + ChoiceArgument: damlledger.MapToValue(sendArgs), + }}, + }}, + ActAs: []string{partySender}, + DisclosedContracts: sendDisclosures, + }, + }) + require.NoError(t, err) + + // Extract CCIPMessageSent event to verify success + eventTemplateId := events.CCIPMessageSent{}.GetTemplateID() + var ccipMessageSent *events.CCIPMessageSent + for _, event := range res.GetTransaction().GetEvents() { + if createdEvent := event.GetCreated(); createdEvent != nil { + if templateId := createdEvent.GetTemplateId(); templateId != nil { + gotTemplateId := fmt.Sprintf("#%s:%s:%s", createdEvent.GetPackageName(), templateId.GetModuleName(), templateId.GetEntityName()) + if gotTemplateId == eventTemplateId { + ccipMessageSent, err = bindings.UnmarshalCreatedEvent[events.CCIPMessageSent](createdEvent) + require.NoError(t, err) + break + } + } + } + } + require.NotNil(t, ccipMessageSent, "CCIPMessageSent event not found") + require.NotEmpty(t, ccipMessageSent.Event.MessageId, "CCIPMessageSent should be created") + require.NotEmpty(t, ccipMessageSent.Event.EncodedMessage, "CCIPMessageSent should contain encoded message") + require.Equal(t, nativeInstrumentId, ccipMessageSent.Event.FeeToken, "CCIPMessageSent should contain feeToken InstrumentId") + + // Verify pool feeBps haircut: 1e10 LINK, minus 5% feeBps => 0.95e10 bridged. + require.Equal(t, int64(9500000000), extractTokenTransferAmountFromEncodedMessageHex(t, ccipMessageSent.Event.EncodedMessage), "encoded token amount should be net after 5% feeBps") + + wantAmount, ok := new(big.Rat).SetString(tokenTransferAmountDecimal) + require.True(t, ok, "token transfer amount should parse as a decimal value") + gotAmount, ok := new(big.Rat).SetString(string(ccipMessageSent.Event.TokenAmountBeforeTokenPoolFees)) + require.True(t, ok, "token amount before token pool fees should parse as a decimal value") + require.Equal(t, 0, wantAmount.Cmp(gotAmount), "token amount before token pool fees should match original transfer amount") + + quotedFeeAmount, ok := new(big.Rat).SetString(feeStr) + require.True(t, ok, "quoted fee should parse as a decimal value") + + // LockOrBurn (Lock path) transfers the gross amount to poolOwner directly via + // LinkRegistry's TransferFactory (sender==receiver==partySender takes the direct + // path); the pool fee is retained by poolOwner (== sender), so only the CCIP fee + // leaves the sender's total Amulet balance. + require.Eventually(t, func() bool { + senderBalanceAfter, balanceErr := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &nativeInstrumentId) + if balanceErr != nil { + return false + } + senderDelta := new(big.Rat).Sub(senderBalanceBefore, senderBalanceAfter) + + return senderDelta.Cmp(quotedFeeAmount) == 0 + }, 15*time.Second, 200*time.Millisecond, "sender Amulet deduction should equal GetFee feeTokenAmount only") + + // poolOwner == sender, so the locked LINK holding moves from sender to sender + // (self-transfer): the pool holds it now, but sender's own LinkHolding balance + // still drops by the full locked amount (senderInputCids are consumed). + require.Eventually(t, func() bool { + senderLinkBalanceAfter, balanceErr := testhelpers.GetHoldingsBalance(t.Context(), senderParticipant, &linkInstrumentId) + if balanceErr != nil { + return false + } + linkDelta := new(big.Rat).Sub(senderLinkBalanceBefore, senderLinkBalanceAfter) + tokenTransferAmountRat, ok := new(big.Rat).SetString(tokenTransferAmountDecimal) + + return ok && linkDelta.Cmp(tokenTransferAmountRat) >= 0 + }, 15*time.Second, 200*time.Millisecond, "sender LINK holding should reflect the locked amount being consumed") + + t.Logf("Send completed") + t.Logf(" Message ID: %s", ccipMessageSent.Event.MessageId) + t.Logf(" Original payload: %s", string(testPayload)) + t.Logf("Success") +} diff --git a/integration-tests/ccip/registry_pool_helpers_test.go b/integration-tests/ccip/registry_pool_helpers_test.go new file mode 100644 index 000000000..b135d6fbc --- /dev/null +++ b/integration-tests/ccip/registry_pool_helpers_test.go @@ -0,0 +1,57 @@ +package tests + +import ( + "context" + + apiv2 "github.com/digital-asset/dazl-client/v8/go/api/com/daml/ledger/api/v2" + "github.com/google/uuid" + + "github.com/smartcontractkit/chainlink-deployments-framework/chain/canton" + "github.com/smartcontractkit/go-daml/pkg/model" + damlledger "github.com/smartcontractkit/go-daml/pkg/service/ledger" + + "github.com/smartcontractkit/chainlink-canton/contracts/v2" +) + +// submitCreateAndExercise submits a single CreateAndExercise command: payload is +// created and choice is exercised on the resulting contract in one atomic +// transaction, authorized by actAs. This is the real usage pattern Initialize is +// designed for - a caller never needs to see the pool's contractId, since it's +// created and exercised on in the same command. +// +// disclosedContracts must include any contract the choice body touches that the +// submitting participant doesn't already have in its own ACS (e.g. a cross-participant +// TAR) - authorization (actAs) and visibility are separate concerns: a party's +// signature can satisfy a choice's controller check without the submitting +// participant being able to see the contract at all. +func submitCreateAndExercise( + ctx context.Context, + participant canton.Participant, + actAs string, + payload interface { + model.CreateCommander + GetTemplateID() string + }, + choice string, + choiceArg any, + disclosedContracts ...*apiv2.DisclosedContract, +) (*apiv2.SubmitAndWaitForTransactionResponse, error) { + createCmd := payload.CreateCommand() + return participant.LedgerServices.Command.SubmitAndWaitForTransaction(ctx, &apiv2.SubmitAndWaitForTransactionRequest{ + Commands: &apiv2.Commands{ + CommandId: uuid.NewString(), + ActAs: []string{actAs}, + Commands: []*apiv2.Command{{ + Command: &apiv2.Command_CreateAndExercise{ + CreateAndExercise: &apiv2.CreateAndExerciseCommand{ + TemplateId: contracts.IdentifierFromBinding(payload), + CreateArguments: damlledger.MapToRecord(createCmd.Arguments), + Choice: choice, + ChoiceArgument: damlledger.MapToValue(choiceArg), + }, + }, + }}, + DisclosedContracts: disclosedContracts, + }, + }) +}