diff --git a/Makefile b/Makefile index 82046d668..1b75dc9da 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ check-frozen-release-artifacts: ## Fail if the branch changes frozen release DAR .PHONY: clean-codecs clean-codecs: ## Remove auto-generated *CodecGen.daml files (regenerated by generate-daml-codecs). - @find contracts -path '*/daml/CCIP/*CodecGen.daml' -not -path '*/.daml/*' -delete 2>/dev/null || true + @find contracts \( -path '*/daml/CCIP/*CodecGen.daml' -o -path '*/daml/Link/*CodecGen.daml' \) -not -path '*/.daml/*' -delete 2>/dev/null || true .PHONY: contracts contracts: clean-codecs compile-contracts inspect-dars upgrade-check generate-bindings diff --git a/contracts/bindings/generated/ccip/burnminttokenpool/burnminttokenpool.go b/contracts/bindings/generated/ccip/burnminttokenpool/burnminttokenpool.go index cad14d491..02262b0dc 100644 --- a/contracts/bindings/generated/ccip/burnminttokenpool/burnminttokenpool.go +++ b/contracts/bindings/generated/ccip/burnminttokenpool/burnminttokenpool.go @@ -1526,7 +1526,7 @@ type LockOrBurn struct { RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } @@ -2473,7 +2473,7 @@ 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"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } diff --git a/contracts/bindings/generated/ccip/clientapi/clientapi.go b/contracts/bindings/generated/ccip/clientapi/clientapi.go index 8f247cf4a..12739e0d6 100644 --- a/contracts/bindings/generated/ccip/clientapi/clientapi.go +++ b/contracts/bindings/generated/ccip/clientapi/clientapi.go @@ -1286,7 +1286,7 @@ func (t *PerPartyRouterPrepareSend) UnmarshalHex(data string) error { // TokenTransfer is a Record type type TokenTransfer struct { Token splice_api_token_holding_v1.InstrumentId `json:"token"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` } // ToMap converts TokenTransfer to a map for DAML arguments diff --git a/contracts/bindings/generated/ccip/extensionapi/extensionapi.go b/contracts/bindings/generated/ccip/extensionapi/extensionapi.go index b1700f359..8466a2fef 100644 --- a/contracts/bindings/generated/ccip/extensionapi/extensionapi.go +++ b/contracts/bindings/generated/ccip/extensionapi/extensionapi.go @@ -1099,7 +1099,7 @@ type TokenPoolLockOrBurn struct { RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } @@ -1259,7 +1259,7 @@ type TokenPoolVerifyOutboundCCVs struct { TokenAdminRegistryCid types.CONTRACT_ID `json:"tokenAdminRegistryCid"` TokenConfigCid types.CONTRACT_ID `json:"tokenConfigCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } diff --git a/contracts/bindings/generated/ccip/lockreleasetokenpool/lockreleasetokenpool.go b/contracts/bindings/generated/ccip/lockreleasetokenpool/lockreleasetokenpool.go index 098263c47..e4fc03475 100644 --- a/contracts/bindings/generated/ccip/lockreleasetokenpool/lockreleasetokenpool.go +++ b/contracts/bindings/generated/ccip/lockreleasetokenpool/lockreleasetokenpool.go @@ -756,7 +756,7 @@ type LockOrBurn struct { RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } @@ -2470,7 +2470,7 @@ 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"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } diff --git a/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go b/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go index 124e00151..3f6121a0e 100644 --- a/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go +++ b/contracts/bindings/generated/ccip/registry/burnminttokenpool/burnminttokenpool.go @@ -1771,7 +1771,7 @@ type LockOrBurn struct { RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } @@ -2831,7 +2831,7 @@ 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"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } diff --git a/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go b/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go index ad32c0a79..095430e04 100644 --- a/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go +++ b/contracts/bindings/generated/ccip/registry/lockreleasetokenpool/lockreleasetokenpool.go @@ -940,7 +940,7 @@ type LockOrBurn struct { RmnRemoteCid types.CONTRACT_ID `json:"rmnRemoteCid"` SendingMessageCid types.CONTRACT_ID `json:"sendingMessageCid"` SenderInputCids []types.CONTRACT_ID `json:"senderInputCids"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } @@ -2892,7 +2892,7 @@ 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"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` Caller types.PARTY `json:"caller"` } diff --git a/contracts/bindings/generated/link/link.go b/contracts/bindings/generated/link/link.go index f9bf878e8..4a12b502e 100644 --- a/contracts/bindings/generated/link/link.go +++ b/contracts/bindings/generated/link/link.go @@ -931,7 +931,7 @@ func (t LockedLinkHolding) ArchiveWithPackageID(contractID string, packageID str // Send is a Record type type Send struct { Sender types.PARTY `json:"sender"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` InputHoldingCids []types.CONTRACT_ID `json:"inputHoldingCids"` Meta splice_api_token_metadata_v1.Metadata `json:"meta"` diff --git a/contracts/bindings/generated/link/tokenadmin/tokenadmin.go b/contracts/bindings/generated/link/tokenadmin/tokenadmin.go new file mode 100644 index 000000000..79c1caa55 --- /dev/null +++ b/contracts/bindings/generated/link/tokenadmin/tokenadmin.go @@ -0,0 +1,2068 @@ +package tokenadmin + +import ( + "errors" + "fmt" + "math/big" + "strings" + + 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 = "link-token-admin" + PackageID = "24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a" + SDKVersion = "3.4.11" +) + +type Template interface { + CreateCommand() *model.CreateCommand + GetTemplateID() string +} + +const ( + IndefiniteTransferHours = types.INT64(2160) +) + +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} +} + +// ApproveAllocate is a Record type +type ApproveAllocate struct { + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + TransferLegId types.TEXT `json:"transferLegId"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveAllocate to a map for DAML arguments +func (t ApproveAllocate) ToMap() map[string]any { + m := make(map[string]any) + + m["receiver"] = t.Receiver.ToMap() + + m["amount"] = t.Amount + + m["transferLegId"] = string(t.TransferLegId) + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveAllocate) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveAllocate) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveAllocate to hex string (Canton MCMS format) +func (t ApproveAllocate) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveAllocate from hex string (Canton MCMS format) +func (t *ApproveAllocate) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveAllocateParams is a Record type +type ApproveAllocateParams struct { + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + TransferLegId types.TEXT `json:"transferLegId"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveAllocateParams to a map for DAML arguments +func (t ApproveAllocateParams) ToMap() map[string]any { + m := make(map[string]any) + + m["receiver"] = t.Receiver.ToMap() + + m["amount"] = t.Amount + + m["transferLegId"] = string(t.TransferLegId) + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveAllocateParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveAllocateParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveAllocateParams to hex string (Canton MCMS format) +func (t ApproveAllocateParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveAllocateParams from hex string (Canton MCMS format) +func (t *ApproveAllocateParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveBurn is a Record type +type ApproveBurn struct { + Holder types.PARTY `json:"holder"` + MaxAmount types.NUMERIC `json:"maxAmount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveBurn to a map for DAML arguments +func (t ApproveBurn) ToMap() map[string]any { + m := make(map[string]any) + + m["holder"] = t.Holder.ToMap() + + m["maxAmount"] = t.MaxAmount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveBurn) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveBurn) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveBurn to hex string (Canton MCMS format) +func (t ApproveBurn) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveBurn from hex string (Canton MCMS format) +func (t *ApproveBurn) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveBurnParams is a Record type +type ApproveBurnParams struct { + Holder types.PARTY `json:"holder"` + MaxAmount types.NUMERIC `json:"maxAmount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveBurnParams to a map for DAML arguments +func (t ApproveBurnParams) ToMap() map[string]any { + m := make(map[string]any) + + m["holder"] = t.Holder.ToMap() + + m["maxAmount"] = t.MaxAmount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveBurnParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveBurnParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveBurnParams to hex string (Canton MCMS format) +func (t ApproveBurnParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveBurnParams from hex string (Canton MCMS format) +func (t *ApproveBurnParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveMint is a Record type +type ApproveMint struct { + Recipient types.PARTY `json:"recipient"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveMint to a map for DAML arguments +func (t ApproveMint) ToMap() map[string]any { + m := make(map[string]any) + + m["recipient"] = t.Recipient.ToMap() + + m["amount"] = t.Amount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveMint) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveMint) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveMint to hex string (Canton MCMS format) +func (t ApproveMint) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveMint from hex string (Canton MCMS format) +func (t *ApproveMint) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveMintParams is a Record type +type ApproveMintParams struct { + Recipient types.PARTY `json:"recipient"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveMintParams to a map for DAML arguments +func (t ApproveMintParams) ToMap() map[string]any { + m := make(map[string]any) + + m["recipient"] = t.Recipient.ToMap() + + m["amount"] = t.Amount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveMintParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveMintParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveMintParams to hex string (Canton MCMS format) +func (t ApproveMintParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveMintParams from hex string (Canton MCMS format) +func (t *ApproveMintParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveTransfer is a Record type +type ApproveTransfer struct { + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveTransfer to a map for DAML arguments +func (t ApproveTransfer) ToMap() map[string]any { + m := make(map[string]any) + + m["receiver"] = t.Receiver.ToMap() + + m["amount"] = t.Amount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveTransfer) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveTransfer) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveTransfer to hex string (Canton MCMS format) +func (t ApproveTransfer) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveTransfer from hex string (Canton MCMS format) +func (t *ApproveTransfer) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ApproveTransferParams is a Record type +type ApproveTransferParams struct { + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ValidForSeconds types.INT64 `json:"validForSeconds"` +} + +// ToMap converts ApproveTransferParams to a map for DAML arguments +func (t ApproveTransferParams) ToMap() map[string]any { + m := make(map[string]any) + + m["receiver"] = t.Receiver.ToMap() + + m["amount"] = t.Amount + + m["reference"] = string(t.Reference) + + m["validForSeconds"] = int64(t.ValidForSeconds) + + return m +} + +func (t ApproveTransferParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ApproveTransferParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ApproveTransferParams to hex string (Canton MCMS format) +func (t ApproveTransferParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ApproveTransferParams from hex string (Canton MCMS format) +func (t *ApproveTransferParams) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CancelAllocate is a Record type +type CancelAllocate struct { +} + +// ToMap converts CancelAllocate to a map for DAML arguments +func (t CancelAllocate) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t CancelAllocate) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CancelAllocate) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CancelAllocate to hex string (Canton MCMS format) +func (t CancelAllocate) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CancelAllocate from hex string (Canton MCMS format) +func (t *CancelAllocate) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CancelBurn is a Record type +type CancelBurn struct { +} + +// ToMap converts CancelBurn to a map for DAML arguments +func (t CancelBurn) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t CancelBurn) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CancelBurn) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CancelBurn to hex string (Canton MCMS format) +func (t CancelBurn) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CancelBurn from hex string (Canton MCMS format) +func (t *CancelBurn) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CancelMint is a Record type +type CancelMint struct { +} + +// ToMap converts CancelMint to a map for DAML arguments +func (t CancelMint) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t CancelMint) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CancelMint) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CancelMint to hex string (Canton MCMS format) +func (t CancelMint) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CancelMint from hex string (Canton MCMS format) +func (t *CancelMint) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// CancelTransfer is a Record type +type CancelTransfer struct { +} + +// ToMap converts CancelTransfer to a map for DAML arguments +func (t CancelTransfer) ToMap() map[string]any { + m := make(map[string]any) + return m +} + +func (t CancelTransfer) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *CancelTransfer) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes CancelTransfer to hex string (Canton MCMS format) +func (t CancelTransfer) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes CancelTransfer from hex string (Canton MCMS format) +func (t *CancelTransfer) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ExecuteAllocate is a Record type +type ExecuteAllocate struct { + TokenAdminCid types.CONTRACT_ID `json:"tokenAdminCid"` + AllocationFactoryCid types.CONTRACT_ID `json:"allocationFactoryCid"` + SettlementExecutor types.PARTY `json:"settlementExecutor"` + SettlementRefId types.TEXT `json:"settlementRefId"` + InputHoldingCids []types.CONTRACT_ID `json:"inputHoldingCids"` + AllocateBeforeHours types.INT64 `json:"allocateBeforeHours"` + SettleBeforeHours types.INT64 `json:"settleBeforeHours"` + RegistryContext splice_api_token_metadata_v1.ChoiceContext `json:"registryContext"` + Meta splice_api_token_metadata_v1.Metadata `json:"meta"` +} + +// ToMap converts ExecuteAllocate to a map for DAML arguments +func (t ExecuteAllocate) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminCid"] = model.NestedToDAMLValue(t.TokenAdminCid) + + m["allocationFactoryCid"] = model.NestedToDAMLValue(t.AllocationFactoryCid) + + m["settlementExecutor"] = t.SettlementExecutor.ToMap() + + m["settlementRefId"] = string(t.SettlementRefId) + + m["inputHoldingCids"] = func() []any { + res := make([]any, 0, len(t.InputHoldingCids)) + for _, e := range t.InputHoldingCids { + res = append(res, e) + } + return res + }() + + m["allocateBeforeHours"] = int64(t.AllocateBeforeHours) + + m["settleBeforeHours"] = int64(t.SettleBeforeHours) + + m["registryContext"] = model.NestedToDAMLValue(t.RegistryContext) + + m["meta"] = model.NestedToDAMLValue(t.Meta) + + return m +} + +func (t ExecuteAllocate) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ExecuteAllocate) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ExecuteAllocate to hex string (Canton MCMS format) +func (t ExecuteAllocate) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ExecuteAllocate from hex string (Canton MCMS format) +func (t *ExecuteAllocate) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ExecuteBurn is a Record type +type ExecuteBurn struct { + TokenAdminCid types.CONTRACT_ID `json:"tokenAdminCid"` + BurnMintFactoryCid types.CONTRACT_ID `json:"burnMintFactoryCid"` + InputHoldingCids []types.CONTRACT_ID `json:"inputHoldingCids"` + RegistryContext splice_api_token_metadata_v1.ChoiceContext `json:"registryContext"` + Meta splice_api_token_metadata_v1.Metadata `json:"meta"` +} + +// ToMap converts ExecuteBurn to a map for DAML arguments +func (t ExecuteBurn) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminCid"] = model.NestedToDAMLValue(t.TokenAdminCid) + + m["burnMintFactoryCid"] = model.NestedToDAMLValue(t.BurnMintFactoryCid) + + m["inputHoldingCids"] = func() []any { + res := make([]any, 0, len(t.InputHoldingCids)) + for _, e := range t.InputHoldingCids { + res = append(res, e) + } + return res + }() + + m["registryContext"] = model.NestedToDAMLValue(t.RegistryContext) + + m["meta"] = model.NestedToDAMLValue(t.Meta) + + return m +} + +func (t ExecuteBurn) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ExecuteBurn) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ExecuteBurn to hex string (Canton MCMS format) +func (t ExecuteBurn) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ExecuteBurn from hex string (Canton MCMS format) +func (t *ExecuteBurn) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ExecuteMint is a Record type +type ExecuteMint struct { + TokenAdminCid types.CONTRACT_ID `json:"tokenAdminCid"` + BurnMintFactoryCid types.CONTRACT_ID `json:"burnMintFactoryCid"` + RegistryContext splice_api_token_metadata_v1.ChoiceContext `json:"registryContext"` + Meta splice_api_token_metadata_v1.Metadata `json:"meta"` +} + +// ToMap converts ExecuteMint to a map for DAML arguments +func (t ExecuteMint) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminCid"] = model.NestedToDAMLValue(t.TokenAdminCid) + + m["burnMintFactoryCid"] = model.NestedToDAMLValue(t.BurnMintFactoryCid) + + m["registryContext"] = model.NestedToDAMLValue(t.RegistryContext) + + m["meta"] = model.NestedToDAMLValue(t.Meta) + + return m +} + +func (t ExecuteMint) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ExecuteMint) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ExecuteMint to hex string (Canton MCMS format) +func (t ExecuteMint) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ExecuteMint from hex string (Canton MCMS format) +func (t *ExecuteMint) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// ExecuteTransfer is a Record type +type ExecuteTransfer struct { + TokenAdminCid types.CONTRACT_ID `json:"tokenAdminCid"` + TransferFactoryCid types.CONTRACT_ID `json:"transferFactoryCid"` + InputHoldingCids []types.CONTRACT_ID `json:"inputHoldingCids"` + TransferTimeoutHours *types.INT64 `json:"transferTimeoutHours" hex:"optional"` + RegistryContext splice_api_token_metadata_v1.ChoiceContext `json:"registryContext"` + Meta splice_api_token_metadata_v1.Metadata `json:"meta"` +} + +// ToMap converts ExecuteTransfer to a map for DAML arguments +func (t ExecuteTransfer) ToMap() map[string]any { + m := make(map[string]any) + + m["tokenAdminCid"] = model.NestedToDAMLValue(t.TokenAdminCid) + + m["transferFactoryCid"] = model.NestedToDAMLValue(t.TransferFactoryCid) + + m["inputHoldingCids"] = func() []any { + res := make([]any, 0, len(t.InputHoldingCids)) + for _, e := range t.InputHoldingCids { + res = append(res, e) + } + return res + }() + + if t.TransferTimeoutHours != nil { + m["transferTimeoutHours"] = map[string]any{ + "_type": "optional", + "value": int64(*t.TransferTimeoutHours), + } + } else { + m["transferTimeoutHours"] = map[string]any{ + "_type": "optional", + "value": nil, + } + } + + m["registryContext"] = model.NestedToDAMLValue(t.RegistryContext) + + m["meta"] = model.NestedToDAMLValue(t.Meta) + + return m +} + +func (t ExecuteTransfer) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *ExecuteTransfer) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes ExecuteTransfer to hex string (Canton MCMS format) +func (t ExecuteTransfer) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes ExecuteTransfer from hex string (Canton MCMS format) +func (t *ExecuteTransfer) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// LinkAllocationAuthorization is a Template type +type LinkAllocationAuthorization struct { + CcipOwner types.PARTY `json:"ccipOwner"` + AdminInstanceId types.TEXT `json:"adminInstanceId"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + TransferLegId types.TEXT `json:"transferLegId"` + Reference types.TEXT `json:"reference"` + ExpiresAt types.TIMESTAMP `json:"expiresAt"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LinkAllocationAuthorization) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkAllocationAuthorization") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LinkAllocationAuthorization) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "Link.TokenAdmin", "LinkAllocationAuthorization") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LinkAllocationAuthorization) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["receiver"] = t.Receiver.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferLegId"] = string(t.TransferLegId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t LinkAllocationAuthorization) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["receiver"] = t.Receiver.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["transferLegId"] = string(t.TransferLegId) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t LinkAllocationAuthorization) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LinkAllocationAuthorization) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LinkAllocationAuthorization to hex string (Canton MCMS format) +func (t LinkAllocationAuthorization) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LinkAllocationAuthorization from hex string (Canton MCMS format) +func (t *LinkAllocationAuthorization) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LinkAllocationAuthorization + +// ExecuteAllocate exercises the ExecuteAllocate choice on this LinkAllocationAuthorization contract +// This method uses the package name in the template ID +func (t LinkAllocationAuthorization) ExecuteAllocate(contractID string, args ExecuteAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "ExecuteAllocate", + Arguments: argsToMap(args), + } +} + +// ExecuteAllocateWithPackageID exercises the ExecuteAllocate choice using the provided package ID instead of package name +func (t LinkAllocationAuthorization) ExecuteAllocateWithPackageID(contractID string, packageID string, args ExecuteAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "ExecuteAllocate", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LinkAllocationAuthorization contract +// This method uses the package name in the template ID +func (t LinkAllocationAuthorization) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t LinkAllocationAuthorization) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// CancelAllocate exercises the CancelAllocate choice on this LinkAllocationAuthorization contract +// This method uses the package name in the template ID +func (t LinkAllocationAuthorization) CancelAllocate(contractID string, args CancelAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "CancelAllocate", + Arguments: argsToMap(args), + } +} + +// CancelAllocateWithPackageID exercises the CancelAllocate choice using the provided package ID instead of package name +func (t LinkAllocationAuthorization) CancelAllocateWithPackageID(contractID string, packageID string, args CancelAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkAllocationAuthorization"), + ContractID: contractID, + Choice: "CancelAllocate", + Arguments: argsToMap(args), + } +} + +// LinkBurnAuthorization is a Template type +type LinkBurnAuthorization struct { + CcipOwner types.PARTY `json:"ccipOwner"` + AdminInstanceId types.TEXT `json:"adminInstanceId"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Holder types.PARTY `json:"holder"` + MaxAmount types.NUMERIC `json:"maxAmount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ExpiresAt types.TIMESTAMP `json:"expiresAt"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LinkBurnAuthorization) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkBurnAuthorization") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LinkBurnAuthorization) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "Link.TokenAdmin", "LinkBurnAuthorization") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LinkBurnAuthorization) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["holder"] = t.Holder.ToMap() + + if t.MaxAmount != "" { + args["maxAmount"] = t.MaxAmount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t LinkBurnAuthorization) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["holder"] = t.Holder.ToMap() + + if t.MaxAmount != "" { + args["maxAmount"] = t.MaxAmount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t LinkBurnAuthorization) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LinkBurnAuthorization) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LinkBurnAuthorization to hex string (Canton MCMS format) +func (t LinkBurnAuthorization) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LinkBurnAuthorization from hex string (Canton MCMS format) +func (t *LinkBurnAuthorization) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LinkBurnAuthorization + +// ExecuteBurn exercises the ExecuteBurn choice on this LinkBurnAuthorization contract +// This method uses the package name in the template ID +func (t LinkBurnAuthorization) ExecuteBurn(contractID string, args ExecuteBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "ExecuteBurn", + Arguments: argsToMap(args), + } +} + +// ExecuteBurnWithPackageID exercises the ExecuteBurn choice using the provided package ID instead of package name +func (t LinkBurnAuthorization) ExecuteBurnWithPackageID(contractID string, packageID string, args ExecuteBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "ExecuteBurn", + Arguments: argsToMap(args), + } +} + +// CancelBurn exercises the CancelBurn choice on this LinkBurnAuthorization contract +// This method uses the package name in the template ID +func (t LinkBurnAuthorization) CancelBurn(contractID string, args CancelBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "CancelBurn", + Arguments: argsToMap(args), + } +} + +// CancelBurnWithPackageID exercises the CancelBurn choice using the provided package ID instead of package name +func (t LinkBurnAuthorization) CancelBurnWithPackageID(contractID string, packageID string, args CancelBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "CancelBurn", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LinkBurnAuthorization contract +// This method uses the package name in the template ID +func (t LinkBurnAuthorization) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t LinkBurnAuthorization) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkBurnAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// LinkMintAuthorization is a Template type +type LinkMintAuthorization struct { + CcipOwner types.PARTY `json:"ccipOwner"` + AdminInstanceId types.TEXT `json:"adminInstanceId"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Recipient types.PARTY `json:"recipient"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ExpiresAt types.TIMESTAMP `json:"expiresAt"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LinkMintAuthorization) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkMintAuthorization") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LinkMintAuthorization) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "Link.TokenAdmin", "LinkMintAuthorization") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LinkMintAuthorization) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["recipient"] = t.Recipient.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t LinkMintAuthorization) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["recipient"] = t.Recipient.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t LinkMintAuthorization) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LinkMintAuthorization) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LinkMintAuthorization to hex string (Canton MCMS format) +func (t LinkMintAuthorization) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LinkMintAuthorization from hex string (Canton MCMS format) +func (t *LinkMintAuthorization) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LinkMintAuthorization + +// ExecuteMint exercises the ExecuteMint choice on this LinkMintAuthorization contract +// This method uses the package name in the template ID +func (t LinkMintAuthorization) ExecuteMint(contractID string, args ExecuteMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "ExecuteMint", + Arguments: argsToMap(args), + } +} + +// ExecuteMintWithPackageID exercises the ExecuteMint choice using the provided package ID instead of package name +func (t LinkMintAuthorization) ExecuteMintWithPackageID(contractID string, packageID string, args ExecuteMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "ExecuteMint", + Arguments: argsToMap(args), + } +} + +// CancelMint exercises the CancelMint choice on this LinkMintAuthorization contract +// This method uses the package name in the template ID +func (t LinkMintAuthorization) CancelMint(contractID string, args CancelMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "CancelMint", + Arguments: argsToMap(args), + } +} + +// CancelMintWithPackageID exercises the CancelMint choice using the provided package ID instead of package name +func (t LinkMintAuthorization) CancelMintWithPackageID(contractID string, packageID string, args CancelMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "CancelMint", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LinkMintAuthorization contract +// This method uses the package name in the template ID +func (t LinkMintAuthorization) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t LinkMintAuthorization) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkMintAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// LinkTokenAdmin is a Template type +type LinkTokenAdmin struct { + CcipOwner types.PARTY `json:"ccipOwner"` + InstanceId types.TEXT `json:"instanceId"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Paused types.BOOL `json:"paused"` + Observers []types.PARTY `json:"observers"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LinkTokenAdmin) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LinkTokenAdmin) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LinkTokenAdmin) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // 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["instanceId"] = string(t.InstanceId) + + // 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["paused"] = bool(t.Paused) + + // 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 LinkTokenAdmin) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // 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["instanceId"] = string(t.InstanceId) + + // 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["paused"] = bool(t.Paused) + + // 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 LinkTokenAdmin) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LinkTokenAdmin) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LinkTokenAdmin to hex string (Canton MCMS format) +func (t LinkTokenAdmin) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LinkTokenAdmin from hex string (Canton MCMS format) +func (t *LinkTokenAdmin) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LinkTokenAdmin + +// ApproveMint exercises the ApproveMint choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) ApproveMint(contractID string, args ApproveMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveMint", + Arguments: argsToMap(args), + } +} + +// ApproveMintWithPackageID exercises the ApproveMint choice using the provided package ID instead of package name +func (t LinkTokenAdmin) ApproveMintWithPackageID(contractID string, packageID string, args ApproveMint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveMint", + Arguments: argsToMap(args), + } +} + +// ApproveBurn exercises the ApproveBurn choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) ApproveBurn(contractID string, args ApproveBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveBurn", + Arguments: argsToMap(args), + } +} + +// ApproveBurnWithPackageID exercises the ApproveBurn choice using the provided package ID instead of package name +func (t LinkTokenAdmin) ApproveBurnWithPackageID(contractID string, packageID string, args ApproveBurn) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveBurn", + Arguments: argsToMap(args), + } +} + +// ApproveTransfer exercises the ApproveTransfer choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) ApproveTransfer(contractID string, args ApproveTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveTransfer", + Arguments: argsToMap(args), + } +} + +// ApproveTransferWithPackageID exercises the ApproveTransfer choice using the provided package ID instead of package name +func (t LinkTokenAdmin) ApproveTransferWithPackageID(contractID string, packageID string, args ApproveTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveTransfer", + Arguments: argsToMap(args), + } +} + +// ApproveAllocate exercises the ApproveAllocate choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) ApproveAllocate(contractID string, args ApproveAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveAllocate", + Arguments: argsToMap(args), + } +} + +// ApproveAllocateWithPackageID exercises the ApproveAllocate choice using the provided package ID instead of package name +func (t LinkTokenAdmin) ApproveAllocateWithPackageID(contractID string, packageID string, args ApproveAllocate) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "ApproveAllocate", + Arguments: argsToMap(args), + } +} + +// SetPaused exercises the SetPaused choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) SetPaused(contractID string, args SetPaused) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "SetPaused", + Arguments: argsToMap(args), + } +} + +// SetPausedWithPackageID exercises the SetPaused choice using the provided package ID instead of package name +func (t LinkTokenAdmin) SetPausedWithPackageID(contractID string, packageID string, args SetPaused) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "SetPaused", + Arguments: argsToMap(args), + } +} + +// SetObservers exercises the SetObservers choice on this LinkTokenAdmin contract +// This method uses the package name in the template ID +func (t LinkTokenAdmin) SetObservers(contractID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// SetObserversWithPackageID exercises the SetObservers choice using the provided package ID instead of package name +func (t LinkTokenAdmin) SetObserversWithPackageID(contractID string, packageID string, args SetObservers) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTokenAdmin"), + ContractID: contractID, + Choice: "SetObservers", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LinkTokenAdmin contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t LinkTokenAdmin) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "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 LinkTokenAdmin) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "MCMSReceiver"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// MCMSReceiverEntrypoint exercises the MCMSReceiver_Entrypoint choice on this LinkTokenAdmin contract via the IMCMSReceiver interface +// This method uses the package name in the template ID +func (t LinkTokenAdmin) MCMSReceiverEntrypoint(contractID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "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 LinkTokenAdmin) MCMSReceiverEntrypointWithPackageID(contractID string, packageID string, args api.MCMSReceiverEntrypoint) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "MCMSReceiver"), + ContractID: contractID, + Choice: "MCMSReceiver_Entrypoint", + Arguments: argsToMap(args), + } +} + +// Verify interface implementations for LinkTokenAdmin + +var _ api.IMCMSReceiver = (*LinkTokenAdmin)(nil) + +// LinkTransferAuthorization is a Template type +type LinkTransferAuthorization struct { + CcipOwner types.PARTY `json:"ccipOwner"` + AdminInstanceId types.TEXT `json:"adminInstanceId"` + InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` + Receiver types.PARTY `json:"receiver"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` + Reference types.TEXT `json:"reference"` + ExpiresAt types.TIMESTAMP `json:"expiresAt"` +} + +// GetTemplateID returns the template ID for this template using the package name +func (t LinkTransferAuthorization) GetTemplateID() string { + return fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTransferAuthorization") +} + +// GetTemplateIDWithPackageID returns the template ID using the provided package ID instead of package name +func (t LinkTransferAuthorization) GetTemplateIDWithPackageID(packageID string) string { + return fmt.Sprintf("%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTransferAuthorization") +} + +// CreateCommand returns a CreateCommand for this template using the package name +func (t LinkTransferAuthorization) CreateCommand() *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["receiver"] = t.Receiver.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateID(), + Arguments: args, + } +} + +// CreateCommandWithPackageID returns a CreateCommand using the provided package ID instead of package name +func (t LinkTransferAuthorization) CreateCommandWithPackageID(packageID string) *model.CreateCommand { + args := make(map[string]any) + + // 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["adminInstanceId"] = string(t.AdminInstanceId) + + // 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["receiver"] = t.Receiver.ToMap() + + if t.Amount != "" { + args["amount"] = t.Amount + } + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["reference"] = string(t.Reference) + + // IMPORTANT: always include non-optional fields (GENMAP/MAP/LIST/[] etc), even if empty + args["expiresAt"] = t.ExpiresAt + + return &model.CreateCommand{ + TemplateID: t.GetTemplateIDWithPackageID(packageID), + Arguments: args, + } +} + +func (t LinkTransferAuthorization) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *LinkTransferAuthorization) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes LinkTransferAuthorization to hex string (Canton MCMS format) +func (t LinkTransferAuthorization) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes LinkTransferAuthorization from hex string (Canton MCMS format) +func (t *LinkTransferAuthorization) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// Choice methods for LinkTransferAuthorization + +// ExecuteTransfer exercises the ExecuteTransfer choice on this LinkTransferAuthorization contract +// This method uses the package name in the template ID +func (t LinkTransferAuthorization) ExecuteTransfer(contractID string, args ExecuteTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "ExecuteTransfer", + Arguments: argsToMap(args), + } +} + +// ExecuteTransferWithPackageID exercises the ExecuteTransfer choice using the provided package ID instead of package name +func (t LinkTransferAuthorization) ExecuteTransferWithPackageID(contractID string, packageID string, args ExecuteTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "ExecuteTransfer", + Arguments: argsToMap(args), + } +} + +// CancelTransfer exercises the CancelTransfer choice on this LinkTransferAuthorization contract +// This method uses the package name in the template ID +func (t LinkTransferAuthorization) CancelTransfer(contractID string, args CancelTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "CancelTransfer", + Arguments: argsToMap(args), + } +} + +// CancelTransferWithPackageID exercises the CancelTransfer choice using the provided package ID instead of package name +func (t LinkTransferAuthorization) CancelTransferWithPackageID(contractID string, packageID string, args CancelTransfer) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "CancelTransfer", + Arguments: argsToMap(args), + } +} + +// Archive exercises the Archive choice on this LinkTransferAuthorization contract +// This method uses the package name in the template ID +func (t LinkTransferAuthorization) Archive(contractID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", PackageName, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// ArchiveWithPackageID exercises the Archive choice using the provided package ID instead of package name +func (t LinkTransferAuthorization) ArchiveWithPackageID(contractID string, packageID string) *model.ExerciseCommand { + return &model.ExerciseCommand{ + TemplateID: fmt.Sprintf("#%s:%s:%s", packageID, "Link.TokenAdmin", "LinkTransferAuthorization"), + ContractID: contractID, + Choice: "Archive", + Arguments: map[string]any{}, + } +} + +// 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) +} + +// SetPaused is a Record type +type SetPaused struct { + Paused types.BOOL `json:"paused"` +} + +// ToMap converts SetPaused to a map for DAML arguments +func (t SetPaused) ToMap() map[string]any { + m := make(map[string]any) + + m["paused"] = bool(t.Paused) + + return m +} + +func (t SetPaused) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetPaused) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetPaused to hex string (Canton MCMS format) +func (t SetPaused) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetPaused from hex string (Canton MCMS format) +func (t *SetPaused) UnmarshalHex(data string) error { + hexCodec := codec.NewHexCodec() + return hexCodec.Unmarshal(data, t) +} + +// SetPausedParams is a Record type +type SetPausedParams struct { + Paused types.BOOL `json:"paused"` +} + +// ToMap converts SetPausedParams to a map for DAML arguments +func (t SetPausedParams) ToMap() map[string]any { + m := make(map[string]any) + + m["paused"] = bool(t.Paused) + + return m +} + +func (t SetPausedParams) MarshalJSON() ([]byte, error) { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Marshal(t) +} + +func (t *SetPausedParams) UnmarshalJSON(data []byte) error { + jsonCodec := codec.NewJsonCodec() + return jsonCodec.Unmarshal(data, t) +} + +// MarshalHex encodes SetPausedParams to hex string (Canton MCMS format) +func (t SetPausedParams) MarshalHex() (string, error) { + hexCodec := codec.NewHexCodec() + return hexCodec.Marshal(t) +} + +// UnmarshalHex decodes SetPausedParams from hex string (Canton MCMS format) +func (t *SetPausedParams) 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 { + ApproveAllocate(args ApproveAllocate) (*bind.EncodedChoice, error) + ApproveAllocateParams(args ApproveAllocateParams) (*bind.EncodedChoice, error) + ApproveBurn(args ApproveBurn) (*bind.EncodedChoice, error) + ApproveBurnParams(args ApproveBurnParams) (*bind.EncodedChoice, error) + ApproveMint(args ApproveMint) (*bind.EncodedChoice, error) + ApproveMintParams(args ApproveMintParams) (*bind.EncodedChoice, error) + ApproveTransfer(args ApproveTransfer) (*bind.EncodedChoice, error) + ApproveTransferParams(args ApproveTransferParams) (*bind.EncodedChoice, error) + CancelAllocate(args CancelAllocate) (*bind.EncodedChoice, error) + CancelBurn(args CancelBurn) (*bind.EncodedChoice, error) + CancelMint(args CancelMint) (*bind.EncodedChoice, error) + CancelTransfer(args CancelTransfer) (*bind.EncodedChoice, error) + ExecuteAllocate(args ExecuteAllocate) (*bind.EncodedChoice, error) + ExecuteBurn(args ExecuteBurn) (*bind.EncodedChoice, error) + ExecuteMint(args ExecuteMint) (*bind.EncodedChoice, error) + ExecuteTransfer(args ExecuteTransfer) (*bind.EncodedChoice, error) + SetObservers(args SetObservers) (*bind.EncodedChoice, error) + SetObserversParams(args SetObserversParams) (*bind.EncodedChoice, error) + SetPaused(args SetPaused) (*bind.EncodedChoice, error) + SetPausedParams(args SetPausedParams) (*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 +} + +// ApproveAllocate encodes parameters for the ApproveAllocate choice. +func (e *encoder) ApproveAllocate(args ApproveAllocate) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveAllocate", args) +} + +// ApproveAllocateParams encodes parameters for the ApproveAllocate choice. +func (e *encoder) ApproveAllocateParams(args ApproveAllocateParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveAllocate", args) +} + +// ApproveBurn encodes parameters for the ApproveBurn choice. +func (e *encoder) ApproveBurn(args ApproveBurn) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveBurn", args) +} + +// ApproveBurnParams encodes parameters for the ApproveBurn choice. +func (e *encoder) ApproveBurnParams(args ApproveBurnParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveBurn", args) +} + +// ApproveMint encodes parameters for the ApproveMint choice. +func (e *encoder) ApproveMint(args ApproveMint) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveMint", args) +} + +// ApproveMintParams encodes parameters for the ApproveMint choice. +func (e *encoder) ApproveMintParams(args ApproveMintParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveMint", args) +} + +// ApproveTransfer encodes parameters for the ApproveTransfer choice. +func (e *encoder) ApproveTransfer(args ApproveTransfer) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveTransfer", args) +} + +// ApproveTransferParams encodes parameters for the ApproveTransfer choice. +func (e *encoder) ApproveTransferParams(args ApproveTransferParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ApproveTransfer", args) +} + +// CancelAllocate encodes parameters for the CancelAllocate choice. +func (e *encoder) CancelAllocate(args CancelAllocate) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CancelAllocate", args) +} + +// CancelBurn encodes parameters for the CancelBurn choice. +func (e *encoder) CancelBurn(args CancelBurn) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CancelBurn", args) +} + +// CancelMint encodes parameters for the CancelMint choice. +func (e *encoder) CancelMint(args CancelMint) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CancelMint", args) +} + +// CancelTransfer encodes parameters for the CancelTransfer choice. +func (e *encoder) CancelTransfer(args CancelTransfer) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("CancelTransfer", args) +} + +// ExecuteAllocate encodes parameters for the ExecuteAllocate choice. +func (e *encoder) ExecuteAllocate(args ExecuteAllocate) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ExecuteAllocate", args) +} + +// ExecuteBurn encodes parameters for the ExecuteBurn choice. +func (e *encoder) ExecuteBurn(args ExecuteBurn) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ExecuteBurn", args) +} + +// ExecuteMint encodes parameters for the ExecuteMint choice. +func (e *encoder) ExecuteMint(args ExecuteMint) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ExecuteMint", args) +} + +// ExecuteTransfer encodes parameters for the ExecuteTransfer choice. +func (e *encoder) ExecuteTransfer(args ExecuteTransfer) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("ExecuteTransfer", 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) +} + +// SetPaused encodes parameters for the SetPaused choice. +func (e *encoder) SetPaused(args SetPaused) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetPaused", args) +} + +// SetPausedParams encodes parameters for the SetPaused choice. +func (e *encoder) SetPausedParams(args SetPausedParams) (*bind.EncodedChoice, error) { + return e.EncodeChoiceArgs("SetPaused", args) +} + +// Verify MCMSEncoder interface implementation +var _ MCMSEncoder = (*encoder)(nil) diff --git a/contracts/bindings/generated/splice/splice_api_token_allocation_v1/splice_api_token_allocation_v1.go b/contracts/bindings/generated/splice/splice_api_token_allocation_v1/splice_api_token_allocation_v1.go index 346a91a56..6a4e95419 100644 --- a/contracts/bindings/generated/splice/splice_api_token_allocation_v1/splice_api_token_allocation_v1.go +++ b/contracts/bindings/generated/splice/splice_api_token_allocation_v1/splice_api_token_allocation_v1.go @@ -515,7 +515,7 @@ func (t *SettlementInfo) UnmarshalHex(data string) error { type TransferLeg struct { Sender types.PARTY `json:"sender"` Receiver types.PARTY `json:"receiver"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` Meta splice_api_token_metadata_v1.Metadata `json:"meta"` } diff --git a/contracts/bindings/generated/splice/splice_api_token_burn_mint_v1/splice_api_token_burn_mint_v1.go b/contracts/bindings/generated/splice/splice_api_token_burn_mint_v1/splice_api_token_burn_mint_v1.go index 2d1eaa447..1c1c36565 100644 --- a/contracts/bindings/generated/splice/splice_api_token_burn_mint_v1/splice_api_token_burn_mint_v1.go +++ b/contracts/bindings/generated/splice/splice_api_token_burn_mint_v1/splice_api_token_burn_mint_v1.go @@ -258,7 +258,7 @@ func (t *BurnMintFactoryPublicFetch) UnmarshalHex(data string) error { // BurnMintOutput is a Record type type BurnMintOutput struct { Owner types.PARTY `json:"owner"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context splice_api_token_metadata_v1.ChoiceContext `json:"context"` } diff --git a/contracts/bindings/generated/splice/splice_api_token_holding_v1/splice_api_token_holding_v1.go b/contracts/bindings/generated/splice/splice_api_token_holding_v1/splice_api_token_holding_v1.go index 55cfe3716..412b380f7 100644 --- a/contracts/bindings/generated/splice/splice_api_token_holding_v1/splice_api_token_holding_v1.go +++ b/contracts/bindings/generated/splice/splice_api_token_holding_v1/splice_api_token_holding_v1.go @@ -63,7 +63,7 @@ func argsToMap(args any) map[string]any { type HoldingView struct { Owner types.PARTY `json:"owner"` InstrumentId InstrumentId `json:"instrumentId"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Lock *Lock `json:"lock" hex:"optional"` Meta splice_api_token_metadata_v1.Metadata `json:"meta"` } diff --git a/contracts/bindings/generated/splice/splice_api_token_transfer_instruction_v1/splice_api_token_transfer_instruction_v1.go b/contracts/bindings/generated/splice/splice_api_token_transfer_instruction_v1/splice_api_token_transfer_instruction_v1.go index 6158ba5f3..10574c63f 100644 --- a/contracts/bindings/generated/splice/splice_api_token_transfer_instruction_v1/splice_api_token_transfer_instruction_v1.go +++ b/contracts/bindings/generated/splice/splice_api_token_transfer_instruction_v1/splice_api_token_transfer_instruction_v1.go @@ -89,7 +89,7 @@ func argsToMap(args any) map[string]any { type Transfer struct { Sender types.PARTY `json:"sender"` Receiver types.PARTY `json:"receiver"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` RequestedAt types.TIMESTAMP `json:"requestedAt"` ExecuteBefore types.TIMESTAMP `json:"executeBefore"` diff --git a/contracts/bindings/generated/utility/registry_app_v0/registry_app_v0.go b/contracts/bindings/generated/utility/registry_app_v0/registry_app_v0.go index 3243ab0b2..9063a364c 100644 --- a/contracts/bindings/generated/utility/registry_app_v0/registry_app_v0.go +++ b/contracts/bindings/generated/utility/registry_app_v0/registry_app_v0.go @@ -898,7 +898,7 @@ func (t *AllocationFactoryTransferInternal) UnmarshalHex(data string) error { // Burn is a Record type type Burn struct { InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` RequestedAt types.TIMESTAMP `json:"requestedAt"` @@ -5142,7 +5142,7 @@ type HolderServiceOfferLock struct { Registrar types.PARTY `json:"registrar"` Locker types.PARTY `json:"locker"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context types.TEXT `json:"context"` HoldingLabel types.TEXT `json:"holdingLabel"` Reference types.TEXT `json:"reference"` @@ -5235,7 +5235,7 @@ type HolderServiceOfferTransfer struct { Registrar types.PARTY `json:"registrar"` Receiver types.PARTY `json:"receiver"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` SenderLabel types.TEXT `json:"senderLabel"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -5326,7 +5326,7 @@ type HolderServiceOfferUnlock struct { Locker types.PARTY `json:"locker"` LockContext types.TEXT `json:"lockContext"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` HoldingLabel types.TEXT `json:"holdingLabel"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -5765,7 +5765,7 @@ func (t *HolderServiceRejectUnlockRequest) UnmarshalHex(data string) error { type HolderServiceRequestBurn struct { Registrar types.PARTY `json:"registrar"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` HoldingLabel types.TEXT `json:"holdingLabel"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -5925,7 +5925,7 @@ type HolderServiceRequestForceTransfer struct { RequestorRationale types.TEXT `json:"requestorRationale"` Registrar types.PARTY `json:"registrar"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` Sender types.PARTY `json:"sender"` @@ -6024,7 +6024,7 @@ type HolderServiceRequestLock struct { Registrar types.PARTY `json:"registrar"` Holder types.PARTY `json:"holder"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context types.TEXT `json:"context"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -6113,7 +6113,7 @@ func (t *HolderServiceRequestLockResult) UnmarshalHex(data string) error { type HolderServiceRequestMint struct { Registrar types.PARTY `json:"registrar"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` HoldingLabel types.TEXT `json:"holdingLabel"` @@ -6201,7 +6201,7 @@ type HolderServiceRequestTransfer struct { Registrar types.PARTY `json:"registrar"` Sender types.PARTY `json:"sender"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` ReceiverLabel types.TEXT `json:"receiverLabel"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -6292,7 +6292,7 @@ type HolderServiceRequestUnlock struct { Holder types.PARTY `json:"holder"` LockContext types.TEXT `json:"lockContext"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` HoldingLabel types.TEXT `json:"holdingLabel"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -6482,7 +6482,7 @@ func (t *InstrumentAllowance) UnmarshalHex(data string) error { // Mint is a Record type type Mint struct { InstrumentId splice_api_token_holding_v1.InstrumentId `json:"instrumentId"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` RequestedAt types.TIMESTAMP `json:"requestedAt"` @@ -13257,7 +13257,7 @@ func (t *RegistrarServiceMergeHolding) UnmarshalHex(data string) error { // RegistrarServiceOfferBurn is a Record type type RegistrarServiceOfferBurn struct { InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` @@ -13341,7 +13341,7 @@ func (t *RegistrarServiceOfferBurnResult) UnmarshalHex(data string) error { // RegistrarServiceOfferMint is a Record type type RegistrarServiceOfferMint struct { InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` Batch registry_v0.Batch `json:"batch"` diff --git a/contracts/bindings/generated/utility/registry_holding_v0/registry_holding_v0.go b/contracts/bindings/generated/utility/registry_holding_v0/registry_holding_v0.go index 416c09594..cee18b80a 100644 --- a/contracts/bindings/generated/utility/registry_holding_v0/registry_holding_v0.go +++ b/contracts/bindings/generated/utility/registry_holding_v0/registry_holding_v0.go @@ -278,7 +278,7 @@ type Holding struct { Owner types.PARTY `json:"owner"` Instrument InstrumentIdentifier `json:"instrument"` Label types.TEXT `json:"label"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Lock *Lock2 `json:"lock" hex:"optional"` } diff --git a/contracts/bindings/generated/utility/registry_v0/registry_v0.go b/contracts/bindings/generated/utility/registry_v0/registry_v0.go index d3d9f325e..40db0c174 100644 --- a/contracts/bindings/generated/utility/registry_v0/registry_v0.go +++ b/contracts/bindings/generated/utility/registry_v0/registry_v0.go @@ -2481,7 +2481,7 @@ type Burn struct { Provider types.PARTY `json:"provider"` Registrar types.PARTY `json:"registrar"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` Batch Batch `json:"batch"` @@ -7092,7 +7092,7 @@ type Lock3 struct { Holder types.PARTY `json:"holder"` Locker types.PARTY `json:"locker"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Context types.TEXT `json:"context"` Reference types.TEXT `json:"reference"` Batch Batch `json:"batch"` @@ -7984,7 +7984,7 @@ type Mint struct { Provider types.PARTY `json:"provider"` Registrar types.PARTY `json:"registrar"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Holder types.PARTY `json:"holder"` Reference types.TEXT `json:"reference"` Batch Batch `json:"batch"` @@ -10220,7 +10220,7 @@ type Transfer2 struct { Sender types.PARTY `json:"sender"` Receiver types.PARTY `json:"receiver"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Reference types.TEXT `json:"reference"` Batch Batch `json:"batch"` } @@ -11783,7 +11783,7 @@ type Unlock struct { Locker types.PARTY `json:"locker"` LockContext types.TEXT `json:"lockContext"` InstrumentIdentifier registry_holding_v0.InstrumentIdentifier `json:"instrumentIdentifier"` - Amount types.NUMERIC `json:"amount"` + Amount types.NUMERIC `json:"amount" hex:"decimal"` Reference types.TEXT `json:"reference"` Batch Batch `json:"batch"` } diff --git a/contracts/cmd/bindings/main.go b/contracts/cmd/bindings/main.go index 5662d8aa0..e078ab133 100644 --- a/contracts/cmd/bindings/main.go +++ b/contracts/cmd/bindings/main.go @@ -114,6 +114,8 @@ func main() { DecimalFields: map[string]bool{ "usdPerUnitGas": true, // FeeQuoter gas price updates "usdPerToken": true, // FeeQuoter token price updates + "amount": true, // LinkTokenAdmin Approve{Mint,Transfer,Allocate}Params + "maxAmount": true, // LinkTokenAdmin ApproveBurnParams }, VariantTagByteMap: map[string]map[string]byte{ "CCIP.LockReleaseTokenPoolV2Types.TransferTimeout": { diff --git a/contracts/contracts.go b/contracts/contracts.go index d73cbab5a..9e1f65e62 100644 --- a/contracts/contracts.go +++ b/contracts/contracts.go @@ -25,7 +25,8 @@ const ( // LINK Token - Link = Package("link") + Link = Package("link") + LinkTokenAdmin = Package("link-token-admin") // MCMS @@ -163,6 +164,9 @@ var Versions map[Package][]string = map[Package][]string{ Link: append(ReleasedVersions[Link], DevVersion), + // Unreleased: ships as a dev DAR until the migration is green-lit. + LinkTokenAdmin: []string{DevVersion}, + MCMSAPI: append(ReleasedVersions[MCMSAPI], DevVersion), MCMSCore: append(ReleasedVersions[MCMSCore], DevVersion), @@ -362,8 +366,9 @@ func GetLegacyDar(packageName Package, version string) ([]byte, error) { } var BindingsOutputDirs = map[Package][]string{ - Coin: []string{"coin"}, - Link: []string{"link"}, + Coin: []string{"coin"}, + Link: []string{"link"}, + LinkTokenAdmin: []string{"link", "tokenadmin"}, ChainlinkAPI: []string{"chainlink", "chainlinkapi"}, MCMSAPI: []string{"mcms", "api"}, diff --git a/contracts/dars/dev/PACKAGE_INFO.md b/contracts/dars/dev/PACKAGE_INFO.md index 9582b4fef..7f63a1019 100644 --- a/contracts/dars/dev/PACKAGE_INFO.md +++ b/contracts/dars/dev/PACKAGE_INFO.md @@ -527,6 +527,45 @@ | `splice-api-token-metadata-v1` | `1.0.0` | [`4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f`](#4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f) | | `splice-api-token-transfer-instruction-v1` | `1.0.0` | [`55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281`](#55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281) | +## link-token-admin-dev.dar +- Name: `link-token-admin` +- Version: `1.0.0` +- Package ID: `24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a` + +### Dependencies: +| Package Name | Version | Package ID | +|---------|---------|---------| +| `chainlink-api` | `2.0.0` | [`b9f630bb75179b06f350030282a2276259016da59ba60677b3a8280d854dc2d8`](#b9f630bb75179b06f350030282a2276259016da59ba60677b3a8280d854dc2d8) | +| `link-token-admin` | `1.0.0` | [`24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a`](#24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a) | +| `mcms-api` | `1.0.0` | [`674d8f60de56afd32698ae19516260217c73dd9ed082680fa840ede4b7665240`](#674d8f60de56afd32698ae19516260217c73dd9ed082680fa840ede4b7665240) | +| `splice-api-token-allocation-instruction-v1` | `1.0.0` | [`275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520`](#275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520) | +| `splice-api-token-allocation-v1` | `1.0.0` | [`93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d`](#93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d) | +| `splice-api-token-burn-mint-v1` | `1.0.0` | [`9cc2cbc838ef38dc2c7f34014c9c452bcf71b8e2a4f939235fc0b5d0924b185e`](#9cc2cbc838ef38dc2c7f34014c9c452bcf71b8e2a4f939235fc0b5d0924b185e) | +| `splice-api-token-holding-v1` | `1.0.0` | [`718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b`](#718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b) | +| `splice-api-token-metadata-v1` | `1.0.0` | [`4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f`](#4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f) | +| `splice-api-token-transfer-instruction-v1` | `1.0.0` | [`55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281`](#55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281) | + +## link-token-admin-test-dev.dar +- Name: `link-token-admin-test` +- Version: `0.0.1` +- Package ID: `9046f5eee39d74add1eae87ca0923a48e0735f70bbda3eee76e7e83f85e8dab4` + +### Dependencies: +| Package Name | Version | Package ID | +|---------|---------|---------| +| `chainlink-api` | `2.0.0` | [`b9f630bb75179b06f350030282a2276259016da59ba60677b3a8280d854dc2d8`](#b9f630bb75179b06f350030282a2276259016da59ba60677b3a8280d854dc2d8) | +| `daml-script` | `3.4.11` | [`fe2e0175af21ba46d48cda3614edb9e30f2bc6972c20eb1d09666fa81703aa2e`](#fe2e0175af21ba46d48cda3614edb9e30f2bc6972c20eb1d09666fa81703aa2e) | +| `link` | `2.1.0` | [`fc1fcb7dbfd1959cafbd54bfa6f6cc88c1678607b0f95ee3f6ee8e9ee8eb2b60`](#fc1fcb7dbfd1959cafbd54bfa6f6cc88c1678607b0f95ee3f6ee8e9ee8eb2b60) | +| `link-token-admin` | `1.0.0` | [`24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a`](#24c448fcf1c5bbe4657bfde6faf79991e2b7b7e44a8efa118dcbde48a93bd15a) | +| `link-token-admin-test` | `0.0.1` | [`9046f5eee39d74add1eae87ca0923a48e0735f70bbda3eee76e7e83f85e8dab4`](#9046f5eee39d74add1eae87ca0923a48e0735f70bbda3eee76e7e83f85e8dab4) | +| `mcms-api` | `1.0.0` | [`674d8f60de56afd32698ae19516260217c73dd9ed082680fa840ede4b7665240`](#674d8f60de56afd32698ae19516260217c73dd9ed082680fa840ede4b7665240) | +| `splice-api-token-allocation-instruction-v1` | `1.0.0` | [`275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520`](#275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520) | +| `splice-api-token-allocation-v1` | `1.0.0` | [`93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d`](#93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d) | +| `splice-api-token-burn-mint-v1` | `1.0.0` | [`9cc2cbc838ef38dc2c7f34014c9c452bcf71b8e2a4f939235fc0b5d0924b185e`](#9cc2cbc838ef38dc2c7f34014c9c452bcf71b8e2a4f939235fc0b5d0924b185e) | +| `splice-api-token-holding-v1` | `1.0.0` | [`718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b`](#718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b) | +| `splice-api-token-metadata-v1` | `1.0.0` | [`4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f`](#4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f) | +| `splice-api-token-transfer-instruction-v1` | `1.0.0` | [`55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281`](#55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281) | + ## mcms-api-dev.dar - Name: `mcms-api` - Version: `1.0.0` diff --git a/contracts/dars/dev/link-token-admin-dev.dar b/contracts/dars/dev/link-token-admin-dev.dar new file mode 100644 index 000000000..e276e25ed Binary files /dev/null and b/contracts/dars/dev/link-token-admin-dev.dar differ diff --git a/contracts/dars/dev/link-token-admin-test-dev.dar b/contracts/dars/dev/link-token-admin-test-dev.dar new file mode 100644 index 000000000..02f8b70a5 Binary files /dev/null and b/contracts/dars/dev/link-token-admin-test-dev.dar differ diff --git a/contracts/link/token-admin/daml.yaml b/contracts/link/token-admin/daml.yaml new file mode 100644 index 000000000..66ef1cdb8 --- /dev/null +++ b/contracts/link/token-admin/daml.yaml @@ -0,0 +1,19 @@ +sdk-version: 3.4.11 +name: link-token-admin +source: daml +version: 1.0.0 +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 + - ../../dependencies/splice/splice-api-token-allocation-v1-1.0.0.dar + - ../../dependencies/splice/splice-api-token-allocation-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 +dependencies: + - daml-prim + - daml-stdlib diff --git a/contracts/link/token-admin/daml/Link/TokenAdmin.daml b/contracts/link/token-admin/daml/Link/TokenAdmin.daml new file mode 100644 index 000000000..568445bca --- /dev/null +++ b/contracts/link/token-admin/daml/Link/TokenAdmin.daml @@ -0,0 +1,578 @@ +-- | MCMS-governed admin surface for registry-issued LINK. +-- | +-- | LINK is issued through the DA Registry (Canton Network Utility DARs), so the +-- | privileged token operations — mint, burn, transfer and allocation — are +-- | exercises of the registry factory's CIP-56 interface choices. Those operations +-- | must sit behind MCMS governance like every other privileged CCIP surface. +-- | +-- | The admin is deliberately two-phase: +-- | +-- | * MCMS exercises MCMSReceiver_Entrypoint on LinkTokenAdmin, which creates an +-- | authorization contract fixing the economics of the operation (recipient, +-- | amount, instrument, validity window). The MCMS wire carries only +-- | serializable params: MCMS cannot transport ContractIds or ChoiceContexts. +-- | * The operator exercises Execute on the authorization contract, supplying +-- | only plumbing (factory CID, input holding CIDs, registry choice context, +-- | disclosures). It cannot alter what was approved. +-- | +-- | ccipOwner is the sole signatory of every template here: the MCMS dispatcher +-- | rejects targets with more than one signatory, and Daml authority does not +-- | survive the MCMSReceiver_Entrypoint boundary, so the factory's admin must be +-- | reachable through ccipOwner itself. +-- | +-- | Execute* takes the live admin contract id and re-checks it: the fetched +-- | admin must match this authorization's ccipOwner / adminInstanceId / +-- | instrumentId and must not be paused. SetPaused and SetObservers archive and +-- | recreate the admin, so a stale admin cid fails the fetch — the operator is +-- | forced to supply the current one, making pause an effective stop on +-- | outstanding authorizations, not just new approvals. +module Link.TokenAdmin where + +import DA.Action (foldlA) +import DA.Crypto.Text (byteCount) +import DA.Map qualified as Map +import DA.Optional (isNone) +import DA.Time (addRelTime, hours, seconds) + +import Splice.Api.Token.HoldingV1 qualified as HoldingV1 +import Splice.Api.Token.MetadataV1 qualified as MetadataV1 +import Splice.Api.Token.BurnMintV1 qualified as BurnMintV1 +import Splice.Api.Token.TransferInstructionV1 qualified as TransferV1 +import Splice.Api.Token.AllocationInstructionV1 qualified as AllocationInstructionV1 +import Splice.Api.Token.AllocationV1 qualified as AllocationV1 + +import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiverView(..)) + +import Chainlink.InstanceAddress qualified as InstanceAddress + +import Link.TokenAdminTypes ( + ApproveMintParams(..), + ApproveBurnParams(..), + ApproveTransferParams(..), + ApproveAllocateParams(..), + SetPausedParams(..), + SetObserversParams(..), + ) +import Link.TokenAdminCodecGen ( + decodeApproveMintParamsAt, + decodeApproveBurnParamsAt, + decodeApproveTransferParamsAt, + decodeApproveAllocateParamsAt, + decodeSetPausedParamsAt, + decodeSetObserversParamsAt, + ) + +-- | Maximum transfer lifetime when no explicit timeout is supplied. Splice +-- | enforces tokenStandardMaxTTL (default 90 days) on transfer lifetimes, so +-- | executeBefore cannot be unbounded. +indefiniteTransferHours : Int +indefiniteTransferHours = 2160 + +template LinkTokenAdmin + with + ccipOwner : Party -- sole signatory; also the DA Registry registrar for LINK + instanceId : Text -- MCMS target id, e.g. "link-token-admin-mainnet-v1" + instrumentId : HoldingV1.InstrumentId -- registry-issued LINK + paused : Bool -- emergency stop: blocks new approvals and Execute* + observers : [Party] -- EDS/off-chain visibility + where + signatory ccipOwner + observer observers + + ensure InstanceAddress.assertValidInstanceId instanceId + && instrumentId.admin == ccipOwner + && instrumentId.id /= "" + + -- | Governed mint approval: authorizes minting `amount` to `recipient`. + -- | functionName = "ApproveMint" + nonconsuming choice ApproveMint : ContractId LinkMintAuthorization + with + recipient : Party + amount : Decimal + reference : Text + validForSeconds : Int + controller ccipOwner + do + assertMsg "LinkTokenAdmin.ApproveMint: paused" (not paused) + assertMsg "LinkTokenAdmin.ApproveMint: amount must be positive" (amount > 0.0) + assertMsg "LinkTokenAdmin.ApproveMint: validForSeconds must be positive" (validForSeconds > 0) + now <- getTime + create LinkMintAuthorization with + ccipOwner + adminInstanceId = instanceId + instrumentId + recipient + amount + reference + expiresAt = addRelTime now (seconds validForSeconds) + + -- | Governed burn approval: authorizes burning up to `maxAmount` of `holder`'s LINK. + -- | functionName = "ApproveBurn" + nonconsuming choice ApproveBurn : ContractId LinkBurnAuthorization + with + holder : Party + maxAmount : Decimal + reference : Text + validForSeconds : Int + controller ccipOwner + do + assertMsg "LinkTokenAdmin.ApproveBurn: paused" (not paused) + assertMsg "LinkTokenAdmin.ApproveBurn: maxAmount must be positive" (maxAmount > 0.0) + assertMsg "LinkTokenAdmin.ApproveBurn: validForSeconds must be positive" (validForSeconds > 0) + now <- getTime + create LinkBurnAuthorization with + ccipOwner + adminInstanceId = instanceId + instrumentId + holder + maxAmount + reference + expiresAt = addRelTime now (seconds validForSeconds) + + -- | Governed transfer approval: authorizes transferring `amount` of + -- | ccipOwner-held LINK to `receiver`. Transfers of user-held LINK are plain + -- | wallet flows and stay outside the governor. + -- | functionName = "ApproveTransfer" + nonconsuming choice ApproveTransfer : ContractId LinkTransferAuthorization + with + receiver : Party + amount : Decimal + reference : Text + validForSeconds : Int + controller ccipOwner + do + assertMsg "LinkTokenAdmin.ApproveTransfer: paused" (not paused) + assertMsg "LinkTokenAdmin.ApproveTransfer: amount must be positive" (amount > 0.0) + assertMsg "LinkTokenAdmin.ApproveTransfer: validForSeconds must be positive" (validForSeconds > 0) + now <- getTime + create LinkTransferAuthorization with + ccipOwner + adminInstanceId = instanceId + instrumentId + receiver + amount + reference + expiresAt = addRelTime now (seconds validForSeconds) + + -- | Governed allocation approval: authorizes allocating `amount` of + -- | ccipOwner-held LINK to `receiver` for leg `transferLegId` of a settlement. + -- | functionName = "ApproveAllocate" + nonconsuming choice ApproveAllocate : ContractId LinkAllocationAuthorization + with + receiver : Party + amount : Decimal + transferLegId : Text + reference : Text + validForSeconds : Int + controller ccipOwner + do + assertMsg "LinkTokenAdmin.ApproveAllocate: paused" (not paused) + assertMsg "LinkTokenAdmin.ApproveAllocate: amount must be positive" (amount > 0.0) + assertMsg "LinkTokenAdmin.ApproveAllocate: validForSeconds must be positive" (validForSeconds > 0) + now <- getTime + create LinkAllocationAuthorization with + ccipOwner + adminInstanceId = instanceId + instrumentId + receiver + amount + transferLegId + reference + expiresAt = addRelTime now (seconds validForSeconds) + + -- | Emergency stop on new approvals. functionName = "SetPaused" + choice SetPaused : ContractId LinkTokenAdmin + with + paused : Bool + controller ccipOwner + do + create this with paused + + -- | Keeps the EDS observer set governable rather than write-once at create time. + -- | functionName = "SetObservers" + choice SetObservers : ContractId LinkTokenAdmin + with + observers : [Party] + controller ccipOwner + do + create this with observers + + -- ========================================== + -- | MCMSReceiver Interface | + -- ========================================== + interface instance MCMSReceiver for LinkTokenAdmin where + view = MCMSReceiverView with + mcmsController = ccipOwner + instanceId + + mcmsEntrypoint functionName operationData contractIds = do + let instAddr = instanceId <> "@" <> partyToText ccipOwner + selfCid <- case Map.lookup instAddr contractIds of + None -> abort $ "E_SELF_CID_NOT_IN_MAP: " <> instAddr + Some cid -> pure (coerceContractId cid : ContractId LinkTokenAdmin) + + case functionName of + "ApproveMint" -> do + case decodeApproveMintParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApproveMint" + Some (ApproveMintParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApproveMint" (finalOffset == byteCount operationData) + _ <- exercise selfCid ApproveMint with + recipient + amount + reference + validForSeconds + pure contractIds -- nonconsuming: self CID unchanged + + "ApproveBurn" -> do + case decodeApproveBurnParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApproveBurn" + Some (ApproveBurnParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApproveBurn" (finalOffset == byteCount operationData) + _ <- exercise selfCid ApproveBurn with + holder + maxAmount + reference + validForSeconds + pure contractIds + + "ApproveTransfer" -> do + case decodeApproveTransferParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApproveTransfer" + Some (ApproveTransferParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApproveTransfer" (finalOffset == byteCount operationData) + _ <- exercise selfCid ApproveTransfer with + receiver + amount + reference + validForSeconds + pure contractIds + + "ApproveAllocate" -> do + case decodeApproveAllocateParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: ApproveAllocate" + Some (ApproveAllocateParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: ApproveAllocate" (finalOffset == byteCount operationData) + _ <- exercise selfCid ApproveAllocate with + receiver + amount + transferLegId + reference + validForSeconds + pure contractIds + + "SetPaused" -> do + case decodeSetPausedParamsAt operationData 0 of + None -> abort "E_INVALID_PARAMS: SetPaused" + Some (SetPausedParams{..}, finalOffset) -> do + assertMsg "E_TRAILING_BYTES: SetPaused" (finalOffset == byteCount operationData) + newCid <- exercise selfCid SetPaused with + paused + 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) + + _ -> abort $ "E_UNKNOWN_FUNCTION: " <> functionName + +-- | Authorization to mint LINK to a recipient via the registry burn-mint +-- | factory. Execute is submitted by the operator as a root command with +-- | actAs [ccipOwner, recipient]: root-level actAs is what supplies the +-- | counterparty authority the factory demands via extraActors. +template LinkMintAuthorization + with + ccipOwner : Party + adminInstanceId : Text -- provenance: which admin approved this + instrumentId : HoldingV1.InstrumentId + recipient : Party + amount : Decimal + reference : Text + expiresAt : Time + where + signatory ccipOwner + observer recipient + + ensure amount > 0.0 + && instrumentId.admin == ccipOwner + + choice ExecuteMint : [ContractId HoldingV1.Holding] + with + tokenAdminCid : ContractId LinkTokenAdmin -- live admin; stale cid fails fetch + burnMintFactoryCid : ContractId BurnMintV1.BurnMintFactory + registryContext : MetadataV1.ChoiceContext -- instrument-configuration + issuer-credentials + meta : MetadataV1.Metadata + controller ccipOwner, recipient + do + tokenAdmin <- fetch tokenAdminCid + assertMsg "LinkMintAuthorization.Execute: token admin mismatch" + (tokenAdmin.ccipOwner == ccipOwner + && tokenAdmin.instanceId == adminInstanceId + && tokenAdmin.instrumentId == instrumentId) + assertMsg "LinkMintAuthorization.Execute: token admin paused" (not tokenAdmin.paused) + + now <- getTime + assertMsg "LinkMintAuthorization.Execute: authorization expired" (now <= expiresAt) + + -- Pin the factory to the instrument's admin before touching it. + factoryView <- exercise burnMintFactoryCid BurnMintV1.BurnMintFactory_PublicFetch with + expectedAdmin = instrumentId.admin + actor = ccipOwner + assertMsg "LinkMintAuthorization.Execute: factory admin mismatch" + (factoryView.admin == instrumentId.admin) + + result <- exercise burnMintFactoryCid BurnMintV1.BurnMintFactory_BurnMint with + expectedAdmin = instrumentId.admin + instrumentId = instrumentId + inputHoldingCids = [] + outputs = + [ BurnMintV1.BurnMintOutput with + owner = recipient + amount = amount + context = MetadataV1.emptyChoiceContext + ] + extraActors = if recipient == instrumentId.admin then [] else [recipient] + extraArgs = MetadataV1.ExtraArgs with + context = registryContext + meta = meta + pure result.outputCids + + choice CancelMint : () + controller ccipOwner + do pure () + +-- | Authorization to burn up to `maxAmount` of `holder`'s LINK via the registry +-- | burn-mint factory. Inputs summing above maxAmount are rejected rather than +-- | partially burned with change: the operator selects or splits holdings first, +-- | keeping the approved ceiling a hard bound with no change-output logic to audit. +template LinkBurnAuthorization + with + ccipOwner : Party + adminInstanceId : Text + instrumentId : HoldingV1.InstrumentId + holder : Party + maxAmount : Decimal + reference : Text + expiresAt : Time + where + signatory ccipOwner + observer holder + + ensure maxAmount > 0.0 + && instrumentId.admin == ccipOwner + + choice ExecuteBurn : () + with + tokenAdminCid : ContractId LinkTokenAdmin -- live admin; stale cid fails fetch + burnMintFactoryCid : ContractId BurnMintV1.BurnMintFactory + inputHoldingCids : [ContractId HoldingV1.Holding] + registryContext : MetadataV1.ChoiceContext + meta : MetadataV1.Metadata + controller ccipOwner, holder + do + tokenAdmin <- fetch tokenAdminCid + assertMsg "LinkBurnAuthorization.Execute: token admin mismatch" + (tokenAdmin.ccipOwner == ccipOwner + && tokenAdmin.instanceId == adminInstanceId + && tokenAdmin.instrumentId == instrumentId) + assertMsg "LinkBurnAuthorization.Execute: token admin paused" (not tokenAdmin.paused) + + now <- getTime + assertMsg "LinkBurnAuthorization.Execute: authorization expired" (now <= expiresAt) + assertMsg "LinkBurnAuthorization.Execute: no input holdings" (not (null inputHoldingCids)) + + total <- foldlA + (\acc cid -> do + holding <- fetch cid + let hv = view holding + assertMsg "LinkBurnAuthorization.Execute: holding owner mismatch" + (hv.owner == holder) + assertMsg "LinkBurnAuthorization.Execute: holding instrumentId mismatch" + (hv.instrumentId == instrumentId) + assertMsg "LinkBurnAuthorization.Execute: holding is locked" + (isNone hv.lock) + pure (acc + hv.amount)) + 0.0 + inputHoldingCids + assertMsg "LinkBurnAuthorization.Execute: inputs exceed approved maxAmount" + (total <= maxAmount) + + factoryView <- exercise burnMintFactoryCid BurnMintV1.BurnMintFactory_PublicFetch with + expectedAdmin = instrumentId.admin + actor = ccipOwner + assertMsg "LinkBurnAuthorization.Execute: factory admin mismatch" + (factoryView.admin == instrumentId.admin) + + _ <- exercise burnMintFactoryCid BurnMintV1.BurnMintFactory_BurnMint with + expectedAdmin = instrumentId.admin + instrumentId = instrumentId + inputHoldingCids = inputHoldingCids + outputs = [] + extraActors = if holder == instrumentId.admin then [] else [holder] + extraArgs = MetadataV1.ExtraArgs with + context = registryContext + meta = meta + pure () + + choice CancelBurn : () + controller ccipOwner + do pure () + +-- | Authorization to transfer ccipOwner-held LINK to a receiver via the +-- | registry transfer factory. The sender is always ccipOwner: the transfer +-- | interface choice is controlled by transfer.sender, and under the MCMS +-- | authority boundary the only sender the governor can authorize is its own +-- | signatory. +template LinkTransferAuthorization + with + ccipOwner : Party + adminInstanceId : Text + instrumentId : HoldingV1.InstrumentId + receiver : Party + amount : Decimal + reference : Text + expiresAt : Time + where + signatory ccipOwner + observer receiver + + ensure amount > 0.0 + && instrumentId.admin == ccipOwner + + choice ExecuteTransfer : TransferV1.TransferInstructionResult + with + tokenAdminCid : ContractId LinkTokenAdmin -- live admin; stale cid fails fetch + transferFactoryCid : ContractId TransferV1.TransferFactory + inputHoldingCids : [ContractId HoldingV1.Holding] -- ccipOwner's holdings + transferTimeoutHours : Optional Int -- None = Splice max TTL + registryContext : MetadataV1.ChoiceContext + meta : MetadataV1.Metadata + controller ccipOwner + do + tokenAdmin <- fetch tokenAdminCid + assertMsg "LinkTransferAuthorization.Execute: token admin mismatch" + (tokenAdmin.ccipOwner == ccipOwner + && tokenAdmin.instanceId == adminInstanceId + && tokenAdmin.instrumentId == instrumentId) + assertMsg "LinkTransferAuthorization.Execute: token admin paused" (not tokenAdmin.paused) + + now <- getTime + assertMsg "LinkTransferAuthorization.Execute: authorization expired" (now <= expiresAt) + assertMsg "LinkTransferAuthorization.Execute: no input holdings" (not (null inputHoldingCids)) + + factoryView <- exercise transferFactoryCid TransferV1.TransferFactory_PublicFetch with + expectedAdmin = instrumentId.admin + actor = ccipOwner + assertMsg "LinkTransferAuthorization.Execute: factory admin mismatch" + (factoryView.admin == instrumentId.admin) + + let executeBefore = case transferTimeoutHours of + None -> addRelTime now (hours indefiniteTransferHours) + Some h -> addRelTime now (hours h) + exercise transferFactoryCid TransferV1.TransferFactory_Transfer with + expectedAdmin = instrumentId.admin + transfer = TransferV1.Transfer with + sender = ccipOwner + receiver = receiver + amount = amount + instrumentId = instrumentId + requestedAt = now + executeBefore = executeBefore + inputHoldingCids = inputHoldingCids + meta = MetadataV1.emptyMetadata + extraArgs = MetadataV1.ExtraArgs with + context = registryContext + meta = meta + + choice CancelTransfer : () + controller ccipOwner + do pure () + +-- | Authorization to allocate ccipOwner-held LINK to a settlement leg via the +-- | registry allocation factory. Governance pins the economics (receiver, +-- | amount, transfer leg); the operator supplies the settlement plumbing +-- | (executor, reference, deadlines). The sender of the transfer leg is always +-- | ccipOwner, mirroring the transfer authorization. +template LinkAllocationAuthorization + with + ccipOwner : Party + adminInstanceId : Text + instrumentId : HoldingV1.InstrumentId + receiver : Party + amount : Decimal + transferLegId : Text + reference : Text + expiresAt : Time + where + signatory ccipOwner + observer receiver + + ensure amount > 0.0 + && instrumentId.admin == ccipOwner + + choice ExecuteAllocate : AllocationInstructionV1.AllocationInstructionResult + with + tokenAdminCid : ContractId LinkTokenAdmin -- live admin; stale cid fails fetch + allocationFactoryCid : ContractId AllocationInstructionV1.AllocationFactory + settlementExecutor : Party -- plumbing (operator chooses executor) + settlementRefId : Text + inputHoldingCids : [ContractId HoldingV1.Holding] -- ccipOwner's holdings + allocateBeforeHours : Int + settleBeforeHours : Int + registryContext : MetadataV1.ChoiceContext + meta : MetadataV1.Metadata + controller ccipOwner + do + tokenAdmin <- fetch tokenAdminCid + assertMsg "LinkAllocationAuthorization.Execute: token admin mismatch" + (tokenAdmin.ccipOwner == ccipOwner + && tokenAdmin.instanceId == adminInstanceId + && tokenAdmin.instrumentId == instrumentId) + assertMsg "LinkAllocationAuthorization.Execute: token admin paused" (not tokenAdmin.paused) + + now <- getTime + assertMsg "LinkAllocationAuthorization.Execute: authorization expired" (now <= expiresAt) + assertMsg "LinkAllocationAuthorization.Execute: settleBefore must exceed allocateBefore" + (settleBeforeHours > allocateBeforeHours) + assertMsg "LinkAllocationAuthorization.Execute: no input holdings" (not (null inputHoldingCids)) + + factoryView <- exercise allocationFactoryCid + AllocationInstructionV1.AllocationFactory_PublicFetch with + expectedAdmin = instrumentId.admin + actor = ccipOwner + assertMsg "LinkAllocationAuthorization.Execute: factory admin mismatch" + (factoryView.admin == instrumentId.admin) + + exercise allocationFactoryCid AllocationInstructionV1.AllocationFactory_Allocate with + expectedAdmin = instrumentId.admin + allocation = AllocationV1.AllocationSpecification with + settlement = AllocationV1.SettlementInfo with + executor = settlementExecutor + settlementRef = AllocationV1.Reference with + id = settlementRefId + cid = None -- no contract settlement ref; extend params if needed + requestedAt = now + allocateBefore = addRelTime now (hours allocateBeforeHours) + settleBefore = addRelTime now (hours settleBeforeHours) + meta = MetadataV1.emptyMetadata + transferLegId = transferLegId + transferLeg = AllocationV1.TransferLeg with + sender = ccipOwner + receiver = receiver + amount = amount + instrumentId = instrumentId + meta = MetadataV1.emptyMetadata + requestedAt = now + inputHoldingCids = inputHoldingCids + extraArgs = MetadataV1.ExtraArgs with + context = registryContext + meta = meta + + choice CancelAllocate : () + controller ccipOwner + do pure () diff --git a/contracts/link/token-admin/daml/Link/TokenAdminCodecGen.daml b/contracts/link/token-admin/daml/Link/TokenAdminCodecGen.daml new file mode 100644 index 000000000..a9894db95 --- /dev/null +++ b/contracts/link/token-admin/daml/Link/TokenAdminCodecGen.daml @@ -0,0 +1,136 @@ +-- AUTO-GENERATED by go-daml codegen. DO NOT EDIT. +module Link.TokenAdminCodecGen where + +import DA.Crypto.Text (BytesHex) +import DA.Optional (fromSome) + +import Link.TokenAdminTypes (ApproveMintParams(..), ApproveBurnParams(..), ApproveTransferParams(..), ApproveAllocateParams(..), SetPausedParams(..), SetObserversParams(..)) + +import MCMS.Codec ( + decodeBoolAt, + decodeDecimalAt, + decodeInt64At, + decodePartyAt, + decodePartyList, + decodeTextAt, + encodeBool, + encodeDecimal, + encodeInt64, + encodeParty, + encodePartyList, + encodeText + ) + + + +-- =============================================== +-- | ApproveMintParams +-- =============================================== + +encodeApproveMintParams : ApproveMintParams -> BytesHex +encodeApproveMintParams params = + encodeParty params.recipient + <> encodeDecimal params.amount + <> encodeText params.reference + <> fromSome (encodeInt64 params.validForSeconds) + +decodeApproveMintParamsAt : BytesHex -> Int -> Optional (ApproveMintParams, Int) +decodeApproveMintParamsAt encoded offset = do + (recipient, offset) <- decodePartyAt encoded offset + (amount, offset) <- decodeDecimalAt encoded offset + (reference, offset) <- decodeTextAt encoded offset + (validForSeconds, offset) <- decodeInt64At encoded offset + Some (ApproveMintParams{..}, offset) + + +-- =============================================== +-- | ApproveBurnParams +-- =============================================== + +encodeApproveBurnParams : ApproveBurnParams -> BytesHex +encodeApproveBurnParams params = + encodeParty params.holder + <> encodeDecimal params.maxAmount + <> encodeText params.reference + <> fromSome (encodeInt64 params.validForSeconds) + +decodeApproveBurnParamsAt : BytesHex -> Int -> Optional (ApproveBurnParams, Int) +decodeApproveBurnParamsAt encoded offset = do + (holder, offset) <- decodePartyAt encoded offset + (maxAmount, offset) <- decodeDecimalAt encoded offset + (reference, offset) <- decodeTextAt encoded offset + (validForSeconds, offset) <- decodeInt64At encoded offset + Some (ApproveBurnParams{..}, offset) + + +-- =============================================== +-- | ApproveTransferParams +-- =============================================== + +encodeApproveTransferParams : ApproveTransferParams -> BytesHex +encodeApproveTransferParams params = + encodeParty params.receiver + <> encodeDecimal params.amount + <> encodeText params.reference + <> fromSome (encodeInt64 params.validForSeconds) + +decodeApproveTransferParamsAt : BytesHex -> Int -> Optional (ApproveTransferParams, Int) +decodeApproveTransferParamsAt encoded offset = do + (receiver, offset) <- decodePartyAt encoded offset + (amount, offset) <- decodeDecimalAt encoded offset + (reference, offset) <- decodeTextAt encoded offset + (validForSeconds, offset) <- decodeInt64At encoded offset + Some (ApproveTransferParams{..}, offset) + + +-- =============================================== +-- | ApproveAllocateParams +-- =============================================== + +encodeApproveAllocateParams : ApproveAllocateParams -> BytesHex +encodeApproveAllocateParams params = + encodeParty params.receiver + <> encodeDecimal params.amount + <> encodeText params.transferLegId + <> encodeText params.reference + <> fromSome (encodeInt64 params.validForSeconds) + +decodeApproveAllocateParamsAt : BytesHex -> Int -> Optional (ApproveAllocateParams, Int) +decodeApproveAllocateParamsAt encoded offset = do + (receiver, offset) <- decodePartyAt encoded offset + (amount, offset) <- decodeDecimalAt encoded offset + (transferLegId, offset) <- decodeTextAt encoded offset + (reference, offset) <- decodeTextAt encoded offset + (validForSeconds, offset) <- decodeInt64At encoded offset + Some (ApproveAllocateParams{..}, offset) + + +-- =============================================== +-- | SetPausedParams +-- =============================================== + +encodeSetPausedParams : SetPausedParams -> BytesHex +encodeSetPausedParams params = + encodeBool params.paused + +decodeSetPausedParamsAt : BytesHex -> Int -> Optional (SetPausedParams, Int) +decodeSetPausedParamsAt encoded offset = do + (paused, offset) <- decodeBoolAt encoded offset + Some (SetPausedParams{..}, 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) + + + diff --git a/contracts/link/token-admin/daml/Link/TokenAdminTypes.daml b/contracts/link/token-admin/daml/Link/TokenAdminTypes.daml new file mode 100644 index 000000000..994aed0b6 --- /dev/null +++ b/contracts/link/token-admin/daml/Link/TokenAdminTypes.daml @@ -0,0 +1,56 @@ +-- | Types for LinkTokenAdmin, extracted so the codec generator can produce +-- | encoders/decoders without pulling in the template definitions. +module Link.TokenAdminTypes where + +-- =========================================================================== +-- MCMS Choice Params Types +-- =========================================================================== + +-- | Params for the ApproveMint choice +data ApproveMintParams = ApproveMintParams + with + recipient : Party -- Party receiving the newly minted holdings + amount : Decimal -- Amount to mint + reference : Text -- Operator reference for audit trail + validForSeconds : Int -- Validity window of the created authorization, relative to execution time + deriving (Eq, Show) + +-- | Params for the ApproveBurn choice +data ApproveBurnParams = ApproveBurnParams + with + holder : Party -- Party whose holdings may be burned + maxAmount : Decimal -- Upper bound on the total burned amount + reference : Text -- Operator reference for audit trail + validForSeconds : Int -- Validity window of the created authorization, relative to execution time + deriving (Eq, Show) + +-- | Params for the ApproveTransfer choice +data ApproveTransferParams = ApproveTransferParams + with + receiver : Party -- Party receiving the transferred holdings + amount : Decimal -- Amount to transfer from ccipOwner-held holdings + reference : Text -- Operator reference for audit trail + validForSeconds : Int -- Validity window of the created authorization, relative to execution time + deriving (Eq, Show) + +-- | Params for the ApproveAllocate choice +data ApproveAllocateParams = ApproveAllocateParams + with + receiver : Party -- Party receiving the allocated holdings + amount : Decimal -- Amount to allocate from ccipOwner-held holdings + transferLegId : Text -- Identifier of the transfer leg within the settlement + reference : Text -- Operator reference for audit trail + validForSeconds : Int -- Validity window of the created authorization, relative to execution time + deriving (Eq, Show) + +-- | Params for the SetPaused choice +data SetPausedParams = SetPausedParams + with + paused : Bool -- Emergency stop on new approvals when True + deriving (Eq, Show) + +-- | Params for the SetObservers choice +data SetObserversParams = SetObserversParams + with + observers : [Party] -- Observer set for EDS/off-chain visibility of the admin contract + deriving (Eq, Show) diff --git a/contracts/link/token-admin/test/daml.yaml b/contracts/link/token-admin/test/daml.yaml new file mode 100644 index 000000000..fafa9efad --- /dev/null +++ b/contracts/link/token-admin/test/daml.yaml @@ -0,0 +1,24 @@ +sdk-version: 3.4.11 +name: link-token-admin-test +version: 0.0.1 +source: daml +build-options: + - -Wno-upgrade-interfaces + - -Wno-crypto-text-is-alpha + - "-Werror=unused-dependency" + - -Wno-template-interface-depends-on-daml-script +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 + - ../../../dependencies/splice/splice-api-token-allocation-v1-1.0.0.dar + - ../../../dependencies/splice/splice-api-token-allocation-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 + - ../../.daml/dist/link-2.1.0.dar +dependencies: + - daml-prim + - daml-stdlib + - daml-script + - ../.daml/dist/link-token-admin-1.0.0.dar diff --git a/contracts/link/token-admin/test/daml/Link/TokenAdminTest.daml b/contracts/link/token-admin/test/daml/Link/TokenAdminTest.daml new file mode 100644 index 000000000..ab2e9bd51 --- /dev/null +++ b/contracts/link/token-admin/test/daml/Link/TokenAdminTest.daml @@ -0,0 +1,1099 @@ +-- | Tests for Link.TokenAdmin: the MCMS-governed admin surface for +-- | registry-issued LINK. +-- | +-- | The MCMSReceiver_Entrypoint is exercised directly (as the pool MCMS tests +-- | do) rather than standing up a full MCMS instance. Link.Token.LinkRegistry +-- | serves as the CIP-56 burn-mint/transfer factory double; a minimal stub +-- | covers the allocation factory, which LinkRegistry does not implement. +module Link.TokenAdminTest where + +import Daml.Script +import DA.Assert ((===)) +import DA.Foldable (forA_) +import DA.Map qualified as Map +import DA.Optional (fromSome) +import DA.TextMap qualified as TextMap +import DA.Time (seconds) + +import Link.Token +import Link.TokenAdmin +import Link.TokenAdminTypes +import Link.TokenAdminCodecGen qualified as CodecGen + +import Splice.Api.Token.AllocationInstructionV1 qualified as AllocationInstructionV1 +import Splice.Api.Token.AllocationV1 qualified as AllocationV1 +import Splice.Api.Token.BurnMintV1 qualified as BurnMintV1 +import Splice.Api.Token.HoldingV1 qualified as HoldingV1 +import Splice.Api.Token.MetadataV1 qualified as MetadataV1 +import Splice.Api.Token.TransferInstructionV1 qualified as TransferV1 + +import MCMS.MCMSReceiver (MCMSReceiver(..), MCMSReceiver_Entrypoint(..)) + +import Chainlink.InstanceAddress qualified as InstanceAddress + +-- ======================================================== +-- | Minimal allocation factory stub | +-- ======================================================== + +-- | Stub allocation implementing the direct-completion path: archives the +-- | funding holdings and creates the allocation in one step. The unsupported +-- | lifecycle choices abort, which is all the governor's Execute relies on. +template StubAllocation + with + stubAdmin : Party + stubAllocation : AllocationV1.AllocationSpecification + stubMeta : MetadataV1.Metadata + where + signatory stubAdmin + + interface instance AllocationV1.Allocation for StubAllocation where + view = AllocationV1.AllocationView with + allocation = stubAllocation + holdingCids = [] + meta = stubMeta + + allocation_executeTransferImpl _self _arg = + abort "StubAllocation: Allocation_ExecuteTransfer not supported" + allocation_cancelImpl _self _arg = + abort "StubAllocation: Allocation_Cancel not supported" + allocation_withdrawImpl _self _arg = + abort "StubAllocation: Allocation_Withdraw not supported" + +template StubAllocationFactory + with + stubFactoryAdmin : Party + stubFactoryInstanceId : Text + where + signatory stubFactoryAdmin + ensure InstanceAddress.assertValidInstanceId stubFactoryInstanceId + + interface instance AllocationInstructionV1.AllocationFactory for StubAllocationFactory where + view = AllocationInstructionV1.AllocationFactoryView with + admin = stubFactoryAdmin + meta = MetadataV1.emptyMetadata + + allocationFactory_allocateImpl _self arg = do + assertMsg "StubAllocationFactory.Allocate: expectedAdmin mismatch" + (arg.expectedAdmin == stubFactoryAdmin) + let leg = arg.allocation.transferLeg + forA_ arg.inputHoldingCids \holdingCid -> do + holding <- fetch holdingCid + let hv = view holding + assertMsg "StubAllocationFactory.Allocate: holding owner mismatch" + (hv.owner == leg.sender) + assertMsg "StubAllocationFactory.Allocate: holding instrumentId mismatch" + (hv.instrumentId == leg.instrumentId) + archive holdingCid + allocationCid <- create StubAllocation with + stubAdmin = stubFactoryAdmin + stubAllocation = arg.allocation + stubMeta = MetadataV1.emptyMetadata + pure AllocationInstructionV1.AllocationInstructionResult with + output = AllocationInstructionV1.AllocationInstructionResult_Completed with + allocationCid = toInterfaceContractId @AllocationV1.Allocation allocationCid + senderChangeCids = [] + meta = MetadataV1.emptyMetadata + + allocationFactory_publicFetchImpl _self arg = do + assertMsg "StubAllocationFactory.PublicFetch: expectedAdmin mismatch" + (arg.expectedAdmin == stubFactoryAdmin) + pure AllocationInstructionV1.AllocationFactoryView with + admin = stubFactoryAdmin + meta = MetadataV1.emptyMetadata + +-- ======================================================== +-- | Shared test fixtures | +-- ======================================================== + +toAnyContractId : ContractId a -> ContractId () +toAnyContractId = coerceContractId + +-- | A LinkRegistry acting as the DA registry factory double, created with the +-- | same party as ccipOwner (self-hosted registrar model). +mkRegistry : Party -> HoldingV1.InstrumentId -> Text -> Script (ContractId LinkRegistry) +mkRegistry admin instrumentId registryInstanceId = + submit admin do + createCmd LinkRegistry with + registryAdmin = admin + registryInstrumentId = instrumentId + instanceId = registryInstanceId + registryMeta = MetadataV1.emptyMetadata + +-- | Mint holdings to `owner` directly through the registry factory, as the +-- | operator would when seeding the flow. +mintViaRegistry + : Party + -> ContractId LinkRegistry + -> HoldingV1.InstrumentId + -> Party + -> Decimal + -> Script [ContractId HoldingV1.Holding] +mintViaRegistry admin registryCid instrumentId owner amount = do + result <- submit (actAs admin <> actAs owner) do + exerciseCmd (toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid) + BurnMintV1.BurnMintFactory_BurnMint with + expectedAdmin = admin + instrumentId = instrumentId + inputHoldingCids = [] + outputs = + [ BurnMintV1.BurnMintOutput with + owner = owner + amount = amount + context = MetadataV1.emptyChoiceContext + ] + extraActors = if owner == admin then [] else [owner] + extraArgs = MetadataV1.ExtraArgs with + context = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + pure result.outputCids + +-- | Exercise the entrypoint as MCMS would (ccipOwner authority only). +exerciseEntrypoint + : Party + -> ContractId LinkTokenAdmin + -> Text + -> Text + -> Map.Map Text (ContractId ()) + -> Script (Map.Map Text (ContractId ())) +exerciseEntrypoint admin adminCid functionName operationData contractIds = + submit admin do + exerciseCmd (toInterfaceContractId @MCMSReceiver adminCid) MCMSReceiver_Entrypoint with + functionName + operationData + contractIds + +-- | The freshly created authorization contract for a given recipient. +findMintAuthorization : Party -> Party -> Decimal -> Script (ContractId LinkMintAuthorization) +findMintAuthorization admin recipient amount = do + authorizations <- query @LinkMintAuthorization admin + case [cid | (cid, auth) <- authorizations, auth.recipient == recipient, auth.amount == amount] of + [cid] -> pure cid + _ -> abort "expected exactly one matching LinkMintAuthorization" + +findBurnAuthorization : Party -> Party -> Decimal -> Script (ContractId LinkBurnAuthorization) +findBurnAuthorization admin holder maxAmount = do + authorizations <- query @LinkBurnAuthorization admin + case [cid | (cid, auth) <- authorizations, auth.holder == holder, auth.maxAmount == maxAmount] of + [cid] -> pure cid + _ -> abort "expected exactly one matching LinkBurnAuthorization" + +findTransferAuthorization : Party -> Party -> Decimal -> Script (ContractId LinkTransferAuthorization) +findTransferAuthorization admin receiver amount = do + authorizations <- query @LinkTransferAuthorization admin + case [cid | (cid, auth) <- authorizations, auth.receiver == receiver, auth.amount == amount] of + [cid] -> pure cid + _ -> abort "expected exactly one matching LinkTransferAuthorization" + +findAllocateAuthorization : Party -> Party -> Decimal -> Script (ContractId LinkAllocationAuthorization) +findAllocateAuthorization admin receiver amount = do + authorizations <- query @LinkAllocationAuthorization admin + case [cid | (cid, auth) <- authorizations, auth.receiver == receiver, auth.amount == amount] of + [cid] -> pure cid + _ -> abort "expected exactly one matching LinkAllocationAuthorization" + +-- ======================================================== +-- | MCMS Entrypoint - ApproveMint + Execute | +-- ======================================================== + +testTokenAdminMintViaEntrypoint : Script () +testTokenAdminMintViaEntrypoint = script do + admin <- allocateParty "token-admin-owner" + alice <- allocateParty "token-admin-alice" + + let instanceId = "link-token-admin-mint" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + newContractIds <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + -- ApproveMint is nonconsuming: the returned map is unchanged. + newContractIds === contractIds + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + outputCids <- submit (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + case outputCids of + [outputCid] -> do + Some holding <- queryContractId alice (fromInterfaceContractId @LinkHolding outputCid) + holding.holdingOwner === alice + holding.holdingAdmin === admin + holding.holdingInstrumentId === instrumentId + holding.holdingAmount === 1000.0 + _ -> abort "expected exactly one output holding" + +-- ======================================================== +-- | MCMS Entrypoint - ApproveBurn + Execute | +-- ======================================================== + +-- Note: the locked-holding rejection in ExecuteBurn (isNone hv.lock) is not +-- exercised here — the LinkHolding double always exposes lock = None, so the +-- branch is unreachable with it. The check remains as defense for real DA +-- Registry holdings, which can carry locks. + +testTokenAdminBurnViaEntrypoint : Script () +testTokenAdminBurnViaEntrypoint = script do + admin <- allocateParty "token-admin-owner-burn" + alice <- allocateParty "token-admin-alice-burn" + + let instanceId = "link-token-admin-burn" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-burn" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveBurn" + (CodecGen.encodeApproveBurnParams ApproveBurnParams with + holder = alice + maxAmount = 100.0 + reference = "burn-ref" + validForSeconds = 3600) + contractIds + + inputCids <- mintViaRegistry admin registryCid instrumentId alice 50.0 + + burnAuthCid <- findBurnAuthorization admin alice 100.0 + + submit (actAs admin <> actAs alice) do + exerciseCmd burnAuthCid ExecuteBurn with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + inputHoldingCids = inputCids + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + -- The burned holding is gone. + [inputCid] <- pure inputCids + None <- queryContractId alice (fromInterfaceContractId @LinkHolding inputCid) + pure () + +-- ======================================================== +-- | MCMS Entrypoint - ApproveTransfer + Execute | +-- ======================================================== + +testTokenAdminTransferViaEntrypoint : Script () +testTokenAdminTransferViaEntrypoint = script do + admin <- allocateParty "token-admin-owner-transfer" + alice <- allocateParty "token-admin-alice-transfer" + + let instanceId = "link-token-admin-transfer" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-transfer" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + -- Fund ccipOwner itself: the governor only moves ccipOwner-held LINK. + inputCids <- mintViaRegistry admin registryCid instrumentId admin 20.0 + + -- A receiver preapproval lets the registry settle the transfer directly. + disclosedRegistry <- fromSome <$> queryDisclosure admin registryCid + preapprovalCid <- submit (disclose disclosedRegistry <> actAs alice) do + exerciseCmd registryCid CreateTransferPreapproval with + receiver = alice + existingPreapprovalCid = None + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveTransfer" + (CodecGen.encodeApproveTransferParams ApproveTransferParams with + receiver = alice + amount = 12.0 + reference = "transfer-ref" + validForSeconds = 3600) + contractIds + + transferAuthCid <- findTransferAuthorization admin alice 12.0 + + -- actAs both parties: the nested preapproval Send exercise is jointly + -- authorized by the preapproval signatories (admin, alice). + transferResult <- submit (actAs admin <> actAs alice) do + exerciseCmd transferAuthCid ExecuteTransfer with + tokenAdminCid = adminCid + transferFactoryCid = toInterfaceContractId @TransferV1.TransferFactory registryCid + inputHoldingCids = inputCids + transferTimeoutHours = None + registryContext = MetadataV1.ChoiceContext with + values = TextMap.fromList + [(transferPreapprovalContextKey, MetadataV1.AV_ContractId (coerceContractId preapprovalCid))] + meta = MetadataV1.emptyMetadata + + [senderChangeCid] <- pure transferResult.senderChangeCids + Some senderChange <- queryContractId admin (fromInterfaceContractId @LinkHolding senderChangeCid) + senderChange.holdingOwner === admin + senderChange.holdingAmount === 8.0 + + case transferResult.output of + TransferV1.TransferInstructionResult_Completed receiverHoldingCids -> do + [receiverHoldingCid] <- pure receiverHoldingCids + Some receiverHolding <- queryContractId alice (fromInterfaceContractId @LinkHolding receiverHoldingCid) + receiverHolding.holdingOwner === alice + receiverHolding.holdingAmount === 12.0 + _ -> abort "expected direct transfer completion" + +-- ======================================================== +-- | MCMS Entrypoint - ApproveAllocate + Execute | +-- ======================================================== + +testTokenAdminAllocateViaEntrypoint : Script () +testTokenAdminAllocateViaEntrypoint = script do + admin <- allocateParty "token-admin-owner-allocate" + alice <- allocateParty "token-admin-alice-allocate" + executor <- allocateParty "token-admin-executor" + + let instanceId = "link-token-admin-allocate" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-allocate" + factoryCid <- submit admin do + createCmd StubAllocationFactory with + stubFactoryAdmin = admin + stubFactoryInstanceId = "stub-allocation-factory" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + inputCids <- mintViaRegistry admin registryCid instrumentId admin 15.0 + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveAllocate" + (CodecGen.encodeApproveAllocateParams ApproveAllocateParams with + receiver = alice + amount = 15.0 + transferLegId = "leg-1" + reference = "allocate-ref" + validForSeconds = 3600) + contractIds + + allocateAuthCid <- findAllocateAuthorization admin alice 15.0 + + allocateResult <- submit admin do + exerciseCmd allocateAuthCid ExecuteAllocate with + tokenAdminCid = adminCid + allocationFactoryCid = toInterfaceContractId @AllocationInstructionV1.AllocationFactory factoryCid + settlementExecutor = executor + settlementRefId = "settlement-1" + inputHoldingCids = inputCids + allocateBeforeHours = 1 + settleBeforeHours = 2 + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + case allocateResult.output of + AllocationInstructionV1.AllocationInstructionResult_Completed allocationCid -> do + Some allocation <- queryContractId admin (fromInterfaceContractId @StubAllocation allocationCid) + allocation.stubAllocation.transferLeg.receiver === alice + allocation.stubAllocation.transferLeg.amount === 15.0 + allocation.stubAllocation.transferLegId === "leg-1" + _ -> abort "expected completed allocation" + + -- The funding holdings were archived with the allocation. + [inputCid] <- pure inputCids + None <- queryContractId admin (fromInterfaceContractId @LinkHolding inputCid) + pure () + +-- ======================================================== +-- | Failure paths | +-- ======================================================== + +-- | Burn inputs summing above the approved maxAmount are rejected outright. +testTokenAdminBurnRejectsOverMax : Script () +testTokenAdminBurnRejectsOverMax = script do + admin <- allocateParty "token-admin-owner-overmax" + alice <- allocateParty "token-admin-alice-overmax" + + let instanceId = "link-token-admin-overmax" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-overmax" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveBurn" + (CodecGen.encodeApproveBurnParams ApproveBurnParams with + holder = alice + maxAmount = 100.0 + reference = "burn-ref" + validForSeconds = 3600) + contractIds + + inputCids1 <- mintViaRegistry admin registryCid instrumentId alice 60.0 + inputCids2 <- mintViaRegistry admin registryCid instrumentId alice 50.0 + + burnAuthCid <- findBurnAuthorization admin alice 100.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd burnAuthCid ExecuteBurn with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + inputHoldingCids = inputCids1 <> inputCids2 + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | Burn with a holding owned by another party is rejected. +testTokenAdminBurnRejectsWrongOwner : Script () +testTokenAdminBurnRejectsWrongOwner = script do + admin <- allocateParty "token-admin-owner-wrongowner" + alice <- allocateParty "token-admin-alice-wrongowner" + bob <- allocateParty "token-admin-bob-wrongowner" + + let instanceId = "link-token-admin-wrongowner" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-wrongowner" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveBurn" + (CodecGen.encodeApproveBurnParams ApproveBurnParams with + holder = alice + maxAmount = 100.0 + reference = "burn-ref" + validForSeconds = 3600) + contractIds + + bobCids <- mintViaRegistry admin registryCid instrumentId bob 50.0 + + burnAuthCid <- findBurnAuthorization admin alice 100.0 + + submitMustFail (actAs admin <> actAs alice <> actAs bob) do + exerciseCmd burnAuthCid ExecuteBurn with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + inputHoldingCids = bobCids + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | Burn with a holding of a different instrument is rejected. +testTokenAdminBurnRejectsWrongInstrument : Script () +testTokenAdminBurnRejectsWrongInstrument = script do + admin <- allocateParty "token-admin-owner-wronginst" + alice <- allocateParty "token-admin-alice-wronginst" + + let linkInstrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + otherInstrumentId = HoldingV1.InstrumentId with + admin = admin + id = "OTHER" + + linkRegistryCid <- mkRegistry admin linkInstrumentId "token-admin-registry-link" + otherRegistryCid <- mkRegistry admin otherInstrumentId "token-admin-registry-other" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-wronginst" + instrumentId = linkInstrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-wronginst" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveBurn" + (CodecGen.encodeApproveBurnParams ApproveBurnParams with + holder = alice + maxAmount = 100.0 + reference = "burn-ref" + validForSeconds = 3600) + contractIds + + otherCids <- mintViaRegistry admin otherRegistryCid otherInstrumentId alice 50.0 + + burnAuthCid <- findBurnAuthorization admin alice 100.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd burnAuthCid ExecuteBurn with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory linkRegistryCid + inputHoldingCids = otherCids + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | Execute after the authorization expired is rejected. +testTokenAdminExecuteRejectsExpired : Script () +testTokenAdminExecuteRejectsExpired = script do + admin <- allocateParty "token-admin-owner-expired" + alice <- allocateParty "token-admin-alice-expired" + + let instanceId = "link-token-admin-expired" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-expired" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 1) + contractIds + + passTime (seconds 2) + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | A factory administered by a different party than instrumentId.admin fails +-- | the PublicFetch pin before anything is minted. +testTokenAdminExecuteRejectsForeignFactory : Script () +testTokenAdminExecuteRejectsForeignFactory = script do + admin <- allocateParty "token-admin-owner-foreign" + alice <- allocateParty "token-admin-alice-foreign" + eve <- allocateParty "token-admin-eve-foreign" + + let instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + eveInstrumentId = HoldingV1.InstrumentId with + admin = eve + id = "LINK" + + eveRegistryCid <- mkRegistry eve eveInstrumentId "token-admin-registry-eve" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-foreign" + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-foreign" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + submitMustFail (actAs admin <> actAs alice <> actAs eve) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory eveRegistryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- ======================================================== +-- | Admin state changes | +-- ======================================================== + +-- | SetPaused stops new approvals and re-keys the contractIds map. +testTokenAdminSetPausedBlocksApprovals : Script () +testTokenAdminSetPausedBlocksApprovals = script do + admin <- allocateParty "token-admin-owner-paused" + alice <- allocateParty "token-admin-alice-paused" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-paused" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-paused" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + newContractIds <- exerciseEntrypoint admin adminCid "SetPaused" + (CodecGen.encodeSetPausedParams SetPausedParams with + paused = True) + contractIds + + -- Consuming choice: the map carries the new admin CID. + case Map.lookup instAddr newContractIds of + Some newCid -> assertMsg "expected new admin CID after SetPaused" (newCid /= toAnyContractId adminCid) + None -> abort "expected admin CID in returned map" + + submitMustFail admin do + exerciseCmd (toInterfaceContractId @MCMSReceiver adminCid) MCMSReceiver_Entrypoint with + functionName = "ApproveMint" + operationData = CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600 + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + pure () + +-- | SetObservers keeps the observer set governable and re-keys the map. +testTokenAdminSetObservers : Script () +testTokenAdminSetObservers = script do + admin <- allocateParty "token-admin-owner-observers" + edsObserver <- allocateParty "token-admin-eds-observer" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-observers" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-observers" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + newContractIds <- exerciseEntrypoint admin adminCid "SetObservers" + (CodecGen.encodeSetObserversParams SetObserversParams with + observers = [edsObserver]) + contractIds + + case Map.lookup instAddr newContractIds of + Some newCid -> do + assertMsg "expected new admin CID after SetObservers" (newCid /= toAnyContractId adminCid) + Some pausedAdmin <- queryContractId admin (coerceContractId newCid : ContractId LinkTokenAdmin) + pausedAdmin.observers === [edsObserver] + None -> abort "expected admin CID in returned map" + +-- ======================================================== +-- | Entrypoint hardening | +-- ======================================================== + +-- | Unknown function names and malformed operationData are rejected. +testTokenAdminUnknownFunctionAndBadParams : Script () +testTokenAdminUnknownFunctionAndBadParams = script do + admin <- allocateParty "token-admin-owner-unknown" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-unknown" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-unknown" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + targetAsMcmsReceiver = toInterfaceContractId @MCMSReceiver adminCid + + -- Unknown function name. + submitMustFail admin do + exerciseCmd targetAsMcmsReceiver MCMSReceiver_Entrypoint with + functionName = "Nope" + operationData = "" + contractIds + + -- Invalid hex fails decoding. + submitMustFail admin do + exerciseCmd targetAsMcmsReceiver MCMSReceiver_Entrypoint with + functionName = "ApproveMint" + operationData = "zz" + contractIds + + -- Valid encoding with a trailing byte. + let encoded = CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = admin + amount = 1.0 + reference = "ref" + validForSeconds = 60 + submitMustFail admin do + exerciseCmd targetAsMcmsReceiver MCMSReceiver_Entrypoint with + functionName = "ApproveMint" + operationData = encoded <> "00" + contractIds + + -- Empty contractIds map. + submitMustFail admin do + exerciseCmd targetAsMcmsReceiver MCMSReceiver_Entrypoint with + functionName = "ApproveMint" + operationData = encoded + contractIds = Map.empty + + pure () + +-- | The MCMS dispatcher precondition: the target must have exactly one +-- | signatory, equal to the party encoded in targetInstanceAddress. +testTokenAdminSingleSignatory : Script () +testTokenAdminSingleSignatory = script do + admin <- allocateParty "token-admin-owner-signatory" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-signatory" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + paused = False + observers = [] + + Some fetched <- queryContractId admin adminCid + signatory fetched === [admin] + +-- | Only ccipOwner authority can drive the entrypoint. +testTokenAdminEntrypointRequiresCcipOwner : Script () +testTokenAdminEntrypointRequiresCcipOwner = script do + admin <- allocateParty "token-admin-owner-auth" + alice <- allocateParty "token-admin-alice-auth" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-auth" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText "link-token-admin-auth" admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + let encoded = CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600 + + submitMustFail alice do + exerciseCmd (toInterfaceContractId @MCMSReceiver adminCid) MCMSReceiver_Entrypoint with + functionName = "ApproveMint" + operationData = encoded + contractIds + + pure () + +-- ======================================================== +-- | Execute* pause enforcement | +-- ======================================================== + +-- | A paused admin contract blocks Execute even for a live, unexpired +-- | authorization: pause stops outstanding operations, not just approvals. +testTokenAdminExecuteBlockedWhenPaused : Script () +testTokenAdminExecuteBlockedWhenPaused = script do + admin <- allocateParty "token-admin-owner-exec-paused" + alice <- allocateParty "token-admin-alice-exec-paused" + + let instanceId = "link-token-admin-exec-paused" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-exec-paused" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + pausedContractIds <- exerciseEntrypoint admin adminCid "SetPaused" + (CodecGen.encodeSetPausedParams SetPausedParams with + paused = True) + contractIds + + pausedAdminCid <- case Map.lookup instAddr pausedContractIds of + Some cid -> pure (coerceContractId cid : ContractId LinkTokenAdmin) + None -> abort "expected admin CID in returned map" + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = pausedAdminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | The admin CID rotates on every consuming choice, so a pre-rotation CID is +-- | archived and fails the fetch inside Execute. +testTokenAdminExecuteRejectsStaleAdminCid : Script () +testTokenAdminExecuteRejectsStaleAdminCid = script do + admin <- allocateParty "token-admin-owner-stale" + alice <- allocateParty "token-admin-alice-stale" + + let instanceId = "link-token-admin-stale" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-stale" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + -- SetObservers rotates the admin without pausing it, isolating the fetch + -- failure from the paused assert. + _ <- exerciseEntrypoint admin adminCid "SetObservers" + (CodecGen.encodeSetObserversParams SetObserversParams with + observers = []) + contractIds + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = adminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | Execute with an admin contract from a different admin instance fails the +-- | mismatch assert even though ccipOwner and instrumentId agree. +testTokenAdminExecuteRejectsForeignAdmin : Script () +testTokenAdminExecuteRejectsForeignAdmin = script do + admin <- allocateParty "token-admin-owner-foreign-admin" + alice <- allocateParty "token-admin-alice-foreign-admin" + + let instanceId = "link-token-admin-foreign-admin" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-foreign-admin" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + otherAdminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId = "link-token-admin-other-instance" + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + submitMustFail (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = otherAdminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + pure () + +-- | Unpausing restores Execute: pause is a stop, not a kill switch. +testTokenAdminUnpauseAllowsExecute : Script () +testTokenAdminUnpauseAllowsExecute = script do + admin <- allocateParty "token-admin-owner-unpause" + alice <- allocateParty "token-admin-alice-unpause" + + let instanceId = "link-token-admin-unpause" + instrumentId = HoldingV1.InstrumentId with + admin = admin + id = "LINK" + + registryCid <- mkRegistry admin instrumentId "token-admin-registry-unpause" + + adminCid <- submit admin do + createCmd LinkTokenAdmin with + ccipOwner = admin + instanceId + instrumentId + paused = False + observers = [] + + let instAddr = InstanceAddress.makeText instanceId admin + contractIds = Map.fromList [(instAddr, toAnyContractId adminCid)] + + _ <- exerciseEntrypoint admin adminCid "ApproveMint" + (CodecGen.encodeApproveMintParams ApproveMintParams with + recipient = alice + amount = 1000.0 + reference = "mint-ref" + validForSeconds = 3600) + contractIds + + pausedContractIds <- exerciseEntrypoint admin adminCid "SetPaused" + (CodecGen.encodeSetPausedParams SetPausedParams with + paused = True) + contractIds + + pausedAdminCid <- case Map.lookup instAddr pausedContractIds of + Some cid -> pure (coerceContractId cid : ContractId LinkTokenAdmin) + None -> abort "expected admin CID in returned map" + + unpausedContractIds <- exerciseEntrypoint admin pausedAdminCid "SetPaused" + (CodecGen.encodeSetPausedParams SetPausedParams with + paused = False) + pausedContractIds + + liveAdminCid <- case Map.lookup instAddr unpausedContractIds of + Some cid -> pure (coerceContractId cid : ContractId LinkTokenAdmin) + None -> abort "expected admin CID in returned map" + + mintAuthCid <- findMintAuthorization admin alice 1000.0 + + outputCids <- submit (actAs admin <> actAs alice) do + exerciseCmd mintAuthCid ExecuteMint with + tokenAdminCid = liveAdminCid + burnMintFactoryCid = toInterfaceContractId @BurnMintV1.BurnMintFactory registryCid + registryContext = MetadataV1.emptyChoiceContext + meta = MetadataV1.emptyMetadata + + case outputCids of + [outputCid] -> do + Some holding <- queryContractId alice (fromInterfaceContractId @LinkHolding outputCid) + holding.holdingOwner === alice + holding.holdingAmount === 1000.0 + _ -> abort "expected exactly one output holding" diff --git a/contracts/link/token-admin/test/daml/Main.daml b/contracts/link/token-admin/test/daml/Main.daml new file mode 100644 index 000000000..7a591cc92 --- /dev/null +++ b/contracts/link/token-admin/test/daml/Main.daml @@ -0,0 +1,26 @@ +module Main where + +import Daml.Script + +import Link.TokenAdminTest + +runAll : Script () +runAll = script do + testTokenAdminMintViaEntrypoint + testTokenAdminBurnViaEntrypoint + testTokenAdminTransferViaEntrypoint + testTokenAdminAllocateViaEntrypoint + testTokenAdminBurnRejectsOverMax + testTokenAdminBurnRejectsWrongOwner + testTokenAdminBurnRejectsWrongInstrument + testTokenAdminExecuteRejectsExpired + testTokenAdminExecuteRejectsForeignFactory + testTokenAdminSetPausedBlocksApprovals + testTokenAdminSetObservers + testTokenAdminUnknownFunctionAndBadParams + testTokenAdminSingleSignatory + testTokenAdminEntrypointRequiresCcipOwner + testTokenAdminExecuteBlockedWhenPaused + testTokenAdminExecuteRejectsStaleAdminCid + testTokenAdminExecuteRejectsForeignAdmin + testTokenAdminUnpauseAllowsExecute diff --git a/contracts/multi-package.yaml b/contracts/multi-package.yaml index 021cf6a6e..3d2c8d215 100644 --- a/contracts/multi-package.yaml +++ b/contracts/multi-package.yaml @@ -2,6 +2,8 @@ packages: - coin - link - link/test + - link/token-admin + - link/token-admin/test - chainlink/api diff --git a/integration-tests/mcms/link_token_admin_registry_test.go b/integration-tests/mcms/link_token_admin_registry_test.go new file mode 100644 index 000000000..0f6fdbace --- /dev/null +++ b/integration-tests/mcms/link_token_admin_registry_test.go @@ -0,0 +1,497 @@ +package tests + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + apiv2 "github.com/digital-asset/dazl-client/v8/go/api/com/daml/ledger/api/v2" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-deployments-framework/chain/canton" + "github.com/smartcontractkit/go-daml/pkg/bind" + "github.com/smartcontractkit/go-daml/pkg/types" + + "github.com/smartcontractkit/chainlink-canton/contracts/v2" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings" + "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/link/tokenadmin" + mcmsApi "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/mcms/api" + splice "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_holding_v1" + spliceMeta "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/splice/splice_api_token_metadata_v1" + registryholding "github.com/smartcontractkit/chainlink-canton/contracts/v2/bindings/generated/utility/registry_holding_v0" + rkledger "github.com/smartcontractkit/chainlink-canton/registry-kit/ledger" + "github.com/smartcontractkit/chainlink-canton/registry-kit/registry" + "github.com/smartcontractkit/chainlink-canton/testhelpers" +) + +const ( + linkTokenAdminInstrumentID = "LINK" + linkTokenAdminTestChainID = int64(1) +) + +// TestLinkTokenAdminRegistry_ApproveAndExecute drives the full governed +// lifecycle against the real DA Registry: MCMS-governed ApproveMint/ApproveBurn +// dispatched through the bypasser role, then operator ExecuteMint/ExecuteBurn +// one-step BurnMintFactory_BurnMint on the bootstrap AllocationFactory — both +// self-hosted (registrar as recipient) and third-party recipient (extraActors +// path) — plus the Execute* pause enforcement (stale admin cid, paused admin, +// unpause). +// +//nolint:paralleltest // Exclusive CTF environment +func TestLinkTokenAdminRegistry_ApproveAndExecute(t *testing.T) { + env := testhelpers.NewTestEnvironment(t, testhelpers.WithNumberOfParticipants(1)) + participant := env.Chain.Participants[0] + party := participant.PartyID + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + testhelpers.ContractCleanup(t, ctx, env.Chain.Participants) + }) + + // The registry utility stack plus link-token-admin (which embeds the splice + // token interfaces, chainlink-api and mcms-api) and mcms-core for the MCMS + // contract itself. + utilityDars, err := registry.LoadUtilityDARs() + require.NoError(t, err, "load registry utility DARs") + tokenAdminDar, err := contracts.GetDar(contracts.LinkTokenAdmin, contracts.DevVersion) + require.NoError(t, err) + mcmsDar, err := contracts.GetDar(contracts.MCMSCore, contracts.DevVersion) + require.NoError(t, err) + _, err = testhelpers.UploadDARstoMultipleParticipants(t.Context(), append(utilityDars, tokenAdminDar, mcmsDar), participant) + require.NoError(t, err) + + ctx := t.Context() + client := rkledger.NewCTFClient(participant) + + bootstrap, err := registry.BootstrapServices(ctx, client, party, linkTokenAdminInstrumentID) + require.NoError(t, err) + + instrumentId := splice.InstrumentId{ + Admin: types.PARTY(party), + Id: types.TEXT(linkTokenAdminInstrumentID), + } + + adminInstanceID := "link-token-admin-itest-" + uuid.NewString()[:8] + adminInstanceAddr := fmt.Sprintf("%s@%s", adminInstanceID, party) + + liveAdminCid := createLinkTokenAdmin(t, ctx, client, party, adminInstanceID, instrumentId) + + // Third-party recipient: the realistic mainnet path, where the minted + // holding's owner differs from the registrar and the factory demands the + // recipient's authority through extraActors. + recipient := testhelpers.AllocateParty(t, participant, "lta-recipient") + testhelpers.GrantCanActAs(t, participant, recipient) + + signers := createSigners(t) + sortedSigners := SortSignersByAddress(signers) + bypasserNonce := 0 + cfg := New2of3Config(signers) + + baseMcmsID := "mcms-lta-" + uuid.NewString()[:8] + mcmsInstanceAddr := fmt.Sprintf("%s@%s", baseMcmsID, party) + mcmsCid := createMCMSMultiRole(t, participant, party, linkTokenAdminTestChainID, baseMcmsID, cfg, 0, nil) + mcmsEncoder := NewMCMSEncoder() + + adminContract := tokenadmin.NewContract(fmt.Sprintf("#%s", tokenadmin.PackageName), "Link.TokenAdmin", "LinkTokenAdmin") + + instDisclosed, err := registry.DiscloseByID(ctx, client, party, bootstrap.InstrumentConfiguration) + require.NoError(t, err) + mintCtx := registry.MintChoiceContext(bootstrap.InstrumentConfiguration, false) + + // Governed mint to the registrar itself: ApproveMint via MCMS, then + // ExecuteMint one-step against the real registry AllocationFactory. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "ApproveMint", + tokenadmin.ApproveMintParams{ + Recipient: types.PARTY(party), + Amount: types.NUMERIC("10.0"), + Reference: types.TEXT("itest-mint-1"), + ValidForSeconds: types.INT64(3600), + }, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + mintAuthCid := findMintAuthorization(t, ctx, participant, party, "itest-mint-1") + + mintedHoldingCid := executeMintOnRegistry(t, ctx, client, []string{party}, mintAuthCid, liveAdminCid, + bootstrap.AllocationFactory, party, "10.0", mintCtx, instDisclosed) + + // Governed burn round-trip of the registrar-held holding. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "ApproveBurn", + tokenadmin.ApproveBurnParams{ + Holder: types.PARTY(party), + MaxAmount: types.NUMERIC("10.0"), + Reference: types.TEXT("itest-burn-1"), + ValidForSeconds: types.INT64(3600), + }, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + burnAuthCid := findBurnAuthorization(t, ctx, participant, party, "itest-burn-1") + + executeBurnOnRegistry(t, ctx, client, []string{party}, burnAuthCid, liveAdminCid, + bootstrap.AllocationFactory, []string{mintedHoldingCid}, mintCtx, instDisclosed) + + // Governed mint to the third-party recipient: the extraActors path against + // the real registry factory. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "ApproveMint", + tokenadmin.ApproveMintParams{ + Recipient: types.PARTY(recipient), + Amount: types.NUMERIC("5.0"), + Reference: types.TEXT("itest-mint-3"), + ValidForSeconds: types.INT64(3600), + }, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + recipientAuthCid := findMintAuthorization(t, ctx, participant, party, "itest-mint-3") + + recipientHoldingCid := executeMintOnRegistry(t, ctx, client, []string{party, recipient}, recipientAuthCid, liveAdminCid, + bootstrap.AllocationFactory, recipient, "5.0", mintCtx, instDisclosed) + + // Burn the recipient-held holding: the burn-side extraActors path. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "ApproveBurn", + tokenadmin.ApproveBurnParams{ + Holder: types.PARTY(recipient), + MaxAmount: types.NUMERIC("5.0"), + Reference: types.TEXT("itest-burn-2"), + ValidForSeconds: types.INT64(3600), + }, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + recipientBurnAuthCid := findBurnAuthorization(t, ctx, participant, party, "itest-burn-2") + + executeBurnOnRegistry(t, ctx, client, []string{party, recipient}, recipientBurnAuthCid, liveAdminCid, + bootstrap.AllocationFactory, []string{recipientHoldingCid}, mintCtx, instDisclosed) + + // Pause enforcement: a fresh approval that must not survive rotation or pause. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "ApproveMint", + tokenadmin.ApproveMintParams{ + Recipient: types.PARTY(party), + Amount: types.NUMERIC("30.0"), + Reference: types.TEXT("itest-mint-2"), + ValidForSeconds: types.INT64(3600), + }, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + pausedAuthCid := findMintAuthorization(t, ctx, participant, party, "itest-mint-2") + + // SetObservers rotates the admin cid without pausing, isolating the fetch + // failure from the paused assert. + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "SetObservers", + tokenadmin.SetObserversParams{Observers: nil}, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + staleAdminCid := liveAdminCid + liveAdminCid = findLinkTokenAdminCid(t, ctx, participant, party, adminInstanceID) + require.NotEqual(t, staleAdminCid, liveAdminCid, "SetObservers should rotate the admin cid") + + _, err = client.SubmitExerciseMulti(ctx, []string{party}, tokenadmin.LinkMintAuthorization{}, pausedAuthCid, "ExecuteMint", + tokenadmin.ExecuteMint{ + TokenAdminCid: types.CONTRACT_ID(staleAdminCid), + BurnMintFactoryCid: types.CONTRACT_ID(bootstrap.AllocationFactory), + RegistryContext: mintCtx, + Meta: spliceMeta.Metadata{}, + }, + []*apiv2.DisclosedContract{instDisclosed}) + require.ErrorContains(t, err, "CONTRACT_NOT_FOUND", "ExecuteMint with a stale admin cid must fail at fetch") + require.NotContains(t, err.Error(), "token admin", "stale admin cid must fail before the admin asserts") + + mcmsCid = dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "SetPaused", + tokenadmin.SetPausedParams{Paused: types.BOOL(true)}, + adminInstanceAddr, liveAdminCid) + bypasserNonce++ + + liveAdminCid = findLinkTokenAdminCid(t, ctx, participant, party, adminInstanceID) + + _, err = client.SubmitExerciseMulti(ctx, []string{party}, tokenadmin.LinkMintAuthorization{}, pausedAuthCid, "ExecuteMint", + tokenadmin.ExecuteMint{ + TokenAdminCid: types.CONTRACT_ID(liveAdminCid), + BurnMintFactoryCid: types.CONTRACT_ID(bootstrap.AllocationFactory), + RegistryContext: mintCtx, + Meta: spliceMeta.Metadata{}, + }, + []*apiv2.DisclosedContract{instDisclosed}) + require.ErrorContains(t, err, "token admin paused", "ExecuteMint with a paused admin must fail on the pause assert") + + dispatchOnAdminViaMCMS(t, participant, party, mcmsEncoder, mcmsCid, mcmsInstanceAddr, bypasserNonce, sortedSigners, + adminContract, "SetPaused", + tokenadmin.SetPausedParams{Paused: types.BOOL(false)}, + adminInstanceAddr, liveAdminCid) + + liveAdminCid = findLinkTokenAdminCid(t, ctx, participant, party, adminInstanceID) + + unpausedHoldingCid := executeMintOnRegistry(t, ctx, client, []string{party}, pausedAuthCid, liveAdminCid, + bootstrap.AllocationFactory, party, "30.0", mintCtx, instDisclosed) + require.NotEmpty(t, unpausedHoldingCid) +} + +func createLinkTokenAdmin( + t *testing.T, + ctx context.Context, + client rkledger.Client, + party, instanceID string, + instrumentId splice.InstrumentId, +) string { + t.Helper() + + res, err := client.SubmitCreate(ctx, party, tokenadmin.LinkTokenAdmin{ + CcipOwner: types.PARTY(party), + InstanceId: types.TEXT(instanceID), + InstrumentId: instrumentId, + Paused: false, + Observers: nil, + }) + require.NoError(t, err) + + cid, ok := rkledger.CreatedContractID(res.GetTransaction(), "LinkTokenAdmin") + require.True(t, ok, "LinkTokenAdmin not created") + + return cid +} + +// dispatchOnAdminViaMCMS bypasser-executes a single LinkTokenAdmin choice and +// returns the refreshed MCMS cid. `nonce` is the bypasser root's operation +// count before this dispatch — SetRoot requires preOpCount to match it, so the +// caller must pass an incrementing counter. `liveAdminCid` must be the current +// admin cid: consuming choices (SetPaused, SetObservers) rotate it, so the +// caller must re-query afterwards. +func dispatchOnAdminViaMCMS( + t *testing.T, + participant canton.Participant, + party string, + mcmsEncoder mcmsApi.MCMSEncoder, + mcmsCid, mcmsInstanceAddr string, + nonce int, + sortedSigners []*MCMSSigner, + adminContract *tokenadmin.Contract, + functionName string, + params any, + adminInstanceAddr, liveAdminCid string, +) string { + t.Helper() + + encoded, err := encodeAdminChoice(adminContract, params) + require.NoError(t, err) + + calls := []mcmsApi.TimelockCall{{ + TargetInstanceAddress: types.TEXT(adminInstanceAddr), + FunctionName: types.TEXT(functionName), + OperationData: types.TEXT(encoded.OperationData), + }} + + bypasserChoice := MustEncodeBypasserExecuteBatch(t, mcmsEncoder, mcmsApi.BypasserExecuteBatchParams{Calls: calls}) + + bypasserMultisigID := MakeMcmsId(mcmsInstanceAddr, MCMSRoleBypasser) + proposal := NewMCMSProposal(int(linkTokenAdminTestChainID), bypasserMultisigID, nonce, false). + AddOperation(mcmsInstanceAddr, bypasserChoice.Choice, bypasserChoice.OperationData). + Build() + + validUntil := time.Now().Add(1 * time.Hour) + signatures, err := proposal.Sign(validUntil, sortedSigners[:2]) + require.NoError(t, err) + + mcmsCid = setRootWithRole(t, participant, party, mcmsCid, "Bypasser", proposal, validUntil, signatures) + + opProof, err := proposal.GetOpProof(0) + require.NoError(t, err) + + return bypasserExecuteBatch(t, participant, party, mcmsCid, map[string]string{ + adminInstanceAddr: liveAdminCid, + }, proposal.Operations[0], opProof) +} + +func encodeAdminChoice(adminContract *tokenadmin.Contract, params any) (*bind.EncodedChoice, error) { + encoder := adminContract.Encoder() + + switch p := params.(type) { + case tokenadmin.ApproveMintParams: + return encoder.ApproveMintParams(p) + case tokenadmin.ApproveBurnParams: + return encoder.ApproveBurnParams(p) + case tokenadmin.SetPausedParams: + return encoder.SetPausedParams(p) + case tokenadmin.SetObserversParams: + return encoder.SetObserversParams(p) + } + + return nil, fmt.Errorf("unsupported LinkTokenAdmin choice params: %T", params) +} + +// executeMintOnRegistry submits ExecuteMint and returns the created registry +// Holding cid, asserting its owner and amount. This is the one-step +// BurnMintFactory_BurnMint path against the real registry AllocationFactory; +// `actAs` must cover ccipOwner plus the recipient when they differ. +func executeMintOnRegistry( + t *testing.T, + ctx context.Context, + client rkledger.Client, + actAs []string, + mintAuthCid, liveAdminCid, allocationFactoryCid, expectedOwner, expectedAmount string, + mintCtx spliceMeta.ChoiceContext, + instDisclosed *apiv2.DisclosedContract, +) string { + t.Helper() + + res, err := client.SubmitExerciseMulti(ctx, actAs, tokenadmin.LinkMintAuthorization{}, mintAuthCid, "ExecuteMint", + tokenadmin.ExecuteMint{ + TokenAdminCid: types.CONTRACT_ID(liveAdminCid), + BurnMintFactoryCid: types.CONTRACT_ID(allocationFactoryCid), + RegistryContext: mintCtx, + Meta: spliceMeta.Metadata{}, + }, + []*apiv2.DisclosedContract{instDisclosed}) + require.NoError(t, err, "ExecuteMint one-step against the registry AllocationFactory") + + for _, event := range res.GetTransaction().GetEvents() { + if created := event.GetCreated(); created != nil && created.GetTemplateId().GetEntityName() == "Holding" { + holding, err := bindings.UnmarshalCreatedEvent[registryholding.Holding](created) + require.NoError(t, err) + require.Equal(t, expectedOwner, string(holding.Owner), "minted holding owner") + assertDecimalEqual(t, holding.Amount, expectedAmount) + + return created.GetContractId() + } + } + + t.Fatal("no registry Holding created by ExecuteMint") + + return "" +} + +// executeBurnOnRegistry submits ExecuteBurn and asserts every input holding is +// archived. `actAs` must cover ccipOwner plus the holder when they differ. +func executeBurnOnRegistry( + t *testing.T, + ctx context.Context, + client rkledger.Client, + actAs []string, + burnAuthCid, liveAdminCid, allocationFactoryCid string, + inputHoldingCids []string, + mintCtx spliceMeta.ChoiceContext, + instDisclosed *apiv2.DisclosedContract, +) { + t.Helper() + + inputs := make([]types.CONTRACT_ID, 0, len(inputHoldingCids)) + for _, cid := range inputHoldingCids { + inputs = append(inputs, types.CONTRACT_ID(cid)) + } + + res, err := client.SubmitExerciseMulti(ctx, actAs, tokenadmin.LinkBurnAuthorization{}, burnAuthCid, "ExecuteBurn", + tokenadmin.ExecuteBurn{ + TokenAdminCid: types.CONTRACT_ID(liveAdminCid), + BurnMintFactoryCid: types.CONTRACT_ID(allocationFactoryCid), + InputHoldingCids: inputs, + RegistryContext: mintCtx, + Meta: spliceMeta.Metadata{}, + }, + []*apiv2.DisclosedContract{instDisclosed}) + require.NoError(t, err, "ExecuteBurn against the registry AllocationFactory") + + burned := make(map[string]bool) + for _, event := range res.GetTransaction().GetEvents() { + if archived := event.GetArchived(); archived != nil && archived.GetTemplateId().GetEntityName() == "Holding" { + burned[archived.GetContractId()] = true + } + } + for _, cid := range inputHoldingCids { + require.True(t, burned[cid], "holding %s should be archived by ExecuteBurn", cid) + } +} + +// assertDecimalEqual compares two decimal strings by value, independent of scale. +func assertDecimalEqual(t *testing.T, got types.NUMERIC, want string) { + t.Helper() + + gotRat, ok := new(big.Rat).SetString(string(got)) + require.True(t, ok, "invalid decimal %q", string(got)) + wantRat, ok := new(big.Rat).SetString(want) + require.True(t, ok, "invalid decimal %q", want) + require.Equal(t, 0, gotRat.Cmp(wantRat), "decimal %s, want %s", string(got), want) +} + +func findLinkTokenAdminCid( + t *testing.T, + ctx context.Context, + participant canton.Participant, + party, instanceID string, +) string { + t.Helper() + + active, err := testhelpers.ListActiveContractsByTemplateId(ctx, participant, contracts.IdentifierFromBinding(tokenadmin.LinkTokenAdmin{})) + require.NoError(t, err) + + for _, ac := range active { + admin, err := bindings.UnmarshalCreatedEvent[tokenadmin.LinkTokenAdmin](ac.GetCreatedEvent()) + require.NoError(t, err) + if string(admin.InstanceId) == instanceID && string(admin.CcipOwner) == party { + return ac.GetCreatedEvent().GetContractId() + } + } + + t.Fatalf("no active LinkTokenAdmin with instanceId %s", instanceID) + + return "" +} + +func findMintAuthorization( + t *testing.T, + ctx context.Context, + participant canton.Participant, + party, reference string, +) string { + t.Helper() + + active, err := testhelpers.ListActiveContractsByTemplateId(ctx, participant, contracts.IdentifierFromBinding(tokenadmin.LinkMintAuthorization{})) + require.NoError(t, err) + + for _, ac := range active { + auth, err := bindings.UnmarshalCreatedEvent[tokenadmin.LinkMintAuthorization](ac.GetCreatedEvent()) + require.NoError(t, err) + if string(auth.CcipOwner) == party && string(auth.Reference) == reference { + return ac.GetCreatedEvent().GetContractId() + } + } + + t.Fatalf("no active LinkMintAuthorization with reference %s", reference) + + return "" +} + +func findBurnAuthorization( + t *testing.T, + ctx context.Context, + participant canton.Participant, + party, reference string, +) string { + t.Helper() + + active, err := testhelpers.ListActiveContractsByTemplateId(ctx, participant, contracts.IdentifierFromBinding(tokenadmin.LinkBurnAuthorization{})) + require.NoError(t, err) + + for _, ac := range active { + auth, err := bindings.UnmarshalCreatedEvent[tokenadmin.LinkBurnAuthorization](ac.GetCreatedEvent()) + require.NoError(t, err) + if string(auth.CcipOwner) == party && string(auth.Reference) == reference { + return ac.GetCreatedEvent().GetContractId() + } + } + + t.Fatalf("no active LinkBurnAuthorization with reference %s", reference) + + return "" +}