1. Overview

This document is a series of tutorials that explain various aspects of the Plutus smart contract platform.

  1. Introduction to the Plutus Platform introduces smart contracts and related terms

  2. Plutus Tx Tutorial explains the basics of using the Plutus Tx compiler to create embedded (on-chain) programs

  3. Ledger and Wallet basics implements a guessing game. Topics covered:

    • Validation scripts

    • Lifting and compiling with the Ledger functions

    • Contract endpoints

    • Paying to and collecting from a script with the Wallet functions

    • Using the playground

  4. Smart Contracts implements a crowdfunding campaign. Topics covered:

    • Parameterising a validator through partial application

    • On-chain time (slot intervals)

    • Working with Ada

    • Blockchain triggers

  5. Multi-stage contracts implements a vesting scheme. Topics covered:

    • Writing a contract that extends over multiple transactions

    • Working with Value

    • Creating and submitting complex transactions

Additional documentation will be added for the following work-in-progress features, when they are available on the mockchain:

  • Forging new currencies

  • Using NFTs to represent permissions

  • Decoding values from their on-chain representation back to Haskell

1.1. Prerequisites

To follow the Ledger and Wallet basics, Smart Contracts, and Multi-stage contracts tutorials you should have access to a recent version of the Plutus Playground.

The examples can all be run in GHCi using a local copy of the Plutus repository.

1.2. Using the libraries locally

To use the libraries (not the Playground) locally, follow these steps.

1.2.1. Linux/macOS

  1. Install the nix package manager following the instructions on https://nixos.org/nix/.

    Make sure to add the IOHK binary cache to your Nix configuration. See the repository README for details.
  2. Clone https://github.com/input-output-hk/plutus.

  3. Move to the plutus-tutorial folder.

  4. Type nix-shell. This will download all of the dependencies and populate the PATH with a correctly configured cabal. If nix-shell results in nix attempting to compile a lot of dependencies then something went wrong earlier and you should go back to step 4.

  5. Type cabal repl plutus-tutorial.

1.2.2. Windows

  1. Install the "Ubuntu 18.04" app from the app store

  2. Open the "Ubuntu 18.04" terminal by clicking on the icon

Then follow the steps for "Linux" above.

2. Introduction to the Plutus Platform

The Plutus Platform is the smart contract platform of the Cardano blockchain. Plutus contracts consist of pieces that run on the blockchain (on-chain code) and pieces that run on a user’s machine (off-chain or client code).

Both on-chain and off-chain code is written in Haskell, and Plutus smart contracts are Haskell programs. Off-chain code is compiled by GHC, the Haskell compiler, and on-chain code is compiled by the Plutus compiler.

2.1. Smart contracts

From the smart contract author’s perspective, the blockchain is a distributed bookkeeping system. It keeps track of who owns how much of a virtual resource (Bitcoin, Ada, etc.) and records when assets are transferred from one entity to another. The owners of digital assets are identified by their public keys, and they may be people or machines.

Smart contracts are programs that control the transfer of resources on the blockchain. When two parties decide to enter a smart contract, they place some of their own assets under the control of an on-chain program which enforces the rules of the contract. Every time someone wants to take assets out of the contract, the program is run, and only if its output is positive are the assets released.

On the Cardano blockchain, the on-chain programs are written in a language called Plutus Core. However, smart contract authors do not write Plutus Core directly. The Plutus Platform is a software development kit which enables smart contract authors to easily write smart contracts, including the logic that will eventually be run on the blockchain as Plutus Core.

To write a smart contract using the Plutus Platform, you can code directly in the Plutus Playground. The Plutus Playground is a lightweight, web-based environment for exploratory Plutus development. Here you can easily write and simulate deployment of your contracts without the overhead of installing and maintaining a full development environment.

You can also use a normal Haskell development environment on your computer to write Plutus programs. See the main Plutus repository for more information.

3. Plutus Tx Tutorial

This tutorial will walk you through the basics of using the Plutus Tx compiler to create embedded programs that can be used when generating transactions.

3.1. What is Plutus Tx?

Plutus applications are written as a single Haskell program, which describes both the code that runs off the chain (on a user’s computer, or in their wallet), and on the chain (as part of transaction validation).

The parts of the program that describe the on-chain code are still just Haskell, but they are compiled into Plutus Core (instead of into the normal compilation target language) and we refer to them as "Plutus Tx" ("Tx" since they usually go into transactions).

Strictly speaking, only a subset of Haskell is supported inside Plutus Tx blocks, but the majority of simple Haskell will work, and the Plutus Tx compiler will tell you if you use something that is unsupported.

The key technique that we use to implement Plutus Tx is called staged metaprogramming. What that means is that the main Haskell program generates another program, in this case the Plutus Core program that will run on the blockchain. Plutus Tx is the mechanism that we use to write those programs. But because Plutus Tx is just part of the main Haskell program we can share types and definitions between the two.

3.2. Template Haskell preliminaries

Plutus Tx makes use of Template Haskell, Haskell’s metaprogramming support. There are a few reasons for this:

  1. Template Haskell allows us to do work at compile time, which is when we do Plutus Tx compilation.

  2. It allows us to wire up the machinery that actually invokes the Plutus Tx compiler.

There are a lot of things you can do with Template Haskell, but we only make very light usage of it, so we will just cover the basics here.

Template Haskell begins with quotes. A Template Haskell quote is a Haskell expression e inside special brackets [|| e ||]. It has type Q (TExp a) where e has type a. TExp a is a representation an expression of type a, i.e. the syntax of the actual Haskell expression which was quoted. The quote lives in the type Q of quotes, which isn’t very interesting for us.

There is also an abbreviation TExpQ a for Q (TExp a), which avoids some parentheses.

You can splice a quote into your program using the $$ operator. This inserts the syntax represented by the quote into the program at the point where the splice is written.

The effect of this is that quote allow us to talk about Haskell programs as values.

The Plutus Tx compiler compiles Haskell expressions (not values!), so naturally it takes a quote (representing an expression) as an argument. The result is a new quote, this time for a Haskell program that represents the compiled program. In Haskell, the type of compile is TExpQ a → TExpQ (CompiledCode a). This is just what we already said:

  • TExpQ a is a quoted representing a program of type a.

  • TExprQ (CompiledCode a) is quote representing a compiled Plutus Core program.

CompiledCode has a type parameter a as well, which corresponds to the type of the original expression. This lets us "remember" what the original type of the Haskell program that we compiled was.

Since compile produces a quote, to use the result we need to splice it back into our program. When you compile the main program, the Plutus Tx compiler will run and the compiled program will be inserted into the main program.

This is all the Template Haskell you need to know! We almost always use the same, very simple pattern, which is to make a quote, immediately call compile and then splice the result back in, so once you are used to that you can mostly ignore it.

3.3. Writing basic PlutusTx programs

{-# LANGUAGE TemplateHaskell     #-} (1)
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude   #-}
module Tutorial.PlutusTx where

import Language.PlutusTx (2)
import Language.PlutusTx.Lift (3)
import Language.PlutusTx.Builtins (4)
import Language.PlutusTx.Prelude (5)

-- $setup (6)
-- >>> import Tutorial.PlutusTx
-- >>> import Language.PlutusTx
-- >>> import Language.PlutusCore
-- >>> import Language.PlutusCore.Evaluation.CkMachine
-- >>> import Data.Text.Prettyprint.Doc
1 Necessary language extensions for the Plutus Tx compiler to work.
2 Main Plutus Tx module.
3 Additional support for lifting.
4 Builtin functions.
5 The Plutus Tx Prelude, discussed further below.
6 Setup for doctest examples.

Here’s the most basic program we can write: one that just evaluates to the integer 1.

We’ve included doctests to the examples that show the Plutus Core that is generated from compilation. The syntax of Plutus Core will look unfamiliar. This is fine, since it is the "assembly language" and you won’t need to inspect the output of the compiler. However, for our purposes it’s instructive to look at it to get a vague idea of what’s going on.
integerOne :: CompiledCode Integer
integerOne = $$(compile (1)
    [|| (1 :: Integer) ||])  (2) (3)

{- |
>>> pretty $ getPlc integerOne
(program 1.0.0
  (con 1)
1 compile turns the TExpQ Integer into a TExpQ (CompiledCode Integer), and the splice inserts the TExpQ (CompiledCode Integer) into the program.
2 The quote has type TExpQ Integer.
3 We always use unbounded integers in Plutus Core, so we have to pin down this numeric literal to an Integer rather than an Int.

We can see how the metaprogramming works here: the Haskell program 1 was turned into a CompiledCode Integer at compile time, which we spliced into our Haskell program, and which we can then inspect at runtime to see the generated Plutus Core (or to put it on the blockchain).

We also see the standard usage pattern here: a TH quote, wrapped in a call to compile, wrapped in a $$ splice. This is how we write all of our Plutus Tx programs.

Here’s a slightly more complex program, namely the identity function on integers.

integerIdentity :: CompiledCode (Integer -> Integer)
integerIdentity = $$(compile [|| \(x:: Integer) -> x ||])

{- |
>>> pretty $ getPlc integerIdentity
(program 1.0.0
  (lam ds (con integer) ds)

3.4. Functions and datatypes

You can use functions inside your expression. In practice, you will usually want to define the entirety of your Plutus Tx program as a definition outside the quote, and then simply call it inside the quote.

{-# INLINABLE plusOne #-} (1)
plusOne :: Integer -> Integer
plusOne x = x `addInteger` 1 (2)

{-# INLINABLE myProgram #-}
myProgram :: Integer
myProgram =
        plusOneLocal :: Integer -> Integer (3)
        plusOneLocal x = x `addInteger` 1

        localTwo = plusOneLocal 1
        externalTwo = plusOne 1
    in localTwo `addInteger` externalTwo

functions :: CompiledCode Integer
functions = $$(compile [|| myProgram ||])

{- |
>>> pretty $ runCk $ getPlc functions (4)
(con 4)
1 Functions which will be used in Plutus Tx programs should be marked with GHC’s INLINABLE pragma. This is usually necessary for non-local functions to be usable in Plutus Tx blocks, as it instructs GHC to keep the information that the Plutus Tx compiler needs. While you may be able to get away with omitting it, it is good practice to always include it.
2 addInteger comes from Language.PlutusTx.Builtins, and is mapped to the builtin integer addition function in Plutus Core.
3 Local functions do not need to be marked as INLINABLE.
4 We’ve used the CK evaluator for Plutus Core to evaluate the program and check that the result was what we expected

We can use normal Haskell datatypes and pattern matching freely:

matchMaybe :: CompiledCode (Maybe Integer -> Integer)
matchMaybe = $$(compile [|| \(x:: Maybe Integer) -> case x of
    Just n -> n
    Nothing -> 0

Unlike functions, datatypes do not need any kind of special annotation to be used inside a quote, hence we can use types like Maybe from the Haskell Prelude. This works for your own datatypes too!

Here’s a small example with a datatype of our own representing a potentially open-ended end date.

-- | Either a specific end date, or "never".
data EndDate = Fixed Integer | Never

-- | Check whether a given time is past the end date.
pastEnd :: CompiledCode (EndDate -> Integer -> Bool)
pastEnd = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n `lessThanEqInteger` current
    Never -> False

We could also have defined the pastEnd function as a separate INLINABLE binding and just referred to it in the quote, but in this case it’s small enough to just write in place.

3.5. Typeclasses

So far we have used functions like lessThanEqInteger for comparing Integer s, which is much less convenient than < from the standard Haskell Ord typeclass.

Plutus Tx does support typeclasses, but we need to redefine the standard typeclasses do so, since we require the class methods to be INLINABLE, and the implementations for types such as Integer use the Plutus Tx builtins.

Redefined versions of many standard typeclasses are available in the Plutus Tx Prelude. As such you should be able to use most typeclass functions in your Plutus Tx programs successfully.

For example, here is a version of the pastEnd function using <. This will be compiled to exactly the same code as the previous definition.

-- | Check whether a given time is past the end date.
pastEnd' :: CompiledCode (EndDate -> Integer -> Bool)
pastEnd' = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n < current
    Never -> False

3.6. The Plutus Tx Prelude

The Language.PlutusTx.Prelude module is a drop-in replacement for the normal Haskell Prelude, but with some functions and typeclasses redefined to be easier for the Plutus Tx compiler to handle (i.e. INLINABLE).

You should use the Plutus Tx Prelude whenever you are writing code that you expect to compile with the Plutus Tx compiler. All of the definitions in the Plutus Tx Prelude have working Haskell definitions, so you can use them in normal Haskell code too, although the Haskell Prelude versions will probably perform better.

To use the Plutus Tx Prelude, use the NoImplicitPrelude language pragma, and import Language.PlutusTx.Prelude.

Plutus Tx has some builtin types and functions available for working with primitive data (integers and bytestrings), as well as a few special functions. These builtins are also exported from the Plutus Tx Prelude.

The error builtin deserves a special mention. error causes the transaction to abort when it is evaluated, which one way to trigger validation failure.

3.7. Lifting values

So far we’ve seen how to define pieces of code statically (when you compile your main Haskell program), but you are likely to want to generate code dynamically (when you run your main Haskell program). For example, you might be writing the body of a transaction to initiate a crowdfunding smart contract, which would need to be parameterized by data determining the size of the goal, the campaign start and end times, etc.

We can do this in the same way that we normally parameterize code in functional programming: we write the static code as a function, and we provide the argument later to configure it.

In our case we have a complication, in that we want to make the argument and apply the function to it at runtime. Plutus Tx provides a mechanism to do this called lifting. Lifting makes it easy to use the same types both inside your Plutus Tx program and in the external code that uses it.

When we talk about "runtime" here, we mean the runtime of the main Haskell program, not when the Plutus Core runs on the chain. We want to configure our code when the main Haskell program runs, as that is when we will be getting user input.

As a very simple example, let’s write an add-one function.

addOne :: CompiledCode (Integer -> Integer)
addOne = $$(compile [|| \(x:: Integer) -> x `addInteger` 1 ||])

Now, suppose we want to apply this to 4 at runtime, giving us a program that computes to 5. We need to lift the argument (4) from Haskell to Plutus Core, and then we need to apply the function to it.

addOneToN :: Integer -> CompiledCode Integer
addOneToN n =
    `applyCode` (1)
    liftCode n (2)

{- |
>>> pretty $ getPlc addOne
(program 1.0.0
      (fun (con integer) (fun (con integer) (con integer)))
      (lam ds (con integer) [ [ addInteger ds ] (con 1) ])
    (builtin addInteger)
>>> let program = getPlc $ addOneToN 4
>>> pretty program
(program 1.0.0
        (fun (con integer) (fun (con integer) (con integer)))
        (lam ds (con integer) [ [ addInteger ds ] (con 1) ])
      (builtin addInteger)
    (con 4)
>>> pretty $ runCk program
(con 5)
1 applyCode applies one CompiledCode to another.
2 liftCode lifts the argument n into a CompiledCode Integer.

We lifted the argument using the liftCode function. In order to use this, a type must have an instance of the Lift class. In practice, you should generate these with the makeLift TH function from Language.PlutusTx.Lift.

liftCode is a little "unsafe" because it ignores any errors that might occur from lifting something that isn’t supported. There is a safeLiftCode if you want to explicitly handle these.

The combined program applies the original compiled lambda to the lifted value (notice that the lambda is a bit complicated now since we have compiled the addition into a builtin).

Here’s an example with our custom datatype. The output is the encoded version of False.

makeLift ''EndDate (1)

pastEndAt :: EndDate -> Integer -> CompiledCode Bool
pastEndAt end current =
    liftCode end
    liftCode current

{- |
>>> let program = getPlc $ pastEndAt Never 5
>>> pretty $ runCk program
  out_Bool (type) (lam case_True out_Bool (lam case_False out_Bool case_False))
1 makeLift generates instances of Lift automatically.

4. Ledger and Wallet basics

This tutorial explains the basics of writing a Plutus contract, using a simple guessing game as an example.

You can run this code in the Plutus Playground - see Testing the contract.

The wallet API and by extension the wallet API tutorial is a work in progress and may be changed without notice.

This tutorial has three parts. In Contract definition we write the contract, including all the data types we need, validator scripts, and contract endpoints that handle the interactions between wallet and blockchain. In Testing the contract we show how to test the contract. Exercises contains a number of questions and exercises.

4.1. Contract definition

We need some language extensions and imports:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE ScopedTypeVariables #-} (1)
{-# LANGUAGE DeriveGeneric       #-} (2)
{-# LANGUAGE OverloadedStrings   #-} (3)
{-# LANGUAGE NoImplicitPrelude   #-} (4)
module Tutorial.ValidatorScripts where

import           Language.PlutusTx.Prelude (5)

import qualified Language.PlutusTx            as PlutusTx (6)

import           Ledger                       (Address, DataScript(..), RedeemerScript(..),
import qualified Ledger                       as L (7)
import qualified Ledger.Ada                   as Ada
import           Ledger.Ada                   (Ada)
import           Ledger.Validation            as V (8)

import           Wallet                       (MonadWallet)

import qualified Wallet                       as W (9)

import qualified Data.ByteString.Lazy.Char8   as C
1 Needed by the Plutus Tx compiler plugin.
2 Needed to allow contract endpoints to be automatically generated in the Plutus Playground.
3 Allows us to use string literals for log messages without having to convert them to Text first.
4 Allows us to use the Plutus Tx Prelude as a replacement for the Haskell Prelude.
5 The Plutus Tx Prelude.
6 Language.PlutusTx lets us translate code between Haskell and Plutus Core (see the PlutusTx tutorial).
7 Ledger has data types for the ledger model.
8 Ledger.Validation contains types and functions that can be used in on-chain code.
9 Wallet is the wallet API. It covers interactions with the wallet, for example generating the transactions that actually get the crowdfunding contract onto the blockchain.

4.1.1. Datatypes

The guessing game involves two moves: First, player A chooses a secret word, and uses the game validator script to lock some Ada (the prize) in a transaction output, providing the hash of the secret word as the data script. Second, player B guesses the secret, by attempting to spend the locked transaction output using the guess as a redeemer script.

Why does the data script need to be hashed?

We need to store enough information that we can validate the guess, but the data script will be posted on the blockchain, so if we put the real answer there then someone could just look at it to work out what to guess! Storing the hash means we can verify the guess without giving the answer away.

Both the hashed secret and the cleartext guess are represented as ByteString values in the on-chain code.

data HashedText = HashedText ByteString
data ClearText = ClearText ByteString (1)

PlutusTx.makeLift ''HashedText
PlutusTx.makeLift ''ClearText (2)

mkDataScript :: String -> DataScript
mkDataScript word =
    let hashedWord = L.plcSHA2_256 (C.pack word)
    in  DataScript (L.lifted (HashedText hashedWord)) (3)

mkRedeemerScript :: String -> RedeemerScript
mkRedeemerScript word =
    let clearWord = C.pack word
    in RedeemerScript (L.lifted (ClearText clearWord)) (4)
1 To avoid any confusion between cleartext and hash we wrap them in data types called HashedText and ClearText, respectively.
2 To enable values of our string types to be lifted to Plutus Core, we need to call makeLift.
3 mkDataScript creates a data script for the guessing game by hashing the string and lifting the hash to its on-chain representation.
4 mkRedeemerScript creates a redeemer script for the guessing game by lifting the string to its on-chain representation.

4.1.2. The validator script

The general form of a validator script is Data → Redeemer → PendingTx → Bool. That is, the validator script is a function of three arguments that produces a value of type Bool indicating whether the validation was a success (or fails with an error). As contract authors we can freely choose the types of Data, and Redeemer. The third argument has to be of type PendingTx because that is the information about the current transaction, provided by the validating node.

In our case, the data script is a HashedText, and the redeemer is a ClearText. This gives us a script with the signature HashedText → ClearText → PendingTx → Bool.

-- | The validator script of the game.
  :: HashedText
  -> ClearText
  -> PendingTx
  -> Bool

The actual game logic is very simple: we compare the hash of the guessed argument with the actual secret hash, returning whether or not they match.

validator (HashedText actual) (ClearText guessed) _ =
    if actual == (sha2_256 guessed) (1)
    then traceH "RIGHT!" True (2)
    else traceH "WRONG!" False
1 We have an instance of Eq for ByteString, so we can just use == here to compare for equality.
2 traceH :: String → a → a returns its second argument after adding its first argument to the log output of this script. The log output is only available in the emulator and on the playground, and will be ignored when the code is run on the real blockchain.

Finally, we can compile this into on-chain code.

-- | The validator script of the game.
gameValidator :: ValidatorScript
gameValidator = ValidatorScript $$(L.compileScript [|| validator ||]) (1)
1 The reference to the validator script that we defined is wrapped in Template Haskell quotes, and then the result of L.compileScript is spliced in (see PlutusTx tutorial for further explanation of this pattern).

4.1.3. Contract endpoints

We can now use the wallet API to create a transaction that produces an output locked by the game validator. This means that the address of the output is the hash of the validator script, and the output can only be spent if the correct redeemer is provided so that the validator accepts the spend.

To create the output we need to know the address, that is the hash of the gameValidator script:

gameAddress :: Address
gameAddress = L.scriptAddress gameValidator

Contract endpoints are functions that use the wallet API to interact with the blockchain. To contract users, endpoints are the visible interface of the contract. A contract environment (such as the Playground) may provide a UI for entering the parameters of the actions provided by the endpoints.

When writing smart contracts we define their endpoints as functions that return a value of type MonadWallet m ⇒ m (). This type indicates that the function uses the wallet API to produce and spend transaction outputs on the blockchain.

The first endpoint we need for our game is the function lock. It pays the specified amount of Ada to the script address.

-- | The "lock" contract endpoint.
lock :: MonadWallet m => String -> Ada -> m ()
lock word adaValue = W.payToScript_ (1)
     W.defaultSlotRange (2)
     (Ada.toValue adaValue) (3)
     (mkDataScript word)
1 payToScript_ is a function of the wallet API. It makes a payment to a script address of the specified value.[1]
2 Transactions have a validity range of slots that controls when they can be validated. The default range is "always".
3 "Value" on Cardano is more general than just Ada. We will see more about this later, but for now toValue allows us to convert our Ada into a general Value.

The second endpoint, guess, creates a transaction that spends the game output using the guessed word as a redeemer.

-- | The "guess" contract endpoint.
guess :: MonadWallet m => String -> m ()
guess word = W.collectFromScript (1)
      (mkRedeemerScript word)
1 collectFromScript is a function of the wallet API. It consumes the unspent transaction outputs at a script address and pays them to a public key address owned by this wallet. It takes the validator script and the redeemer scripts as arguments.

If we run guess now, nothing will happen. Why? Because in order to spend all outputs at the script address, the wallet needs to be aware of this address before the outputs are produced. That way, it can scan incoming blocks from the blockchain for outputs at that address, and doesn’t have to keep a record of all unspent outputs of the entire blockchain. So before the game starts, players need to run the following action:

-- | The "startGame" contract endpoint, telling the wallet to start watching
--   the address of the game script.
startGame :: MonadWallet m => m ()
startGame = W.startWatching gameAddress (1)
1 startWatching is a function of the wallet API. It instructs the wallet to keep track of all outputs at the address.
What if I need to know about transactions that happened in the past?

At the moment, the wallet API assumes that you only care about transactions that happen after the contract begins. This may well change in the future, however.

Player 2 needs to call startGame before Player 1 uses the lock endpoint, to ensure that Player 2’s wallet is watching of the game address.

Endpoints can have any number of parameters: lock has two parameters, guess has one and startGame has none. For each endpoint we include a call to mkFunction at the end of the contract definition, by writing $(mkFunction 'lock), $(mkFunction 'guess) and $(mkFunction 'startGame) in three separate lines. This causes the Haskell compiler to generate a schema for the endpoint. The Plutus Playground then uses this schema to present an HTML form to the user where the parameters can be entered.

4.2. Testing the contract

To test this contract, open the Plutus Playground and click the "Game" button above the editor field. Then click "Compile".

You can now create a trace using the endpoints lock, guess and startGame. A trace represents a series of events in the execution of a conract, such as participants taking actions or time passing.

For a successful run of the game, click Wallet 1’s startGame button, then Wallet 2’s lock button and finally Wallet 1’s guess button. Three boxes appear in the "Actions" section, numbered 1 to 3. In the second box, type "plutus" in the first input and 8 in the second input. In the third box type "plutus". The trace should look like the screenshot below.

A trace for the guessing game

Now click "Evaluate". This button sends the contract code and the trace to the server, and populates the "Transactions" section of the UI with the results. The logs tell us that there were three successful transactions. The first transaction is the initial transaction of the blockchain. It distributes the initial funds to the participating wallets. The second and third transactions are related to our game: One transaction from the lock action (submitted by Wallet 2) and one transaction from the guess action (submitted by Wallet 1).

Emulator log for a successful game

If you change the word "plutus" in the third item of the trace to "pluto" and click "Evaluate", the log shows that validation of the guess transaction failed.

Emulator log for a failed attempt

4.3. Exercises

  1. Run traces for a successful game and a failed game in the Playground, and examine the logs after each trace.

  2. Change the error case of the validator script to traceH "WRONG!" (error ()) and run the trace again with a wrong guess. Note how this time the log does not include the error message.

  3. Look at the trace shown below. What will the logs say after running "Evaluate"?

A trace for the guessing game

5. Smart Contracts

This tutorial shows how to implement a crowdfunding campaign as a Plutus contract, using the wallet API to submit it to the blockchain.

You can run this code in the Plutus Playground - see Testing the contract.

The wallet API and by extension the wallet API tutorial is a work in progress and may be changed without much warning.

The tutorial has three parts. In Contract definition we write the contract, including all the data types we need, validator scripts, and contract endpoints that handle the interactions between wallet and blockchain. In Testing the contract we show how to test the contract. Exercises contains a number of questions and exercises related to this contract.

5.1. Contract definition

We need the same language extensions and imports as before:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE NoImplicitPrelude   #-}
module Tutorial.WalletAPI where

import           Language.PlutusTx.Prelude
import qualified Language.PlutusTx            as PlutusTx
import qualified Ledger.Interval              as I
import           Ledger                       (Address, DataScript(..), PubKey(..),
                                               RedeemerScript(..), Slot(..),
                                               TxId, ValidatorScript(..))
import qualified Ledger                       as L
import qualified Ledger.Ada                   as Ada
import           Ledger.Ada                   (Ada)
import           Ledger.Validation            (PendingTx, PendingTx'(..), PendingTxIn'(..), PendingTxOut)
import qualified Ledger.Validation            as V
import           Wallet                       (MonadWallet, EventHandler(..), EventTrigger)
import qualified Wallet                       as W

5.1.1. Datatypes

The crowdfunding campaign has the following parameters:

  • Funding target

  • End date

  • Collection deadline

  • Campaign owner

If the funding target is reached at the end date, then the campaign owner may collect all the funds. If it isn’t reached, or if the owner does not collect the funds before the collection deadline, then the contributors are entitled to a refund.

In Haskell:

data Campaign = Campaign {
      fundingTarget      :: Ada, (1)
      endDate            :: Slot, (2)
      collectionDeadline :: Slot, (2)
      campaignOwner      :: PubKey (3)

PlutusTx.makeLift ''Campaign (4)
1 The type of Ada values is Ada.
2 Dates are expressed in terms of slots, and their type is Slot.
3 The campaign owner is identified by their public key.
4 Just like we did in the guessing game, we need to call makeLift for data types that we want to lift to Plutus Core.

Now we need to figure out what the campaign will look like on the blockchain. Which transactions are involved, who submits them, and in what order?

Each contributor pays their contribution to the address of the campaign script. When the slot endDate is reached, the campaign owner submits a single transaction, spending all inputs from the campaign address and paying them to a pubkey address. If the funding target isn’t reached, or the campaign owner fails to collect the funds, then each contributor can claim a refund, in the form of a transaction that spends their own contribution. This means that the validator script is going to be run once per contribution, and we need to tell it which of the two outcomes it should check for.

We can encode the two possible actions in a data type called Action.

data CampaignAction = Collect | Refund
PlutusTx.makeLift ''CampaignAction

Now we need one final bit of information, namely the identity (public key) of each contributor, so that we know the rightful recipient of a refund. This data can’t be part of the redeemer script because a reclaim could be made by anyone, not just the original contributor. Therefore the public key is going to be stored in the data script of the contribution.

data Contributor = Contributor PubKey
PlutusTx.makeLift ''Contributor
What is the role of the data script?

Pay-to-script outputs contain a (hash of a) validator script and a data script, but their address is the hash of the validator script only, not of the data script. The wallet uses the address to track the state of a contract, by watching the outputs at that address. So the separate data script allows us to have multiple outputs belonging to the same contract but with different data scripts.

In the crowdfunding campaign the data script contains a Contributor value, which is used to verify the "refund" transaction. If that data was part of the validator script, then each contribution would go to a unique address, and the campaign owner would have to be informed of all the addresses through some other mechanism.

5.1.2. The validator script

The general form of a validator script is Data → RedeemerScript → PendingTx → Bool. The types of data and redeemer scripts are Contributor and CampaignAction, respectively, so the signature of the validator script is:

type CampaignValidator =
     -> CampaignAction
     -> PendingTx
     -> Bool

If we want to implement CampaignValidator we need to have access to the parameters of the campaign, so that we can check if the selected CampaignAction is allowed. In Haskell we can do this by writing a function mkValidator :: Campaign → CampaignValidator that takes a Campaign and produces a CampaignValidator.

We then need to compile this into on-chain code using L.compileScript, which we do in mkValidatorScript.

mkValidatorScript :: Campaign -> ValidatorScript
mkValidatorScript campaign = ValidatorScript val where
  val =
      $$(L.compileScript [|| mkValidator ||])
      `L.applyScript` (1)
      L.lifted campaign (2)

mkValidator :: Campaign -> CampaignValidator
1 applyScript applies one Script to another.
2 Ledger.lifted campaign gives us the on-chain representation of campaign.
Parameterizing validators

You may wonder why we have to use L.applyScript to supply the Campaign argument. Why can we not write L.lifted campaign inside the validator script? The reason is that campaign is not known at the time the validator script is compiled. The names of lifted and compile indicate their chronological order: mkValidator is compiled (via a compiler plugin) to Plutus Core when GHC compiles the contract module, and the campaign value is lifted to Plutus Core at runtime, when the contract module is executed. But we know that mkValidator is a function, and that is why we can apply it to the campaign definition.

Before we check whether act is permitted, we define a number of intermediate values that will make the checking code much more readable. These definitions are placed inside a let block, which is closed by a corresponding in below.

In the declaration of the function we pattern match on the arguments to get the information we care about:

    Campaign {fundingTarget, endDate, collectionDeadline, campaignOwner} (1)
    p@PendingTx{pendingTxInputs=ins, pendingTxOutputs=outs,pendingTxValidRange=txnValidRange} =  (2) (3)
1 This binds the parameters of the Campaign.
2 This binds ins to the list of all inputs of the current transaction, outs to the list of all its outputs, and txnValidRange to the validity interval of the pending transaction.
3 The underscores in the match stand for fields whose values are not we are not interested int. The fields are pendingTxFee (the fee of this transaction), pendingTxForge (how much, if any, value was forged) and PendingTxIn (the current transaction input) respectively.
Validity ranges

In the extended UTXO model with scripts that underlies Plutus, each transaction has a validity range, an interval of slots during which it may be validated by core nodes.

The validity interval is passed to validator scripts via the PendingTx argument, and it is the only information we have about the current time. For example, if txnValidRange was the interval between slots 10 (inclusive) and 20 (exclusive), then we would know that the current slot number is greater than or equal to 10, and less than 20. In terms of clock time we could say that the current time is between the beginning of slot 10 and the end of slot 19.

Then we compute the total value of all transaction inputs, using foldr on the list of inputs ins.

There is a limit on the number of inputs a transaction may have, and thus on the number of contributions in this crowdfunding campaign. In this tutorial we ignore that limit, because it depends on the details of the implementation of Plutus on the Cardano chain.
        totalInputs :: Ada
        totalInputs =
            let addToTotal PendingTxIn{pendingTxInValue=vl} total = (1)
                  let adaVl = Ada.fromValue vl
                  in total + adaVl
            in foldr addToTotal zero ins (2)
1 Defines a function that adds the Ada value of a PendingTxIn to the total.
2 Applies addToTotal to each transaction input, summing up the results.

We now have all the information we need to check whether the action act is allowed:

    in case act of
        Refund ->
                Contributor pkCon = con

In the Refund branch we check that the outputs of this transaction all go to the contributor identified by pkCon. To that end we define a predicate

                contribTxOut :: PendingTxOut -> Bool
                contribTxOut o =
                  case V.pubKeyOutput o of
                    Nothing -> False
                    Just pk -> pk == pkCon

We check if o is a pay-to-pubkey output. If it isn’t, then the predicate contribTxOut is false. If it is, then we check if the public key matches the one we got from the data script.

The predicate contribTxOut is applied to all outputs of the current transaction:

                contributorOnly = all contribTxOut outs

For the contribution to be refundable, three conditions must hold. The collection deadline must have passed, all outputs of this transaction must go to the contributor con, and the transaction was signed by the contributor.

            in I.before collectionDeadline txnValidRange && (1)
               contributorOnly &&
               p `V.txSignedBy` pkCon
1 To check whether the collection deadline has passed, we use before :: a → Interval a → Bool.

The second branch is valid in a successful campaign.

        Collect ->

In the Collect case, the current slot must be between deadline and collectionDeadline, the target must have been met, and and transaction has to be signed by the campaign owner.

            I.contains (I.interval endDate collectionDeadline) txnValidRange && (1)
            totalInputs >= fundingTarget &&
            p `V.txSignedBy` campaignOwner
1 We use interval :: Slot → Slot → SlotRange and contains :: Interval a → Interval a → Bool to ensure that the transaction’s validity range, txnValidRange, is completely contained in the time between campaign deadline and collection deadline.

5.1.3. Contract endpoints

Now that we have the validator script, we need to set up contract endpoints for contributors and the campaign owner. The endpoints for the crowdfunding campaign are more complex than the endpoints of the guessing game because we need to do more than just create or spend a single transaction output. As a contributor we need to watch the campaign and claim a refund if it fails. As the campaign owner we need to collect the funds, but only if the target has been reached before the deadline has passed.

Both tasks can be implemented using blockchain triggers.

Blockchain triggers

The wallet API allows us to specify a pair of EventTrigger and EventHandler to automatically run collect. An event trigger describes a condition of the blockchain and can be true or false. There are four basic triggers: slotRangeT is true when the slot number is in a specific range, fundsAtAddressGeqT is true when the total value of unspent outputs at an address is within a range, alwaysT is always true and neverT is never true. We also have boolean connectives andT, orT and notT to describe more complex conditions.

We will need to know the address of a campaign, which amounts to hashing the output of mkValidatorScript:

campaignAddress :: Campaign -> Address
campaignAddress cmp = L.scriptAddress (mkValidatorScript cmp)

Contributors put their public key in a data script:

mkDataScript :: PubKey -> DataScript
mkDataScript pk = DataScript (L.lifted (Contributor pk))

When we want to spend the contributions we need to provide a RedeemerScript value. In our case this is just the CampaignAction:

mkRedeemer :: CampaignAction -> RedeemerScript
mkRedeemer action = RedeemerScript (L.lifted (action))
The collect endpoint

The collect endpoint does not require any user input, so it can be run automatically as soon as the campaign is over, provided the campaign target has been reached. The function collectFundsTrigger gives us the EventTrigger that describes a successful campaign.

collectFundsTrigger :: Campaign -> EventTrigger
collectFundsTrigger c = W.andT
    (W.fundsAtAddressGeqT (campaignAddress c) (Ada.toValue (fundingTarget c))) (1)
    (W.slotRangeT (W.interval (endDate c) (collectionDeadline c))) (2)
1 We use W.intervalFrom to create an open-ended interval that starts at the funding target.
2 With W.interval we create an interval from the campaign’s end date (inclusive) to the collection deadline (inclusive).

fundsAtAddressGeqT and slotRangeT take Value and Interval Slot arguments respectively. The Interval type is part of the wallet-api package.

The campaign owner can collect contributions when two conditions hold: The funds at the address must have reached the target, and the current slot must be greater than the campaign deadline but smaller than the collection deadline.

Now we can define an event handler that collects the contributions:

collectionHandler :: MonadWallet m => Campaign -> EventHandler m
collectionHandler cmp = EventHandler $ \_ -> do

EventHandler is a function of one argument, which we ignore in this case (the argument tells us which of the conditions in the trigger are true, which can be useful if we used orT to build a complex condition). In our case we don’t need this information because we know that both the fundsAtAddressGeqT and the slotRangeT conditions hold when the event handler is run, so we can call collectFromScript immediately.

    W.logMsg "Collecting funds"
    let redeemerScript = mkRedeemer Collect
        range          = W.interval (endDate cmp) (collectionDeadline cmp)
    W.collectFromScript range (mkValidatorScript cmp) redeemerScript (1)
1 To collect the funds we use collectFromScript, which expects a validator script and a redeemer script.
The trigger mechanism is a feature of the wallet, not of the blockchain. That means that the wallet needs to be running when the condition becomes true, so that it can react to it and submit transactions. Anything that happens in an EventHandler is a normal interaction with the blockchain facilitated by the wallet.

With that, we can write the scheduleCollection endpoint to register a collectFundsTrigger and collect the funds automatically if the campaign is successful:

scheduleCollection :: MonadWallet m => Campaign -> m ()
scheduleCollection cmp = W.register (collectFundsTrigger cmp) (collectionHandler cmp)

Now the campaign owner only has to run scheduleCollection at the beginning of the campaign and the wallet will collect the funds automatically.

This takes care of the functionality needed by campaign owners. We need another contract endpoint for making contributions and claiming a refund in case the goal was not reached.

The contribute endpoint

After contributing to a campaign we do not need any user input to determine whether we are eligible for a refund of our contribution. Eligibility is defined entirely in terms of the blockchain state, and therefore we can use the event mechanism to automatically process our refund.

To contribute to a campaign we need to pay the desired amount to a script address, and provide our own public key as the data script. In the guessing game we used payToScript_, which returns () instead of the transaction that was submitted. For the crowdfunding contribution we need to hold on the transaction. Why?

Think back to the guess action of the game. We used collectFromScript to collect all outputs at the game address. This works only if all all outputs are unlocked by the same redeemer (see also exercise 3 of the previous tutorial).

In our crowdfunding campaign, the redeemer is a signed Action. In case of a refund, we sign the Refund action with our public key, allowing us to unlock our own contribution. But if we try to use the same redeemer to unlock other contributions the script will fail, invalidating the entire transaction. We therefore need a way to restrict the outputs that collectFromScript spends. To achieve this, the wallet API provides collectFromScriptTxn, which takes an additional TxId parameter and only collects outputs produced by that transaction. To get the TxId parameter we need to hold on to the transaction that commits our contribution, which we can do with payToScript.

refundHandler :: MonadWallet m => TxId -> Campaign -> EventHandler m
refundHandler txid cmp = EventHandler $ \_ -> do
    W.logMsg "Claiming refund"
    let redeemer  = mkRedeemer Refund
        range     = W.intervalFrom (collectionDeadline cmp)
    W.collectFromScriptTxn range (mkValidatorScript cmp) redeemer txid

Now we can register the refund handler when we make the contribution. The condition for being able to claim a refund is:

refundTrigger :: Campaign -> EventTrigger
refundTrigger c = W.andT
    (W.fundsAtAddressGtT (campaignAddress c) zero)
    (W.slotRangeT (W.intervalFrom (collectionDeadline c)))

The contribute action has two effects: It makes the contribution using the wallet API’s payToScript function, and it registers a trigger to automatically claim a refund if it is possible to do so.

contribute :: MonadWallet m => Campaign -> Ada -> m ()
contribute cmp adaAmount = do
    pk <- W.ownPubKey
    let dataScript = mkDataScript pk
        amount = Ada.toValue adaAmount

    tx <- W.payToScript W.defaultSlotRange (campaignAddress cmp) amount dataScript (1)
    W.logMsg "Submitted contribution"

    let txId = L.hashTx tx (2)

    W.register (refundTrigger cmp) (refundHandler txId cmp)
    W.logMsg "Registered refund trigger"
1 payToScript returns the transaction that was submitted (unlike payToScript_ which returns unit).
2 L.hashTx gives the TxId of a transaction.

5.2. Testing the contract

There are two ways to test a Plutus contract. We can run it interactively in the Playground, or test it like any other program by writing some unit and property tests. Both methods give the same results because they do the same thing behind the scenes: Generate some transactions and evaluate them on the mockchain. The emulator performs the same validity checks (including running the compiled scripts) as the slot leader would for the real blockchain, so we can be confident that our contract works as expected when we deploy it.

5.2.1. Playground

We need to tell the Playground what our contract endpoints are, so that it can generate a UI for them. This is done by adding a call to mkFunctions for the endpoints to the end of the script:

$(mkFunctions ['scheduleCollection, 'contribute])
We can’t use the usual Haskell syntax highlighting for this line because the entire script is compiled and executed as part of the test suite for the wallet-api project. The Playground-specific mkFunctions is defined in a different library (plutus-playground-lib) and it is not available for this tutorial.

Alternatively, you can click the "Crowdfunding" button in the Playground to load the sample contract including the mkFunctions line. Note that the sample code differs slightly from what is written in this tutorial, because it does not include some of the intermediate definitions of contract endpoints such as startCampaign (which was superseded by scheduleCollection) and contribute (superseded by contribute2).

Either way, once the contract is defined we click "Compile" to get a list of endpoints:

Compiling a contract

We can then simulate a campaign by adding actions for scheduleCollection and contribute. Note that we also need to add a number of empty blocks to make sure the time advances past the endDate of the campaign.

Contract actions

A click on "Evaluate" runs the simulation and returns the result. We can see in the logs that the campaign finished successfully:


5.2.2. Emulator

Testing contracts with unit and property tests requires more effort than running them in the Playground, but it has several advantages. In a unit test we have much more fine-grained control over the mockchain. For example, we can simulate network outages that cause a wallet to fall behind in its notifications, and we can deploy multiple contracts on the same mockchain to see how they interact. And by writing smart contracts the same way as all other software we can use the same tools (versioning, continuous integration, release processes, etc.) without having to set up additional infrastructure.

We plan to write a tutorial on this soon. Until then we would like to refer you to the test suite in the plutus-use-cases project in the Plutus repository.

You can run the test suite with nix build -f default.nix localPackages.plutus-use-cases or cabal test plutus-use-cases.

5.3. Exercises

  1. Run traces for successful and failed campaigns

  2. Change the validator script to produce more detailed log messages using traceH

  3. Write a variation of the crowdfunding campaign that uses

data Campaign = Campaign {
      fundingTargets     :: [(Slot, Ada)],
      collectionDeadline :: Slot,
      campaignOwner      :: PubKey

where fundingTargets is a list of slot numbers with associated Ada amounts. The campaign is successful if the funding target for one of the slots has been reached before that slot begins. For example, campaign with Campaign [(Slot 20, Ada 100), (Slot 30, Ada 200)] is successful if the contributions amount to 100 Ada or more by slot 20, or 200 Ada or more by slot 30.

6. Multi-stage contracts

In this part of the tutorial we will implement a simple vesting scheme, where money is locked by a contract and may only be retrieved after some time has passed.

This is our first example of a contract that covers multiple transactions, with a contract state that changes over time.

6.1. Contract definition

We need similar language extensions and imports to before:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE NoImplicitPrelude   #-}
module Tutorial.Vesting where

import           Language.PlutusTx.Prelude
import qualified Prelude                   as Haskell

import qualified Language.PlutusTx         as PlutusTx

import           Ledger                    (Address, DataScript(..), RedeemerScript(..), Slot, TxOutRef, TxIn, ValidatorScript(..))
import qualified Ledger                    as L
import qualified Ledger.Interval           as Interval
import qualified Ledger.Slot               as Slot
import           Ledger.Validation         (PendingTx, PendingTx'(..))
import qualified Ledger.Validation         as V
import           Ledger.Value              (Value)
import qualified Ledger.Value              as Value

import           Wallet                    (MonadWallet, PubKey)
import qualified Wallet                    as W
import qualified Wallet.API                as WAPI

import qualified Data.Map                  as Map
import qualified Data.Set                  as Set (1)

import           GHC.Generics              (Generic)
1 We need a few more standard Haskell data structures for this tutorial.

In our vesting scheme the money will be released in two tranches (parts): A smaller part will be available after an initial number of slots have passed, and the entire amount will be released at the end. The owner of the vesting scheme does not have to take out all the money at once: they can take out any amount up to the total that has been released so far. The remaining funds stay locked and can be retrieved later.

6.1.1. Datatypes

Let’s start with the data types.

-- | Tranche of a vesting scheme.
data VestingTranche = VestingTranche {
    vestingTrancheDate   :: Slot,
    -- ^ When this tranche is released
    vestingTrancheAmount :: Value (1)
    -- ^ How much money is locked in this tranche
    } deriving (Generic)

PlutusTx.makeLift ''VestingTranche

-- | A vesting scheme consisting of two tranches. Each tranche defines a date
--   (slot) after which an additional amount of money can be spent.
data Vesting = Vesting {
    vestingTranche1 :: VestingTranche,
    -- ^ First tranche
    vestingTranche2 :: VestingTranche,
    -- ^ Second tranche
    vestingOwner    :: PubKey
    -- ^ The recipient of the scheme (who is authorised to take out money once
    --   it has been released)
    } deriving (Generic)

PlutusTx.makeLift ''Vesting

-- | The total amount vested
totalAmount :: Vesting -> Value
totalAmount (Vesting tranche1 tranche2 _) =
    vestingTrancheAmount tranche1 + vestingTrancheAmount tranche2

-- | The amount guaranteed to be available from a given tranche in a given slot range.
availableFrom :: VestingTranche -> Slot.SlotRange -> Value
availableFrom (VestingTranche d v) range =
    -- The valid range is an open-ended range starting from the tranche vesting date
    let validRange = Interval.from d
    -- If the valid range completely contains the argument range (meaning in particular
    -- that the start slot of the argument range is after the tranche vesting date), then
    -- the money in the tranche is available, otherwise nothing is available.
    in if validRange `Interval.contains` range then v else zero
1 Value is the general type of assets on chain, which includes tokens other than Ada. Most of the functions work very similarly, so there is usually little reason not to use Value instead of Ada.

6.1.2. The validator script

What should our data and redeemer scripts be? The vesting scheme only has a single piece of information that we need to keep track of, namely how much money is still locked in the contract. We can get this information from the contract’s transaction output, so we don’t need to store it in the data script. The type of our data script is therefore the unit type ().

The redeemer script usually carries the parameters of the action that is performed on the contract. In this vesting scheme however, there is only a single action (withdraw), and its only parameter is the amount withdrawn, which we obtain by comparing the amounts locked in the scheme before and after the transaction. Therefore the redeemer script is also of type ().

That gives our validator script the signature: Vesting → () → () → PendingTx → Bool

vestingValidatorScript :: Vesting -> ValidatorScript
vestingValidatorScript v = ValidatorScript $
    $$(L.compileScript [|| vestingValidator ||]) `L.applyScript` L.lifted v

vestingValidator :: Vesting -> () -> () -> PendingTx -> Bool
vestingValidator v@(Vesting {vestingTranche1, vestingTranche2, vestingOwner}) _ _ p@PendingTx{pendingTxValidRange = range} =
        -- We need the hash of this validator script in order to ensure
        -- that the pending transaction locks the remaining amount of funds
        -- at the contract address.
        ownHash = V.ownHash p

        -- Value that has been released so far under the scheme.
        released = availableFrom vestingTranche1 range
            + availableFrom vestingTranche2 range

        -- Value that has not been released yet.
        unreleased :: Value
        unreleased = totalAmount v - released

To check whether the withdrawal is legitimate we need to: . Ensure that the amount taken out does not exceed the current limit . Check whether the transaction has been signed by the vesting owner

We will call these conditions con1 and con2.

        -- 'con1' is true if the amount that remains locked in the contract
        -- is greater than or equal to 'unreleased'.
        con1 :: Bool
        con1 =
            let remaining = V.valueLockedBy p ownHash (1)
            in remaining `Value.geq` unreleased

        -- 'con2' is true if the pending transaction 'p' has  been signed
        -- by the owner of the vesting scheme
        con2 :: Bool
        con2 = V.txSignedBy p vestingOwner

    in con1 && con2
1 We use the valueLockedBy function to get the amount of value paid by pending transaction p to the script address ownHash.

6.1.3. Contract endpoints

We need three endpoints:

  • vestFunds to lock the funds in a vesting scheme

  • registerVestingScheme, used by the owner to start watching the scheme’s address

  • withdraw, used by the owner to take out some funds.

The first two are very similar to endpoints we defined for earlier contracts.

contractAddress :: Vesting -> Address
contractAddress vst = L.scriptAddress (vestingValidatorScript vst)

vestFunds :: MonadWallet m => Vesting -> m ()
vestFunds vst = do
    let amt = totalAmount vst
        adr = contractAddress vst
        dataScript = DataScript (L.lifted ())
    W.payToScript_ W.defaultSlotRange adr amt dataScript

registerVestingScheme :: MonadWallet m =>  Vesting -> m ()
registerVestingScheme vst = WAPI.startWatching (contractAddress vst)

The last endpoint, withdraw, is different. We need to create a transaction that spends the contract’s current unspent transaction output and puts the Ada that remains back at the script address.

We are going to use the wallet API to build the transaction "by hand", that is without using collectFromScript. The signature of createTxAndSubmit is WalletAPI m ⇒ SlotRange → Set.Set TxIn → [TxOut] → m Tx. So we need a slot range, a set of inputs and a list of outputs.

withdraw :: (MonadWallet m) => Vesting -> Value -> m ()
withdraw vst vl = do

    let address = contractAddress vst
        validator = vestingValidatorScript vst

    -- The transaction's validity range should begin with the current slot and
    -- last indefinitely.
    range <- Haskell.fmap WAPI.intervalFrom WAPI.slot

    -- The input should be the UTXO of the vesting scheme.
    utxos <- WAPI.outputsAt address (1)

        -- the redeemer script containing the unit value ()
        redeemer  = RedeemerScript (L.lifted ())

        -- Turn the 'utxos' map into a set of 'TxIn' values
        mkIn :: TxOutRef -> TxIn
        mkIn r = L.scriptTxIn r validator redeemer

        ins = Set.map mkIn (Map.keysSet utxos)
1 We can get the outputs at an address (as far as they are known by the wallet) with outputsAt, which returns a map of TxOutRef to TxOut.

Our transaction has either one or two outputs. If the scheme is finished (no money is left in it) then there is only one output, a pay-to-pubkey output owned by us. If any money is left in the scheme then there will be an additional pay-to-script output locked by the vesting scheme’s validator script that keeps the remaining value.

    ownOutput <- W.ownPubKeyTxOut vl (1)

    -- Now to compute the difference between 'vl' and what is currently in the
    -- scheme:
        currentlyLocked = Map.foldr (\txo vl' -> vl' + L.txOutValue txo) zero utxos
        remaining = currentlyLocked - vl

        otherOutputs = if Value.isZero remaining
                       then []
                       else [L.scriptTxOut remaining validator (DataScript (L.lifted ()))]

    -- Finally we have everything we need for `createTxAndSubmit`
    _ <- WAPI.createTxAndSubmit range ins (ownOutput:otherOutputs)

    pure ()
1 We can create a public key output to our own key with ownPubKeyTxOut.

6.2. Exercises

  • Write an extended version of registerVestingScheme that also registers a trigger to collect the remaining funds at the end of the scheme.


Extended UTxO

The ledger model on which the Plutus Platform is based.

On-chain code

Code written as part of a smart contract which executes on the chain during transaction validation.

Off-chain code

Code written as part of a smart contract which executes off the chain, usually in a user’s wallet.

Plutus Core

A small functional programming language designed to run as on-chain code.

Plutus IR

An intermediate language that compiles to Plutus Core, for use as a target language for compiler writers.

Plutus Platform

The combined software support for writing smart contracts, including:

  1. Libraries for writing off-chain code in Haskell.

  2. Libraries for writing on-chain code in Plutus Tx.

  3. Emulator support for testing smart contracts.

Plutus Tx

A subset of Haskell which is compiled into Plutus Core.

1. The underscore is a Haskell naming convention, indicating that payToScript_ is a variant of payToScript which ignores its return value and produces a () instead.