Familiar and functional
Elm syntax, pure functions, currying and pipelines. If you have written Elm, Haskell or F#, you can read an Elci contract on day one.
Development preview · Plutus V3
Elci is a pure, strongly typed functional language with Elm syntax. Your validators compile to compact Plutus V3 scripts through Julc, with strict data boundaries, multi-purpose validators and testing built in.
module Main exposing (spend)
import Julc.Ledger exposing (PubKeyHash, ScriptContext)
{-| Accept only transactions signed by the owner.
-}
spend : PubKeyHash -> () -> () -> ScriptContext -> Bool
spend owner _ _ ctx =
List.any
(\signer -> signer == owner)
ctx.txInfo.signatoriesWhy Elci
On-chain code has no second chance. Elci gives you a small, predictable functional language, and a compiler that refuses programs it cannot represent faithfully rather than erasing types to raw data.
Elm syntax, pure functions, currying and pipelines. If you have written Elm, Haskell or F#, you can read an Elci contract on day one.
Hindley–Milner inference, exhaustive pattern matching and rejected unreachable branches. Errors quote your code, name what differs and suggest a fix.
Datums, redeemers and parameters are decoded strictly from their types. Malformed data fails before your logic runs.
Named validators combine spend, mint, withdraw, certify, vote and propose handlers, with deployment parameters and ABI drift checks.
Unit tests, transaction scenarios on the deployed script, and property tests that shrink failures, all written in Elci.
Julc's PIR verifier, optimizer and UPLC pipeline, its standard library, typed Map, Array and Bytes, and a fast native CLI.
See it in action
Real code from the Elci examples. Each sample compiles, and every test passes.
module Vesting exposing (VestingAction(..), VestingDatum, validate)
import Interval
import Julc.Ledger exposing (PubKeyHash, ScriptContext)
import Tx
type alias VestingDatum =
{ beneficiary : PubKeyHash
, deadline : Int
}
type VestingAction
= Claim
validate : VestingDatum -> VestingAction -> ScriptContext -> Bool
validate datum action ctx =
case action of
Claim ->
Interval.isEntirelyAfter datum.deadline ctx.txInfo.validRange
&& Tx.signedBy datum.beneficiary ctxA typed datum and redeemer, the validity range and the signatories. The datum and redeemer are strictly decoded from their types before validate runs.
claimFrom : Int -> Scenario
claimFrom time =
Scenario.spend "vesting" (Scenario.outRef "vesting" 0) (Data.encode datum)
|> Scenario.redeemer (Data.encode Claim)
|> Scenario.validFrom time
claimsAfterTheDeadline : Scenario
claimsAfterTheDeadline =
claimFrom 1001
|> Scenario.signedBy beneficiary
claimsAtTheDeadlineFail : Scenario
claimsAtTheDeadlineFail =
claimFrom 1000
|> Scenario.signedBy beneficiary
|> Scenario.expectFailureA scenario is a transaction described as data. elci test compiles the validator exactly as elci build does and runs the deployed script in a complete ledger context.
{-| Whenever a signed claim's validity range starts, it succeeds exactly when that is
after the deadline.
-}
claimsSucceedExactlyAfterTheDeadline : Int -> Property
claimsSucceedExactlyAfterTheDeadline time =
let
signed =
claimFrom time
|> Scenario.signedBy beneficiary
in
if time > datum.deadline then
Property.scenario signed
else
Property.scenario (Scenario.expectFailure signed)A function returning a Property runs 100 times with generated arguments. A failure is shrunk to a small counterexample and reported with a reproducible seed.
[project]
source-directories = ["src"]
target = "plutus-v3-pv11-uplc-1.1.0"
julc-version = "0.1.0-pre20-ebaf028-SNAPSHOT"
# One script: the treasury address spends payouts and withdraws its staking rewards.
[validators.treasury]
boundary = "julc-strict-v1"
parameters = ["admin"]
spend = "Treasury.spend"
withdraw = "Treasury.withdraw"
# A separate script and policy id.
[validators.badge]
boundary = "julc-strict-v1"
parameters = ["admin"]
mint = "Badge.mint"Configuration, not syntax, decides deployment. One validator can spend and withdraw with shared parameters, and another is a separate minting policy.
module Example.Offer exposing (validate)
import Julc.Ledger exposing (ScriptContext)
import Julc.Math as Math
validate : Int -> Int -> ScriptContext -> Bool
validate minimumAda offeredLovelace _ =
Math.floorDiv offeredLovelace 1000000 >= minimumAdaMap a Julc on-chain Java library in elci.toml and import it like an Elm module. Elci compiles the library's real Java source into your script.
How it works
Elci owns the language: parsing, modules, type inference and lowering to typed PIR. Julc owns the rest: verification, linking, optimization and UPLC. The two meet at a checked, revisioned contract.
validator.plutus.jsonvalidator.plutusvalidator.flatabi.jsonLearning path
The documentation is one guided path. Start at the top, or jump to what you need.
Build the CLI on Java 25, or compile the native binary.
02Create, test and build a signed spending validator.
03Functions, records, custom types, collections and modules.
04Purposes, the ledger context, datums, redeemers and parameters.
05Unit tests, transaction scenarios and property tests.
06Tutorials, Java libraries, costs and how Elci compiles.
Build with AI
AI models have seen little Elci. A starter pack written for agents gives them the language, the APIs and the error fixes in one read, and elci check and elci test let them verify their own code. It works with Claude Code, Codex, Cursor, Copilot and any agent that can run a shell command.
You are writing Cardano smart contracts in Elci, an Elm-syntax language that compiles to Plutus V3. Before writing any code, read https://elci-lang.dev/ai/starter-pack.md and follow it strictly. After every change, run `elci check .` and `elci test .` and fix every ELCI error before continuing.
Quickstart
elci init creates a validator with a deployment parameter and two transaction scenarios. elci test runs them against the deployed script, and elci build writes Cardano-ready artifacts.
Follow the guided path from installation to a tested, deployable validator.