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 ""
+}