Scala bindings

This page provides a basic Scala programmer’s introduction to working with Digital Asset distributed ledger, using the Scala programming language and the Ledger API.

Introduction

The Scala bindings is a client implementation of the Ledger API. The Scala bindings library lets you write applications that connect to the Digital Asset distributed ledger using the Scala programming language.

There are two main components:

  • Scala codegen
    DAML to Scala code generator. Use this to generate Scala classes from DAML models. The generated Scala code provides a type safe way of creating contracts (CreateCommand) and exercising contract choices (ExerciseCommand).
  • Akka Streams-based API
    The API that you use to send commands to the ledger and receive transactions back.

In order to use the Scala bindings, you should be familiar with:

Getting started

If this is your first experience with the Scala bindings library, we recommend that you start by looking at the quickstart-scala example.

To use the Scala bindings, set up the following dependencies in your project:

lazy val codeGenDependencies = Seq(
  "com.daml.scala" %% "bindings" % daSdkVersion,
)

lazy val applicationDependencies = Seq(
  "com.daml.scala" %% "bindings-akka" % daSdkVersion,
)

We recommend separating generated code and application code into different modules. There are two modules in the example below:

  • scala-codegen
    This modules contains all generated Scala classes.
  • application
    This is the application code that makes use of the generated Scala classes.
lazy val `scala-codegen` = project
  .in(file("scala-codegen"))
  .settings(
    name := "scala-codegen",
    commonSettings,
    libraryDependencies ++= codeGenDependencies,
    sourceGenerators in Compile += (damlScala in Compile).taskValue,
    damlScala in Compile := {
      generateScalaFrom(
        darFile = darFile,
        packageName = "com.digitalasset.quickstart.iou.model",
        outputDir = (sourceManaged in Compile).value,
        cacheDir = streams.value.cacheDirectory / name.value
      )
    }
  )

lazy val `application` = project
  .in(file("application"))
  .settings(
    name := "application",
    commonSettings,
    libraryDependencies ++= codeGenDependencies ++ applicationDependencies,
  )
  .dependsOn(`scala-codegen`)

scala-codegen module uses the following function to generate Scala classes from a DAR file.

def generateScalaFrom(
    darFile: File,
    packageName: String,
    outputDir: File,
    cacheDir: File): Seq[File] = {

  require(
    darFile.getPath.endsWith(".dar") && darFile.exists(),
    s"DAR file doest not exist: ${darFile.getPath: String}")

  val cache = FileFunction.cached(cacheDir, FileInfo.hash) { _ =>
    if (outputDir.exists) IO.delete(outputDir.listFiles)
    CodeGen.generateCode(List(darFile), packageName, outputDir, Novel)
    (outputDir ** "*.scala").get.toSet
  }
  cache(Set(darFile)).toSeq
}

You can get the entire build.sbt file from the daml repository on GitHub.

Generating Scala code from the command line

The above example demonstrates how to use Scala codegen from sbt. You can also call Scala codegen directly from a command line.

  1. Install the latest version of the DAML SDK.

  2. Download the latest version of the Scala codegen command line interface.

  3. Build a DAR file from a DAML model. Refer to Building DAML projects for more instructions.

  4. Run Scala codegen:

    $ java -jar <parth-to-codegen-main-jar> scala <path-to-DAR-file>=<package-name> \
        --output-directory=<path-to-output-directory> --verbosity=<0|1|2|3|4>
    

Here is an example, assuming SDK Version: 0.12.17, DAR file: ./quickstart-scala.dar, package name: com.digitalasset.quickstart.iou.model, codegen output directory: ./codegen-out and verbosity level: 2 (INFO):

$ java -jar codegen-main-100.12.17.jar scala ./quickstart-scala.dar=com.digitalasset.quickstart.iou.model \
    --output-directory=./codegen-out --verbosity=2
...
[INFO ] Scala Codegen result:
Number of generated templates: 3
Number of not generated templates: 0
Details:

The output above tells that codegen produced Scala classes for 3 templates without errors (empty Details: line).

Example code

In this section we will demonstrate how to use the Scala bindings library.

This section refers to the IOU DAML example from the Quickstart guide and quickstart-scala example that we already mentioned above.

Please keep in mind that quickstart-scala example compiles with -Xsource:2.13 scalac option, this is to activate the fix for a Scala bug that forced users to add extra imports for implicits that should not be needed.

Create a contract and send a CreateCommand

To create a Scala class representing an IOU contract, you need the following imports:

import com.digitalasset.ledger.client.binding.{Primitive => P}
import com.digitalasset.quickstart.iou.model.{Iou => M}

the definition of the issuer Party:

  private val issuer = P.Party("Alice")

and the following code to create an instance of the M.Iou class:

  val iou = M.Iou(
    issuer = issuer,
    owner = issuer,
    currency = "USD",
    amount = BigDecimal("1000.00"),
    observers = List())

To send a CreateCommand (keep in mind the following code snippet is part of the Scala for comprehension expression):

    createCmd = iou.create
    _ <- clientUtil.submitCommand(issuer, issuerWorkflowId, createCmd)
    _ = logger.info(s"$issuer created IOU: $iou")
    _ = logger.info(s"$issuer sent create command: $createCmd")

For more details on how to submit a command, please refer to the implementation of com.digitalasset.quickstart.iou.ClientUtil#submitCommand.

Receive a transaction, exercise a choice and send an ExerciseCommand

To receive a transaction as a newOwner and decode a CreatedEvent for IouTransfer contract, you need the definition of the newOwner Party:

  private val newOwner = P.Party("Bob")

and the following code that handles subscription and decoding:

    _ <- clientUtil.subscribe(newOwner, offset0, None) { tx =>
      logger.info(s"$newOwner received transaction: $tx")
      decodeCreated[M.IouTransfer](tx).foreach { contract: Contract[M.IouTransfer] =>
        logger.info(s"$newOwner received contract: $contract")

To exercise IouTransfer_Accept choice on the IouTransfer contract that you received and send a corresponding ExerciseCommand:

        val exerciseCmd = contract.contractId.exerciseIouTransfer_Accept(actor = newOwner)
        clientUtil.submitCommand(newOwner, newOwnerWorkflowId, exerciseCmd) onComplete {
          case Success(_) =>
            logger.info(s"$newOwner sent exercise command: $exerciseCmd")
            logger.info(s"$newOwner accepted IOU Transfer: $contract")
          case Failure(e) =>
            logger.error(s"$newOwner failed to send exercise command: $exerciseCmd", e)
        }

Fore more details on how to subscribe to receive events for a particular party, please refer to the implementation of com.digitalasset.quickstart.iou.IouMain#newOwnerAcceptsAllTransfers.