- Overview
- Tutorials
- Getting started
- Get started with Canton and the JSON Ledger API
- Get Started with Canton, the JSON Ledger API, TypeScript and WebSockets
- Get Started with Canton, the JSON Ledger API, and TypeScript
- Get started with Canton Network App Dev Quickstart
- Using the JSON Ledger API
- Get started with smart contract development
- Basic contracts
- Test templates using Daml scripts
- Build the Daml Archive (.dar) file
- Data types
- Transform contracts using choices
- Add constraints to a contract
- Parties and authority
- Compose choices
- Handle exceptions (Deprecated)
- Work with dependencies
- Functional programming 101
- The Daml standard library
- Test Daml contracts
- Next steps
- Application development
- Getting started
- Development how-tos
- Component how-tos
- Explanations
- References
- Application development
- Smart contract development
- Daml language cheat sheet
- Daml language reference
- Overview: Template Structure
- Reference: Templates
- Reference: Choices
- Reference: Updates
- Reference: Data Types
- Reference: Built-in Functions
- Reference: Expressions
- Reference: Functions
- Reference: Daml File Structure
- Reference: Daml Packages
- Reference: Contract Keys
- Reference: Interfaces
- Reference: Exceptions (Deprecated)
- Fixity, Associativity and Precedence
- Daml standard library
- DA.Action.State.Class
- DA.Action.State
- DA.Action
- DA.Assert
- DA.Bifunctor
- DA.Crypto.Text
- DA.Date
- DA.Either
- DA.Exception
- DA.Fail
- DA.Foldable
- DA.Functor
- DA.Internal.Interface.AnyView.Types
- DA.Internal.Interface.AnyView
- DA.List.BuiltinOrder
- DA.List.Total
- DA.List
- DA.Logic
- DA.Map
- DA.Math
- DA.Monoid
- DA.NonEmpty.Types
- DA.NonEmpty
- DA.Numeric
- DA.Optional
- DA.Record
- DA.Semigroup
- DA.Set
- DA.Stack
- DA.Text
- DA.TextMap
- DA.Time
- DA.Traversable
- DA.Tuple
- DA.Validation
- GHC.Show.Text
- GHC.Tuple.Check
- Prelude
- Daml Script
- Smart contract upgrading reference
- Glossary of concepts
Interfaces¶
This section contains a step-by-step tutorial that explains what interfaces are, and how they can be used to provide a stable public API for contracts.
This tutorial should be of particular interest if you would like to learn how CIP-0056 (the Canton Network Token Standard) works.
Hint
Remember that you can load all the code for this section into a folder called intro-interfaces by running dpm new intro-interfaces --template daml-intro-interfaces
Community library¶
This example uses the theme of a community library. Library members can donate new items to the library catalog, and borrow items in the catalog.
The members of this library currently have books and discs they want to donate. These are clearly different things and each have unique fields (e.g. books have authors, discs have formats).
However, this is not an exhaustive list: in the future, new members may want to donate entirely new types of items, for example magazines. We would like to be able to do that without upgrading the Daml code of the library itself.
This is possible using interfaces: these let us define a stable API (items that can be loaned), while allowing for several implementations that can be independently changed.
Project structure¶
In About project structures you learned that it is important to keep the scripts and templates in separate packages.
This is even more important when dealing with interfaces: packages containing interfaces can never be upgraded.
By placing the interface in a dedicated package, and using dedicated packages for the contract templates implementing the interfaces, we will be able to upgrade the latter.
We end up with the following packages:
catalog-itemDefines the
CatalogIteminterface, the API for items we can borrow from the librarybookDefines a
Bookcontract, and itsCatalogItemimplementationdiscDefines a
Disccontract, and itsCatalogItemimplementationlibraryDefines a community library in terms of
CatalogItem, so it can be extended with new catalog items (e.g. magazines) without affecting this packagelibrary-testsContains some basic Daml Script tests to show how the library can be used
Remember that you can dpm build --all at the root of the multi-package project to build all sub-packages.
Two contract templates¶
First, let’s take a look at the contract templates underpinning our library. Note that these live in separate packages. In a real-world use case, you can imagine that these packages are authored by different institutions.
We have a contract template for books:
template Book
with
owner : Party
title : Text
loanedTo : Optional Party
author : Text
observers : [Party]
where
signatory owner
observer observers
And we have a contract template for discs:
data DiscFormat = DVD | BluRay
deriving (Eq, Show)
template Disc
with
owner : Party
title : Text
borrowedBy : Optional Party
format : DiscFormat -- only on Disc
observers : [Party]
where
signatory owner
observer observers
Note how discs has a slightly different set of fields:
We have different info:
formatrather than anauthorA different naming convention is used for borrowing:
borrowedByrather thanloanedTo
Mismatches like this are exceedingly common when looking at real-world assets, and prevent us from writing generic transactions dealing with these.
By defining an interface and getting the developers of the book and disc packages to align on this, we can fix that problem.
Defining the interface¶
The first step to defining the interface is to create a Daml record to serve as the “interface view” (or simply “view”).
The view includes fields for values that we require for both books and discs (and future catalog items!).
data CatalogItemView = CatalogItemView
with
owner : Party
media : Text
title : Text
loanedTo : Optional Party
This interface view also defines the shape of the data that is returned when querying the ledger API or PQS for “all catalog items”.
For example, in the ledger API you can use InterfaceFilter in filtersByParty.
Next, we define the interface type, and we tie it to the CatalogItemView:
interface CatalogItem where
viewtype CatalogItemView
Aside from the view, an interface can define a number of functions. These must be implemented by the contract templates that want to implement this interface. You can think of these as “abstract functions” in Object-Oriented-Programming terminology.
In our simple example, we want to be able to do three things with catalog items:
Disclose them, so other library members can see them;
Borrow them, and of course;
Return them again.
discloseTo : [Party] -> Update (ContractId CatalogItem)
loanTo : Party -> Update (ContractId CatalogItem)
returnToOwner : Update (ContractId CatalogItem)
Finally, we can also define a number of choices on interfaces. These generally work the same way as choices on regular contract templates.
However, there are a two important things to notice:
In the implementation for this choices, we are able to use the functions we previously associated with the interface. This will allow us to exercise these choices on all catalog items, current and future.
The choices have access to a function named view, which returns an instance of the interface view type. In our case, this is
CatalogItemView. This allows us to access the required fields from the view type. This is necessary since we don’t know what fields the concrete contract type has (it could be a book, a disc, or something else entirely).
choice CatalogItem_LoanTo : ContractId CatalogItem
with
borrower : Party
controller (view this).owner
do
assert (isNone (view this).loanedTo)
loanTo this borrower
choice CatalogItem_ReturnToOwner : ContractId CatalogItem
controller (view this).loanedTo
do
returnToOwner this
choice CatalogItem_Disclose : ContractId CatalogItem
with
observers : [Party]
controller (view this).owner
do
discloseTo this observers
Implementing the interface¶
Now that we have defined the interface,
the respective authors of the book and disc packages can decide to implement it,
such that their assets can be used in the community library.
Implementing an interface concretely means:
Providing a conversion function from the concrete contract to the interface view
Implementing the functions defined in the interface
This implementation is added to the concrete contract templates in the Daml code.
First we have the contract template, as we already saw before:
template Book
with
owner : Party
title : Text
loanedTo : Optional Party
author : Text
observers : [Party]
where
signatory owner
observer observers
And then we can place the interface implementation immediately after, using the keywords interface instance:
interface instance CatalogItem for Book where
view = CatalogItemView with
owner
media = "Book"
title
loanedTo
discloseTo newObservers = do
disclosedBook <- create this with
observers = observers ++ filter (not . (`elem` observers)) newObservers
pure (toInterfaceContractId disclosedBook)
loanTo borrower = toInterfaceContractId <$> create this with loanedTo = Some borrower
returnToOwner = toInterfaceContractId <$> create this with loanedTo = None
Implementing the view is easy: we just need to construct a CatalogItemView based on our concrete Book.
In a sense, it is analogous to using CREATE VIEW in SQL databases to create consistent interfaces, which is considered a good database design pattern.
Implementing the required functions is similarly straightforward. If we make the comparison to Object-Oriented-Programming again, this would be implementing the abstract methods of an abstract class or interface.
One thing you will notice is that we use the function toInterfaceContractId from the standard library.
This is required to implement the methods correctly.
Our interface defined that these functions must return ConstractId CatalogItem,
and just returning the result of create this would give us a ContractId Book instead.
toInterfaceContractId can safely convert contract IDs of a concrete contract to a contract ID of an interface implemented by that concrete contract template.
Using the interface¶
We can now write stable Daml code against the CatalogItem interface, without relying on specific versions of the book and disc packages.
For the purpose of this tutorial, we will continue to do this in Daml, by writing an on-ledger model for the community library. But bear in mind that you can also jump straight to the Ledger API from here, and use that to, for example: list all catalog item contracts, read from their interface view, or exercise choices defined on the interface.
In Daml, we can use these interfaces in the same way as we use regular contract templates.
In this abbreviated snippet, we show only one choice on the Library, but you can find the full version in the directory you created for this tutorial.
template Library
with
items : [ContractId CatalogItem] -- both books and discs
members : [Party]
where
signatory members
observer members
choice Library_Donate : (ContractId Library, ContractId CatalogItem)
with
owner : Party
item : ContractId CatalogItem
controller owner
do
itemView <- view <$> fetch item
assertMsg "Only owner can donate item" (itemView.owner == owner)
assertMsg "Owner must be member of library" (owner `elem` members)
assertMsg "Item already present in library" (not (item `elem` items))
newItem <- exercise item CatalogItem_Disclose with observers = members
newLibrary <- create this with items = newItem :: items
pure (newLibrary, newItem)
In the Library_Donate choice, we use view, just as we did in the choices we defined in the interface earlier, to obtain the interface view.
This then lets us access the fields of that view, in particular owner.
Conclusion¶
With that, we can rest assured our library contract will be able to deal with any current and future catalog items, and package publishers can add arbitrary catalog items by implementing our interface.
At the end of this tutorial, you may find it disappointing that this community library does not actually exist. However, the mechanisms explained here underpin a lot of the important activity on the Canton network.
In particular, CIP-0056, the Canton Network Token Standard, defines tokens using a Daml interface. This allows application implementers to interact with various assets, such as CC or USDC, using a well-defined API that is future- and backwards-compatible.