# Daml Script¶

Daml Script provides a simple way of testing Daml models and getting quick feedback in Daml studio. In addition to running it in a virtual ledger in Daml Studio, you can also point it against an actual ledger. This means that you can use it for application scripting, to test automation logic and also for ledger initialization.

You can also use Daml Script interactively using Daml REPL.

Hint

Remember that you can load all the example code by running daml new script-example --template script-example

## Usage¶

Our example for this tutorial consists of 2 templates.

First, we have a template called Coin:

template Coin
with
issuer : Party
owner : Party
where
signatory issuer, owner


This template represents a coin issued to owner by issuer. Coin has both the owner and the issuer as signatories.

Second, we have a template called CoinProposal:

template CoinProposal
with
coin : Coin
where
signatory coin.issuer
observer coin.owner

choice Accept : ContractId Coin
controller coin.owner
do create coin


CoinProposal is only signed by the issuer and it provides a single Accept choice which, when exercised by the controller will create the corresponding Coin.

Having defined the templates, we can now move on to write Daml scripts that operate on these templates. To get access to the API used to implement Daml scripts, you need to add the daml-script library to the dependencies field in daml.yaml.

dependencies:
- daml-prim
- daml-stdlib
- daml-script


We also enable the ApplicativeDo extension. We will see below why this is useful.

{-# LANGUAGE ApplicativeDo #-}

module ScriptExample where

import Daml.Script


Since on an actual ledger parties cannot be arbitrary strings, we define a record containing all the parties that we will use in our script so that we can easily swap them out.

data LedgerParties = LedgerParties with
bank : Party
alice : Party
bob : Party


Let us now write a function to initialize the ledger with 3 CoinProposal contracts and accept 2 of them. This function takes the LedgerParties as an argument and return something of type Script () which is Daml script’s equivalent of Scenario ().

initialize : LedgerParties -> Script ()
initialize parties = do


First we create the proposals. To do so, we use the submit function to submit a transaction. The first argument is the party submitting the transaction. In our case, we want all proposals to be created by the bank so we use parties.bank. The second argument must be of type Commands a so in our case Commands (ContractId CoinProposal, ContractId CoinProposal, ContractId CoinProposal) corresponding to the 3 proposals that we create. However, Commands requires that the individual commands do not depend on each other. This matches the restriction on the Ledger API where a transaction consists of a list of commands. Using ApplicativeDo we can still use do-notation as long as we respect this and the last statement in the do-block is of the form return expr or pure expr. In Commands we use createCmd instead of create and exerciseCmd instead of exercise.

  (coinProposalAlice, coinProposalBob, coinProposalBank) <- submit parties.bank $do coinProposalAlice <- createCmd (CoinProposal (Coin parties.bank parties.alice)) coinProposalBob <- createCmd (CoinProposal (Coin parties.bank parties.bob)) coinProposalBank <- createCmd (CoinProposal (Coin parties.bank parties.bank)) pure (coinProposalAlice, coinProposalBob, coinProposalBank)  Now that we have created the CoinProposals, we want Alice and Bob to accept the proposal while the Bank will ignore the proposal that it has created for itself. To do so we use separate submit statements for Alice and Bob and call exerciseCmd.  coinAlice <- submit parties.alice$ exerciseCmd coinProposalAlice Accept
coinBob <- submit parties.bob \$ exerciseCmd coinProposalBob Accept


Finally, we call pure () on the last line of our script to match the type Script ().

  pure ()


### Party management¶

We have now defined a way to initialize the ledger so we can write a test that checks that the contracts that we expect exist afterwards.

First, we define the signature of our test. We will create the parties used here in the test, so it does not take any arguments.

test : Script ()
test = do


Now, we create the parties using the allocateParty function. This uses the party management service to create new parties with the given display name. Note that the display name does not identify a party uniquely. If you call allocateParty twice with the same display name, it will create 2 different parties. This is very convenient for testing since a new party cannot see any old contracts on the ledger so using new parties for each test removes the need to reset the ledger. We factor out party allocation into a functions so we can reuse it in later sections.

allocateParties : Script LedgerParties
allocateParties = do
alice <- allocateParty "alice"
bob <- allocateParty "bob"
bank <- allocateParty "Bank"
pure (LedgerParties bank alice bob)


We now call the initialize function that we defined before on the parties that we have just allocated.

  initialize parties


### Queries¶

To verify the contracts on the ledger, we use the query function. We pass it the type of the template and a party. It will then give us all active contracts of the given type visible to the party. In our example, we expect to see one active CoinProposal for bank and one Coin contract for each of Alice and Bob. We get back list of (ContractId t, t) pairs from query. In our tests, we do not need the contract ids, so we throw them away using map snd.

  proposals <- query @CoinProposal bank
assertEq [CoinProposal (Coin bank bank)] (map snd proposals)

aliceCoins <- query @Coin alice
assertEq [Coin bank alice] (map snd aliceCoins)

bobCoins <- query @Coin bob
assertEq [Coin bank bob] (map snd bobCoins)


### Running a Script¶

To run our script, we first build it with daml build and then run it by pointing to the DAR, the name of our script, and the host and port our ledger is running on.

daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:test --ledger-host localhost --ledger-port 6865

Up to now, we have worked with a script (test) that is entirely self-contained. This is fine for running unit-test type script in the IDE, but for more complex use-cases you may want to vary the inputs of a script and inspect its outputs, ideally without having to recompile it. To that end, the daml script command supports the flags --input-file and --output-file. Both flags take a filename, and said file will be read/written as JSON, following the Daml-LF JSON Encoding.

The --output-file option instructs daml script to write the result of the given --script-name to the given filename (creating the file if it does not exist; overwriting it otherwise). This is most useful if the given program has a type Script b, where b is a meaningful value. In our example, we can use this to write out the party ids that have been allocated by allocateParties:

daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:allocateParties --ledger-host localhost --ledger-port 6865 --output-file ledger-parties.json

The resulting file will look similar to the following but the actual party ids will be different each time you run it:

{
}


Next, we want to call the initialize function with those parties using the --input-file flag. If the --input-file flag is specified, the --script-name flag must point to a function of one argument returning a Script, and the function will be called with the result of parsing the input file as its argument. For example, we can initialize our ledger using the initialize function defined above. It takes a LedgerParties argument, so a valid file for --input-file would look like:

Using the previosuly created -ledger-parties.json file, we can initialize our ledger as follows:

daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:initialize --ledger-host localhost --ledger-port 6865 --input-file ledger-parties.json

## Using Daml Script for Ledger Initialization¶

You can use Daml script to initialize a ledger on startup. To do so, specify an init-script: ScriptExample:initializeUser field in your daml.yaml. This will automatically be picked up by daml start and used to initialize sandbox. During development not being able to control party ids can often be inconvenient. Here, we rely on users which do put us in control of their id. User ids can be used in Navigator, triggers & other tools instead of party ids.

initializeUser : Script ()
initializeUser = do
parties <- allocateParties
bank <- validateUserId "bank"
alice <- validateUserId "alice"
bob <- validateUserId "bob"
_ <- createUser (User bank (Some parties.bank)) [CanActAs parties.bank]
_ <- createUser (User alice (Some parties.alice)) [CanActAs parties.alice]
_ <- createUser (User bob (Some parties.bob)) [CanActAs parties.bob]
initialize parties


### Migrating from Scenarios¶

Existing scenarios that you used for ledger initialization can be translated to Daml script but there are a few things to keep in mind:

1. You need to add daml-script to the list of dependencies in your daml.yaml.
2. You need to import the Daml.Script module.
3. Calls to create, exercise, exerciseByKey and createAndExercise need to be suffixed with Cmd, e.g., createCmd.
4. Instead of specifying a scenario field in your daml.yaml, you need to specify an init-script field. The initialization script is specified via Module:identifier for both fields.
5. In Daml script, submit and submitMustFail are limited to the functionality provided by the ledger API: A list of independent commands consisting of createCmd, exerciseCmd, createAndExerciseCmd and exerciseByKeyCmd. There are two issues you might run into when migrating an existing scenario:
1. Your commands depend on each other, e.g., you use the result of a create within a following command in the same submit. In this case, you have two options: If it is not important that they are part of a single transaction, split them into multiple calls to submit. If you do need them to be within the same transaction, you can move the logic to a choice and call that using createAndExerciseCmd.
2. You use something that is not part of the 4 ledger API command types, e.g., fetch. For fetch and fetchByKey, you can instead use queryContractId and queryContractKey with the caveat that they do not run within the same transaction. Other types of Update statements can be moved to a choice that you call via createAndExerciseCmd.
6. Instead of Scenario’s getParty, Daml Script provides you with allocateParty and allocatePartyWithHint. There are a few important differences:
1. Allocating a party always gives you back a new party (or fails). If you have multiple calls to getParty with the same string and expect to get back the same party, you should instead allocate the party once at the beginning and pass it along to the rest of the code.
2. If you want to allocate a party with a specific party id, you can use allocatePartyWithHint x (PartyIdHint x) as a replacement for getParty x. Note that while this is supported in Daml Studio, some ledgers can behave differently and ignore the party id hint or interpret it another way. Try to not rely on any specific party id.
7. Instead of pass and passToDate, Daml Script provides passTime and setTime.

## Using Daml Script in Distributed Topologies¶

So far, we have run Daml script against a single participant node. It is also more possible to run it in a setting where different parties are hosted on different participant nodes. To do so, pass the --participant-config participants.json file to daml script instead of --ledger-host and ledger-port. The file should be of the format

{
"default_participant": {"host": "localhost", "port": 6866, "access_token": "default_jwt", "application_id": "myapp"},
"participants": {
"one": {"host": "localhost", "port": 6865, "access_token": "jwt_for_alice", "application_id": "myapp"},
"two": {"host": "localhost", "port": 6865, "access_token": "jwt_for_bob", "application_id": "myapp"}
},
"party_participants": {"alice": "one", "bob": "two"}
}


This will define a participant called one, a default participant and it defines that the party alice is on participant one. Whenever you submit something as party, we will use the participant for that party or if none is specified default_participant. If default_participant is not specified, using a party with an unspecified participant is an error.

allocateParty will also use the default_participant. If you want to allocate a party on a specific participant, you can use allocatePartyOn which accepts the participant name as an extra argument.

## Running Daml Script against Ledgers with Authorization¶

To run Daml Script against a ledger that verifies authorization, you need to specify an access token. There are two ways of doing that:

1. Specify a single access token via --access-token-file path/to/jwt. This token will then be used for all requests so it must provide claims for all parties that you use in your script.
2. If you need multiple tokens, e.g., because you only have single-party tokens you can use the access_token field in the participant config specified via --participant-config. The section on using Daml Script in distributed topologies contains an example. Note that you can specify the same participant twice if you want different auth tokens.

If you specify both --access-token-file and --participant-config, the participant config takes precedence and the token from the file will be used for any participant that does not have a token specified in the config.

## Running Daml Script against the HTTP JSON API¶

In some cases, you only have access to the HTTP JSON API but not to the gRPC of a ledger, e.g., on Daml Hub. For this usecase, Daml script can be run against the JSON API. Note that if you do have access to the gRPC Ledger API, running Daml script against the JSON API does not have any advantages.

To run Daml script against the JSON API you have to pass the --json-api parameter to daml script. There are a few differences and limitations compared to running Daml Script against the gRPC Ledger API:

1. When running against the JSON API, the --host argument has to contain an http:// or https:// prefix, e.g., daml script --host http://localhost --port 7575 --json-api.
2. The JSON API only supports single-command submissions. This means that within a single call to submit you can only execute one ledger API command, e.g., one createCmd or one exerciseCmd.
3. The JSON API requires authorization tokens even when it is run against a ledger that doesn’t verify authorization. The section on authorization describes how to specify the tokens.
4. The parties used for command submissions and queries must match the parties specified in the token exactly. For command submissions that means actAs and readAs must match exactly what you specified whereas for queries the union of actAs and readAs must match the parties specified in the query.
5. If you use multiple parties within your Daml Script, you need to specify one token per party or every submission and query must specify all parties of the multi-party token.
6. getTime will always return the Unix epoch in static time mode since the time service is not exposed via the JSON API.
7. setTime is not supported and will throw a runtime error.