- Features
- Installation
- Writing contracts
- Updating Jambhala
- Updating Plutus dependencies
- Troubleshooting
Jambhala brings Cardano development nirvana by presenting five jewels:
💎 #1: Painless installation of Cardano tooling
- Only
gitandnixare 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-nodeandcardano-cliusing 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
Preludemodule which includes both thePlutusTx.Preludeand common Haskell items .- No need to use
NoImplicitPreludepragma and manually importPlutusTx.Preludeor Haskell's prelude - No need to use
hidingclauses or qualified imports to avoid nameclashes:PlutusTxversions 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 Plutus types 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.Utilsprovides 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-clitasks that can be run as terminal commands. - A Haskell-based executable CLI (
j cli) 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-appsusing a single command. - No wrangling of dependency boilerplate: just build your project environment and get to work, then bump
plutus-appsfor a specific project whenever you like.
💎 #5: Learn from a wealth of high-quality tutorials and samples
- Cardano CLI Guru provides a series of easy-to-follow
cardano-clitutorials 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
gitinstalled. Rungit -vin a terminal window to confirm. - This project assumes the use of
VS Codium(a preconfigured installation is provided) orVS Codeas editor. Other editors will require alternative workflows that are not covered here. - Jambhala and
cardano-nodeare storage-intensive. We suggest you have at least50GBof 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
Note: if no configuration file exists at
/etc/nix/nix.confit's possible the file is located elsewhere, depending on your OS. Runfind / -name "nix.conf" 2>/dev/nullto find the location of the file (this may take several minutes). -
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 ca-derivations # 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-appscurrently doesn't build successfully onaarch64architecture:# 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-daemonto 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-submodulesNote: 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-submodulesflag.
Before proceeding to Step 5, navigate to your project directory in a terminal window and run the Jambhala Readiness Test to confirm that 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 statussection. Ifdirenvversion2.30or 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
direnvinto 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
.zprofileor.bash_profilefile (depending on which shell you use)..zshrcand.bashrcare 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-projectYou 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 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) 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 + cand 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 environment loads successfully, you can get started using Jambhala's j commands (also called "recipes"). These provide concise commands to run common tasks from within the Jambhala environment.
To see available "recipes", run j --list. You'll see a list of commands displayed along with brief descriptions of what they do. You can run any of these by adding j in front of the command. Specific j commands will be explained in later sections.
We'll run our first j command to launch the Jambhala setup wizard (j setup), which is the final step of the installation process. It helps set up required Cardano tooling like cardano-node and cardano-cli, and personalizes your project if you're using Developer Mode.
j setupIf you created your repository by generating from the template (Development Mode), the wizard will begin by providing a series of prompts allowing you to personalize your project:
- Your
.cabalandLICENSEfiles will be customized using your answers to the prompts. - You can optionally move the Jambhala
READMEfile to thedocsdirectory, and create a newREADMEfor your project.
Next, the setup wizard will prompt you to install cardano-node and cardano-cli.
- You'll need a fully-synced
cardano-nodewithcardano-clito 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-nodeandcardano-clithrough 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-cliis provided in thecardano-cli-guru/tutorialdirectory.
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 j code 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) may not work properly in the editor.
Because the j code editor is installed via Nix, it isn't possible to update VS Codium or install additional extensions in the usual manner from within the application. Extensions can instead be added to the flake.nix file and installed via Nix the next time you load the Jambhala environment. To add an extension:
-
Click the
Extensionsicon 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
Installbutton. -
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
nixpkgsrepository 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.nixand 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 reloadin your terminal (inside your project root directory). -
Run the
j codecommand 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=trueWhile 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 makes certain opinionated decisions in order to vastly reduce the boilerplate required to write Plutus contracts.
- Jambhala uses a custom
Preludemodule which includes both thePlutusTx.Preludeand common Haskell items.- No need to use
NoImplicitPreludepragma and manually importPlutusTx.Preludeand Haskell's prelude - No need to use
hidingclauses or qualified imports to avoid nameclashes:PlutusTxversions 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/Samplesfor 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
-- A syntactic convenience for constructing record values:
, NamedFieldPuns
-- 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 destructuring record values:
, 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 j cli utility and only serve your own contracts, you can modify the main action in j-cli/Main.hs accordingly:
main :: IO ()
main = runJamb contracts -- << replace `allContracts` with `contracts` to hide sample contracts
where allContracts = contracts <> samplesTo 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.Plutus
Jambhala.Utils
Prelude
-- Add new contracts here, i.e.:
Contracts.MyContract
Contracts.MyOtherContract
...
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 j 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/Samplesfor 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:
GiveParamandGrabParam. These represent the types of the inputs our off-chain endpoint actions will accept. - Two methods,
giveandgrab, 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 = GrabHere 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
GiveandGrab, 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:
ContractMis a type synonym for a more polymorphicContractmonad defined in theplutus-appslibraries.
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
EmulatorActionvalues.
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)
Jambhala includes a simple command-line utility (j cli), which reduces boilerplate and provides a simple API for common contract-related tasks.
You can run the following command to view the names of available contracts in your project, for use with other commands:
j cli -lj 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, j cli will produce serialised JSON versions of them along with your contract script when you use the j cli -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
dataExportsmust be a list, even if you are only exporting a single value.
In this example, running j cli -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
}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 MyContractThen 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 j cli.
You can calculate the script hash for any available contract like this:
j cli -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:
j cli -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:
j cli -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.
To start a GHCi REPL session, run j repl and then load your contract:
j repl
Prelude Contracts λ > :m Contracts.MyContract
Prelude Contracts.MyContract λ >To launch a Hoogle server with docs for the Haskell packages this project is using, open a new bash terminal from the project root directory and run the following command:
j hoogleThe 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.
If you created your repository by forking Jambhala (Learning Mode), you can update Jambhala using the j update command:
j updateThis 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 (specifically jambhala.cabal and/or src/Contracts.hs), you may encounter a merge conflict that you'll need to resolve.
If you've made changes but not committed them, you can try j update -f to discard any uncommitted changes to jambhala.cabal and src/Contracts.hs. After this, you'll need to re-add your contract modules to jambhala.cabal and your contract imports/exports to src/Contracts.hs.
If you've committed changes to these files, you'll need to resolve the conflict yourself. The simplest way to do this is to copy/paste the latest contents from https://github.com/iburzynski/jambhala for the conflicting files, commit your changes, and run j update again. After this, you'll need to re-add your contract modules to jambhala.cabal and your contract imports/exports to src/Contracts.hs.
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 Jambhala environment automatically adds the upstream template as a remote source. You can run the j update command to fetch any changes to the template and attempt to merge them:
j updateNote that this command is distinct from the
j cli -ucommand discussed below, which updates only theplutus-appsdependency incabal.project, not Jambhala itself.
You will need to manually resolve any resulting merge conflicts.
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.
j cli provides a utility to easily update plutus-apps to the most recent revision and adjust all related dependencies. Run the j cli -u command to pull the latest revision and generate a new cabal.project file.
j cli -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 j cli -u command with an additional argument to set plutus-apps to a specific commit hash or tag:
j cli -u 38979da68816ab84faf6bfec6d0e7b6d47af651aYou can run the j pa-history command to view the full commit history for plutus-apps:
j 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 j cli -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-cliusing Nix (i.e. via Cardano EZ-Installer), thecardano-node/cardano-clicommands 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 NixNOTE: make sure this snippet is above the
direnvhook in these files. You must then restart the shell for the changes to take effect.
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-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. -
j rebuild: cleaning the.direnv,.cabal, anddist-newstyledirectories of all build artifacts and rebuilding the project may resolve certain issuesj 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.
-
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/storecan resolve such errors (the contents of this directory are always safe to remove and will not break any functionality). After removing the directory, run thejrebuildcommand to rebuild the project with Cabal before trying again.
For assistance or bug reporting, file an issue or email ian.burzynski@emurgo.io.
