Module 0 · Lesson 2 · ~30 min read + interactive

Daml — Privacy-Aware Smart Contracts

Daml is the language every Canton contract is written in. This is a working-knowledge lesson, not a Daml masterclass — your goal is to read Daml code, understand what ledger changes it will cause, and know what's happening when your Go code submits a command.

The one-line version

Daml is a pure functional DSL for defining templated contracts, their data, and the choices (state transitions) that each stakeholder is allowed to exercise — with the privacy model baked into the type system.

The shape of a Daml template

Canton's unit of currency in every sense is the template. A template describes a kind of contract — its fields, who the stakeholders are, and what actions can be taken on instances of it.

module Iou where

template Iou
  with
    issuer : Party
    owner  : Party
    amount : Decimal
    currency : Text
  where
    signatory issuer, owner
    observer  []

    choice Transfer : ContractId Iou
      with
        newOwner : Party
      controller owner
      do
        create this with owner = newOwner

    choice Settle : ()
      controller issuer
      do
        return ()

Five concepts in fifteen lines. All of them matter.

template
The class-equivalent. Defines the shape (fields via with) and the behavior (choices via choice). An instance of a template is a contract.
signatory
A party whose consent is required to create or consume this contract. Both the issuer and owner must authorize creating an IOU — either party could refuse to sign. Signatories are the strongest form of stakeholder.
observer
A party who can see the contract but isn't required to authorize changes. Observers see reads; signatories authorize writes. The empty list here means there are no additional observers beyond the signatories.
choice
A state transition. The only way to change the ledger is by exercising a choice. Each choice has a controller (who's allowed to invoke it) and a body (what it does, in do syntax that resembles Haskell).
controller
The party (or parties) authorized to exercise this choice. On Transfer, only the owner can transfer. On Settle, only the issuer can settle. If a non-controller tries, the transaction is rejected before it even reaches the synchronizer.

Consuming vs non-consuming choices

By default a choice is consuming — exercising it archives the current contract. The Transfer choice above consumes the old IOU and creates a new one with a different owner. On the ledger, you now have: old IOU (archived), new IOU (active).

Why? Because Daml wants state changes to be explicit, immutable, and auditable. There's no mutation in place. Every historical state is visible in the transaction log forever.

-- explicitly non-consuming: contract stays active
nonconsuming choice Peek : Decimal
  controller owner
  do
    return amount

Peek doesn't archive the contract — useful for read-only queries that still need to pass through the ledger (and thus be ordered and auditable).

Privacy — who sees what, and why it matters

This is where Daml earns its place over "just use Solidity." Consider the IOU above. When Alice (issuer) creates an IOU for Bob (owner):

Now Alice transfers the IOU to Carol. Carol becomes a new signatory on the resulting contract. The Transfer transaction informs Carol's participant. Bob's participant still sees the archive event (because Bob was a stakeholder on the archived contract), but Bob never sees the new contract's contents — he sees that his IOU was transferred, but not to whom.

Mental model

Privacy in Daml is type-level and mandatory. You can't accidentally broadcast a contract. The set of parties who see a contract is determined entirely by the signatory and observer declarations — nothing else. The compiler enforces this.

Interactive — fiddle with the ledger

Below is a tiny simulated Daml ledger. The Iou template from above is pre-loaded, and you can see what different actions do to the ledger state. This isn't a real Daml compiler — it's a faithful simulation of the lifecycle so you can see what "create," "exercise," and "archive" mean as ledger operations.

Ledger state (Iou contracts)

Available actions

Play with it for a minute. Notice: (1) every action mutates the ledger by archiving and/or creating, never by editing in place; (2) every contract knows its signatories; (3) if you wanted a real Daml runtime check, only the owner could transfer and only the issuer could settle — this toy simulator lets you exercise anything to keep the mechanics visible.

Reading a real Daml file — what to pattern-match on

You'll see Daml files in the wild. Here's a checklist for reading any unfamiliar template:

  1. Module name and imports at the top (module Foo where). Ignore for now.
  2. Data declarations — simple records for data that isn't itself a contract. Shaped like data Point = Point { x: Int, y: Int }.
  3. Template declarations — the important ones. For each:
    • Read the with block — that's the shape of an instance.
    • Read signatory — that tells you who must authorize creation.
    • Read each choice — name, return type, arguments, controller, body. That's the state machine.
  4. Ignore (at first) the ensure clauses, key/maintainer clauses, and interface implementations. Come back to them when you need them.

What Daml compiles to

Daml source (.daml) is compiled to an intermediate called DAML-LF (Ledger Fragment) — a binary, versioned, Protobuf-serialized form. This is what participant nodes actually execute. You won't write DAML-LF by hand, but you'll see it mentioned in Canton's logs, the wire protocol, and upgrade-compatibility discussions.

For the capstone

In the Module 7 capstone, you'll build a Go client that submits commands and streams transactions against a running Canton sandbox. The sandbox comes with a simple Daml model pre-compiled (typically an Iou- or AssetTransfer-shaped model). You don't need to write Daml — but you need to recognize what's happening on the ledger when your Go code submits a command.

Comparison to things you already know

ConceptSoliditySQL schemaDaml
State container contract (one instance per deployment) TABLE template + many contract instances
Row / instance n/a (contract is singleton) row contract
Mutation Mutable storage fields UPDATE / DELETE Archive + create (immutable)
Who can act Anyone with gas Anyone with permissions Controllers only (type-enforced)
Who can read Everyone Granted via GRANT Signatories & observers only (type-enforced)
History Global chain history Optional (audit tables) Always — transaction stream per party

Takeaways