From c5fc73fc98dc66a94f842d8890435907f065626d Mon Sep 17 00:00:00 2001 From: Adam Dean Date: Mon, 23 Feb 2026 07:40:41 -0700 Subject: [PATCH 1/2] Examples Review - 2026-02-23 - Create _test_harness in examples folder that allows for quick iteration of all examples to ensure all examples pass `trix check` validation with latest version. The only exception/expected error here is `semantic_errors.tx3` which is intentionally broken. - Move the non-functional, aspirational examples to an aspirational directory with a README explaining their nature. These are prone to causing confusion if left in the main examples folder. - Updates to several existing scripts to bring them inline with the latest version of Trix/Tx3 and make sure they pass validation. --- examples/_test_harness/.gitignore | 1 + examples/_test_harness/devnet.toml | 11 ++++ examples/_test_harness/main.tx3 | 29 +++++++++ examples/_test_harness/run_checks.sh | 62 +++++++++++++++++++ examples/_test_harness/tests/basic.toml | 33 ++++++++++ examples/_test_harness/trix.toml | 7 +++ examples/aspirational/README.md | 20 ++++++ examples/{ => aspirational}/jpg.tx3 | 0 examples/{ => aspirational}/levvy.simple.tx3 | 0 examples/{ => aspirational}/levvy.tx3 | 0 .../{ => aspirational}/plutus_addresses.tx3 | 0 examples/{ => aspirational}/spans.tx3 | 0 examples/{ => aspirational}/splash.tx3 | 0 examples/swap_static.tx3 | 2 +- examples/transfer_nft.tx3 | 14 +++-- 15 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 examples/_test_harness/.gitignore create mode 100644 examples/_test_harness/devnet.toml create mode 100644 examples/_test_harness/main.tx3 create mode 100755 examples/_test_harness/run_checks.sh create mode 100644 examples/_test_harness/tests/basic.toml create mode 100644 examples/_test_harness/trix.toml create mode 100644 examples/aspirational/README.md rename examples/{ => aspirational}/jpg.tx3 (100%) rename examples/{ => aspirational}/levvy.simple.tx3 (100%) rename examples/{ => aspirational}/levvy.tx3 (100%) rename examples/{ => aspirational}/plutus_addresses.tx3 (100%) rename examples/{ => aspirational}/spans.tx3 (100%) rename examples/{ => aspirational}/splash.tx3 (100%) diff --git a/examples/_test_harness/.gitignore b/examples/_test_harness/.gitignore new file mode 100644 index 00000000..7c0bbcc6 --- /dev/null +++ b/examples/_test_harness/.gitignore @@ -0,0 +1 @@ +.tx3 \ No newline at end of file diff --git a/examples/_test_harness/devnet.toml b/examples/_test_harness/devnet.toml new file mode 100644 index 00000000..4d43aa98 --- /dev/null +++ b/examples/_test_harness/devnet.toml @@ -0,0 +1,11 @@ +[[utxos]] +address = "@alice" +value = 100000000000 + +[[utxos]] +address = "@charlie" +value = 100000000000 + +[[utxos]] +address = "@bob" +value = 100000000000 diff --git a/examples/_test_harness/main.tx3 b/examples/_test_harness/main.tx3 new file mode 100644 index 00000000..d01ddd76 --- /dev/null +++ b/examples/_test_harness/main.tx3 @@ -0,0 +1,29 @@ +party Sender; + +party Receiver; + +tx transfer_nft( + policy: Bytes, + asset_name: Bytes, + quantity: Int, +) { + input gas_source { + from: Sender, + min_amount: fees + min_utxo(token_target), + } + + input token_source { + from: Sender, + min_amount: AnyAsset(policy, asset_name, quantity), + } + + output token_target { + to: Receiver, + amount: AnyAsset(policy, asset_name, quantity) + min_utxo(token_target), + } + + output change_target { + to: Sender, + amount: gas_source + token_source - AnyAsset(policy, asset_name, quantity) - min_utxo(token_target) - fees, + } +} diff --git a/examples/_test_harness/run_checks.sh b/examples/_test_harness/run_checks.sh new file mode 100755 index 00000000..9302a9db --- /dev/null +++ b/examples/_test_harness/run_checks.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Test harness: runs `trix check` against each .tx3 example file +# Usage: ./run_checks.sh [path_to_examples_dir] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +EXAMPLES_DIR="${1:-$(dirname "$SCRIPT_DIR")}" +HARNESS_DIR="$SCRIPT_DIR" +MAIN_TX3="$HARNESS_DIR/main.tx3" +BACKUP="$HARNESS_DIR/main.tx3.bak" + +# Save original main.tx3 +cp "$MAIN_TX3" "$BACKUP" + +PASS=0 +FAIL=0 +PASS_FILES=() +FAIL_FILES=() + +for tx3_file in "$EXAMPLES_DIR"/*.tx3; do + filename="$(basename "$tx3_file")" + + # Copy example content into main.tx3 + cp "$tx3_file" "$MAIN_TX3" + + # Run trix check + if output=$(cd "$HARNESS_DIR" && trix check 2>&1); then + PASS=$((PASS + 1)) + PASS_FILES+=("$filename") + else + FAIL=$((FAIL + 1)) + FAIL_FILES+=("$filename") + echo "FAIL: $filename" + echo "$output" | sed 's/^/ /' + echo "" + fi +done + +# Restore original main.tx3 +cp "$BACKUP" "$MAIN_TX3" +rm "$BACKUP" + +echo "=========================================" +echo "RESULTS: $PASS passed, $FAIL failed out of $((PASS + FAIL)) files" +echo "=========================================" + +if [ ${#PASS_FILES[@]} -gt 0 ]; then + echo "" + echo "PASSING:" + for f in "${PASS_FILES[@]}"; do + echo " ✓ $f" + done +fi + +if [ ${#FAIL_FILES[@]} -gt 0 ]; then + echo "" + echo "FAILING:" + for f in "${FAIL_FILES[@]}"; do + echo " ✗ $f" + done +fi \ No newline at end of file diff --git a/examples/_test_harness/tests/basic.toml b/examples/_test_harness/tests/basic.toml new file mode 100644 index 00000000..530f3d9d --- /dev/null +++ b/examples/_test_harness/tests/basic.toml @@ -0,0 +1,33 @@ +file="./main.tx3" + +[[wallets]] +name = "bob" +balance = 10000000 + +[[wallets]] +name = "alice" +balance = 5000000 + +[[transactions]] +description = "bob sends 2 ada to alice" +template = "transfer" +signers = ["bob"] +args = { quantity = 2000000, sender = "@bob", receiver = "@alice" } + +[[transactions]] +description = "alice sends 2 ada to bob" +template = "transfer" +signers = ["alice"] +args = { quantity = 2000000, sender = "@alice", receiver = "@bob" } + +[[expect]] +from = "@bob" + +[[expect.min_amount]] +amount = 9638899 + +[[expect]] +from = "@alice" + +[[expect.min_amount]] +amount = 4638899 diff --git a/examples/_test_harness/trix.toml b/examples/_test_harness/trix.toml new file mode 100644 index 00000000..cf7c9059 --- /dev/null +++ b/examples/_test_harness/trix.toml @@ -0,0 +1,7 @@ +[protocol] +name = "_test_harness" +version = "0.0.0" +main = "main.tx3" + +[ledger] +family = "cardano" diff --git a/examples/aspirational/README.md b/examples/aspirational/README.md new file mode 100644 index 00000000..50afbf90 --- /dev/null +++ b/examples/aspirational/README.md @@ -0,0 +1,20 @@ +# Aspirational Examples + +The `.tx3` files in this directory represent real-world Cardano contract +interfaces (JPG Store, Levvy Finance, SplashDEX, etc.) written in Tx3 syntax +that is **not yet supported** by the language. + +These examples are **non-functional** and will not pass `trix check`. They exist +to capture desired language features and serve as design references for future +development. + +## Why these fail + +| File | Unsupported features used | +|------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| `jpg.tx3` | Old-style policy constructors, array type syntax (`[]`), `outputs ... as` iteration, `required_signers:`, inline `metadata` | +| `levvy.simple.tx3` | Old-style policy constructors, `PlutusAddress()`, `now()`, `valid_from`/`valid_until` | +| `levvy.tx3` | All of the above plus `batch foreach`, `filter` lambdas, `let` bindings, `if/else`, `.sum()`, `Address()` | +| `splash.tx3` | Old-style policy constructors, `Option<>`, array type syntax, `IndexOf()`, `Asset()` | +| `plutus_addresses.tx3` | `func` definitions, `return`, `if/else`, `Hash<>` generics, union types (`\|`) | +| `spans.tx3` | References non-existent fields on inputs; unbalanced transaction | \ No newline at end of file diff --git a/examples/jpg.tx3 b/examples/aspirational/jpg.tx3 similarity index 100% rename from examples/jpg.tx3 rename to examples/aspirational/jpg.tx3 diff --git a/examples/levvy.simple.tx3 b/examples/aspirational/levvy.simple.tx3 similarity index 100% rename from examples/levvy.simple.tx3 rename to examples/aspirational/levvy.simple.tx3 diff --git a/examples/levvy.tx3 b/examples/aspirational/levvy.tx3 similarity index 100% rename from examples/levvy.tx3 rename to examples/aspirational/levvy.tx3 diff --git a/examples/plutus_addresses.tx3 b/examples/aspirational/plutus_addresses.tx3 similarity index 100% rename from examples/plutus_addresses.tx3 rename to examples/aspirational/plutus_addresses.tx3 diff --git a/examples/spans.tx3 b/examples/aspirational/spans.tx3 similarity index 100% rename from examples/spans.tx3 rename to examples/aspirational/spans.tx3 diff --git a/examples/splash.tx3 b/examples/aspirational/splash.tx3 similarity index 100% rename from examples/splash.tx3 rename to examples/aspirational/splash.tx3 diff --git a/examples/swap_static.tx3 b/examples/swap_static.tx3 index cf6b7895..4b889a92 100644 --- a/examples/swap_static.tx3 +++ b/examples/swap_static.tx3 @@ -39,7 +39,7 @@ tx swap( datum: PoolState { pair_a: pool.pair_a - ask, pair_b: pool.pair_b + bid, - ...pool.datum + ...pool }, amount: pool, } diff --git a/examples/transfer_nft.tx3 b/examples/transfer_nft.tx3 index 307cf104..d01ddd76 100644 --- a/examples/transfer_nft.tx3 +++ b/examples/transfer_nft.tx3 @@ -3,25 +3,27 @@ party Sender; party Receiver; tx transfer_nft( - token: Asset, + policy: Bytes, + asset_name: Bytes, + quantity: Int, ) { input gas_source { from: Sender, - min_amount: fees + minutxo(token_target), + min_amount: fees + min_utxo(token_target), } input token_source { from: Sender, - min_amount: token, + min_amount: AnyAsset(policy, asset_name, quantity), } - + output token_target { to: Receiver, - amount: token + minutxo(self), + amount: AnyAsset(policy, asset_name, quantity) + min_utxo(token_target), } output change_target { to: Sender, - amount: gas_source + token_source - token - minutxo(token_target) - fees, + amount: gas_source + token_source - AnyAsset(policy, asset_name, quantity) - min_utxo(token_target) - fees, } } From b5697852ab4655f508b7afe57e744ce8f3ee99cb Mon Sep 17 00:00:00 2001 From: Adam Dean Date: Mon, 23 Feb 2026 10:54:50 -0700 Subject: [PATCH 2/2] Examples Review - 2026-02-23 - Update lang_tour, reference_script, and introduce new policy_variants examples to document and fill gaps left by current examples --- examples/lang_tour.tx3 | 11 ++- examples/policy_variants.tx3 | 141 ++++++++++++++++++++++++++++++++++ examples/reference_script.tx3 | 1 + 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 examples/policy_variants.tx3 diff --git a/examples/lang_tour.tx3 b/examples/lang_tour.tx3 index 9af2be78..f69728a5 100644 --- a/examples/lang_tour.tx3 +++ b/examples/lang_tour.tx3 @@ -3,6 +3,7 @@ env { field_b: Bytes, field_c: Bool, field_d: List, + field_e: Address, } party MyParty; @@ -13,6 +14,7 @@ type MyRecord { field3: Bytes, field4: List, field5: Map, + field6: Bool, } type MyVariant { @@ -76,15 +78,21 @@ tx my_tx( output named_output { to: MyParty, datum: MyRecord { - field1: quantity, + field1: quantity + -1, field2: (54 + 10) - (8 + 2), field4: [1, 2, 3, source.field1], field5: {1: "Value1", 2: "Value2",}, + field6: true, ...source }, amount: AnyAsset(source.field3, source.field2, source.field1) + Ada(40) + min_utxo(named_output), } + output? optional_change { + to: MyParty, + amount: source - Ada(40) - fees, + } + signers { MyParty, 0x0F5B22E57FEEB5B4FD1D501B007A427C56A76884D4978FAFEF979D9C, @@ -102,6 +110,7 @@ tx my_tx( locals { local_var: concat("Lang", "Tour"), + negated_flag: !field_c, } cardano::vote_delegation_certificate { diff --git a/examples/policy_variants.tx3 b/examples/policy_variants.tx3 new file mode 100644 index 00000000..2cd05001 --- /dev/null +++ b/examples/policy_variants.tx3 @@ -0,0 +1,141 @@ +// Policy definition styles and how each is used in practice. +// +// A policy name resolves to its hash when used in expressions. This means +// you can use a policy name anywhere a policy hash (Bytes) is expected: +// in AnyAsset(), output addresses, etc. +// +// The script and ref fields provide metadata for the compiler and TII +// output but are not currently accessible as sub-properties (e.g., +// MyPolicy.script is not supported — this is a known limitation). + +// Assign syntax — just a policy hash. +policy HashOnly = 0xABCDEF1234; + +// Constructor with only hash — equivalent to assign, constructor form. +policy HashConstructor { + hash: 0xABCDEF1234, +} + +// Hash + inline native script CBOR. The compiler knows about the script, +// but you must still attach it as a native witness in the transaction. +policy NativeScriptPolicy { + hash: 0xbd3ae991b5aafccafe5ca70758bd36a9b2f872f57f6d3a1ffa0eb777, + script: 0x820181820400, +} + +// Hash + reference UTxO. The compiler knows where the script lives on-chain, +// but you must still include the reference input in the transaction. +policy RefScriptPolicy { + hash: 0xef7a1cebb2dc7de884ddf82f8fcbc91fe9750dcd8c12ec7643a99bbe, + ref: 0xDEADBEEF1234, +} + +// Named assets bind a readable name to a policy hash + asset name. +asset NativeToken = 0xbd3ae991b5aafccafe5ca70758bd36a9b2f872f57f6d3a1ffa0eb777."NATIVE"; +asset RefToken = 0xef7a1cebb2dc7de884ddf82f8fcbc91fe9750dcd8c12ec7643a99bbe."REFTOKEN"; + +party Minter; + +// Using a policy name directly in AnyAsset — NativeScriptPolicy resolves to +// its hash, so AnyAsset(NativeScriptPolicy, ...) works like +// AnyAsset(0xbd3a..., ...). +tx mint_native_named(quantity: Int) { + input source { + from: Minter, + min_amount: fees, + } + + mint { + amount: AnyAsset(NativeScriptPolicy, "NATIVE", quantity), + } + + output { + to: Minter, + amount: source + AnyAsset(NativeScriptPolicy, "NATIVE", quantity) - fees, + } + + // Native scripts must be provided as a witness. Since we can't yet + // reference NativeScriptPolicy.script, the CBOR must be repeated here. + cardano::native_witness { + script: 0x820181820400, + } +} + +// Using a named asset with its matching policy. NativeToken is bound to +// the same policy hash as NativeScriptPolicy, so this is equivalent to +// the AnyAsset form above but more readable. +tx mint_native_asset(quantity: Int) { + input source { + from: Minter, + min_amount: fees, + } + + mint { + amount: NativeToken(quantity), + } + + output { + to: Minter, + amount: source + NativeToken(quantity) - fees, + } + + cardano::native_witness { + script: 0x820181820400, + } +} + +// Minting via a plutus policy whose script lives in a reference UTxO. +// Using RefScriptPolicy in AnyAsset resolves to its hash. The reference +// input must point to the same UTxO declared in the policy's ref field. +tx mint_with_ref_dynamic(quantity: Int) { + input source { + from: Minter, + min_amount: fees, + } + + reference policy_script { + ref: 0xDEADBEEF1234#0, + } + + collateral { + from: Minter, + min_amount: fees, + } + + mint { + amount: AnyAsset(RefScriptPolicy, "REFTOKEN", quantity), + redeemer: (), + } + + output { + to: Minter, + amount: source + AnyAsset(RefScriptPolicy, "REFTOKEN", quantity) - fees, + } +} + +// Same as above but using the named asset RefToken instead of AnyAsset. +tx mint_with_ref_asset(quantity: Int) { + input source { + from: Minter, + min_amount: fees, + } + + reference policy_script { + ref: 0xDEADBEEF1234#0, + } + + collateral { + from: Minter, + min_amount: fees, + } + + mint { + amount: RefToken(quantity), + redeemer: (), + } + + output { + to: Minter, + amount: source + RefToken(quantity) - fees, + } +} diff --git a/examples/reference_script.tx3 b/examples/reference_script.tx3 index 7471ea14..433aeb9b 100644 --- a/examples/reference_script.tx3 +++ b/examples/reference_script.tx3 @@ -13,6 +13,7 @@ tx publish_plutus( cardano::publish { to: Receiver, amount: Ada(quantity), + datum: 0xD87980, version: 3, script: 0x5101010023259800a518a4d136564004ae69, }