Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.canton.network/llms.txt

Use this file to discover all available pages before exploring further.

This tutorial walks you through creating a v2 version of a Daml package. You’ll start with a simple template, add an optional field and a new choice, then verify that the upgrade compiles and that existing contracts work with the new code.

Prerequisites

  • A working dpm installation with the Daml SDK
  • Familiarity with Daml templates and choices (Module 3)
  • A text editor or Daml Studio

Step 1: Create the v1 Package

Start by scaffolding a new project. The dpm new command creates the directory structure and a daml.yaml file:
dpm new com-example-licensing
For this tutorial, set up the directory structure manually to keep things explicit:
mkdir -p daml/v1/daml
Create daml/v1/daml.yaml:
# daml/v1/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 1.0.0
source: daml
dependencies:
  - daml-prim
  - daml-stdlib
Create daml/v1/daml/Main.daml:
-- daml/v1/daml/Main.daml
module Main where

data LicenseInfo = LicenseInfo
  with
    holder : Party
    issuer : Party
    product : Text
  deriving (Eq, Show)

template License
  with
    info : LicenseInfo
  where
    signatory info.issuer
    observer info.holder

    choice Revoke : ()
      controller info.issuer
      do pure ()
Build and verify:
cd daml/v1
dpm build
The SDK also provides a built-in upgrades example template you can generate with dpm new upgrade-demo --template upgrades-example. See the example source for details.

Step 2: Create the v2 Package

Copy the package — cp -r v1 v2 — and bump the version. The package name must stay the same — this is how Daml knows it’s an upgrade:
# daml/v2/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 2.0.0
source: daml
dependencies:
  - daml-prim
  - daml-stdlib
upgrades: ../v1/.daml/dist/com-example-licensing-1.0.0.dar
The upgrades field points to the v1 DAR. This is what tells dpm build to validate that v2 is a compatible upgrade of v1. Now make backward-compatible changes. Add an Optional field to the record and a new choice to the template:
-- daml/v2/daml/Main.daml
module Main where

data LicenseInfo = LicenseInfo
  with
    holder : Party
    issuer : Party
    product : Text
    expiryDate : Optional Time  -- NEW: optional expiry date
  deriving (Eq, Show)

template License
  with
    info : LicenseInfo
  where
    signatory info.issuer
    observer info.holder

    choice Revoke : ()
      controller info.issuer
      do pure ()

    -- NEW: choice to renew the license with an expiry date
    choice Renew : ContractId License
      with
        newExpiry : Time
      controller info.issuer
      do create this with
           info = info with expiryDate = Some newExpiry
The changes follow SCU compatibility rules:
  • expiryDate is an Optional field with implicit None default for v1 contracts
  • Renew is a new choice (doesn’t exist in v1, so no backward-compatibility issue)

Step 3: Verify Compatibility

Build the v2 package:
cd daml/v2
dpm build
If the build succeeds, the compiler has verified that v2 is a valid upgrade of v1. The upgrades field in daml.yaml triggers this check — without it, dpm build compiles v2 in isolation and performs no cross-version validation. The compiler checks all the SCU rules: no removed fields, no type changes, new fields are Optional, etc. If you’ve introduced a breaking change, the compiler will report an error telling you what rule was violated.

Step 4: Test Approximated Cross-Version Behavior

To verify the upgrade path, add a test script to the v2 package. First, add the daml-script dependency to daml/v2/daml.yaml:
# daml/v2/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 2.0.0
source: daml
dependencies:
  - daml-prim
  - daml-stdlib
  - daml-script
upgrades: ../v1/.daml/dist/com-example-licensing-1.0.0.dar
Then create a test script that simulates a v1 contract (with expiryDate = None) and exercises the new v2 Renew choice:
-- daml/v2/daml/UpgradeTest.daml
module UpgradeTest where

import Main
import Daml.Script
import DA.Date (Month(..), datetime)

testUpgradePath : Script ()
testUpgradePath = do
  issuer <- allocateParty "Issuer"
  holder <- allocateParty "Holder"

  -- Create a contract with no expiryDate (simulating a v1 contract)
  licenseCid <- submit issuer do
    createCmd License with
      info = LicenseInfo with
        holder
        issuer
        product = "Widget Pro"
        expiryDate = None

  -- Exercise the new v2 Renew choice
  newLicenseCid <- submit issuer do
    exerciseCmd licenseCid Renew with
      newExpiry = datetime 2026 Dec 31 0 0 0

  -- Verify the renewed license has the expiry set
  Some renewed <- queryContractId holder newLicenseCid
  assertMsg "Should have expiry"
    (renewed.info.expiryDate == Some (datetime 2026 Dec 31 0 0 0))
Run the test from the v2 package directory:
cd daml/v2
dpm test
This test runs within a single package version, so it approximates rather than fully reproduces cross-version behavior. On a real ledger with both v1 and v2 DARs uploaded, the runtime handles the version resolution between actual v1 contracts and v2 code. See Testing Upgrades for strategies to test real cross-version scenarios.

Step 5: Deploy Both Versions

In a real deployment, you may have both DAR files uploaded to your validator. The order matters: upload v1 first (if not already uploaded), then v2. For new validators, they only need to upload v2 provided that it is SCU compatible with v1.
# Upload v1 (if not already on the ledger)
curl -X POST "http://localhost:7575/v2/packages" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @daml/v1/.daml/dist/com-example-licensing-1.0.0.dar

# Upload v2
curl -X POST "http://localhost:7575/v2/packages" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @daml/v2/.daml/dist/com-example-licensing-2.0.0.dar
Once v2 is uploaded and vetted on all stakeholders’ validators, the new choice becomes available on existing v1 contracts.

What Happens Under the Hood

When a validator receives a v2 DAR:
  1. If automatic vetting is enabled, the validator node vets the new package alongside v1. Otherwise, vetting must be manually done.
  2. Both packages remain active — v1 contracts aren’t affected
  3. When v2 code fetches a v1 contract, the runtime fills Optional fields with None
  4. When v1 code fetches a v2 contract where Optional fields are None, the fetch succeeds (the fields are simply ignored)
  5. When v1 code fetches a v2 contract where an Optional field has a non-None value, the fetch fails to prevent data loss
This design ensures that mixed-version operation is safe: no data is silently lost, and incompatible interactions fail explicitly rather than corrupting state. See Package Selection to learn how different versions are resolved at runtime.

Next Steps