Introduction
Smart contracts for the Cardano blockchain are created using the Plutus which is a platform that provides a programming language based on Haskell (Plutus) and an SDK that contains all the necessary tools to make smart contract development possible.
At the moment, there are new ways of writting Cardano smart contracts without haaving to learn Haskell. Projects such as Aiken, Helios, and make it possible to delevelop smart contracts without using Plutus and therefore you do not need to learn Haskell. The aim of this guide is to show you how to setup the entire environment from scratch such that you understand how all the pieces are tied together. In the end you will have a ready environment for contract development.
To get started developing Plutus smart contracts, you need to setup your environment first and the most important thing to understand is that Plutus development is based on Haskell and therefore the platform itself is made available as a set of Haskell libraries.
Haskell Compiler, Nix and Cabal
The tools we want to setup first are Nix, GHC and Cabal. Nix is a tool which allows us to build and manage software packages in a reproducible way in diffrent environments. It also comes with a programming language to use for building packages. I tend to think of it like Docker but much better and reproducability. We need to set it up so that its much easier managing versions of the Plutus packages. You can find instructions on how to set it up here.
Next, we need to install GHC, a Haskell compiler and Cabal which is a tool used for managing Haskell libraries/packages. Think of it loosely like npm or Maven for Java development. According to IOG, the currently recommended GHC version is 8.10.7 and Cabal should be at least 3.8. While you could install these tools individually, a better way is to use GHCUp which will allow you to set up both from one place. Also note you could use stack which is a new Haskell build tool intended to replace Cabal. Installation instructions for GHCUp. Once you have installed it you can run the command ghcup tui
on your terminal to get a simple interface that will enable you install the right versions of GHC and Cabal
The Plutus Application Framework
This provides the basic set of libraries needed for Plutus development. It is a set of libraries your project will be complied and built on and is available on the IOHK repository as plutus-apps
. The best way to set this up is using the nix method explained as earlier as this allows you to switch to different versions of the platform libraries easily. Before you proceed make sure Nix is installed on your machine.
In general to set up plutus-apps, you will:
- Clone the IOHK plutus-apps repository to a local directory on your machine.
- From the local repo, check-out a recent tagged release. At the time of writting this is release v1.1.0.
- While still in that directory, use the command
nix-shell
to enter into the nix-environment. This command will start fetching and building all the required Plutus packages. Note that for the first time, this may take quite some time to build everything up. Just make sure you set up the IOHK binary cache to avoid compiling a huge number of the packages and tools.
The Plutus Project
When the nix-shell
command completes building the Plutus packages you should get a nix shell where all those packages are available as dependencies for your Plutus project. Keep this shell open, we shall come back to it in a few.
Now we need to create an empty haskell project in a local directory. This will be the project that will house all your smart contracts. To create the project, do cabal init
from the terminal. This will prompt for some preferences and you can use the defaults for all the questions.
Once the project is ready, we need to add a cabal.project
file which will allow us to list packages we need in our project but are not on Hackage (Haskell’s package repo. Similar to npm repo for Javascript). So go ahead and create a new file and name it cabal.project. Inside the file, put in the contents below:
-- Custom repository for cardano haskell packages
-- See https://github.com/input-output-hk/cardano-haskell-packages on how to use CHaP in a Haskell project.
repository cardano-haskell-packages
url: https://input-output-hk.github.io/cardano-haskell-packages
secure: True
root-keys:
3e0cce471cf09815f930210f7827266fd09045445d65923e6d0238a6cd15126f
443abb7fb497a134c343faf52f0b659bd7999bc06b7f63fa76dc99d631f9bea1
a86a1f6ce86c449c46666bda44268677abf29b5b2d2eb5ec7af903ec2f117a82
bcec67e8e99cabfa7764d75ad9b158d72bfacf70ca1d0ec8bc6b4406d1bf8413
c00aae8461a256275598500ea0e187588c35a5d5d7454fb57eac18d9edb86a56
d4a35cd3121aa00d18544bb0ac01c3e1691d618f462c46129271bccf39f7e8ee
-- See CONTRIBUTING.adoc for how to update index-state
index-state: 2023-01-26T17:52:04Z
-- See CONTRIBUTING.adoc for how to update index-state
index-state:
, hackage.haskell.org 2023-02-05T14:22:36Z
, cardano-haskell-packages 2023-01-26T17:52:04Z
packages:
./
-- We never, ever, want this.
write-ghc-environment-files: never
-- Always build tests and benchmarks.
tests: true
benchmarks: true
-- The only sensible test display option, since it allows us to have colourized
-- 'tasty' output.
test-show-details: direct
allow-newer:
-- cardano-ledger packages need aeson >2, the following packages have a
-- too restictive upper bounds on aeson, so we relax them here. The hackage
-- trustees can make a revision to these packages cabal file to solve the
-- issue permanently.
, ekg:aeson
, ekg-json:aeson
, openapi3:aeson
, servant:aeson
, servant-client-core:aeson
, servant-server:aeson
constraints:
-- cardano-prelude-0.1.0.0 needs
, protolude <0.3.1
-- cardano-ledger-byron-0.1.0.0 needs
, cardano-binary <1.5.0.1
-- plutus-core-1.0.0.1 needs
, cardano-crypto-class >2.0.0.0
, algebraic-graphs <0.7
-- cardano-ledger-core-0.1.0.0 needs
, cardano-crypto-class <2.0.0.1
-- cardano-crypto-class-2.0.0.0.1 needs
, cardano-prelude <0.1.0.1
-- dbvar from cardano-wallet needs
, io-classes <0.3.0.0
-- newer typed-protocols need io-classes>=0.3.0.0 which is incompatible with dbvar's constraint above
, typed-protocols==0.1.0.0
, aeson >= 2
, hedgehog >= 1.1
-- The plugin will typically fail when producing Haddock documentation. However,
-- in this instance you can simply tell it to defer any errors to runtime (which
-- will never happen since you're building documentation).
--
-- So, any package using 'PlutusTx.compile' in the code for which you need to
-- generate haddock documentation should use the following 'haddock-options'.
package plutus-ledger
haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors"
package plutus-script-utils
haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors"
package plutus-contract
haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors"
-- These packages appear in our dependency tree and are very slow to build.
-- Empirically, turning off optimization shaves off ~50% build time.
-- It also mildly improves recompilation avoidance.
-- For dev work we don't care about performance so much, so this is okay.
package cardano-ledger-alonzo
optimization: False
package ouroboros-consensus-shelley
optimization: False
package ouroboros-consensus-cardano
optimization: False
package cardano-api
optimization: False
package cardano-wallet
optimization: False
package cardano-wallet-core
optimization: False
package cardano-wallet-cli
optimization: False
package cardano-wallet-launcher
optimization: False
package cardano-wallet-core-integration
optimization: False
-- Waiting for plutus-apps CHaP to be published
source-repository-package
type: git
location: https://github.com/input-output-hk/plutus-apps.git
tag: v1.0.0
subdir:
cardano-streaming
doc
freer-extras
marconi
marconi-mamba
playground-common
pab-blockfrost
plutus-chain-index
plutus-chain-index-core
plutus-contract
plutus-contract-certification
plutus-example
plutus-ledger
plutus-ledger-constraints
plutus-pab
plutus-pab-executables
plutus-script-utils
plutus-tx-constraints
plutus-use-cases
rewindable-index
-- Direct dependency.
-- Compared to others, cardano-wallet doesn't bump dependencies very often.
-- Making it a good place to start when bumping dependencies.
-- As, for example, bumping the node first highly risks breaking API with the wallet.
-- Unless early bug fixes are required, this is fine as the wallet tracks stable releases of the node.
-- And it is indeed nice for plutus-apps to track stable releases of the node too.
--
-- The current version is dated 2022/08/10
source-repository-package
type: git
location: https://github.com/input-output-hk/cardano-wallet
tag: 18a931648550246695c790578d4a55ee2f10463e
subdir:
lib/cli
lib/core
lib/core-integration
lib/dbvar
lib/launcher
lib/numeric
lib/shelley
lib/strict-non-empty-containers
lib/test-utils
lib/text-class
-- Should follow cardano-wallet.
source-repository-package
type: git
location: https://github.com/input-output-hk/cardano-addresses
tag: b7273a5d3c21f1a003595ebf1e1f79c28cd72513
subdir:
-- cardano-addresses-cli
command-line
-- cardano-addresses
core
-- This is needed because we rely on an unreleased feature
-- https://github.com/input-output-hk/cardano-ledger/pull/3111
source-repository-package
type: git
location: https://github.com/input-output-hk/cardano-ledger
tag: da3e9ae10cf9ef0b805a046c84745f06643583c2
subdir:
eras/alonzo/impl
eras/alonzo/test-suite
eras/babbage/impl
eras/babbage/test-suite
eras/byron/chain/executable-spec
eras/byron/crypto
eras/byron/crypto/test
eras/byron/ledger/executable-spec
eras/byron/ledger/impl
eras/byron/ledger/impl/test
eras/shelley/impl
eras/shelley/test-suite
eras/shelley-ma/impl
eras/shelley-ma/test-suite
libs/cardano-ledger-core
libs/cardano-ledger-pretty
libs/cardano-protocol-tpraos
libs/cardano-data
libs/vector-map
libs/set-algebra
libs/small-steps
libs/small-steps-test
libs/non-integral
This file generally instructs Cabal to include the listed packages as dependencies when it builds you project.
We are almost done. Now, do cabal update
to fetch these dependencies into the project. Finally, do cabal build
. This should build your project although there is no code that uses the packages for now.
When you get a successful build, you are ready to start writting Plutus code for your smart contracts. Here is a minimal Plutus contract to test if everything is set up correctly. Create a file called TestPlutus.hs
in the src
directory of your project and paste in the contents below:
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ImportQualifiedPost #-}
module TestContract (validator, wrapped, serialized, hash, HelloDatum (..), HelloRedeemer (..)) where
import Cardano.Api.Shelley (PlutusScript (..))
import Codec.Serialise (serialise)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Short as BSS
import Plutus.V2.Ledger.Api qualified as PlutusV2
import Ledger.Typed.Scripts as Scripts
import PlutusTx
import PlutusTx.Prelude
import Cardano.Api
newtype HelloDatum = HelloDatum Integer
PlutusTx.unstableMakeIsData ''HelloDatum
newtype HelloRedeemer = HelloRedeemer Integer
PlutusTx.unstableMakeIsData ''HelloRedeemer
-- This validator always validates true
{-# INLINABLE run #-}
run :: HelloDatum -> HelloRedeemer -> PlutusV2.ScriptContext -> Bool
run _ _ _ = True
-- Entry
wrapped :: BuiltinData -> BuiltinData -> BuiltinData -> ()
wrapped = Scripts.mkUntypedValidator run
validator :: PlutusV2.Validator
validator = PlutusV2.mkValidatorScript $$(PlutusTx.compile [|| wrapped ||])
serialized :: PlutusScript PlutusScriptV2
serialized = PlutusScriptSerialised . BSS.toShort . BSL.toStrict . serialise $ validator
hash :: Scripts.ValidatorHash
hash = validatorHash validator
Finally, update the .cabal file in the project to add this file under the exposed-modules
section:
library
import: warnings
exposed-modules: MyLib
, TestContract
Then update the build-depends
section to add all the packages used in TestContract.hs
build-depends: base ^>=4.14.3.0
, bytestring
, cardano-api
, filepath
, plutus-core
, plutus-ledger
, plutus-ledger-api
, plutus-tx
, plutus-tx-plugin
, serialise
, aeson
Do cabal build
and if it compiles, you have successfully setup the environment to get started with Plutus.
You can get the full test project repo here