Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
875e62d
feat: add CIP-57 parsing crate with blueprint and schema structures
sofia-bobbiesi Dec 3, 2025
e8d4749
Refactor blueprint and schema modules: consolidate structures and rem…
sofia-bobbiesi Dec 4, 2025
cec9b31
chore: renaming, removing unnecessary deps & fixing warnings
nicolasLuduena Dec 4, 2025
9779598
Merge branch 'main' into 258-feat-cip57-parsing-crate
nicolasLuduena Feb 5, 2026
82b199b
feat: first draft at aiken imports
nicolasLuduena Feb 6, 2026
84eb7d4
refactor: decouple loading with from parser
nicolasLuduena Feb 6, 2026
5ec5bd3
fix: cip57 purpose is oneOf object instead of array
nicolasLuduena Feb 6, 2026
a2fb44f
chore: remove import plan
nicolasLuduena Feb 6, 2026
7174b14
fix: remove redundant branches of code
nicolasLuduena Feb 6, 2026
c2b4dca
fix: some warnings are gone
nicolasLuduena Feb 6, 2026
c20187a
refactor: rename `interop` module to `importing`
nicolasLuduena Feb 6, 2026
2f44dd8
chore: remove cardano prefix on imports
nicolasLuduena Feb 6, 2026
8ebf318
refactor: move import tests with the rest of tests
nicolasLuduena Feb 6, 2026
8fbf3c9
feat: single variant types have no explicit constructor
nicolasLuduena Feb 6, 2026
912952c
fix: Data has no internal structure
nicolasLuduena Feb 6, 2026
1601a1c
feat: parsing import example with analyze phase to make sure that it …
nicolasLuduena Feb 6, 2026
20ce92b
chore: formating
nicolasLuduena Feb 6, 2026
0e79d50
fix: use the same serde_json lib as everywhere else
nicolasLuduena Feb 6, 2026
e42d42a
chore: remove unnecessary code branch
nicolasLuduena Feb 6, 2026
5be14b8
fix: ast & tir files up-to-date
nicolasLuduena Feb 6, 2026
23a7429
feat: TxDef to tx3 source code
nicolasLuduena Feb 6, 2026
13bada8
refactor: resolve types from blueprint logic extracted into function
nicolasLuduena Feb 6, 2026
726b457
feat: types from plutus public on importing crate
nicolasLuduena Feb 6, 2026
98d41cd
feat: enhance type import handling and update example datum structure
sofia-bobbiesi Feb 25, 2026
df3a087
Merge branch 'main' into blueprint-imports-with-friendly-aiken-compiler
sofia-bobbiesi Feb 25, 2026
ac8d93b
feat: add handling for empty default variant cases in TypeDef
sofia-bobbiesi Feb 26, 2026
7bdbdc9
update plutus.json example to the lastest aiken version
sofia-bobbiesi Feb 26, 2026
80ada80
feat: enhance type name handling and improve Aiken name mapping logic
sofia-bobbiesi Feb 26, 2026
2d24bac
refactor: remove redundant check for empty fields
sofia-bobbiesi Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
[workspace]
resolver = "2"
members = ["bin/tx3c",
"crates/tx3-cardano",
"crates/tx3-lang",
"crates/tx3-resolver",
"crates/tx3-tir",

members = [
"bin/tx3c",
"crates/tx3-cardano",
"crates/tx3-lang",
"crates/tx3-resolver",
"crates/tx3-tir",
"crates/cip-57",
]

[workspace.package]
Expand Down
18 changes: 18 additions & 0 deletions crates/cip-57/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "cip-57"
description = "CIP-57 compatibility (JSON parsing and serialization)"
publish.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
keywords.workspace = true
documentation.workspace = true
homepage.workspace = true
readme.workspace = true


[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.137"
634 changes: 634 additions & 0 deletions crates/cip-57/examples/plutus.json

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions crates/cip-57/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! This module defines the structures for a Blueprint, including its preamble, validators, and definitions.

use serde::{Deserialize, Serialize};
use serde_json::Number;
use std::collections::BTreeMap;
/// Represents a blueprint containing preamble, validators, and optional definitions.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Blueprint {
pub preamble: Preamble,
pub validators: Vec<Validator>,
pub definitions: Option<Definitions>,
}

/// Represents the preamble of a blueprint, including metadata such as title, description, version, and compiler information.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Preamble {
pub title: String,
pub description: Option<String>,
pub version: String,
pub plutus_version: String,
pub compiler: Option<Compiler>,
pub license: Option<String>,
}

/// Represents the compiler information in the preamble.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Compiler {
pub name: String,
pub version: Option<String>,
}

/// Represents a validator in the blueprint, including its title, description, compiled code, hash, datum, redeemer, and parameters.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Validator {
pub title: String,
pub description: Option<String>,
pub compiled_code: Option<String>,
pub hash: Option<String>,
pub datum: Option<Argument>,
pub redeemer: Option<Argument>,
pub parameters: Option<Vec<Parameter>>,
}

/// Represents an argument in a validator, including its title, description, purpose, and schema reference.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Argument {
pub title: Option<String>,
pub description: Option<String>,
pub purpose: Option<PurposeArray>,
pub schema: Reference,
}

/// Represents a purpose which can be either a single purpose or an object with oneOf.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum PurposeArray {
Single(Purpose),
OneOf(PurposeOneOf),
}

/// Represents a purpose object with a oneOf field containing an array of purposes.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PurposeOneOf {
pub one_of: Vec<Purpose>,
}

/// Represents the purpose of an argument, which can be spend, mint, withdraw, or publish.
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Purpose {
Spend,
Mint,
Withdraw,
Publish,
}

/// Represents a reference to a schema.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Reference {
#[serde(rename = "$ref")]
pub reference: Option<String>,
}

/// Represents a parameter in a validator, including its title, description, purpose, and schema reference.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Parameter {
pub title: Option<String>,
pub description: Option<String>,
pub purpose: Option<PurposeArray>,
pub schema: Reference,
}

/// Represents the definitions in a blueprint, which is a map of definition names to their corresponding definitions.
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
pub struct Definitions {
#[serde(flatten, default)]
pub inner: BTreeMap<String, Definition>,
}

/// Represents a definition in the blueprint, including its title, description, data type, any_of schemas, items, keys, and values.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Definition {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_type: Option<DataType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<Vec<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<ReferencesArray>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keys: Option<Reference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub values: Option<Reference>,
}

/// Represents an array of references which can be either a single reference or an array of references.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum ReferencesArray {
Single(Reference),
Array(Vec<Reference>),
}

/// Represents a schema in a definition, including its title, description, data type, index, and fields.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub data_type: DataType,
pub index: Number,
pub fields: Vec<Field>,
}

/// Represents the data type of a schema, which can be integer, bytes, list, map, or constructor.
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum DataType {
Integer,
Bytes,
List,
Map,
Constructor,
}

/// Represents a field in a schema, including its title and reference.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Field {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "$ref")]
pub reference: String,
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;

#[test]
fn deserialize_plutus_json_into_blueprint() {
let manifest = env!("CARGO_MANIFEST_DIR");
let path = PathBuf::from(manifest).join("examples").join("plutus.json");

let failure_msg = format!("failed to read example file: {}", path.display());
let json = fs::read_to_string(&path).expect(&failure_msg);

let bp: Blueprint = serde_json::from_str(&json).expect("failed to deserialize blueprint");

assert!(
!bp.preamble.title.is_empty(),
"preamble.title should not be empty"
);
assert!(!bp.validators.is_empty(), "expected at least one validator");
}
}
2 changes: 2 additions & 0 deletions crates/tx3-lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ hex = { workspace = true }
serde = { workspace = true }

tx3-tir = { version = "0.15.1", path = "../tx3-tir" }
cip-57 = { path = "../cip-57" }
serde_json = "1.0.137"

miette = { version = "7.4.0", features = ["fancy"] }
pest = { version = "2.7.15", features = ["miette-error", "pretty-print"] }
Expand Down
9 changes: 9 additions & 0 deletions crates/tx3-lang/src/analyzing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,15 @@ impl Analyzable for StructConstructor {
_ => unreachable!(),
};

if type_def.cases.len() == 1 {
let only_case_name = type_def.cases[0].name.value.clone();
let requested_case = self.case.name.value.clone();

if requested_case == "Default" || requested_case == self.r#type.value {
self.case.name.value = only_case_name;
}
}

for case in type_def.cases.iter() {
scope.track_variant_case(case);
}
Expand Down
84 changes: 84 additions & 0 deletions crates/tx3-lang/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ impl AsRef<str> for Identifier {

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Program {
#[serde(default)]
pub imports: Vec<ImportDef>,
pub env: Option<EnvDef>,
pub txs: Vec<TxDef>,
pub types: Vec<TypeDef>,
Expand All @@ -187,6 +189,13 @@ pub struct Program {
pub(crate) scope: Option<Rc<Scope>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ImportDef {
pub path: StringLiteral,
pub alias: Option<Identifier>,
pub span: Span,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EnvField {
pub name: String,
Expand Down Expand Up @@ -934,6 +943,28 @@ impl TypeDef {
pub(crate) fn find_case(&self, case: &str) -> Option<&VariantCase> {
self.cases.iter().find(|x| x.name.value == case)
}

pub fn to_tx3_source(&self) -> String {
let name = &self.name.value;
// Implicit cases don't have an explicit constructor on its usage
if self.cases.len() == 1 && self.cases[0].name.value == "Default" {
let fields = &self.cases[0].fields;
let fields_str = fields
.iter()
.map(|f| format!("{}: {}", f.name.value, f.r#type))
.collect::<Vec<_>>()
.join(", ");
format!("type {} {{ {} }}", name, fields_str)
} else {
let cases_str = self
.cases
.iter()
.map(VariantCase::to_tx3_source)
.collect::<Vec<_>>()
.join(", ");
format!("type {} {{ {} }}", name, cases_str)
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
Expand All @@ -953,6 +984,59 @@ impl VariantCase {
pub(crate) fn find_field(&self, field: &str) -> Option<&RecordField> {
self.fields.iter().find(|x| x.name.value == field)
}

fn to_tx3_source(&self) -> String {
let name = &self.name.value;
if self.fields.is_empty() {
name.clone()
} else {
let fields_str = self
.fields
.iter()
.map(|f| format!("{}: {}", f.name.value, f.r#type))
.collect::<Vec<_>>()
.join(", ");
format!("{} {{ {} }}", name, fields_str)
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn to_tx3_source_omits_braces_for_empty_default_case() {
let type_def = TypeDef {
name: Identifier::new("Data"),
cases: vec![VariantCase {
name: Identifier::new("Default"),
fields: vec![],
span: Span::DUMMY,
}],
span: Span::DUMMY,
};

assert_eq!(type_def.to_tx3_source(), "type Data");
}

#[test]
fn to_tx3_source_keeps_braces_for_default_with_fields() {
let type_def = TypeDef {
name: Identifier::new("OutputReference"),
cases: vec![VariantCase {
name: Identifier::new("Default"),
fields: vec![RecordField::new("transaction_id", Type::Bytes)],
span: Span::DUMMY,
}],
span: Span::DUMMY,
};

assert_eq!(
type_def.to_tx3_source(),
"type OutputReference { transaction_id: Bytes }"
);
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
Expand Down
5 changes: 1 addition & 4 deletions crates/tx3-lang/src/cardano.rs
Original file line number Diff line number Diff line change
Expand Up @@ -845,10 +845,7 @@ impl IntoLower for CardanoBlock {
#[cfg(test)]
mod tests {
use super::*;
use crate::{
analyzing::analyze,
ast::{self, *},
};
use crate::{analyzing::analyze, ast::*};
use pest::Parser;

macro_rules! input_to_ast_check {
Expand Down
Loading