- Features
- Installation
- Using the
jamb
CLI - Writing contracts
- Updating Jambhala
- Updating Plutus dependencies
- Troubleshooting
Jambhala brings Cardano development nirvana by presenting five jewels:
💎 #1: Painless installation of Cardano tooling
- Only
git
, andnix
are required to get started. - Jambhala's Readiness Test confirms system readiness before proceeding with installation, preventing wastage of time and resources on failed builds.
- Jambhala's setup wizard handles everything else, including easy installation of
cardano-node
andcardano-cli
using Cardano EZ-Installer. - plutus-apps is installed internally to your project, so you don't need to maintain a central clone and use its associated Nix shell as the entry point for your projects (See Jewel #4 below).
- A preconfigured VS Codium editor is included, allowing you to start coding immediately (you can still use your editor of choice if preferred).
💎 #2: Minimize contract boilerplate
- Jambhala uses a custom
Prelude
module which includes both thePlutusTx.Prelude
and common Haskell items .- No need to use
NoImplicitPrelude
pragma, manually importPlutusTx.Prelude
and Haskell's prelude - No need to use
hiding
clauses or qualified imports to avoid nameclashes:PlutusTx
versions of functions that clash with their Haskell counterparts are prefixed withp
(for prefix functions) and#
(for infix operators)
- No need to use
- The amount of required language extensions for Plutus development are significantly reduced, and commonly required extensions are enabled by default.
- Common Plutustypes and functions are re-exported from their respective modules by
Jambhala.Plutus
, so you don't need to keep track of messy import boilerplate. You can always import Plutus modules explicitly if you prefer. Jambhala.Utils
provides common utility functions to avoid contract clutter.
💎 #3: Perform common cardano-cli
and Plutus tasks with simple commands
- Jambhala includes Cardano CLI Guru, which provides utility scripts for common
cardano-cli
tasks that can be run as terminal commands. - A Haskell-based executable CLI called
jamb
lets you easily perform common tasks with your Plutus contracts, including:- Computing validator hashes and script addresses
- Running emulator tests
- Serializing contracts and their associated input data to JSON files
💎 #4: Keep projects in sync with plutus-apps
- 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-apps
using a single command. - No wrangling of dependency boilerplate: just build your project environment and get to work, then bump
plutus-apps
for a specific project whenever you like. - Serve Haddock documentation for the specific
plutus-apps
revision your project uses with theserve-docs
command.
💎 #5: Learn from a wealth of high-quality tutorials and samples
- Cardano CLI Guru provides a series of easy-to-follow
cardano-cli
tutorials that teach you how to build increasingly complex transactions and native scripts. - Numerous sample contracts are included to help you learn Plutus quickly.
- Jambhalucid provides an example frontend user interface for sample contracts built with Next.js, TypeScript and Lucid/
use-cardano
(UNDER CONSTRUCTION).
- This project uses the Nix package manager to build a fully-functioning and reproducible Cardano 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 Jambhala.
- Your system must have
bash
andgit
installed. Runbash --version
andgit -v
in a terminal window to confirm.- NOTE for MacOS users: MacOS may ship with versions of
bash
andgrep
that are incompatible with this workflow. You should installbash
/grep
using Homebrew first before proceeding.
- NOTE for MacOS users: MacOS may ship with versions of
- This project assumes the use of
VS Codium
(a preconfigured installation is provided) orVS Code
as editor andbash
as shell (also provided in the Nix development environment). Other tools will require alternative workflows that are not covered here. - Jambhala and
cardano-node
are storage-intensive. We suggest you have at least50GB
of free disk space before proceeding further.
- 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 should 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: Add your username to trusted-users (also include 'root' to prevent overriding default setting) trusted-users = root your-username # Step 2c: Avoid unwanted garbage collection with nix-direnv keep-outputs = true
-
Mac users with Apple silicon hardware (M1/M2 chip) also need to add the following, as
plutus-apps
currently doesn't build successfully onaarch64
architecture:# Step 2d: Adjust system and platforms for aarch64 compatibility: system = x86_64-darwin extra-platforms = x86_64-darwin aarch64-darwin
-
🚨 IMPORTANT! You must restart the
nix-daemon
to apply the changesLinux:
sudo systemctl restart nix-daemon
MacOS:
sudo launchctl stop org.nixos.nix-daemon sudo launchctl start org.nixos.nix-daemon
There are two "modes" you can choose from to use Jambhala, depending on your use case:
Learning Mode is recommended if you are currently learning Cardano/Plutus or just experimenting with Jambhala, as opposed to developing a full-fledged project.
Jambhala is under active development, with new features and learning materials being added on a continuous basis. To take advantage of the latest additions, it's better to work inside a fork of the repository. Your fork will maintain a historical link with the source repository upstream, which makes it easier to fetch and merge upstream changes into your local Jambhala setup (see Updating Jambhala for instructions).
Cons:
- Github only allows one fork of a repository at a time
- Contributions to a fork aren't reflected in your activity on Github
For these reasons, Learning Mode isn't well-suited for developing your own projects using Jambhala.
To use Jambhala in Learning Mode, just click the Fork
button at the top of this repository's page on Github to create your fork. Then proceed to Step 4.
Development Mode allows you to generate a completely independent repository, which can be personalized for your project. You can generate as many repositories from the template as you like, and your contributions to them will be reflected in your Github activity.
Cons:
- Generating from a Github template creates a new repository with no historical link to the upstream template.
- This makes it more difficult to incorporate updates to Jambhala released after your repository is generated.
It's still possible to update Jambhala in Development Mode, although it requires more manual labor to resolve merge conflicts (see Updating Jambhala for instructions).
To use Jambhala in Development Mode, select the green Use this template
button on this repository's Github page, and select Create a new repository
to generate a new repository from the Jambhala template.
Clone your new repository in a terminal session:
git clone https://github.com/PATH-TO/YOUR-REPO.git --recurse-submodules
Note: Jambhala uses git submodules to incorporate companion projects (like Cardano EZ-Installer, Cardano CLI Guru, and Jambhalucid). These projects are maintained as separate repositories. To include them when you clone your fork, you must use the
--recurse-submodules
flag.
Before proceeding to Step 5, navigate to your project directory in a terminal window and run the Jambhala Readiness Test to confirm whether your system is ready to install the Jambhala environment:
./ready?
The script will prepare your system to use Jambhala and check for any issues with your Nix configuration.
Jambhala uses the direnv
utility to provide seamless loading of the Nix environment whenever you navigate into the project directory tree. The Readiness Test will prompt you to install direnv
using Nix and configure it to work with your shell if you don't already have a compatible version installed and configured.
Jambhala requires direnv
version >= 2.30
, which may not be available in the packaging systems for certain older operating systems (for instance, any Ubuntu system below version 22.10
). For convenience, the Readiness Test allows you to install a compatible version using Nix.
While not recommended, if you prefer to install direnv
through a different method you may do the following:
- Visit the direnv installation page and check which version is available for your OS in the
Packaging status
section. Ifdirenv
version2.30
or higher is available for your machine, follow the instructions to install it. Otherwise use the Readiness Test to install a compatible version using Nix. - The final step is to hook
direnv
into your shell. Running the Readiness Test (./ready?
) will complete this step for you, but if you prefer to do it manually you can follow the instructions for your preferred shell here.
Note: MacOS has two types of shell sessions: login and interactive. If using login sessions, you'll need to add the appropriate hook to your
.zprofile
or.bash_profile
file (depending on which shell you use)..zshrc
and.bashrc
are used for interactive sessions. For convenience, the Readiness Test adds hooks to all four of these files for Mac users.
After the ./ready?
script completes, correct any issues and re-run it until all tests pass.
-
Open a new terminal window and navigate to your project directory:
cd path-to-your-project
You should now see the following message (if not, return to Step 4 and complete the Readiness Test):
direnv: error /home/path-to-your-project/.envrc is blocked. Run `direnv allow` to approve its content
This is a security measure, since
.envrc
files can run arbitrary shell commands. Make sure you always trust the author of a project and inspect the contents of its.envrc
file before runningdirenv allow
.When you're ready, enter
direnv allow
to 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) or if you see any warning such as
warning: ignoring substitute
, this means your system is not configured correctly and Nix is attempting to build packages from source that it should be fetching from a cache. Exit withCTRL + c
and repeat Step 4, 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
jsetup
to launch the Jambhala setup wizard:jsetup
-
The setup wizard runs differently depending on whether you're using Jambhala in Learning or Development mode:
- If you created your repository by forking (Learning Mode), the wizard will simply build the project without any personalization.
- If you created your repository by generating from the template (Development Mode), the wizard will provide a series of prompts to personalize your project.
- Your
.cabal
andLICENSE
files will be customized using your answers to the prompts. - The Jambhala
README
file will be moved to thedocs
directory, and a newREADME
for your project will be created
- Your
-
The project will then be built using
cabal
. While seemingly redundant (since the project's dependencies have already been built using Nix), acabal build
is necessary for proper Haskell Language Server support in VS Codium/Code. This step will also take some time to complete:_=_ q(-_-)p '_) (_` /__/ \ _(<_ / )_ _________(__\_\_|_/__)_________ Om Dzambhala Dzalentraye Svaha!
-
Installing
cardano-node
&cardano-cli
- Once the project has built, the setup wizard will prompt you to install
cardano-node
andcardano-cli
. - You'll need a fully-synced
cardano-node
withcardano-cli
to submit example transactions to the blockchain. The setup wizard runs Cardano EZ-Installer to easily install and configure these tools in a single step. - This installation method also makes it easy to update your node/cli to a new version later.
- If you've already installed
cardano-node
andcardano-cli
through other means, you can also configure your existing installation to work with Jambhala. - See the Cardano EZ-Installer README for more information.
- A tutorial with guided exercises for learning to use
cardano-cli
is provided in thecardano-cli-guru/tutorial
directory.
- Once the project has built, the setup wizard will prompt you to install
Jambhala's development environment includes a preconfigured instance of VS Codium (a community-driven, freely-licensed distribution of VS Code without Microsoft branding or telemetry). This "Jambhala Editor" comes with all the required extensions for Cardano development already installed via Nix.
Simply use the jcode
command from your project's root directory in your terminal to open the Jambhala editor and start coding!
Note: when you open a Haskell (
*.hs
) file for the first time in your editor, you may see the following popup appear:
How do you want the extension to manage/discover HLS and the relevant toolchain?
Manually via PATH Cancel Automatically via GHCup
Select Manually via PATH
. Our project is using the Haskell tooling installed via Nix in the development environment, not a system-wide installation via GHCup. If you select GHCup the Haskell Language Server (HLS) won't work properly in the editor.
Because the Jambhala Editor is installed via Nix, it isn't possible to install additional extensions in the usual manner from within the application. Instead, they can be added to the flake.nix
file and installed via Nix when you load the development environment using direnv
. To add an extension:
-
Click the
Extensions
icon in the left menu panel and look up the extension in the marketplace. -
Click on the extension you wish to install and click the gear icon next to the
Install
button. -
Select
Copy Extension ID
. -
Visit https://search.nixos.org/packages?channel=unstable and paste the Extension ID into the search. If a matching result is returned, your extension is available in the
nixpkgs
repository and can be added to VS Codium.*Note: in some rare cases, extensions are proprietary and thus aren't compatible with VS Codium (only VS Code).
-
Open
flake.nix
and find the following section:# flake.nix ... vscodeExtensions = with pkgs.vscode-extensions; [ asvetliakov.vscode-neovim dracula-theme.theme-dracula haskell.haskell jnoortheen.nix-ide justusadam.language-haskell mkhl.direnv ms-python.python ms-python.vscode-pylance ]; ...
-
Paste the Extension ID into the list of extensions on a new line and save the changes.
-
Close VS Codium, and run
direnv reload
in your terminal (inside your project root directory). -
Run the
jcode
command to relaunch VS Codium. Your extension should now be installed and ready for use.
* If your desired extension isn't available in nixpkgs
, it is still possible to add it to flake.nix
, but the process is more complex and will not be covered here. You can file an issue to request a particular extension be added if you feel it will be beneficial to Jambhala users, and I will consider adding it to the flake upstream.
Jambhala's VS Codium editor comes with the neovim
extension installed, but it's disabled by default when you load the editor using jcode
. If you prefer to use Vim keybindings, you can enable neovim
by changing the VIM_MODE
environment variable in the .env
file:
VIM_MODE=true
While the Jambhala Editor provides the most rapid route to start coding, you can also use Jambhala with your existing VS Code/Codium installation.
Loading the editor from within the Nix environment provides the most reliable experience, as it prevents errors that can be encountered when extensions (particularly the Haskell extension) load before the Nix environment has finished loading via direnv
.
Open the project root directory in your terminal and run one of the following, depending on your preferred editor:
code .
or...
codium .
Alternatively, you can simply start VS Code/Codium and use the File > Open Folder...
menu option to load your project directory. This method is more convenient, but occasionally results in errors indicating the Haskell extension is unable to determine the project's version of GHC. This is caused by the issue explained above, and may require occasionally reloading the project or require you to Restart Haskell LSP Server
from the command palette (CTRL + SHIFT + P
). For the best experience, launch your editor from your terminal inside the project directory.
The first time you open the project, you'll be prompted to install some recommended extensions if you don't have them already: haskell
, direnv
and Nix IDE
. Follow the prompt to install these, and close/relaunch your editor before continuing.
Accept any pop-up prompts from the direnv
extension when 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 -l
You can calculate the validator hash for any available contract like this:
jamb -s CONTRACT
where 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 CONTRACT
where 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). Contracts are saved in the assets
directory of the cardano-cli-guru
submodule, where they can be used to easily submit transactions via cardano-cli
, assisted by the various utility scripts provided by cardano-cli-guru
. When the command finishes, you'll get a CONTRACT.plutus
file at cardano-cli-guru/assets/scripts/plutus
that contains a JSON envelope of the UPLC code:
{
"type": "PlutusScriptV2",
"description": "",
"cborHex": "5907c05907bd0100003232323232323232323..."
}
This file can now be used in on-chain transactions.
Jambhala makes certain opinionated decisions in order to vastly reduce the boilerplate required to write Plutus contracts.
- Jambhala uses a custom
Prelude
module which includes both thePlutusTx.Prelude
and common Haskell items.- No need to use
NoImplicitPrelude
pragma and manually importPlutusTx.Prelude
and Haskell's prelude - No need to use
hiding
clauses or qualified imports to avoid nameclashes:PlutusTx
versions of functions that clash with their Haskell counterparts are prefixed withp
(for prefix functions) and#
(for infix operators)
- No need to use
- Many common Plutus types and functions are available via a single import from
Jambhala.Plutus
, which aggregates and re-exports items from the various Plutus modules. - See the sample contracts in
src/Contracts/Samples
for more examples of handling imports with Jambhala. - You can still import Plutus modules directly if you prefer, or if you need something from a Plutus module that isn't included in
Jambhala.Plutus
. Qualified or restricted imports may be required if you want to combine both approaches, due to overlapping exports.
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
-- Allows automatic derivation of certain typeclasses (like FromJSON/ToJSON):
, DeriveAnyClass
, DeriveGeneric
-- Allows defining typeclass instances for type synonyms:
, FlexibleInstances
-- Allows post-fix style qualified import declarations
, ImportQualifiedPost
-- Allows writing type signatures for methods in typeclass instances:
, InstanceSigs
-- A syntactic convenience for writing single-argument lambdas containing case expressions (used by Jambhala's utilities):
, LambdaCase
-- Allows more than one type parameter in class and instance declarations (required to lift parameters in parameterized validators):
, MultiParamTypeClasses
-- Allows more readable representations of large integers (i.e. 1_000_000), useful for lovelace quantities
, NumericUnderscores
-- Allows construction of Text and other string-like values as string literals:
, OverloadedStrings
-- A syntactic convenience for working with record values (used by Jambhala's utilities):
, RecordWildCards
-- Allows referencing type variables in multiple scopes (required to lift parameters in parameterized validators):
, ScopedTypeVariables
-- Required for all Plutus contracts to translate between Plutus and Haskell:
, TemplateHaskell
-- Provides a convenient way to disambiguate type variables inline
, TypeApplications
-- Allows type-level functions (used in Jambhala's ValidatorEndpoints & MintingEndpoint classes):
, TypeFamilies
Beyond these, the sample contracts include only the specific language extensions needed to compile their code. Extensions enabled by default are still declared explicitly in the sample contracts when they are introduced for the first time, in order to explain their use.
Keep in mind that Haskell language extensions are experimental modifications to compiler behavior: they should be used only when they provide a concrete benefit 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 = contracts <> samples
To create a new contract, create a new .hs
file in the src/Contracts
directory, and write a module declaration, i.e.:
module Contracts.MyContract where
In 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.Plutus
Jambhala.Utils
Prelude
-- Add new contracts here, i.e.:
Contracts.MyContract
Contracts.MyOtherContract
...
🚨 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 Codium/Code or stage changes from the command line withgit add
.
We're now ready to write our contract. We begin by defining a predicate function to express the validator or minting policy logic.
We then define a custom type synonym for our contract, using either ValidatorContract
or MintingContract
with a type-level string as an identifier:
type MyValidator = ValidatorContract "my-validator"
or...
type MyMintingPolicy = MintingContract "my-minting-policy"
The string identifier will be used to reference the contract in jamb
CLI commands.
Then we compile the predicate code into Plutus (using Template Haskell) and apply the appropriate Jambhala constructor function based on the type of our contract. We must provide a type signature for this value with the type synonym chosen above:
contract :: MyValidator
contract = mkValidatorContract $$(compile [||validator||])
or...
contract :: MyMintingPolicy
contract = mkMintingContract $$(compile [||policy||])
See the contracts in
src/Contracts/Samples
for examples of predicate definition and compilation.
Jambhala provides an enhanced variant of the plutus-apps
blockchain emulator with a simpler and more intuitive API. This tool (and the associated utilities imported from Jambhala.Utils
) allows us to write simple and readable off-chain code in Haskell and conduct simple tests of our contracts.
The emulator environment's behavior doesn't always perfectly match the way contracts behave on-chain (for instance, fee amounts may vary and should not be used for predictive purposes). For rigorous testing of contracts intended for production, more robust tools like plutus-simple-model
should be used. However, the emulator provides a way to confirm the expected behavior of on-chain scripts, as well as a good way to practice Haskell fundamentals.
After defining a custom type synonym for our contract using either ValidatorContract
or MintingContract
and a type-level string identifier, we must instantiate one of two corresponding typeclasses to implement emulation endpoints.
The ValidatorEndpoints
class consists of the following:
- Two associated data types:
GiveParam
andGrabParam
. These represent the types of the inputs our off-chain endpoint actions will accept. - Two methods,
give
andgrab
, which define the off-chain endpoint actions through which we can lock and unlock UTxOs at our contract's script address in an emulated blockchain environment.
GiveParam
and GrabParam
are associated data types, which are used to declare custom data types associated with an instance of a particular typeclass:
instance ValidatorEndpoints MyContract where
newtype GiveParam MyContract = Give {lovelace :: Integer}
data GrabParam MyContract = Grab
Here we've declared two new data types associated with MyContract
: GiveParam MyContract
and GrabParam MyContract
. We've used the more efficient newtype
keyword to declare our GiveParam
type, since it has a single constructor and a single field. Our GrabParam
type contains no fields (in this simple hypothetical example, we don't need to provide any information to unlock UTxOs from the contract address). We can think of this type as equivalent to the Unit type (()
), but we'll use the value Grab
to construct it, rather than ()
.
Note: We can call the constructors whatever we like, but the sample contracts use
Give
andGrab
, which provide semantic clarity in the context of our emulator tests.
In order for the emulator to work properly, we also need to be able to encode and decode parameter values to/from JSON format. This necessitates a bit of boilerplate deriving
code for each of our parameter types:
instance ValidatorEndpoints MyContract where
newtype GiveParam MyContract = Give {lovelace :: Integer}
deriving (Generic, FromJSON, ToJSON)
data GrabParam MyContract = Grab
deriving (Generic, FromJSON, ToJSON)
We're now ready to implement the give
and grab
endpoint methods, which have the following signatures:
give :: GiveParam MyContract -> ContractM MyContract ()
grab :: GrabParam MyContract -> ContractM MyContract ()
They accept values of our newly-created GiveParam
and GrabParam
types, and return a unit value inside a monadic context called ContractM
, which is parameterized by our MyContract
type.
Note:
ContractM
is a type synonym for a more polymorphicContract
monad defined in theplutus-apps
libraries.
The give
and grab
endpoint actions can contain arbitrary Haskell code depending on the nature of the contract. Their role is to construct and submit transactions, ideally conducting some preliminary validation mirroring the logic of the contract's on-chain script. The purpose of this preliminary off-chain validation is to prevent unnecessary submission of invalid transactions.
Ultimately, give
and grab
need to submit a transaction to the script address and await confirmation, using the submitAndConfirm
function. This function takes a Transaction
value constructed via the Tx
constructor and two fields: lookups
(which define which information is visible to the transaction) and constraints
(which define the conditions under which the transaction succeeds or fails):
submitAndConfirm
Tx
{ lookups = scriptLookupsFor contract,
constraints = mustPayToScriptWithDatum contract () lovelace
}
The MintingEndpoint
class is similar to ValidatorEndpoints
, but is simpler due to the comparative simplicity of minting policies vs. validators. It consists of the following:
- One associated data type:
MintParam
- One method,
mint
, which defines the off-chain endpoint action through which we can mint assets with the policy.
instance MintingEndpoint MyMinting where
data MintParam MyMinting = Mint
{ tokenName :: !TokenName,
tokenQuantity :: !Integer
}
deriving (Generic, FromJSON, ToJSON)
mint :: MintParam MyMinting -> ContractM MyMinting ()
mint (Mint tokenName tokenQuantity) = do
submitAndConfirm
Tx
{ lookups = scriptLookupsFor contract,
constraints = mustMint contract tokenName tokenQuantity
}
Note: Off-chain endpoint code is more complex than on-chain predicate functions and is beyond the scope of our tutorial at this time: refer to the sample contracts containing off-chain emulation for more examples.
Now that we've implemented our endpoint actions, we're ready to define our emulator test. We begin by declaring a variable for our test (i.e. test :: EmulatorTest
).
The test is constructed by calling the initEmulator
function (imported from Jambhala.Utils
). This function requires a type application (i.e. @MyContract
) to disambiguate the type of the contract being emulated. It then receives two arguments:
- The number of mock wallets the test requires (expressed as an integer literal)
- A comma-separated list of
EmulatorAction
values.
EmulatorAction
values primarily consist of (indirect) calls to the endpoints we defined in the ValidatorEndpoints
or MintingEndpoint
instance for our contract. These can be conveniently expressed using fromWallet
and toWallet
(for validator contracts) or forWallet
(for minting contracts), which provide a pseudo-code like semantics when used with infix notation:
test :: EmulatorTest
test =
initEmulator @MyContract
2
[ Give {lovelace = 3_000_000} `fromWallet` 1,
Grab `toWallet` 2
]
This test is initialized with 2 mock wallets. Wallet 1 gives 3 ADA to the script address, then Wallet 2 claims the gift.
EmulatorAction
values can also be included in the list to simulate the passage of time. The waitUntil
action takes a slot number and advances the emulated blockchain to that slot, which is useful for testing contracts involving deadlines (see Vesting.hs
and ParamVesting.hs
)
The jamb
CLI can perform various operations on your contracts, including calculating its script hash, testing it using a blockchain emulator, and compiling it into a .plutus
file. To do this we need to prepare a JambExports
value in each of our contracts. Start by declaring a value of this type (i.e. exports ::
JambExports
).
Construct the exported contract using the defExports
and export
utility functions (imported from Jambhala.Utils
):
exports :: JambExports
exports = export (defExports contract)
The defExports
(default exports) function takes a contract value and produces an ExportTemplate
value containing the contract's name and its script. The result of this function is then passed to export
, which constructs an export package compatible with the CLI.
The JambExports
can optionally include a list of DataExport
values. These are sample values to supply as inputs during transaction construction with cardano-cli
. Any value of a type with a ToData
instance can be exported.
If data exports are included, the jamb
CLI will produce serialised JSON versions of them along with your contract script when you use the jamb -w
command. These optional exports are included as additional input to defExports
using record update syntax and the dataExports
attribute:
exports :: JambExports
exports =
export
(defExports contract) -- Parentheses are required when using record update syntax
{ dataExports =
[
() `toJSONfile` "unit",
42 `toJSONfile` "forty-two"
]
}
Note: the value provided for
dataExports
must be a list, even if you are only exporting a single value.
In this example, running jamb -w my-contract
will serialise contract
into a .plutus
file and save it to cardano-cli-guru/assets/scripts/plutus/my-contract.plutus
. It will also produce a serialised JSON representation of a unit value (saved to cardano-cli-guru/assets/data/unit.json
) and the integer 42 (saved to cardano-cli-guru/assets/data/forty-two.json
).
An emulator test value (::
EmulatorTest
) can also be optionally included in the record input, using the emulatorTest
attribute:
exports :: JambExports
exports =
export
(defExports contract)
{ dataExports =
[
() `toJSONfile` "unit",
42 `toJSONfile` "forty-two"
],
emulatorTest = test
}
Adding to the Contract Map
Once we've completed our exports
value, the final step is to make our contract visible to the CLI. We go to src/Contracts/Contracts.hs
and import our contract's module as a qualified import, i.e.:
import Contracts.MyContract qualified as MyContract
Then we add a new entry to the contracts
list containing the exports
value:
contracts :: Contracts
contracts = [
MyContract.exports
]
Once our contract has been added to the list, it can now be operated on by the jamb
CLI.
- Compute the hash of the script:
jamb -s my-contract
- Run the provided emulator test:
jamb -t my-contract
- Write the contract and associated data exports to files:
jamb -w my-contract
To start a GHCi REPL session, run jrepl
and then load your contract:
jrepl
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-docs
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.
Note: This will require significant additional build time and storage space the first time the docs are served. Alternatively you can view Haddock docs for the most recent version of
plutus-apps
at https://input-output-hk.github.io/plutus-apps/main/.
Since Jambhala is under active development and is closely tracking the progress of plutus-apps
, its codebase changes frequently.
If you created your repository by forking Jambhala (Learning Mode), you can update Jambhala using the jupdate
command:
jupdate
This will fetch and merge changes from the upstream Jambhala repository, bringing your fork in sync while preserving any local changes you've made.
If you've made changes to any core Jambhala files, you may encounter a merge conflict that you'll need to resolve. You may find a VS Code extension like Git Merger to be helpful with this.
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 jsetup
wizard added the upstream template as a remote source. You can now run the jupdate
command to fetch any changes to the template and attempt to merge them:
jupdate
Note that this command is distinct from the
jamb -u
command discussed below, which updates only theplutus-apps
dependency incabal.project
, not Jambhala itself.
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 38979da68816ab84faf6bfec6d0e7b6d47af651a
You can run the pa-history
command to view the full commit history for plutus-apps
:
pa-history
Use 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
Additional issues and solutions will be documented here as they're encountered during public testing.
Nix stops working after updating Mac OS
Nix installation on Macs appends a code snippet to /etc/zshrc
and /etc/bashrc
, which is required for Nix to work properly with zsh
and bash
shells. Unfortunately one or both of these additions may be erased after updating your Mac.
This will break:
- all Nix commands
- any Jambhala commands that use Nix under the hood
- if you installed
cardano-node
/cardano-cli
using Nix (i.e. via Cardano EZ-Installer), thecardano-node
/cardano-cli
commands and associated aliases to start the node
To resolve the issue, edit ~/.zprofile
, ~/.zshrc
, ~/.bash_profile
, and ~/.bashrc
, adding the following snippet to the top of each file:
# Nix
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix
NOTE: make sure this snippet is above the
direnv
hook in these files. You must then restart the shell for the changes to take effect.
'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.
VS Codium/Code doesn't provide Haskell IDE support: hangs with Processing: 1/x
:
It's possible the project dependencies haven't been properly built via cabal
, which is a requirement for Haskell Language Server support in VS Codium/Code. Run cabal build
to build the dependencies.
-
Restart Haskell LSP Server: restarting
haskell-language-server
often fixes common issues with Haskell in VS Code. Open the command palette (Ctrl + Shift + p
) and begin typingRestart Haskell LSP Server
until 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. -
jrebuild
: cleaning thedist-newstyle
directory of all build artifacts and rebuilding the project may resolve certain issuesjrebuild
Note that it will be time-consuming to
cabal build
the project from scratch, so be sure to exhaust all other troubleshooting options before attempting. -
Delete
~/.local/state/cabal/store
: if you have pre-existing Haskell tooling installed system-wide (i.e. via GHCup, or the Haskell extension in VS Codium/Code), it's possible that build artifacts from Cabal can conflict with the Jambhala environment, resulting in Cabal errors or preventing IDE features from Haskell Language Server to work correctly in the editor. Removing~/.local/state/cabal/store
can resolve such errors (the contents of this directory are always safe to remove and will not break any functionality). After removing the directory, run thejrebuild
command to rebuild the project with Cabal before trying again.
For assistance or bug reporting, file an issue or email [email protected]
.