,____ (\=-,
\ `'-.______/ /
`-._.-"( |
\ '--\ |
^^ ^^
▄█ ▄████████ ▄▄▄▄███▄▄▄▄ ▀█████████▄ ▄█ █▄ ▄████████ ▄█ ▄████████
███ ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ▄███▄▄▄██▀ ▄███▄▄▄▄███▄▄ ███ ███ ███ ███ ███
███ ▀███████████ ███ ███ ███ ▀▀███▀▀▀██▄ ▀▀███▀▀▀▀███▀ ▀███████████ ███ ▀███████████
███ ███ ███ ███ ███ ███ ███ ██▄ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ▄ ███ ███
█▄ ▄███ ███ █▀ ▀█ ███ █▀ ▄█████████▀ ███ █▀ ███ █▀ █████▄▄██ ███ █▀
▀▀▀▀▀▀ ▀
Jambhala brings Plutus development nirvana by presenting three jewels:
💎 #1: Minimizes contract boilerplate
PlutusTx.Preludeis enabled as prelude project-wide by default via mixin- Certain common Haskell language extensions are enabled by default
- Common Plutus and Haskell types and functions are re-exported from their respective modules by
Jambhala.PlutusandJambhala.Haskell, so we don't need to keep track of messy import boilerplate. We can always import Plutus or Haskell modules explicitly if we prefer. Jambhala.Utilsprovides common utility functions which are consumed by thejambCLI, to avoid contract clutter.
💎 #2: Performs common Plutus tasks with simple commands
- Compute validator hashes.
- Run emulator tests on contracts.
- Serialize contracts to
.plutusfiles.
💎 #3: Keeps projects in sync with plutus-apps
- With Jambhala, we don't need to maintain a central clone of the plutus-apps repository and use its associated Nix shell as the entry point for development of every project.
- Relying on a central
plutus-appsinstance forces us to use the same revision for every project we develop. If we update ourplutus-appsto use a more recent revision:- we need to adjust all of our individual projects'
cabal.projectfiles by hand to reflect any changes in dependencies - we risk breaking our older projects if the API of
plutus-appshas changed
- we need to adjust all of our individual projects'
- Even setting up a single project with this approach is painful, because in the absence of an up-to-date template, we must manually copy and adjust boilerplate from
plutus-appsinto ourcabal.projectfile. This in turn quickly becomes outdated given the pace of Plutus development. - Jhambala uses haskell.nix to provide a fully self-reliant Plutus development environment for each of your projects, which can be brought up to date with the current state of
plutus-appsusing a single command. No more wrangling of dependency boilerplate: just build your project environment and get to work, then bumpplutus-appsfor a specific project whenever you like. - Serve Haddock documentation for the specific
plutus-appsrevision your project uses with theserve-docscommand.
- This project uses the Nix package manager, Nix flakes, and IOG's haskell.nix infrastructure to build a fully-functioning and reproducible Plutus development environment.
- Nix is only compatible with Unix-like operating systems, so you must be using a Linux distribution, MacOS, or WSL2 (Windows Subsystem for Linux) to install this project locally.
- This project assumes the use of
VS Codeas editor andbashas shell. Other tools will require alternative workflows that are not covered here. - This project is storage-intensive. We suggest you have at least
30GBof free disk space before proceeding further. - NOTE for MacOS users: MacOS may ship with versions of
bashandgrepthat are incompatible with this workflow. You should installbash/grepusing Homebrew first before proceeding. - You'll need a fully-synced Cardano Node and the
cardano-clibinary in order to submit example transactions to the blockchain.
- If you're setting up Nix on your system for the first time, try Determinate Systems' Zero-to-Nix in lieu of the official installer, as it provides an easier tool for installing and uninstalling Nix.
- Alternatively, you may follow the instructions for multi-user installation for your OS at nixos.org. This approach will require some additional configuration and it will be harder to uninstall Nix if you need to. It is only recommended if you've previously installed Nix on your system, as it will detect and repair a previous installation as needed.
- When you are finished installing Nix, close the terminal session and open a fresh one.
-
Edit
/etc/nix/nix.conf: this requires root access to edit. Use a terminal-based editor likenano(i.e.):$ sudo nano /etc/nix/nix.conf
-
Modify the file following the instructions below:
# Sample /etc/nix/nix.conf # Step 2a: Add this line to enable Flakes if missing (if you used the Zero-to-Nix installer this should already be added) experimental-features = nix-command flakes # Step 2b: Avoid unwanted garbage collection with nix-direnv keep-outputs = true keep-derivations = true # Step 2c: Set up binary cache # (add to existing substituters and trusted-public-keys lines if present, separated by spaces) substituters = https://cache.nixos.org https://cache.iog.io https://cache.zw3rk.com trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk=🚨 IMPORTANT! You must restart the
nix-daemonto apply the changesLinux:
$ sudo systemctl restart nix-daemon
MacOS: first find the name of the
nix-daemonservice$ sudo launchctl list | grep nixThen stop and restart the service
$ sudo launchctl stop <NAME> $ sudo launchctl start <NAME>
-
This setup uses
direnvto provide seamless loading of the Nix environment whenever you navigate into the project directory tree. -
The
direnvextension for VS Code integrates this environment with your editor, providing full IDE support for Plutus development. -
Jambhala requires
direnvversion>= 2.30, which may not be available in the packaging systems for certain older operating systems (for instance, any Ubuntu system below version22.10). -
Visit the direnv installation page and check which version is available for your OS in the
Packaging statussection. If yourdirenvversion2.30or higher is available, follow the instructions to install it and hook it into your shell. -
If
direnvversion2.30+isn't available for your OS through the standard installation method above, you can usenixto install it. Just run the following:$ nix-env -iA nixpkgs.direnv
-
When you load the project in VS Code for the first time, you will be prompted to install the direnv extension.
-
On this repository's Github page, select the green
Use this templatebutton and selectCreate a new repositoryto fork the template. -
Clone your new repository in a
bashterminal session:$ git clone https://github.com/PATH-TO/YOUR-REPO.git
-
Open the project root directory in your terminal session:
$ cd path-to-your-projectYou should now see the following message:
$ direnv: error /home/path-to-your-project/.envrc is blocked. Run `direnv allow` to approve its contentThis is a security measure, since
.envrcfiles can run arbitrary shell commands. Make sure you always trust the author of a project and inspect the contents of its.envrcfile before runningdirenv allow.When you're ready, enter
direnv allowto approve the content:$ direnv allow
-
You can ignore the following warning:
direnv: ([/nix/store/.../bin/direnv export bash]) is taking a while to execute. Use CTRL-C to give up. -
It will take significant time (~2 hours) to set up the environment the first time. You can recite the Yellow Dzambhala Mantra while you wait:
_=_ q(-_-)p '_) (_` /__/ \ _(<_ / )_ _________(__\_\_|_/__)_________ Om Dzambhala Dzalentraye Svaha! -
Some dependencies will need to be built from source, but if you see "building" for certain packages that should be downloadable from a binary cache (particularly GHC, the Linux kernel, and other non-Haskell related dependencies) or if you see any warning such as
warning: ignoring substitute, this means your binary cache was not set up correctly and Nix is attempting to build packages from source that it should be fetching from a cache. Exit withCTRL + cand repeat Step 2, then try again. Make sure to restart thenix-daemon! -
If you see any HTTP-related errors, it means the IOG binary cache is non-responsive. Wait a bit and try again later.
-
Once the Nix environment build process completes, run
setupto launch the setup wizard:$ setup
Answer the prompts to personalize your project. Then the project will be built using
cabal. This step will also take some time to complete:_=_ q(-_-)p '_) (_` /__/ \ _(<_ / )_ _________(__\_\_|_/__)_________ Om Dzambhala Dzalentraye Svaha!
- You can now start VS Code and use the
File > Open Folder...menu option to load the starter kit. - You will be prompted to install some recommended extensions if you don't have them already:
haskell,direnvandNix IDE. - Accept any pop-up prompts from the
direnvextension if you encounter them.
Jambhala includes a simple command-line utility called jamb, which reduces boilerplate and provides a simple API for the following uses:
You can run the following command to view the names of available contracts in your project, for use with other commands:
$ jamb -lYou can calculate the validator hash for any available contract like this:
$ jamb -s CONTRACTwhere CONTRACT is the name of the contract to hash.
You can run the emulator test defined for a contract with the following command:
$ jamb -t CONTRACTwhere CONTRACT is the name of the contract to test.
you can run the following command from the workspace terminal to write a contract to a .plutus file:
$ jamb -w CONTRACT [FILENAME]where CONTRACT is the name of the contract to compile, and [FILENAME] is an optional file name (the contract name is used as the filename by default if no argument is given). When the command finishes, you should get a compiled/CONTRACT.plutus file that contains a JSON envelope of the UPLC code.
{
"type": "PlutusScriptV2",
"description": "",
"cborHex": "5907c05907bd0100003232323232323232323..."
}This file can be used to submit transactions on-chain.
Jambhala makes certain opinionated decisions in order to vastly reduce the boilerplate required to write Plutus contracts.
- Jambhala is configured to use
PlutusTx.Preludeas its default prelude via amixinin the.cabalfile. - This eliminates the need to include both the
{#- LANGUAGE NoImplicitPrelude #-}extension andimport PlutusTx.Preludein your contract files.
-
Many common Plutus types and functions are available with a single import from
Jambhala.Plutus. -
If you need to use regular Haskell functions that would normally be imported in the standard Prelude or from other modules in
base, import them fromJambhala.Haskellor from the specificbasemodules they reside in -
For example, if you need to use the IO type in signatures and the
putStrLnandprintfunctions, import them fromJambhala.Haskelllike so:import Jambhala.Haskell ( IO, print, putStrLn )
-
See the sample contracts in
src/Contracts/Samplesfor more examples of handling imports with Jambhala.
The following language extensions are enabled project-wide by Jambhala using the default-extensions setting in the library stanza of the .cabal file:
default-extensions:
-- Allows promotion of types to "kinds", enabling more expressive type-level programming (required for all Plutus contracts):
DataKinds
-- A syntactic convenience for writing single-argument lambdas containing case expressions (used in Jambhala's utilities)
, LambdaCase
-- Allows construction of Text and other string-like values as string literals:
, OverloadedStrings
-- A syntactic convenience for working with record values (used in Jambhala's utilities):
, RecordWildCards
-- Required for all Plutus contracts to translate between Plutus and Haskell:
, TemplateHaskell
Beyond these, the sample contracts include only the specific language extensions needed to compile their code. Keep in mind that Haskell language extensions are experimental modifications to compiler behavior: they should be used only when necessary and with clear understanding of their purpose. It is better to add extensions incrementally as they become needed than to add a multitude of modifications to the compiler as boilerplate in every file.
The source code for the sample Plutus contracts live in the src/Contracts/Samples folder.
If you want to hide the sample contracts from the jamb utility and only serve your own contracts, you can modify the main action in app/Main.hs accordingly:
main :: IO ()
main = runJamb contracts -- << replace `allContracts` with `contracts` to hide sample contracts
where allContracts = samples <> contractsTo create a new contract, create a new .hs file in the src/Contracts directory, and write a module declaration, i.e.:
module Contracts.MyContract whereIn the jambhala.cabal file, add your module name (i.e. Contracts.MyContract) to the exposed-modules section of the library stanza:
library
import: common
exposed-modules:
Contracts
Jambhala.CLI
Jambhala.Haskell
Jambhala.Plutus
Jambhala.Utils
-- Add new contracts here, i.e.:
Contracts.MyContract
...
🚨 IMPORTANT: you must stage any new contract files you create to git before they are visible to Nix for compilation. Use the Source Control option in the left sidebar of VS Code or stage changes from the command line with git add.
You're now ready to write your contract, which should contain a Validator value (by convention in the samples this is called validator). See the contracts in src/Contracts/Samples for example validators.
The jamb CLI can perform various operations on your contracts, including calculating its validator hash, testing it using the emulator, and compiling it into a .plutus file. To do this you need to prepare a ContractExports value in each of your contracts, which has the following type definition:
data ContractExports = ContractExports { getValidator :: !Validator
, getTest :: !(Maybe (EmulatorTrace ())) }In our contract, we construct the exports value like this:
-- Prepare exports for jamb CLI:
exports :: ContractExports
exports = ContractExports { getValidator = validator, getTest = Just test }or in shorter notation:
exports :: ContractExports
exports = ContractExports validator $ Just testwhere validator and test are values defined in our contract of types Validator and EmulatorTrace (), respectively.
For a contract without an emulator test, construct the exports with a Nothing value:
exports :: ContractExports
exports = ContractExports validator Nothingmodule Contracts.MyContract whereIn src/Contracts/Contracts.hs, import your contract module as a qualified import, i.e.:
import qualified Contracts.MyContract as MyContractThen add a new tuple entry to the contracts Map, containing a name string for your contract and reference to its exports like so:
contracts :: Contracts
contracts = M.fromList [
("my-contract", MyContract.exports)
]Once your contract has been added to the map, it can now be operated on by the jamb CLI:
$ jamb -s my-contract
$ jamb -t my-contract
$ jamb -w my-contractTo start a GHCi REPL session, run repl and then load your contract:
$ repl
Prelude Contracts λ > :m Contracts.MyContract
Prelude Contracts.MyContract λ >To serve docs for the specific revision of plutus-apps this project is using, open a new bash terminal from the project root directory and run the following command:
$ serve-docsNote: This will require significant additional build time and storage space the first time the docs are served.
The script will look up the specific plutus-apps revision hash from the cabal.project file, clone the plutus-apps repository (if it doesn't already exist) and checkout this revision, then launch a new nix develop shell and serve the docs at http://0.0.0.0:8002/.
To view the correct Haddock documentation for the revision you are using, open http://0.0.0.0:8002/haddock in your browser.
Since Jambhala is under active development and is closely tracking the progress of plutus-apps, its codebase changes frequently.
Unlike forks, Github repositories generated from templates have unique histories, so they aren't able to fetch and merge upstream changes as smoothly. However it's still possible to merge updates from an upstream template into your project with a little manual effort.
The setup wizard added the upstream template as a remote source. You can now run the update-jambhala command to fetch any changes to the template and attempt to merge them:
$ update-jambhalaNote that this command is distinct from the
jamb -ucommand, which updates only theplutus-appsdependency incabal.project.
You will need to manually resolve the resulting merge conflicts. You may find a VS Code extension like Git Merger to be helpful with this.
The non-Hackage dependencies in the cabal.project file are following the plutus-apps library, with sha256 hashes calculated for each source-repository-package entry.
jamb provides a utility to easily update plutus-apps to the most recent revision and adjust all related dependencies. Run the jamb -u command to pull the latest revision and generate a new cabal.project file.
$ jamb -u🚨 WARNING! This operation rewrites your cabal.project file according to the most recent plutus-apps commit, and may cause your environment and/or contracts to break. You should use at your own risk, but you can also easily restore a previous cabal.project file by following the instructions for Restoring a previous version below.
You can also use the jamb -u command with an additional argument to set plutus-apps to a specific commit hash or tag:
$ jamb -u 38979da68816ab84faf6bfec6d0e7b6d47af651aYou can run the pa-history command to view the full commit history for plutus-apps:
$ pa-historyUse the up/down keys to navigate or type q to quit.
🚨 WARNING! The code in the sample contracts and Jambhala.Plutus module have been designed for compatibility with very recent commits of plutus-apps - this means pointing plutus-apps to older tags/commits is much more likely to result in breakage. Use this feature at your own risk!
Before the jamb -u command rewrites your cabal.project file, a backup of your existing cabal.project will be created in the backups/ directory in case you need to roll back the update. Just delete the current cabal.project file, copy the backup file and rename it to cabal.project. Then run direnv allow or reload the project in VS Code and your previous project state will be restored.
Since Nix flakes require pure inputs to guarantee reproducibility, and the content associated with a particular Git repository/tag can change, we need to hash any repositories we include in cabal.project. This means if we need to manually change any dependencies or add additional ones, we'll need to calculate new hashes and replace the existing ones.
While not recommended, if you need to change the revision of Plutus dependencies manually, you can calculate sha256 hashes for them using the nix-prefetch-git utility, which has been provided with this project's Nix development environment.
Use the following command to calculate a hash:
$ nix-prefetch-git LOCATION TAG
Here is an example of how we'd calculate a hash for the plutus-apps dependency with tag 5dda0323ef30c92bfebd520ac8d4bc5a46580c5c:
$ nix-prefetch-git https://github.com/input-output-hk/plutus-apps.git 5dda0323ef30c92bfebd520ac8d4bc5a46580c5c
...
git revision is 5dda0323ef30c92bfebd520ac8d4bc5a46580c5c
path is /nix/store/mzjqwvfc2qmmvg9llskjyvkdph8hv4i4-plutus-apps-5dda032
git human-readable version is -- none --
Commit date is 2023-01-19 17:41:18 +0000
hash is 05ggi69w2n0cnhfyifpa83aphq6avk0fd9zvxywn1scwxza85r1a
{
"url": "https://github.com/input-output-hk/plutus-apps.git",
"rev": "5dda0323ef30c92bfebd520ac8d4bc5a46580c5c",
"date": "2023-01-19T17:41:18+00:00",
"path": "/nix/store/mzjqwvfc2qmmvg9llskjyvkdph8hv4i4-plutus-apps-5dda032",
"sha256": "05ggi69w2n0cnhfyifpa83aphq6avk0fd9zvxywn1scwxza85r1a",
"fetchLFS": false,
"fetchSubmodules": false,
"deepClone": false,
"leaveDotGit": false
}The hash string must now be added as a comment prexied with --sha256: anywhere inside the source-repository-package stanza like so:
source-repository-package
type: git
location: https://github.com/input-output-hk/plutus-apps.git
tag: 5dda0323ef30c92bfebd520ac8d4bc5a46580c5c
--sha256: 05ggi69w2n0cnhfyifpa83aphq6avk0fd9zvxywn1scwxza85r1a
'hs-source-dirs: app' specifies a directory which does not exist.
> Warning: 'hs-source-dirs: app' specifies a directory which does not exist.
> building
> Preprocessing library for jambhala-0.1.0.0..
> Error: Setup: can't find source for Contracts/... in src,
You need to stage your new module file in git so it becomes visible to the Nix environment. Use the Source Control option in the left sidebar of VS Code or stage changes from the command line with git add. Then try the operation that caused the error again.
-
Restart Haskell LSP Server: restarting
haskell-language-serveroften fixes common issues with Haskell in VS Code. Open the command palette (Ctrl + Shift + p) and begin typingRestart Haskell LSP Serveruntil you see this option, and select it. In the future it will be pinned to the top of the command palette options and easier to find. -
rebuild: cleaning thedist-newstyledirectory of all build artifacts and rebuilding the project may resolve certain issues$ rebuild
Note that it will be time-consuming to rebuild the project from scratch, so be sure to exhaust all other troubleshooting options before attempting.
For assistance or bug reporting, file an issue or email ian.burzynski@emurgo.io.