Skip to content

Anatomy of a project

This page walks through the files in an Elci project and what each one does, so you know where new code, tests and configuration belong.

Every project has an elci.toml that makes it one of two kinds:

KindCreated withConfigured byCommands
Validator projectelci init <dir>One or more [validators.<name>] tablescheck, test, build, apply
Function projectelci init <dir> --purpose functionOne [entrypoint] tablecheck, test, build, eval

A validator project holds scripts you deploy. A function project names one function or value for elci eval to run. It is not a validator, which makes it a good playground for learning the language, and the Learn pages use one. A project can’t be both:

elci: ELCI4001: elci.toml mixes [entrypoint] with [validators.*]; use one form

Here is a validator project with two validators, a shared helper module and a test module, after elci build:

  • Directorytreasury/
    • elci.toml configuration
    • README.md
    • .gitignore contains build/
    • Directorysrc/ your modules
      • Auth.elm shared helper
      • Badge.elm the badge minting policy
      • Treasury.elm the treasury validator
    • Directorytests/ test modules, never deployed
      • TreasuryTest.elm
    • Directorybuild/ generated by elci build
      • Directoryvalidators/
        • elci-outputs.json
        • Directorybadge/
          • abi.json
          • validator.flat
          • validator.plutus
          • validator.plutus.json
        • Directorytreasury/
          • abi.json
          • validator.flat
          • validator.plutus
          • validator.plutus.json

The configuration for that project:

elci.toml
[project]
source-directories = ["src"]
target = "plutus-v3-pv11-uplc-1.1.0"
julc-version = "0.1.0-pre20-ebaf028-SNAPSHOT"
# The treasury script: holds payouts until a beneficiary claims them.
[validators.treasury]
boundary = "julc-strict-v1"
parameters = ["admin"]
spend = "Treasury.spend"
# A separate script: a minting policy for badges.
[validators.badge]
boundary = "julc-strict-v1"
parameters = ["admin"]
mint = "Badge.mint"
# Julc Java libraries, imported in Elci as `import Julc.Math as Math`.
[java-modules]
"Julc.Math" = "org.julclang.stdlib.lib.MathLib"
[tests]
source-directories = ["tests"]
SectionWhat it configures
[project]source-directories, the on-chain target and the julc-version the compiler must match. Optionally library-jars for custom Java libraries.
[validators.<name>]One deployed script with its own hash: boundary, handler keys (spend, mint, withdraw, certify, vote, propose), and optionally parameters, spend-datum and expected-abi.
[entrypoint]A function project: module, function and purpose = "function".
[java-modules]Maps an Elm-style module name to a Julc on-chain Java library class. The core modules, such as Tx and Interval, need no mapping.
[tests]Where test modules live. The default is ["tests"].

Validator names use lowercase letters, digits, - and _, and start with a letter. The name becomes the directory under build/validators/, and tests refer to the validator by that name.

The file uses a strict subset of TOML: section headers, key = "value" with quoted strings (no escapes), and arrays of quoted strings. Full-line # comments are fine, but a comment at the end of a line is not:

elci: ELCI4001: Expected a quoted string without escapes: "Main.spend" # the handler

Unknown or duplicate keys are errors too, which catches typos:

elci: ELCI4001: Unknown configuration key: [validators.main] boundry

elci.toml reference lists every key.

Each module lives at the path that matches its name under a source directory. Treasury is src/Treasury.elm, and Market.Pricing would be src/Market/Pricing.elm. Handler keys in elci.toml point at exposed functions as Module.function.

Put code shared by several validators in its own module, such as Auth.elm above, and import it where you need it. Each validator only includes the code its handlers use.

To share Elci code between projects, keep it in its own directory and add that directory to source-directories. See Modules and imports.

Test modules live under tests/ and are compiled only by elci test. They are never part of check, build or the published scripts. A test is an exposed, zero-argument declaration of type Bool, Expectation, Scenario or Property (or an exposed function that returns a Property). Other declarations are helpers:

PASS TreasuryTest.beneficiaryReleases (cpu 11698810, mem 46679)
PASS TreasuryTest.strangerCannotRelease (cpu 12077243, mem 48611)
2 passed, 0 failed

See Unit tests for the details.

elci build writes everything under build/, and .gitignore keeps it out of version control. For each validator it writes a directory under build/validators/:

FileContents
validator.plutus.jsonJulc’s validator artifact (type, purpose, cborHex, hash, params, sizeBytes), which Cardano Client Lib applications load
validator.plutusA Plutus V3 text envelope
validator.flatThe script’s UPLC in FLAT encoding
abi.jsonParameters, handlers, and how every datum and redeemer type is encoded
applied.plutus.jsonWritten by elci apply: the script with its parameters applied, and its hash

build/validators/elci-outputs.json records which files Elci wrote. When you remove a validator from elci.toml, the next full build deletes that validator’s files. Any other files in those directories are left alone. elci build . --validator treasury builds only the named validator and removes nothing else. Every selected validator compiles before anything is written, so a failed build leaves the previous outputs untouched.

A function project writes build/validator.flat, build/validator.plutus and build/abi.json. It writes no validator.plutus.json, because a function isn’t deployed.

You want to…Put it in
Write a validatorA module in src/, plus a handler key in a [validators.<name>] table
Share helpers between validatorsAnother module in src/, imported where needed
Reuse Elci code across projectsA separate source directory, added to source-directories
Use a Julc Java libraryA [java-modules] entry, then import it
Use your own Java librarylibrary-jars in [project], plus a [java-modules] entry
Test a validator or functionA module under tests/
Pin the on-chain data formatA committed copy of abi.json, referenced by expected-abi
Experiment with the languageA separate function project (elci init playground --purpose function)