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.
Two kinds of project
Section titled “Two kinds of project”Every project has an elci.toml that makes it one of two kinds:
| Kind | Created with | Configured by | Commands |
|---|---|---|---|
| Validator project | elci init <dir> | One or more [validators.<name>] tables | check, test, build, apply |
| Function project | elci init <dir> --purpose function | One [entrypoint] table | check, 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 formThe layout
Section titled “The layout”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
elci.toml
Section titled “elci.toml”The configuration for that project:
[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"]| Section | What 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 handlerUnknown or duplicate keys are errors too, which catches typos:
elci: ELCI4001: Unknown configuration key: [validators.main] boundryelci.toml reference lists every key.
src/: your modules
Section titled “src/: your modules”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.
tests/: test modules
Section titled “tests/: test modules”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 failedSee Unit tests for the details.
build/: generated files
Section titled “build/: generated files”elci build writes everything under build/, and .gitignore keeps it out of version control. For each
validator it writes a directory under build/validators/:
| File | Contents |
|---|---|
validator.plutus.json | Julc’s validator artifact (type, purpose, cborHex, hash, params, sizeBytes), which Cardano Client Lib applications load |
validator.plutus | A Plutus V3 text envelope |
validator.flat | The script’s UPLC in FLAT encoding |
abi.json | Parameters, handlers, and how every datum and redeemer type is encoded |
applied.plutus.json | Written 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.
Where things go
Section titled “Where things go”| You want to… | Put it in |
|---|---|
| Write a validator | A module in src/, plus a handler key in a [validators.<name>] table |
| Share helpers between validators | Another module in src/, imported where needed |
| Reuse Elci code across projects | A separate source directory, added to source-directories |
| Use a Julc Java library | A [java-modules] entry, then import it |
| Use your own Java library | library-jars in [project], plus a [java-modules] entry |
| Test a validator or function | A module under tests/ |
| Pin the on-chain data format | A committed copy of abi.json, referenced by expected-abi |
| Experiment with the language | A separate function project (elci init playground --purpose function) |