Skip to content

JuLC Standard Library Usage Guide

The JuLC standard library provides 13 on-chain libraries in the org.julclang.stdlib.lib package. Each library is annotated with @OnchainLibrary and compiled from Java source to UPLC. All methods are static and can be called directly from your validator code.

Library Import Path Purpose
ContextsLib org.julclang.stdlib.lib.ContextsLib Script context, TxInfo field access, signatory checks, datum lookup
ListsLib org.julclang.stdlib.lib.ListsLib List construction, traversal, search, and higher-order functions
ValuesLib org.julclang.stdlib.lib.ValuesLib Multi-asset Value comparison, arithmetic, and extraction
MapLib org.julclang.stdlib.lib.MapLib Association list (map) lookup, insert, delete, keys/values
OutputLib org.julclang.stdlib.lib.OutputLib Output filtering by address/token, lovelace summation, datum extraction
MathLib org.julclang.stdlib.lib.MathLib abs, max, min, pow, floorDiv, floorMod, divMod, quotRem, expMod (PV11), sign
IntervalLib org.julclang.stdlib.lib.IntervalLib Time interval construction, containment, bound extraction
CryptoLib org.julclang.stdlib.lib.CryptoLib Hash functions and signature verification
ByteStringLib org.julclang.stdlib.lib.ByteStringLib ByteString slicing, comparison, encoding, serialization
BitwiseLib org.julclang.stdlib.lib.BitwiseLib Bitwise AND/OR/XOR, shift, rotate, bit read/write
AddressLib org.julclang.stdlib.lib.AddressLib Credential extraction, address type checks
BlsLib org.julclang.stdlib.lib.BlsLib BLS12-381 G1/G2/pairing operations over the typed JulcG1/JulcG2/JulcMlResult values; MSM requires PV11
NativeValueLib (PV11) org.julclang.stdlib.lib.NativeValueLib Native MaryEra Value insert, lookup, union, contains, scale

Method Description
signedBy(txInfo, pkh) Check if a PubKeyHash is in the signatories
getTxInfo(ctx) Extract TxInfo from ScriptContext (legacy; prefer ctx.txInfo())
getRedeemer(ctx) Extract redeemer from ScriptContext
getSpendingDatum(ctx) Extract optional spending datum
txInfoInputs(txInfo) Get list of inputs
txInfoOutputs(txInfo) Get list of outputs
txInfoSignatories(txInfo) Get signatories list
txInfoValidRange(txInfo) Get valid time range
txInfoMint(txInfo) Get minted value
txInfoFee(txInfo) Get transaction fee
txInfoId(txInfo) Get transaction ID
txInfoRefInputs(txInfo) Get reference inputs
txInfoWithdrawals(txInfo) Get withdrawals map
txInfoRedeemers(txInfo) Get redeemers map
findOwnInput(ctx) Find the input being validated
getContinuingOutputs(ctx) Get outputs to the same script address
findDatum(txInfo, hash) Look up datum by hash
valueSpent(txInfo) Total value of all inputs
valuePaid(txInfo, addr) Values paid to an address
ownHash(ctx) Get own script hash
ownInputScriptHash(ctx) Get script hash of own input → byte[]
scriptOutputsAt(txInfo, hash) Get outputs at a script hash
listIndex(list, index) Get element at index
trace(msg) Emit a trace message
Method Description
empty() Create an empty list
prepend(list, elem) Prepend element to list
length(list) Number of elements
isEmpty(list) Check if list is empty
head(list) First element
tail(list) All elements except first
reverse(list) Reverse a list
concat(a, b) Concatenate two lists
nth(list, n) Element at index n
take(list, n) First n elements
drop(list, n) Drop first n elements
contains(list, elem) Check if list contains element (EqualsData)
containsInt(list, target) Check if integer list contains value (EqualsInteger)
containsBytes(list, target) Check if bytestring list contains value (EqualsByteString)
hasDuplicateInts(list) Check for duplicate integers
hasDuplicateBytes(list) Check for duplicate bytestrings
any(list, pred) True if any element matches predicate (HOF)
all(list, pred) True if all elements match predicate (HOF)
find(list, pred) Find first matching element (HOF)
foldl(f, init, list) Left fold (HOF)
map(list, f) Transform each element (HOF)
filter(list, pred) Keep elements matching predicate (HOF)
zip(a, b) Zip two lists into pairs (HOF)
Method Description
lovelaceOf(value) Extract lovelace amount
assetOf(value, policy, token) Extract specific asset amount
containsPolicy(value, policy) Check if policy exists in value
geq(a, b) Lovelace-only >= comparison
geqMultiAsset(a, b) Multi-asset >= comparison
leq(a, b) Multi-asset <= comparison
eq(a, b) Multi-asset equality
isZero(value) Check if all amounts are zero
singleton(policy, token, amount) Create single-asset Value
negate(value) Negate all amounts
flatten(value) Flatten to list of (policy, token, amount) triples
flattenTyped(value) Flatten to typed JulcList<AssetEntry> with .policyId(), .tokenName(), .amount()
add(a, b) Add two Values
subtract(a, b) Subtract value b from value a
countTokensWithQty(mint, policy, qty) Count tokens with exact quantity under policy
findTokenName(mint, policy, qty) Find token name with exact quantity under policy
refBytes(ref) Seed bytes for a TxOutRef: txId ++ 2-byte index
uniqueTokenName(ref) Collision-resistant token name: blake2b_256(refBytes(ref))
Method Description
lookup(map, key) Look up key; returns Optional (Constr 0/1)
member(map, key) Check if key exists
insert(map, key, value) Insert key-value pair
delete(map, key) Remove key from map
keys(map) Extract all keys as list
values(map) Extract all values as list
toList(map) Convert map to pair list
fromList(list) Create map from pair list
size(map) Number of entries
Method Description
txOutAddress(txOut) Extract address from output
txOutValue(txOut) Extract value from output
txOutDatum(txOut) Extract datum from output
outputsAt(outputs, address) Filter outputs by address
countOutputsAt(outputs, address) Count outputs at address
uniqueOutputAt(outputs, address) Exactly one output at address (aborts otherwise)
outputsWithToken(outputs, policy, token) Filter outputs by token
valueHasToken(value, policy, token) Check if value contains token
lovelacePaidTo(outputs, address) Sum lovelace paid to address
paidAtLeast(outputs, address, min) Check minimum lovelace at address
getInlineDatum(txOut) Get inline datum (aborts if not inline)
resolveDatum(txOut, datumsMap) Resolve datum from inline or hash lookup
findOutputWithToken(outputs, scriptHash, policy, token) Find output at script address with specific token
findInputWithToken(inputs, scriptHash, policy, token) Find input at script address with specific token
Method Description
abs(x) Absolute value
max(a, b) Maximum of two integers
min(a, b) Minimum of two integers
pow(base, exp) Exponentiation
sign(x) Sign: -1, 0, or 1
floorDiv(a, b) Floor division
floorMod(a, b) Floor modulo
divMod(a, b) Floor division and modulo as Tuple2
quotRem(a, b) Quotient and remainder as Tuple2
expMod(base, exp, mod) Modular exponentiation (PV11 only)

Use MathLib.floorDiv and MathLib.floorMod for BigInteger floor division. java.lang.Math.floorDiv and Math.floorMod are valid for normal Java int/long calls, but the JDK does not provide BigInteger overloads for those methods.

Method Description
contains(interval, point) Check if point is within interval
always() The (-inf, +inf) interval
after(t) The [t, +inf) interval
before(t) The (-inf, t] interval
between(low, high) The [low, high] interval
never() The empty interval
isEmpty(interval) Check if interval is empty
finiteUpperBound(interval) Extract finite upper bound (-1 if infinite)
finiteLowerBound(interval) Extract finite lower bound (-1 if infinite)
Method Description
sha2_256(bs) SHA2-256 hash
sha3_256(bs) SHA3-256 hash
blake2b_256(bs) Blake2b-256 hash
blake2b_224(bs) Blake2b-224 hash (key hashes)
keccak_256(bs) Keccak-256 hash
verifyEd25519Signature(key, msg, sig) Verify Ed25519 signature
verifyEcdsaSecp256k1(key, msg, sig) Verify ECDSA secp256k1 signature
verifySchnorrSecp256k1(key, msg, sig) Verify Schnorr secp256k1 signature
ripemd_160(bs) RIPEMD-160 hash
Method Description
at(bs, index) Get byte at index
cons(byte_, bs) Prepend a byte
slice(bs, start, length) Extract a slice
length(bs) Length of bytestring
drop(bs, n) Drop first n bytes
take(bs, n) Take first n bytes
append(a, b) Concatenate two bytestrings
empty() Empty bytestring
zeros(n) Bytestring of n zero bytes
equals(a, b) Equality check
lessThan(a, b) Lexicographic a < b
lessThanEquals(a, b) Lexicographic a <= b
integerToByteString(endian, width, i) Convert integer to bytestring
byteStringToInteger(endian, bs) Convert bytestring to integer
encodeUtf8(s) Encode string as UTF-8 bytes
decodeUtf8(bs) Decode UTF-8 bytes to string
serialiseData(d) Serialize Data to CBOR bytes
hexNibble(n) Convert nibble (0-15) to hex ASCII code
toHex(bs) Convert bytestring to hex-encoded bytestring
intToDecimalString(n) Convert integer to decimal digit bytestring
utf8ToInteger(bs) Parse UTF-8 decimal string to integer (inverse of intToDecimalString)
Method Description
andByteString(padding, a, b) Bitwise AND
orByteString(padding, a, b) Bitwise OR
xorByteString(padding, a, b) Bitwise XOR
complementByteString(bs) Bitwise complement
readBit(bs, index) Read bit at index
writeBits(bs, indices, value) Write bits at indices
shiftByteString(bs, n) Shift by n bits
rotateByteString(bs, n) Rotate by n bits
countSetBits(bs) Count set bits (popcount)
findFirstSetBit(bs) Index of first set bit (-1 if none)
Method Description
credentialHash(address) Extract payment credential hash bytes
isScriptAddress(address) Check if address has ScriptCredential
isPubKeyAddress(address) Check if address has PubKeyCredential
paymentCredential(address) Extract payment Credential

Import: org.julclang.stdlib.lib.ContextsLib

ContextsLib provides access to the Plutus V3 ScriptContext, TxInfo, and ScriptInfo types. For modern validators using typed ScriptContext, you can access fields directly (e.g., ctx.txInfo()) instead of using the legacy accessor methods.

The most common operation: verify that a specific public key hash signed the transaction.

import org.julclang.ledger.*;
import org.julclang.stdlib.lib.ContextsLib;
import java.math.BigInteger;
@SpendingValidator
class AuthorizedSpend {
record Datum(byte[] owner) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
return ContextsLib.signedBy(txInfo, datum.owner());
}
}

You can use either the typed field access on TxInfo directly or the ContextsLib accessor methods:

@SpendingValidator
class FieldAccessExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
// Direct typed field access (preferred)
JulcList<TxInInfo> inputs = txInfo.inputs();
JulcList<TxOut> outputs = txInfo.outputs();
JulcList<PubKeyHash> signatories = txInfo.signatories();
Interval validRange = txInfo.validRange();
Value mint = txInfo.mint();
BigInteger fee = txInfo.fee();
TxId txId = txInfo.id();
// Or via ContextsLib (equivalent, legacy style)
JulcList<TxInInfo> inputs2 = ContextsLib.txInfoInputs(txInfo);
Value mint2 = ContextsLib.txInfoMint(txInfo);
return true;
}
}

For spending validators that need to identify their own UTxO and find outputs returning to the same script address:

@SpendingValidator
class StatefulValidator {
record State(BigInteger counter) {}
@Entrypoint
static boolean validate(State datum, PlutusData redeemer, ScriptContext ctx) {
// findOwnInput returns Optional encoded as Constr(0, [txInInfo]) or Constr(1, [])
PlutusData.ConstrData ownInputOpt = ContextsLib.findOwnInput(ctx);
// getContinuingOutputs finds outputs to the same script address
PlutusData.ListData continuingOutputs = ContextsLib.getContinuingOutputs(ctx);
// Get own script hash (works for both minting and spending)
PlutusData.BytesData ownHash = ContextsLib.ownHash(ctx);
// Get script hash of own input as byte[]
byte[] scriptHash = ContextsLib.ownInputScriptHash(ctx);
return !Builtins.nullList(continuingOutputs);
}
}

Emit debug trace messages that appear in transaction evaluation logs:

@SpendingValidator
class TracingValidator {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
ContextsLib.trace("Starting validation");
TxInfo txInfo = ctx.txInfo();
if (ListsLib.isEmpty(txInfo.inputs())) {
ContextsLib.trace("No inputs found");
return false;
}
ContextsLib.trace("Validation passed");
return true;
}
}

Import: org.julclang.stdlib.lib.ListsLib

ListsLib provides list construction, traversal, searching, and higher-order functions. In Plutus, lists are singly-linked (cons lists). Most operations are O(n).

Indexing (ADR-043). list.get(i) walks the list, so it costs about 683,000 CPU per index step at PV11 costs. When one list variable is indexed at two or more sites, or inside a loop, either convert it yourself once with list.toArray() and index the JulcArray (PV11 only, O(1) per access), or compile at the opt-in pv11-costed level, where the compiler performs exactly that rewrite automatically (pv11.o9.list-to-array), placing the conversion at the innermost expression that contains every index site (for two sites in one expression the output is byte-identical to the manual form written there). At pv11-costed an out-of-range index fails at IndexArray with IndexArray: index I out of bounds for array of size N instead of HeadList/TailList: empty list; the failure point is the same. The default pv11-safe level leaves get as written.

Array literals (ADR-046). JulcArray.of(a, b, c) writes an array down: on-chain it is JulcList.of(a, b, c).toArray(), with the same Data-encoded element representation every JulcArray<T> has; get decodes by the element type, declared (JulcArray<BigInteger> fees = JulcArray.of(...)) or inferred from the elements under var (elements of different types are rejected). At the default pv11-safe level an array whose elements are all literals (integers, byte strings, strings, booleans, nested list literals) becomes one UPLC array constant, length() on it becomes a constant, and get(i) with a literal index becomes the element; get(i) with a runtime index keeps the access over the embedded constant. The same folds apply to list.toArray() and JulcArray.fromList(list) over a JulcList.of literal. A literal index outside the array stays as written and fails at runtime with IndexArray’s text, as before. Nothing changes at none/baseline; spell a negative literal element as new BigInteger("-5"). Do not use var for the literal: the compiler then types the elements as Data and get returns raw PlutusData, although javac infers JulcArray<BigInteger>.

@SpendingValidator
class ListExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// Size and emptiness
long count = outputs.size(); // or ListsLib.length(outputs)
boolean empty = outputs.isEmpty(); // or ListsLib.isEmpty(outputs)
// Element access
TxOut first = outputs.head(); // or ListsLib.head(outputs)
JulcList<TxOut> rest = outputs.tail(); // or ListsLib.tail(outputs)
return count > 0;
}
}

JulcList.of(...) builds a fixed-size list from any number of elements. Elements are auto-wrapped to Data (BigInteger → IntData, byte[] → BytesData, boolean → Constr, String → UTF-8 BytesData, PlutusData → as-is). Call .toPlutusData() when a PlutusData list is needed:

// Before: manual mkCons chain
PlutusData inputs = Builtins.listData(
Builtins.mkCons(Builtins.iData(pkh),
Builtins.mkCons(Builtins.iData(recipient), Builtins.mkNilData())));
// After: JulcList.of with auto-wrapping
PlutusData inputs = JulcList.of(pkh, recipient).toPlutusData();
// Works for any arity — e.g. ZK public inputs
PlutusData publicInputs = JulcList.of(pub0, pub1, pub2, pub3, pub4).toPlutusData();
@SpendingValidator
class ListSearchExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<PubKeyHash> signatories = txInfo.signatories();
// Check for duplicates (O(n^2))
boolean hasDups = ListsLib.hasDuplicateBytes(signatories);
return !hasDups;
}
}

The HOF methods (any, all, find, foldl, map, filter, zip) accept lambda expressions. These are compiled via PIR and require lambda support.

javac-compiled projects: the static ListsLib forms below are compiler intrinsics with no Java declaration, so javac rejects them in a Gradle project; they compile only from source text (testkit strings, the playground). Use the JulcList instance methods any, all, filter and map, and write a fold as an accumulator loop. Avoid list.find(...) for now: the compiled result is an optional value, not the element. Compiled any and all evaluate the predicate for every element. See Value-Oriented Contract Code.

@SpendingValidator
class HofExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// any: true if any output has more than 5 ADA
boolean hasLargeOutput = ListsLib.any(outputs,
out -> ValuesLib.lovelaceOf(out.value()) > 5_000_000);
// all: true if all outputs go to pub key addresses
boolean allPubKey = ListsLib.all(outputs,
out -> AddressLib.isPubKeyAddress(out.address()));
// filter: keep only outputs above 2 ADA
JulcList<TxOut> largeOutputs = ListsLib.filter(outputs,
out -> ValuesLib.lovelaceOf(out.value()) > 2_000_000);
// foldl: sum all output lovelace
BigInteger totalLovelace = ListsLib.foldl(
(acc, out) -> acc + ValuesLib.lovelaceOf(out.value()),
BigInteger.ZERO,
outputs);
return hasLargeOutput;
}
}

These HOF methods are also available as instance methods on JulcList. Lambda parameter types are auto-inferred from the list element type:

// Instance method equivalents
boolean hasLargeOutput = outputs.any(
out -> ValuesLib.lovelaceOf(out.value()) > 5_000_000);
JulcList<TxOut> largeOutputs = outputs.filter(
out -> ValuesLib.lovelaceOf(out.value()) > 2_000_000);
// Chaining is supported
var result = outputs.filter(out -> isLarge(out)).map(out -> transform(out));

foldl is only available as a static call (ListsLib.foldl) because it takes two lambda parameters plus an initial value.

JuLC supports for-each iteration over lists directly, which is often more readable than HOFs:

@SpendingValidator
class ForEachExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
BigInteger total = BigInteger.ZERO;
for (TxOut out : txInfo.outputs()) {
total = total.add(ValuesLib.lovelaceOf(out.value()));
}
return total.compareTo(BigInteger.valueOf(10_000_000)) > 0;
}
}

Import: org.julclang.stdlib.lib.ValuesLib

ValuesLib operates on Plutus Value types, which are nested maps: Map<PolicyId, Map<TokenName, Integer>>. Lovelace is stored under the empty bytestring policy and token name.

@SpendingValidator
class ValueExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
TxOut firstOutput = txInfo.outputs().head();
Value outputValue = firstOutput.value();
// Extract lovelace
BigInteger lovelace = ValuesLib.lovelaceOf(outputValue);
// Extract a specific native token amount
byte[] policyId = new byte[]{/* ... */};
byte[] tokenName = new byte[]{/* ... */};
BigInteger tokenAmount = ValuesLib.assetOf(outputValue, policyId, tokenName);
// Check if a policy exists
boolean hasPolicy = ValuesLib.containsPolicy(outputValue, policyId);
return lovelace.compareTo(BigInteger.valueOf(2_000_000)) >= 0;
}
}
@SpendingValidator
class ValueComparisonExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
TxOut out1 = txInfo.outputs().head();
TxOut out2 = txInfo.outputs().tail().head();
Value v1 = out1.value();
Value v2 = out2.value();
// Multi-asset comparison (checks ALL policy/token pairs)
boolean v1GreaterOrEqual = ValuesLib.geqMultiAsset(v1, v2);
boolean v1LessOrEqual = ValuesLib.leq(v1, v2);
boolean valuesEqual = ValuesLib.eq(v1, v2);
boolean valueIsZero = ValuesLib.isZero(v1);
// Lovelace-only comparison
boolean lovelaceGeq = ValuesLib.geq(v1, v2);
return v1GreaterOrEqual;
}
}
@SpendingValidator
class ValueArithmeticExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
Value inputValue = txInfo.inputs().head().resolved().value();
Value outputValue = txInfo.outputs().head().value();
// Create a singleton value
byte[] policy = new byte[]{/* ... */};
byte[] token = new byte[]{/* ... */};
Value fee = ValuesLib.singleton(policy, token, BigInteger.valueOf(100));
// Add and subtract values
Value combined = ValuesLib.add(inputValue, fee);
Value difference = ValuesLib.subtract(inputValue, outputValue);
// Negate a value (flip all amounts)
Value negated = ValuesLib.negate(fee);
// Flatten to inspect all assets
PlutusData.ListData triples = ValuesLib.flatten(inputValue);
return ValuesLib.geqMultiAsset(inputValue, outputValue);
}
}

ValuesLib.flattenTyped() returns a JulcList<AssetEntry> for type-safe iteration over all assets in a Value. Each AssetEntry provides .policyId(), .tokenName(), and .amount() field access without manual destructuring.

@SpendingValidator
class TokenLeakCheck {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxOut output = ctx.txInfo().outputs().head();
long nonAdaCount = 0;
for (AssetEntry asset : ValuesLib.flattenTyped(output.value())) {
byte[] policy = asset.policyId();
byte[] name = asset.tokenName();
BigInteger amount = asset.amount();
if (Builtins.lengthOfByteString(policy) > 0) {
nonAdaCount = nonAdaCount + 1;
}
}
return nonAdaCount == 1;
}
}

Tip: Use flattenTyped() instead of flatten() whenever you need to inspect individual assets. The raw flatten() returns PlutusData.ListData requiring manual Builtins.constrFields() + headList/tailList destructuring.

One-shot minting policies and NFT state threads commonly derive a token name from a TxOutRef. Hashing the reference produces a collision-resistant, deterministic 32-byte name (the maximum asset-name length). Uniqueness in practice also requires the minting policy to enforce that the seed reference is consumed:

@MintingValidator
class OneShotMint {
@Entrypoint
static boolean validate(PlutusData redeemer, ScriptContext ctx) {
TxOutRef seed = /* the UTxO this policy requires to be spent */;
byte[] expectedName = ValuesLib.uniqueTokenName(seed);
// ... check the minted token name equals expectedName
return true;
}
}

The canonical derivation is blake2b_256(txId ++ integerToByteString(true, 2, index)). For a different hash algorithm, apply it to the seed bytes yourself:

byte[] name = CryptoLib.sha2_256(ValuesLib.refBytes(seed));

The index is encoded as fixed 2-byte big-endian so index 0 still contributes bytes (a minimal-width encoding would drop it) and the result is identical on-chain and off-chain.


Import: org.julclang.stdlib.lib.MapLib

In Plutus, maps are association lists (List<Pair<Data, Data>>), not hash maps. Lookups are O(n). Insert prepends (shadowing existing keys).

@SpendingValidator
class MapExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
// Withdrawals is a Map<Credential, BigInteger>
JulcMap<Credential, BigInteger> withdrawals = txInfo.withdrawals();
// Check membership and lookup
PlutusData key = /* some key */;
boolean exists = withdrawals.containsKey(key);
// Size
long mapSize = withdrawals.size();
return exists;
}
}
@SpendingValidator
class MapModifyExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
PlutusData.MapData myMap = Builtins.mapData(Builtins.mkNilPairData());
// Insert entries
PlutusData key1 = Builtins.iData(BigInteger.ONE);
PlutusData val1 = Builtins.iData(BigInteger.valueOf(100));
myMap = MapLib.insert(myMap, key1, val1);
PlutusData key2 = Builtins.iData(BigInteger.valueOf(2));
PlutusData val2 = Builtins.iData(BigInteger.valueOf(200));
myMap = MapLib.insert(myMap, key2, val2);
// Lookup returns Optional: Constr(0, [value]) or Constr(1, [])
PlutusData.ConstrData result = MapLib.lookup(myMap, key1);
boolean found = Builtins.constrTag(result) == 0;
// Delete a key
myMap = MapLib.delete(myMap, key1);
// Extract keys and values as lists
PlutusData.ListData allKeys = MapLib.keys(myMap);
PlutusData.ListData allValues = MapLib.values(myMap);
return MapLib.size(myMap) == 1;
}
}

Use for-each with JulcMap or iterate over the pair list:

@SpendingValidator
class MapIterateExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcMap<Credential, BigInteger> withdrawals = txInfo.withdrawals();
// For-each on a map iterates over key-value pairs
BigInteger totalWithdrawn = BigInteger.ZERO;
for (var entry : withdrawals) {
BigInteger amount = entry.value();
totalWithdrawn = totalWithdrawn.add(amount);
}
return totalWithdrawn.compareTo(BigInteger.ZERO) > 0;
}
}

OutputLib – Transaction Output Utilities

Section titled “OutputLib – Transaction Output Utilities”

Import: org.julclang.stdlib.lib.OutputLib

OutputLib provides high-level operations for filtering and inspecting transaction outputs. It uses typed ledger types (TxOut, Address, Value, OutputDatum).

@SpendingValidator
class OutputFilterExample {
record Datum(Address recipient) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// Filter outputs by address
JulcList<TxOut> recipientOutputs = OutputLib.outputsAt(outputs, datum.recipient());
// Count outputs at address
long count = OutputLib.countOutputsAt(outputs, datum.recipient());
// Get the unique output at an address (aborts if != 1)
TxOut uniqueOutput = OutputLib.uniqueOutputAt(outputs, datum.recipient());
return count >= 1;
}
}
@SpendingValidator
class TokenFilterExample {
record Datum(byte[] policyId, byte[] tokenName) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// Find all outputs containing a specific token
JulcList<TxOut> tokenOutputs = OutputLib.outputsWithToken(
outputs, datum.policyId(), datum.tokenName());
// Check if a specific output has a token
TxOut firstOutput = outputs.head();
boolean hasToken = OutputLib.valueHasToken(
firstOutput.value(), datum.policyId(), datum.tokenName());
return !tokenOutputs.isEmpty();
}
}
@SpendingValidator
class PaymentCheckExample {
record Datum(Address recipient, BigInteger minPayment) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// Sum lovelace paid to an address
BigInteger totalPaid = OutputLib.lovelacePaidTo(outputs, datum.recipient());
// Check if minimum payment is met
boolean sufficient = OutputLib.paidAtLeast(
outputs, datum.recipient(), datum.minPayment());
return sufficient;
}
}
@SpendingValidator
class DatumExample {
record MyDatum(BigInteger value) {}
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
TxOut output = txInfo.outputs().head();
// Get inline datum directly (aborts if not inline)
PlutusData inlineDatum = OutputLib.getInlineDatum(output);
// Or resolve datum (handles both inline and hash-based)
// Pass the datums map from TxInfo
PlutusData.MapData datumsMap = (PlutusData.MapData)(Object) txInfo.datums();
PlutusData resolvedDatum = OutputLib.resolveDatum(output, datumsMap);
return true;
}
}

Import: org.julclang.stdlib.lib.MathLib

MathLib provides common mathematical functions operating on BigInteger. All computations use Plutus integer arithmetic.

@SpendingValidator
class MathExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
BigInteger a = BigInteger.valueOf(42);
BigInteger b = BigInteger.valueOf(-10);
BigInteger absVal = MathLib.abs(b); // 10
BigInteger maxVal = MathLib.max(a, b); // 42
BigInteger minVal = MathLib.min(a, b); // -10
BigInteger signVal = MathLib.sign(b); // -1
BigInteger powVal = MathLib.pow(a, BigInteger.valueOf(3)); // 42^3
return absVal.compareTo(BigInteger.ZERO) > 0;
}
}

divMod returns floor division and modulo. quotRem returns Java-style truncating quotient and remainder. Both return Tuple2<BigInteger, BigInteger>; use .first() and .second() to access results.

Compatibility note: MathLib.divMod is floor-based. Code that needs Java-style truncating division should use MathLib.quotRem.

PV11 only: MathLib.expMod lowers to the Batch 6 ExpModInteger builtin. The other division and modulo helpers in this section do not require PV11.

import org.julclang.core.types.Tuple2;
@SpendingValidator
class DivModExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
BigInteger a = BigInteger.valueOf(17);
BigInteger b = BigInteger.valueOf(5);
// divMod returns (floor division, floor modulo)
Tuple2<BigInteger, BigInteger> dm = MathLib.divMod(a, b);
BigInteger quotient = dm.first(); // 3
BigInteger remainder = dm.second(); // 2
// Modular exponentiation: base^exp mod modulus
BigInteger result = MathLib.expMod(
BigInteger.valueOf(2),
BigInteger.valueOf(10),
BigInteger.valueOf(1000)); // 1024 mod 1000 = 24
return remainder.compareTo(BigInteger.ZERO) >= 0;
}
}

Import: org.julclang.stdlib.lib.IntervalLib

IntervalLib operates on Plutus Interval (POSIXTimeRange) types. Use these for time-locked validators. Time values are POSIX milliseconds as BigInteger.

import org.julclang.ledger.*;
import org.julclang.stdlib.lib.IntervalLib;
import org.julclang.stdlib.lib.ContextsLib;
@SpendingValidator
class TimeLockValidator {
record Datum(byte[] beneficiary, BigInteger deadline) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
Interval validRange = txInfo.validRange();
// Check if the transaction's valid range falls after the deadline
BigInteger lowerBound = IntervalLib.finiteLowerBound(validRange);
boolean pastDeadline = lowerBound.compareTo(datum.deadline()) >= 0;
// Check if beneficiary signed
boolean signed = ContextsLib.signedBy(txInfo, datum.beneficiary());
return pastDeadline && signed;
}
}
@SpendingValidator
class IntervalExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
Interval txRange = txInfo.validRange();
// Check if a specific point is within the transaction's valid range
BigInteger checkTime = BigInteger.valueOf(1700000000000L);
boolean timeInRange = IntervalLib.contains(txRange, checkTime);
// Construct intervals
Interval alwaysValid = IntervalLib.always();
Interval neverValid = IntervalLib.never();
Interval afterNoon = IntervalLib.after(BigInteger.valueOf(1700000000000L));
Interval beforeMidnight = IntervalLib.before(BigInteger.valueOf(1700100000000L));
Interval window = IntervalLib.between(
BigInteger.valueOf(1700000000000L),
BigInteger.valueOf(1700100000000L));
// Check emptiness
boolean empty = IntervalLib.isEmpty(neverValid);
// Extract bounds
BigInteger upper = IntervalLib.finiteUpperBound(txRange);
BigInteger lower = IntervalLib.finiteLowerBound(txRange);
return timeInRange;
}
}

Import: org.julclang.stdlib.lib.CryptoLib

CryptoLib wraps Plutus cryptographic builtins for hashing and signature verification. These are also available directly via Builtins.

@SpendingValidator
class HashExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
byte[] data = new byte[]{1, 2, 3};
byte[] sha256 = CryptoLib.sha2_256(data);
byte[] sha3 = CryptoLib.sha3_256(data);
byte[] blake256 = CryptoLib.blake2b_256(data);
byte[] blake224 = CryptoLib.blake2b_224(data); // Used for key hashes
byte[] keccak = CryptoLib.keccak_256(data);
byte[] ripemd = CryptoLib.ripemd_160(data);
return Builtins.lengthOfByteString(sha256) == 32;
}
}
@SpendingValidator
class SigVerifyExample {
record Datum(byte[] pubKey, byte[] message) {}
@Entrypoint
static boolean validate(Datum datum, byte[] signature, ScriptContext ctx) {
// Ed25519 signature verification
boolean valid = CryptoLib.verifyEd25519Signature(
datum.pubKey(), datum.message(), signature);
return valid;
}
}
@SpendingValidator
class Secp256k1Example {
record Datum(byte[] key, byte[] msgHash) {}
@Entrypoint
static boolean validate(Datum datum, byte[] sig, ScriptContext ctx) {
// ECDSA secp256k1 (Ethereum-compatible)
boolean ecdsaValid = CryptoLib.verifyEcdsaSecp256k1(
datum.key(), datum.msgHash(), sig);
// Schnorr secp256k1 (Bitcoin Taproot compatible)
boolean schnorrValid = CryptoLib.verifySchnorrSecp256k1(
datum.key(), datum.msgHash(), sig);
return ecdsaValid || schnorrValid;
}
}

Import: org.julclang.stdlib.lib.ByteStringLib

ByteStringLib provides operations on byte[] (ByteString in Plutus). Includes slicing, comparison, and encoding/serialization.

@SpendingValidator
class ByteStringExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
byte[] data = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05};
// Length
long len = ByteStringLib.length(data); // 5
// Element access
long firstByte = ByteStringLib.at(data, 0); // 1
// Slicing
byte[] firstTwo = ByteStringLib.take(data, 2); // [0x01, 0x02]
byte[] lastThree = ByteStringLib.drop(data, 2); // [0x03, 0x04, 0x05]
byte[] middle = ByteStringLib.slice(data, 1, 3); // [0x02, 0x03, 0x04]
// Construction
byte[] withPrefix = ByteStringLib.cons(0xFF, data);
byte[] combined = ByteStringLib.append(firstTwo, lastThree);
// Concatenate 3+ parts without nesting append calls
byte[] joined = Builtins.concat(firstTwo, lastThree, withPrefix);
byte[] emptyBs = ByteStringLib.empty();
byte[] zeroes = ByteStringLib.zeros(32);
return len == 5;
}
}
@SpendingValidator
class ByteStringCompareExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
byte[] a = new byte[]{0x01, 0x02};
byte[] b = new byte[]{0x01, 0x03};
// Comparison
boolean eq = ByteStringLib.equals(a, b);
boolean lt = ByteStringLib.lessThan(a, b);
boolean lte = ByteStringLib.lessThanEquals(a, b);
// Integer <-> ByteString conversion
byte[] encoded = ByteStringLib.integerToByteString(true, 8, 256);
long decoded = ByteStringLib.byteStringToInteger(true, encoded);
// Data serialization (to CBOR)
PlutusData someData = Builtins.iData(BigInteger.valueOf(42));
byte[] cbor = ByteStringLib.serialiseData(someData);
return lt;
}
}

ByteStringLib.utf8ToInteger() parses a UTF-8-encoded decimal string into an integer. This is the inverse of intToDecimalString().

// Parse "42" bytes → integer 42
byte[] bs = "42".getBytes();
BigInteger n = ByteStringLib.utf8ToInteger(bs); // 42
// Roundtrip property:
// ByteStringLib.utf8ToInteger(ByteStringLib.intToDecimalString(n)) == n

Import: org.julclang.stdlib.lib.BitwiseLib

BitwiseLib provides bit-level operations on byte[]. The padding parameter in AND/OR/XOR controls behavior when bytestrings have different lengths (true = zero-extend shorter, false = truncate longer).

@SpendingValidator
class BitwiseExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
byte[] a = new byte[]{(byte) 0xFF, (byte) 0x0F};
byte[] b = new byte[]{(byte) 0x0F, (byte) 0xF0};
// Bitwise operations (padding=false truncates to shorter length)
byte[] andResult = BitwiseLib.andByteString(false, a, b); // [0x0F, 0x00]
byte[] orResult = BitwiseLib.orByteString(false, a, b); // [0xFF, 0xFF]
byte[] xorResult = BitwiseLib.xorByteString(false, a, b); // [0xF0, 0xFF]
byte[] complement = BitwiseLib.complementByteString(a); // [0x00, 0xF0]
return true;
}
}
@SpendingValidator
class BitManipExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
byte[] data = new byte[]{(byte) 0b10110100};
// Read individual bits
boolean bit0 = BitwiseLib.readBit(data, 0);
// Count set bits (popcount)
long popcount = BitwiseLib.countSetBits(data); // 4
// Find first set bit
long firstSet = BitwiseLib.findFirstSetBit(data);
// Shift and rotate
byte[] shifted = BitwiseLib.shiftByteString(data, 2);
byte[] rotated = BitwiseLib.rotateByteString(data, 2);
return popcount > 0;
}
}

Import: org.julclang.stdlib.lib.AddressLib

AddressLib inspects Plutus Address types: extracting credential hashes and checking whether an address is a script or public key address.

@SpendingValidator
class AddressExample {
@Entrypoint
static boolean validate(PlutusData datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
TxOut output = txInfo.outputs().head();
Address addr = output.address();
// Extract the payment credential hash (works for both PubKey and Script)
byte[] credHash = AddressLib.credentialHash(addr);
// Check address type
boolean isScript = AddressLib.isScriptAddress(addr);
boolean isPubKey = AddressLib.isPubKeyAddress(addr);
// Extract the full credential (for pattern matching)
Credential cred = AddressLib.paymentCredential(addr);
return isPubKey;
}
}
@SpendingValidator
class DestinationCheckExample {
record Datum(byte[] allowedRecipient) {}
@Entrypoint
static boolean validate(Datum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
boolean allOutputsValid = true;
for (TxOut out : txInfo.outputs()) {
Address addr = out.address();
if (AddressLib.isPubKeyAddress(addr)) {
byte[] hash = AddressLib.credentialHash(addr);
if (!Builtins.equalsByteString(hash, datum.allowedRecipient())) {
allOutputsValid = false;
} else {
allOutputsValid = allOutputsValid;
}
} else {
allOutputsValid = allOutputsValid;
}
}
return allOutputsValid;
}
}

BlsLib wraps the Plutus V3 BLS12-381 builtins for elliptic curve cryptography. All methods are static. Base curve and pairing methods are available on PV10+; g1MultiScalarMul and g2MultiScalarMul require PV11.

BLS values have their own types (ADR-047): JulcG1 and JulcG2 for points, JulcMlResult for a Miller-loop result, and for multi-scalar multiplication the native lists JulcScalars, JulcG1Points and JulcG2Points (all in org.julclang.core.types). They are opaque native UPLC values, not byte[] and not PlutusData: the compiler rejects a compressed byte string where a point is required, a G2 point where a G1 point is required, a JulcList where a native list is required, and a point in a datum, redeemer, record, list or == (JULC0041), or at a validator/compileMethod boundary (JULC0042); the check applies at initializers, helper arguments, return, both branches of a conditional, and declarations and assignments inside loops. Only g1Compress/g2Compress turn a point into bytes and only g1Uncompress/g2Uncompress turn bytes back into a point.

Method Description
g1Add(JulcG1 a, JulcG1 b) Add two G1 points
g1Neg(JulcG1 a) Negate a G1 point
g1ScalarMul(BigInteger scalar, JulcG1 g1) Scalar multiplication of G1
g1Equal(JulcG1 a, JulcG1 b) Check G1 equality
g1Compress(JulcG1 g1) Compress G1 to 48 bytes
g1Uncompress(byte[] compressed) Uncompress bytes to G1 (fails on an invalid encoding)
g1HashToGroup(byte[] msg, byte[] dst) Hash to G1
g2Add(JulcG2 a, JulcG2 b) Add two G2 points
g2Neg(JulcG2 a) Negate a G2 point
g2ScalarMul(BigInteger scalar, JulcG2 g2) Scalar multiplication of G2
g2Equal(JulcG2 a, JulcG2 b) Check G2 equality
g2Compress(JulcG2 g2) Compress G2 to 96 bytes
g2Uncompress(byte[] compressed) Uncompress bytes to G2 (fails on an invalid encoding)
g2HashToGroup(byte[] msg, byte[] dst) Hash to G2
millerLoop(JulcG1 g1, JulcG2 g2) Compute the Miller loop
mulMlResult(JulcMlResult a, JulcMlResult b) Multiply two Miller-loop results
finalVerify(JulcMlResult a, JulcMlResult b) Final pairing verification
g1MultiScalarMul(JulcScalars scalars, JulcG1Points points) Σ scalarᵢ · pointᵢ on G1 (PV11 only)
g2MultiScalarMul(JulcScalars scalars, JulcG2Points points) Σ scalarᵢ · pointᵢ on G2 (PV11 only)

The native lists come from Builtins:

Producer Description
Builtins.scalars(BigInteger...) A native list integer from literal or runtime integers
Builtins.scalarsFromList(JulcList<BigInteger>) Decode a Data list of integers element by element (fails at a non-integer element)
Builtins.g1Points(JulcG1...) / g2Points(JulcG2...) A native point list from points
Builtins.g1PointsFromCompressed(JulcList<byte[]>) / g2PointsFromCompressed(...) Uncompress each element of a Data list of compressed points, in order (fails at a non-bytes element or an invalid encoding)
import org.julclang.core.types.JulcG1;
import org.julclang.core.types.JulcG2;
import org.julclang.core.types.JulcMlResult;
import org.julclang.core.types.JulcList;
import org.julclang.stdlib.Builtins;
import org.julclang.stdlib.lib.BlsLib;
import java.math.BigInteger;
class BlsExamples {
// G1 operations: only compress leads out to bytes, only uncompress leads back in
static byte[] scaled(byte[] message, byte[] dst, BigInteger scalar) {
JulcG1 p = BlsLib.g1HashToGroup(message, dst);
JulcG1 sum = BlsLib.g1Add(p, BlsLib.g1ScalarMul(scalar, p));
return BlsLib.g1Compress(sum);
}
// Pairing: e(2P, Q) == e(P, 2Q)
static boolean bilinear(byte[] message, byte[] dst) {
JulcG1 p = BlsLib.g1HashToGroup(message, dst);
JulcG2 q = BlsLib.g2HashToGroup(message, dst);
JulcMlResult left = BlsLib.millerLoop(BlsLib.g1ScalarMul(BigInteger.TWO, p), q);
JulcMlResult right = BlsLib.millerLoop(p, BlsLib.g2ScalarMul(BigInteger.TWO, q));
return BlsLib.finalVerify(left, right);
}
// Multi-scalar multiplication (PV11): 3·P1 + 5·P2 in one builtin call
static boolean msmEqualsChain(byte[] dst) {
JulcG1 p1 = BlsLib.g1HashToGroup(new byte[]{1}, dst);
JulcG1 p2 = BlsLib.g1HashToGroup(new byte[]{2}, dst);
JulcG1 msm = BlsLib.g1MultiScalarMul(
Builtins.scalars(BigInteger.valueOf(3), BigInteger.valueOf(5)),
Builtins.g1Points(p1, p2));
JulcG1 chain = BlsLib.g1Add(BlsLib.g1ScalarMul(BigInteger.valueOf(3), p1),
BlsLib.g1ScalarMul(BigInteger.valueOf(5), p2));
return BlsLib.g1Equal(msm, chain);
}
// The same over lists that arrived as Data (a redeemer, say), decoded element by element
static boolean verify(JulcList<BigInteger> scalars, JulcList<byte[]> compressedPoints, byte[] expected) {
JulcG1 sum = BlsLib.g1MultiScalarMul(
Builtins.scalarsFromList(scalars),
Builtins.g1PointsFromCompressed(compressedPoints));
return BlsLib.g1Equal(sum, BlsLib.g1Uncompress(expected));
}
}

Multi-scalar multiplication semantics (pinned by the VM and the conformance suite): every scalar is checked first (each must fit 512 bytes of two’s complement, −2^4095 to 2^4095−1, or the builtin fails, extra scalars beyond the shorter list included), then the two lists are zipped to the shorter one (the extra entries of the longer list are ignored), and the empty sum is the identity. On the pinned cardano-node-11.0.1 PV11 costs a single g1MultiScalarMul is smaller than the manual g1ScalarMul/g1Add chain from three points and cheaper in CPU from seven: the builtin costs about 322 million CPU to enter plus 25 million per point, the chain about 77 million per point (measured over hashed points, whose 52.5 million hashToGroup each is the same on both sides). Nothing rewrites one form into the other; choose MSM for larger sums.

Migration from the byte[] API: a local, helper parameter or return type that named a BLS value as byte[] (byte[] p = Builtins.bls12_381_G1_hashToGroup(...), static byte[] point(...)) is now JULC0041; declare it as JulcG1/JulcG2/JulcMlResult or use var. Code that used var compiles unchanged to the same bytes. Compressed encodings remain byte[].

Off-chain: BlsLib methods throw UnsupportedOperationException — use JulcEval.forSource() for UPLC evaluation.


NativeValueLib – Native Value Operations (PV11)

Section titled “NativeValueLib – Native Value Operations (PV11)”

PV11 target only. These operations use native UPLC Value builtins (CIP-153). JulcValue is opaque and cannot be used as PlutusData or as the ledger API’s Data-encoded Value.

NativeValueLib provides explicit native MaryEra Value operations. Convert Data once with fromData, keep intermediate operations in JulcValue, and convert back with toData only at a Data boundary.

Automatic sharing (ADR-042). At the default pv11-safe level the compiler binds a repeated fromData(x) / Builtins.unValueData(x) of the same variable once per scope when that conversion is already the first thing the scope evaluates, including a conversion repeated inside a loop body. The output is byte-identical to writing the JulcValue binding yourself, and results, traces and failures never change. A conversion that sits behind another partial step is deliberately left alone: in NativeValueLib.contains(NativeValueLib.fromData(out), NativeValueLib.fromData(required)) inside a loop, fromData(out) runs first on every iteration, so fromData(required) is not hoisted (doing so would change which malformed input fails first). Writing the binding explicitly, once, before the loop is still the clearest style and works at every optimization level.

Automatic projection sharing (ADR-044). The same rule applies to record field access: txInfo.outputs() repeated on one TxInfo variable, out.value() repeated on one loop item, or two different fields of one variable (txInfo.outputs() and txInfo.fee()) are bound once per scope at the default level when the first of them is already the first thing the scope evaluates. For a repeated field the output is byte-identical to binding the field yourself (the fields-list sharing has no source-level spelling), and results, traces and failures never change. A projection that only appears in one branch of a conditional, or that sits behind a saturated call or a trace, is left as written; binding it explicitly before the branch remains the way to share it at every level.

Method Description
insertCoin(policyId, tokenName, amount, value) Insert/update token quantity
lookupCoin(policyId, tokenName, value) Look up token quantity (0 if absent)
union(a, b) Merge two Values by adding quantities
contains(a, b) Check a >= b element-wise
scale(scalar, value) Scale all quantities
fromData(mapData) Convert Map-encoded PlutusData to native Value
toData(value) Convert native Value back to Map encoding
Builtins.emptyValue() The empty Value, as a literal constant (ADR-045)
Builtins.singletonValue(policyId, tokenName, quantity) A Value holding exactly one token quantity
Builtins.lovelaceValue(quantity) A Value holding exactly that many lovelace

Builtins.emptyValue(), Builtins.singletonValue(...) and Builtins.lovelaceValue(...) write a native Value down instead of decoding one. emptyValue() is a UPLC Value constant (it needs the PV11 target, which is where NativeValueLib is legal anyway); the other two are insertCoin into it and keep the builtin’s rules: a zero quantity yields the empty Value, a non-zero quantity needs keys of at most 32 bytes and a quantity within the signed 128-bit range, and fails with InsertCoin’s text otherwise. They live in Builtins rather than NativeValueLib so that programs which do not use them keep their bytes at every level.

JulcValue required = NativeValueLib.union(
Builtins.singletonValue(policyId, tokenName, BigInteger.ONE),
Builtins.lovelaceValue(BigInteger.valueOf(2_000_000)));
return NativeValueLib.contains(NativeValueLib.fromData(minted), required);

Literal folding (ADR-045). At the default pv11-safe level a call of any NativeValueLib operation whose arguments are all literals (constants, or locals bound once to a literal) is replaced at compile time by the Value, integer, boolean or Data it evaluates to, computed by the same code the VM runs. The required Value above is one constant in the script; fromData and contains stay because minted is runtime data. A literal call the builtin would reject (a 33-byte key with a non-zero quantity, an overflowing union or scale, a negative quantity under contains) is left as written and fails at runtime exactly as before. Nothing is folded at none/baseline, and no algebraic identity is applied: scale(1, v) or union(v, empty()) with a runtime v stay calls. A fold never grows the script: a literal local that several calls share keeps its one constant, and a call that would copy that constant into its own site stays a call. Spell a negative literal quantity as new BigInteger("-5"); BigInteger.valueOf(-5) is a runtime subtraction, not a constant.

import org.julclang.core.PlutusData;
import org.julclang.core.types.JulcValue;
import org.julclang.stdlib.lib.NativeValueLib;
JulcValue value = NativeValueLib.fromData(dataEncodedValue);
BigInteger quantity = NativeValueLib.lookupCoin(policyId, tokenName, value);
JulcValue updated = NativeValueLib.insertCoin(
policyId, tokenName, BigInteger.valueOf(100), value);
PlutusData result = NativeValueLib.toData(updated);

Migration from the pre-ADR-032 experimental API: change native intermediates previously declared as PlutusData to JulcValue. Ledger Value and ValuesLib remain Data-encoded and unchanged. JuLC reports JULC0041 for Data/native mixing and JULC0042 when a JulcValue is exposed as a compiled method’s Data argument.

Off-chain: NativeValueLib methods throw UnsupportedOperationException — use JulcEval.forSource() for UPLC evaluation.


All stdlib libraries are annotated with @OnchainLibrary and compile to UPLC for on-chain execution. Some methods use casts like (PlutusData)(Object) that are no-ops on-chain but may throw ClassCastException off-chain. Methods that work off-chain are noted in the source Javadoc.

For off-chain testing, use UPLC evaluation via the JuLC testkit rather than calling library methods directly in JVM code.

JuLC requires immutable variable semantics. In for-each and while loops used as accumulators, both branches of an if must assign the accumulator variable:

// Correct: both branches assign result
for (TxOut out : outputs) {
if (someCondition) {
result = result.prepend(out);
} else {
result = result; // identity assignment required
}
}

When calling stdlib methods that take BytesData/MapData typed parameters from user code, pass PlutusData (not the specific subtype) to avoid type confusion at the UPLC boundary.

When constructing policy IDs or token names to pass to ValuesLib.assetOf(), use the byte[] overload rather than manually wrapping with Builtins.bData(). The library handles wrapping internally:

// Correct: pass byte[] directly
BigInteger amount = ValuesLib.assetOf(value, policyId, tokenName);
// The library internally wraps with bData for the UPLC comparison

When checking if a minting policy’s own token exists in a value (common in minting validators), use the (byte[])(Object) cast on ContextsLib.ownHash():

@MintingValidator
class MyTokenPolicy {
@Entrypoint
static boolean validate(PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
byte[] ownPolicy = (byte[])(Object) ContextsLib.ownHash(ctx);
// Check if minted value contains our policy
boolean hasMint = ValuesLib.containsPolicy(txInfo.mint(), ownPolicy);
// Or get a specific token amount
BigInteger qty = ValuesLib.assetOf(txInfo.mint(), ownPolicy, "TOKEN".getBytes());
return hasMint && qty.equals(BigInteger.ONE);
}
}

The (byte[])(Object) cast is required because ownHash() returns a ValidatorHash (which is ByteStringType at UPLC level but a different Java type at source level). The cast is a no-op at UPLC level.

MapLib.lookup() returns an Optional encoded as Plutus Data:

  • Constr(0, [value]) for Some (found)
  • Constr(1, []) for None (not found)

Check the tag with Builtins.constrTag(result) == 0 to determine if the lookup succeeded:

PlutusData.ConstrData result = MapLib.lookup(myMap, key);
if (Builtins.constrTag(result) == 0) {
PlutusData value = Builtins.headList(Builtins.constrFields(result));
// use value
}

The map HOF wraps each lambda result to Data, so the returned list always has PlutusData elements regardless of input type. Use Builtins.unIData() or Builtins.unBData() to extract typed values from mapped results.

For ScriptContext and TxInfo, prefer direct typed field access over the ContextsLib wrapper methods:

// Preferred: direct access
TxInfo txInfo = ctx.txInfo();
JulcList<TxOut> outputs = txInfo.outputs();
// Legacy (still works)
TxInfo txInfo = ContextsLib.getTxInfo(ctx);
JulcList<TxOut> outputs = ContextsLib.txInfoOutputs(txInfo);

Methods Also Available via Instance Methods

Section titled “Methods Also Available via Instance Methods”

Many list and map operations are available as instance methods on JulcList and JulcMap, which can be more readable:

JulcList<TxOut> outputs = txInfo.outputs();
// Library style
long count = ListsLib.length(outputs);
boolean empty = ListsLib.isEmpty(outputs);
TxOut first = ListsLib.head(outputs);
// Instance method style (equivalent)
long count = outputs.size();
boolean empty = outputs.isEmpty();
TxOut first = outputs.head();
// HOF methods are also available as instance calls
boolean anyLarge = outputs.any(out -> isLarge(out));
JulcList<TxOut> filtered = outputs.filter(out -> isLarge(out));
JulcList<PlutusData> mapped = outputs.map(out -> transform(out));

The following features require protocol version 11 or later and will not work on PV10 networks:

  • Builtins.expModInteger() — Modular exponentiation (tag 87, CIP-109)
  • Builtins.dropList() — Drop the first n elements from a list (tag 88, CIP-132)
  • JulcArray<T> — Immutable arrays with O(1) random access (tags 89-91, CIP-138); JulcArray.of(...) writes one down and folds to an array constant at pv11-safe (ADR-046); at the opt-in pv11-costed level the compiler also promotes a repeatedly indexed JulcList variable to an array automatically (ADR-043)
  • BLS multi-scalar multiplication — BlsLib.g1MultiScalarMul/g2MultiScalarMul over the typed JulcScalars and JulcG1Points/JulcG2Points lists (tags 92-93, CIP-133; ADR-047)
  • NativeValueLib — Native MaryEra Value operations (CIP-153)

Together these are the exact released PV11 Batch 6 set, tags 87-100. Builtins.multiIndexArray (tag 101, CIP-156) is a future/unreleased operation: the API remains visible for forward development, but the current compiler rejects it and its VM implementation is not ledger-valid at PV11. The retained experimental placeholder uses the legacy (array, indices) order rather than CIP-156’s proposed indices-first signature, so it must not be treated as a conformant preview.