From fd2cea7e73368b96ceaee49d1f2b1ae9d08f05a7 Mon Sep 17 00:00:00 2001 From: twwu123 Date: Thu, 26 Feb 2026 17:31:19 +0800 Subject: [PATCH 1/5] remove csl from main whisky package --- .../whisky-examples/src/tx/unlock_fund.rs | 2 +- .../src/converter/tx_builder_body/mod.rs | 2 + .../src/converter/tx_builder_body/output.rs | 65 ++ packages/whisky-pallas/src/lib.rs | 2 + packages/whisky-pallas/src/tx_parser/mod.rs | 1 + .../whisky-pallas/src/tx_parser/parsable.rs | 49 + packages/whisky-pallas/src/utils/aiken.rs | 106 ++ packages/whisky-pallas/src/utils/data.rs | 186 ++++ packages/whisky-pallas/src/utils/mod.rs | 10 + packages/whisky-pallas/src/utils/script.rs | 45 + packages/whisky-pallas/src/utils/staking.rs | 21 + packages/whisky-pallas/src/utils/value.rs | 37 + .../src/wrapper/witness_set/native_script.rs | 9 +- .../src/wrapper/witness_set/plutus_data.rs | 6 +- .../src/wrapper/witness_set/plutus_script.rs | 5 + packages/whisky/Cargo.toml | 30 +- packages/whisky/src/builder/data.rs | 50 +- packages/whisky/src/builder/mod.rs | 3 +- packages/whisky/src/builder/tx_eval.rs | 1 + packages/whisky/src/lib.rs | 28 +- packages/whisky/src/parser/mod.rs | 4 +- packages/whisky/src/transaction/inputs.rs | 2 + packages/whisky/src/utils/blueprint.rs | 15 +- .../whisky/tests/csl_integration_tests.rs | 987 ------------------ .../whisky/tests/pallas_integration_tests.rs | 25 - packages/whisky/tests/tx_builder.rs | 19 +- packages/whisky/tests/tx_tester.rs | 5 +- 27 files changed, 635 insertions(+), 1080 deletions(-) create mode 100644 packages/whisky-pallas/src/converter/tx_builder_body/output.rs create mode 100644 packages/whisky-pallas/src/tx_parser/parsable.rs create mode 100644 packages/whisky-pallas/src/utils/aiken.rs create mode 100644 packages/whisky-pallas/src/utils/data.rs create mode 100644 packages/whisky-pallas/src/utils/script.rs create mode 100644 packages/whisky-pallas/src/utils/staking.rs create mode 100644 packages/whisky-pallas/src/utils/value.rs delete mode 100644 packages/whisky/tests/csl_integration_tests.rs diff --git a/packages/whisky-examples/src/tx/unlock_fund.rs b/packages/whisky-examples/src/tx/unlock_fund.rs index 290826b3..1d90dc26 100644 --- a/packages/whisky-examples/src/tx/unlock_fund.rs +++ b/packages/whisky-examples/src/tx/unlock_fund.rs @@ -9,7 +9,7 @@ pub async fn unlock_fund( collateral: &UTxO, ) -> Result { let mut tx_builder = TxBuilder::new_core(); - let pub_key_hash = deserialize_address(my_address).pub_key_hash; + let pub_key_hash = deserialize_address(my_address)?.pub_key_hash; tx_builder // .spending_plutus_script_v1() diff --git a/packages/whisky-pallas/src/converter/tx_builder_body/mod.rs b/packages/whisky-pallas/src/converter/tx_builder_body/mod.rs index 4492916f..1ab83ec6 100644 --- a/packages/whisky-pallas/src/converter/tx_builder_body/mod.rs +++ b/packages/whisky-pallas/src/converter/tx_builder_body/mod.rs @@ -1,5 +1,7 @@ pub mod input; +pub mod output; pub mod value; pub use input::*; +pub use output::*; pub use value::*; diff --git a/packages/whisky-pallas/src/converter/tx_builder_body/output.rs b/packages/whisky-pallas/src/converter/tx_builder_body/output.rs new file mode 100644 index 00000000..7062eadc --- /dev/null +++ b/packages/whisky-pallas/src/converter/tx_builder_body/output.rs @@ -0,0 +1,65 @@ +use crate::converter::{bytes_from_bech32, convert_value}; +use whisky_common::{LanguageVersion, Output, WError}; + +use crate::wrapper::transaction_body::{ + Datum, DatumKind, ScriptRef, ScriptRefKind, TransactionOutput, +}; + +pub fn convert_output(output: &Output) -> Result { + let datum: Option = match &output.datum { + Some(datum_source) => match datum_source { + whisky_common::Datum::Inline(datum_str) => Some(Datum::new(DatumKind::Data { + plutus_data_hex: datum_str.to_string(), + })?), + whisky_common::Datum::Hash(datum_str) => { + let datum = Datum::new(DatumKind::Data { + plutus_data_hex: datum_str.to_string(), + })?; + + let datum_hash_str = datum.hash()?; + Some(Datum::new(DatumKind::Hash { + datum_hash: datum_hash_str, + })?) + } + whisky_common::Datum::Embedded(datum_str) => None, + }, + None => None, + }; + + let script_ref = match &output.reference_script { + Some(script_source) => match script_source { + whisky_common::OutputScriptSource::ProvidedScriptSource(provided_script_source) => { + let plutus_script = match provided_script_source.language_version { + LanguageVersion::V1 => ScriptRef::new(ScriptRefKind::PlutusV1Script { + plutus_v1_script_hex: provided_script_source.script_cbor.clone(), + })?, + LanguageVersion::V2 => ScriptRef::new(ScriptRefKind::PlutusV2Script { + plutus_v2_script_hex: provided_script_source.script_cbor.clone(), + })?, + LanguageVersion::V3 => ScriptRef::new(ScriptRefKind::PlutusV3Script { + plutus_v3_script_hex: provided_script_source.script_cbor.clone(), + })?, + }; + Some(plutus_script) + } + whisky_common::OutputScriptSource::ProvidedSimpleScriptSource( + provided_simple_script_source, + ) => { + let native_script = ScriptRef::new(ScriptRefKind::NativeScript { + native_script_hex: provided_simple_script_source.script_cbor.clone(), + })?; + Some(native_script) + } + }, + None => None, + }; + + let tx_output = TransactionOutput::new( + &bytes_from_bech32(&output.address)?, + convert_value(&output.amount.clone())?, + datum, + script_ref, + )?; + + Ok(tx_output) +} diff --git a/packages/whisky-pallas/src/lib.rs b/packages/whisky-pallas/src/lib.rs index 15d66503..37861717 100644 --- a/packages/whisky-pallas/src/lib.rs +++ b/packages/whisky-pallas/src/lib.rs @@ -4,6 +4,8 @@ pub mod tx_parser; pub mod utils; pub mod wrapper; +pub use utils::address::*; + use crate::tx_builder::core_pallas::CorePallas; use whisky_common::{Protocol, TxBuilderBody}; diff --git a/packages/whisky-pallas/src/tx_parser/mod.rs b/packages/whisky-pallas/src/tx_parser/mod.rs index 1ada7e0b..128e2919 100644 --- a/packages/whisky-pallas/src/tx_parser/mod.rs +++ b/packages/whisky-pallas/src/tx_parser/mod.rs @@ -5,6 +5,7 @@ mod inputs; mod metadata; mod mints; pub mod outputs; +mod parsable; mod reference_inputs; mod required_signers; mod validity_range; diff --git a/packages/whisky-pallas/src/tx_parser/parsable.rs b/packages/whisky-pallas/src/tx_parser/parsable.rs new file mode 100644 index 00000000..e071df3d --- /dev/null +++ b/packages/whisky-pallas/src/tx_parser/parsable.rs @@ -0,0 +1,49 @@ +use pallas::ledger::primitives::{conway::Tx, Fragment}; +use whisky_common::{TxParsable, TxTester, WError}; + +use crate::{tx_parser::parse, WhiskyPallas}; + +impl TxParsable for WhiskyPallas { + fn parse( + &mut self, + tx_hex: &str, + resolved_utxos: &[whisky_common::UTxO], + ) -> Result<(), whisky_common::WError> { + self.tx_builder_body = parse(tx_hex, resolved_utxos)?; + Ok(()) + } + + fn get_required_inputs( + &mut self, + tx_hex: &str, + ) -> Result, whisky_common::WError> { + let tx_bytes = hex::decode(tx_hex) + .map_err(|_| WError::new("get_required_inputs", "error deserialising tx_hex"))?; + let pallas_tx = Tx::decode_fragment(&tx_bytes) + .map_err(|_| WError::new("get_required_inputs", "error decoding tx fragment"))?; + let mut required_inputs = Vec::new(); + for inputs in pallas_tx.transaction_body.inputs.iter() { + required_inputs.push(whisky_common::UtxoInput { + tx_hash: inputs.transaction_id.to_string(), + output_index: inputs.index as u32, + }); + } + Ok(required_inputs) + } + + fn get_builder_body(&self) -> whisky_common::TxBuilderBody { + self.tx_builder_body.clone() + } + + fn get_builder_body_without_change(&self) -> whisky_common::TxBuilderBody { + let mut tx_body = self.tx_builder_body.clone(); + if !tx_body.outputs.is_empty() { + tx_body.outputs.pop(); + } + tx_body + } + + fn to_tester(&self) -> TxTester { + TxTester::new(&self.tx_builder_body) + } +} diff --git a/packages/whisky-pallas/src/utils/aiken.rs b/packages/whisky-pallas/src/utils/aiken.rs new file mode 100644 index 00000000..e9e9b43e --- /dev/null +++ b/packages/whisky-pallas/src/utils/aiken.rs @@ -0,0 +1,106 @@ +use pallas_primitives::PlutusScript; +use uplc::{Fragment, PlutusData}; +use whisky_common::{BuilderDataType, WError}; + +use crate::utils::encode_json_str_to_plutus_datum; + +pub fn apply_double_cbor_encoding(script: &str) -> Result { + let bytes: Vec = hex::decode(script).map_err(|e| { + WError::new( + "apply_double_cbor_encoding - invalid script bytes", + &format!("Hex decode error: {}", e.to_string()), + ) + })?; + + let single_encoded_script = + PlutusScript::<3>::decode_fragment(&bytes.clone()).map_err(|e| { + WError::new( + "apply_double_cbor_encoding - invalid script bytes", + &format!("PlutusScript decode error: {}", e.to_string()), + ) + })?; + + let encoded_bytes = single_encoded_script.encode_fragment().map_err(|e| { + WError::new( + "apply_double_cbor_encoding - invalid script bytes", + &format!("PlutusScript encode error: {}", e.to_string()), + ) + })?; + + Ok(hex::encode(encoded_bytes)) +} + +pub fn apply_params_to_script( + plutus_script: &str, + params_to_apply: &[&str], + param_type: BuilderDataType, +) -> Result { + let double_encoded_script = apply_double_cbor_encoding(plutus_script)?; + let plutus_script = + PlutusScript::<3>::decode_fragment(&hex::decode(&double_encoded_script).map_err(|e| { + WError::new( + "apply_params_to_script - invalid script bytes", + &format!("Hex decode error: {}", e.to_string()), + ) + })?) + .map_err(|e| { + WError::new( + "apply_params_to_script - invalid script bytes", + &format!("PlutusScript decode error: {}", e.to_string()), + ) + })?; + + let mut plutus_list: Vec = vec![]; + for param in params_to_apply { + match param_type { + BuilderDataType::JSON => { + let plutus_data = encode_json_str_to_plutus_datum(param).map_err(|e| { + WError::new( + "apply_params_to_script - invalid parameter", + &format!("JSON to PlutusData error: {}", e.to_string()), + ) + })?; + plutus_list.push(plutus_data); + } + BuilderDataType::CBOR => { + let plutus_data = + PlutusData::decode_fragment(&hex::decode(param).map_err(|e| { + WError::new( + "apply_params_to_script - invalid parameter", + &format!("Hex decode error: {}", e.to_string()), + ) + })?) + .map_err(|e| { + WError::new( + "apply_params_to_script - invalid parameter", + &format!("PlutusData decode error: {}", e.to_string()), + ) + })?; + plutus_list.push(plutus_data); + } + } + } + + let bytes = uplc::tx::apply_params_to_script( + &plutus_list.encode_fragment().map_err(|e| { + WError::new( + "apply_params_to_script - invalid parameters", + &format!("PlutusData encode error: {}", e.to_string()), + ) + })?, + &plutus_script.encode_fragment().map_err(|e| { + WError::new( + "apply_params_to_script - invalid script bytes", + &format!("PlutusScript encode error: {}", e.to_string()), + ) + })?, + ) + .map_err(|e| { + WError::new( + "apply_params_to_script - applying parameters to script failed", + &format!("Apply params error: {}", e.to_string()), + ) + })?; + + Ok(hex::encode(bytes)) +} diff --git a/packages/whisky-pallas/src/utils/data.rs b/packages/whisky-pallas/src/utils/data.rs new file mode 100644 index 00000000..dcdc133a --- /dev/null +++ b/packages/whisky-pallas/src/utils/data.rs @@ -0,0 +1,186 @@ +use pallas_primitives::{BigInt, BoundedBytes, MaybeIndefArray, PlutusData}; + +use serde_json::Value; +use uplc::{Constr, KeyValuePairs}; +use whisky_common::WError; + +pub fn encode_json_str_to_plutus_datum(json: &str) -> Result { + let value: serde_json::Value = serde_json::from_str(json).map_err(WError::from_err( + "encode_json_str_to_plutus_datum - from_str", + ))?; + + encode_json_value_to_plutus_datum(value) +} + +pub fn encode_json_value_to_plutus_datum(value: Value) -> Result { + fn encode_number(x: serde_json::Number) -> Result { + if let Some(x) = x.as_u64() { + Ok(PlutusData::BigInt(BigInt::Int((x as i64).into()))) + } else if let Some(x) = x.as_i64() { + Ok(PlutusData::BigInt(BigInt::Int(x.into()))) + } else { + Err(WError::new( + "encode_number - ", + "floats not allowed in plutus datums", + )) + } + } + + fn encode_string(s: &str, is_key: bool) -> Result { + if s.starts_with("0x") { + hex::decode(&s[2..]) + .map(|bytes| PlutusData::BoundedBytes(BoundedBytes::from(bytes))) + .map_err(WError::from_err("encode_string - hex decode")) + } else if is_key { + // try first as integer + if let Ok(x) = s.parse::() { + Ok(PlutusData::BigInt(BigInt::Int((x as i64).into()))) + } else if let Ok(x) = s.parse::() { + Ok(PlutusData::BigInt(BigInt::Int(x.into()))) + } else { + // if not integer, encode as bytes + Ok(PlutusData::BoundedBytes(BoundedBytes::from( + s.as_bytes().to_vec(), + ))) + } + } else { + Ok(PlutusData::BoundedBytes(BoundedBytes::from( + s.as_bytes().to_vec(), + ))) + } + } + + fn encode_array(json_arr: Vec) -> Result { + let mut arr: Vec = Vec::new(); + for item in json_arr { + arr.push(encode_json_value_to_plutus_datum(item)?); + } + Ok(PlutusData::Array(MaybeIndefArray::Def(arr))) + } + + match value { + Value::Object(obj) => { + if obj.len() == 1 { + let (k, v) = obj.into_iter().next().unwrap(); + match k.as_str() { + "int" => match v { + Value::Number(x) => encode_number(x), + _ => Err(WError::new( + "encode_json_value_to_plutus_datum - int", + "expected number for int type", + )), + }, + "bytes" => match v { + Value::String(s) => encode_string(&s, false), + _ => Err(WError::new( + "encode_json_value_to_plutus_datum - bytes", + "expected string for bytes type", + )), + }, + "list" => match v { + Value::Array(arr) => encode_array(arr), + _ => Err(WError::new( + "encode_json_value_to_plutus_datum - list", + "expected array for list type", + )), + }, + "map" => match v { + Value::Array(map_vec) => { + let mut map: Vec<(PlutusData, PlutusData)> = vec![]; + for entry in map_vec { + match entry { + Value::Object(entry_obj) => { + let raw_key = entry_obj.get("k").ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - map entry", + "missing key in map entry", + ) + })?; + let raw_value = entry_obj.get("v").ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - map entry", + "missing value in map entry", + ) + })?; + let encoded_key = + encode_json_value_to_plutus_datum(raw_key.clone())?; + let encoded_value = + encode_json_value_to_plutus_datum(raw_value.clone())?; + map.push((encoded_key, encoded_value)); + } + _ => { + return Err(WError::new( + "encode_json_value_to_plutus_datum - map entry", + "expected object for map entry", + )) + } + } + } + Ok(PlutusData::Map(KeyValuePairs::from(map))) + } + _ => Err(WError::new( + "encode_json_value_to_plutus_datum - map", + "expected array for map type", + )), + }, + _ => Err(WError::new( + "encode_json_value_to_plutus_datum", + "unknown type key", + )), + } + } else { + if obj.len() != 2 { + return Err(WError::new( + "encode_json_value_to_plutus_datum - constr", + "expected object with single key for constr type", + )); + } + let variant: u64 = obj + .get("constructor") + .ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - constr", + "missing constr key for constr type", + ) + })? + .as_u64() + .ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - constr", + "expected unsigned integer for constr variant", + ) + })?; + let fields_json = obj + .get("fields") + .ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - constr", + "missing fields key for constr type", + ) + })? + .as_array() + .ok_or_else(|| { + WError::new( + "encode_json_value_to_plutus_datum - constr", + "expected array for constr fields", + ) + })?; + let mut fields: Vec = Vec::new(); + for field_json in fields_json { + fields.push(encode_json_value_to_plutus_datum(field_json.clone())?); + } + return Ok(PlutusData::Constr(Constr { + tag: variant + 121, + any_constructor: None, + fields: MaybeIndefArray::Def(fields), + })); + } + } + _ => { + return Err(WError::new( + "encode_json_value_to_plutus_datum", + "expected object with single key for typed value", + )) + } + } +} diff --git a/packages/whisky-pallas/src/utils/mod.rs b/packages/whisky-pallas/src/utils/mod.rs index 7a83093c..5e7f11f7 100644 --- a/packages/whisky-pallas/src/utils/mod.rs +++ b/packages/whisky-pallas/src/utils/mod.rs @@ -1,13 +1,23 @@ pub mod address; +pub mod aiken; pub mod constants; +pub mod data; pub mod evaluator; pub mod fee; pub mod phase_two; pub mod required_signatures; +pub mod script; +pub mod staking; +pub mod value; pub use address::*; +pub use aiken::*; pub use constants::*; +pub use data::*; pub use evaluator::*; pub use fee::*; pub use phase_two::*; pub use required_signatures::*; +pub use script::*; +pub use staking::*; +pub use value::*; diff --git a/packages/whisky-pallas/src/utils/script.rs b/packages/whisky-pallas/src/utils/script.rs new file mode 100644 index 00000000..2f43d3a6 --- /dev/null +++ b/packages/whisky-pallas/src/utils/script.rs @@ -0,0 +1,45 @@ +use whisky_common::{LanguageVersion, WError}; + +use crate::wrapper::witness_set::{native_script::NativeScript, plutus_script::PlutusScript}; + +pub fn get_native_script_hash(script: &str) -> Result { + NativeScript::decode_bytes(&hex::decode(script).map_err(|e| { + WError::new( + "WhiskyPallas - Decoding native script:", + &format!("Hex decode error: {}", e.to_string()), + ) + })?) + .map(|ns| ns.hash()) +} + +pub fn get_script_hash(script: &str, version: LanguageVersion) -> Result { + match version { + LanguageVersion::V1 => Ok( + PlutusScript::<1>::decode_bytes(&hex::decode(script).map_err(|e| { + WError::new( + "WhiskyPallas - Decoding Plutus script:", + &format!("Hex decode error: {}", e.to_string()), + ) + })?)? + .hash(), + ), + LanguageVersion::V2 => Ok( + PlutusScript::<2>::decode_bytes(&hex::decode(script).map_err(|e| { + WError::new( + "WhiskyPallas - Decoding Plutus script:", + &format!("Hex decode error: {}", e.to_string()), + ) + })?)? + .hash(), + ), + LanguageVersion::V3 => Ok( + PlutusScript::<3>::decode_bytes(&hex::decode(script).map_err(|e| { + WError::new( + "WhiskyPallas - Decoding Plutus script:", + &format!("Hex decode error: {}", e.to_string()), + ) + })?)? + .hash(), + ), + } +} diff --git a/packages/whisky-pallas/src/utils/staking.rs b/packages/whisky-pallas/src/utils/staking.rs new file mode 100644 index 00000000..c698991b --- /dev/null +++ b/packages/whisky-pallas/src/utils/staking.rs @@ -0,0 +1,21 @@ +use whisky_common::WError; + +use crate::wrapper::transaction_body::RewardAccount; + +pub fn script_hash_to_stake_address(script_hash: &str, network_id: u8) -> Result { + let script_hash_bytes = hex::decode(script_hash).map_err(|e| { + WError::new( + "script_hash_to_stake_address - invalid script hash", + &format!("Hex decode error: {}", e.to_string()), + ) + })?; + let header_byte: u8 = if network_id == 1 { + 0b1111_0001 // Mainnet + } else { + 0b1111_0000 // Testnet + }; + // concat header byte and script hash bytes + let mut address_bytes = vec![header_byte]; + address_bytes.extend(script_hash_bytes); + RewardAccount::from_bytes(&address_bytes)?.to_bech32() +} diff --git a/packages/whisky-pallas/src/utils/value.rs b/packages/whisky-pallas/src/utils/value.rs new file mode 100644 index 00000000..ff2c13f7 --- /dev/null +++ b/packages/whisky-pallas/src/utils/value.rs @@ -0,0 +1,37 @@ +use whisky_common::{Asset, Output, WError}; + +use crate::converter::convert_output; + +pub fn get_min_utxo_value(output: &Output, coins_per_utxo_size: &u64) -> Result { + // loop over output's assets and check if it has lovelaces + let mut has_lovelaces = false; + for asset in &output.amount { + if asset.policy() == "lovelace" { + has_lovelaces = true; + break; + } + } + match has_lovelaces { + true => { + let wrapped_output = convert_output(output)?; + let cbor_length = wrapped_output.encode()?.len(); + let min_utxo_value = coins_per_utxo_size * (cbor_length as u64 + 160); + Ok(min_utxo_value.to_string()) + } + false => { + // if it doesn't have lovelaces, we need to add a dummy lovelace to calculate the min utxo value + let mut dummy_value = output.amount.clone(); + dummy_value.push(Asset::new_from_str("lovelace", &u64::MAX.to_string())); + let dummy_output = Output { + address: output.address.clone(), + datum: output.datum.clone(), + reference_script: output.reference_script.clone(), + amount: dummy_value, + }; + let wrapped_output = convert_output(&dummy_output)?; + let cbor_length = wrapped_output.encode()?.len(); + let min_utxo_value = coins_per_utxo_size * (cbor_length as u64 + 160); + Ok(min_utxo_value.to_string()) + } + } +} diff --git a/packages/whisky-pallas/src/wrapper/witness_set/native_script.rs b/packages/whisky-pallas/src/wrapper/witness_set/native_script.rs index ec9b2406..a17f792d 100644 --- a/packages/whisky-pallas/src/wrapper/witness_set/native_script.rs +++ b/packages/whisky-pallas/src/wrapper/witness_set/native_script.rs @@ -2,7 +2,10 @@ use std::str::FromStr; use pallas::{ crypto::hash::Hash, - ledger::primitives::{conway::NativeScript as PallasNativeScript, Fragment}, + ledger::{ + primitives::{conway::NativeScript as PallasNativeScript, Fragment}, + traverse::ComputeHash, + }, }; use whisky_common::WError; @@ -104,4 +107,8 @@ impl NativeScript { })?; Ok(Self { inner }) } + + pub fn hash(&self) -> String { + self.inner.compute_hash().to_string() + } } diff --git a/packages/whisky-pallas/src/wrapper/witness_set/plutus_data.rs b/packages/whisky-pallas/src/wrapper/witness_set/plutus_data.rs index 519349d2..872ece3b 100644 --- a/packages/whisky-pallas/src/wrapper/witness_set/plutus_data.rs +++ b/packages/whisky-pallas/src/wrapper/witness_set/plutus_data.rs @@ -8,16 +8,16 @@ pub struct PlutusData { impl PlutusData { pub fn new(plutus_data_hex: String) -> Result { - let bytes = hex::decode(plutus_data_hex).map_err(|e| { + let bytes = hex::decode(&plutus_data_hex).map_err(|e| { WError::new( "WhiskyPallas - Creating Plutus data:", - &format!("Hex decode error: {}", e), + &format!("Hex decode error: {} for {}", e, &plutus_data_hex), ) })?; let inner = PallasPlutusData::decode_fragment(&bytes).map_err(|e| { WError::new( "WhiskyPallas - Creating Plutus data:", - &format!("Fragment decode error: {}", e), + &format!("Fragment decode error: {} for {}", e, plutus_data_hex), ) })?; Ok(Self { inner }) diff --git a/packages/whisky-pallas/src/wrapper/witness_set/plutus_script.rs b/packages/whisky-pallas/src/wrapper/witness_set/plutus_script.rs index 99772f77..c1d17422 100644 --- a/packages/whisky-pallas/src/wrapper/witness_set/plutus_script.rs +++ b/packages/whisky-pallas/src/wrapper/witness_set/plutus_script.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use pallas::codec::utils::Bytes; use pallas::ledger::primitives::conway::PlutusScript as PallasPlutusScript; use pallas::ledger::primitives::Fragment; +use pallas::ledger::traverse::ComputeHash; use whisky_common::WError; #[derive(Debug, PartialEq, Eq, Clone)] @@ -43,4 +44,8 @@ impl PlutusScript { })?; Ok(Self { inner }) } + + pub fn hash(&self) -> String { + self.inner.compute_hash().to_string() + } } diff --git a/packages/whisky/Cargo.toml b/packages/whisky/Cargo.toml index 6b91b78c..cd692d65 100644 --- a/packages/whisky/Cargo.toml +++ b/packages/whisky/Cargo.toml @@ -12,29 +12,14 @@ crate-type = ["cdylib", "rlib"] [features] default = ["full"] -full = ["csl", "wallet", "provider", "pallas"] +full = ["wallet", "provider", "pallas"] -# CSL-based transaction building -csl = [ - "dep:whisky-csl", - "dep:getrandom", - "dep:cryptoxide", - "dep:serde-wasm-bindgen", - "dep:uplc", - "dep:wasm-bindgen", - "dep:rand_os", - "dep:noop_proc_macro", - "dep:pallas-codec", - "dep:pallas-primitives", - "dep:pallas-traverse", -] -# Wallet functionality (implies csl) -wallet = ["csl", "dep:whisky-wallet"] +# Wallet functionality +wallet = ["pallas", "dep:whisky-wallet"] -# Provider/API functionality (implies csl) +# Provider/API functionality provider = [ - "csl", "dep:whisky-provider", "dep:maestro-rust-sdk", "dep:reqwest", @@ -43,7 +28,12 @@ provider = [ ] # Pallas integration -pallas = ["dep:whisky-pallas"] +pallas = ["dep:whisky-pallas", + "dep:pallas-codec", + "dep:pallas-primitives", + "dep:pallas-traverse", + "dep:uplc" + ] [dependencies] # Always required diff --git a/packages/whisky/src/builder/data.rs b/packages/whisky/src/builder/data.rs index 40db3668..f7ff7be0 100644 --- a/packages/whisky/src/builder/data.rs +++ b/packages/whisky/src/builder/data.rs @@ -1,3 +1,12 @@ +use pallas_primitives::{ + conway::{DatumOption, PseudoDatumOption}, + MaybeIndefArray, PlutusData, +}; +use pallas_traverse::ComputeHash; +use serde_json::json; +use uplc::{Constr, Fragment}; +use whisky_pallas::utils::encode_json_str_to_plutus_datum; + use crate::*; #[derive(Clone, Debug)] @@ -11,10 +20,11 @@ impl WData { match self { WData::CBOR(data) => Ok(data.clone()), WData::JSON(data) => { - let data_cbor = - &csl::PlutusData::from_json(data, csl::PlutusDatumSchema::DetailedSchema) - .map_err(WError::from_err("WData - to_cbor"))? - .to_hex(); + let data_cbor = bytes_to_hex( + &encode_json_str_to_plutus_datum(data)? + .encode_fragment() + .map_err(WError::from_err("WData - to_cbor"))?, + ); Ok(data_cbor.clone()) } } @@ -22,10 +32,13 @@ impl WData { pub fn to_hash(&self) -> Result { let cbor = self.to_cbor()?; - let hash = &csl::hash_plutus_data( - &csl::PlutusData::from_hex(&cbor).map_err(WError::from_err("WData - to_hash"))?, - ) - .to_hex(); + let decoded_hex = + hex::decode(cbor).map_err(WError::from_err("WData - to_hash - hex decode"))?; + let plutus_data = PlutusData::decode_fragment(&decoded_hex) + .map_err(|_| WError::new("WData to_hash", "error decoding cbor"))?; + let hash = DatumOption::compute_hash(&PseudoDatumOption::Data( + pallas_codec::utils::CborWrap(plutus_data), + )); Ok(hash.to_string()) } } @@ -41,3 +54,24 @@ pub struct WDatum { pub type_: String, pub data: WData, } + +#[test] +fn test_wdata_to_cbor() { + let cbor = &WData::JSON( + json!({ + "constructor": 0, + "fields": [] + }) + .to_string(), + ) + .to_cbor() + .unwrap(); + + let cbor2 = PlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: MaybeIndefArray::Def(vec![]), + }); + println!("CBOR: {}", cbor); + println!("CBOR2: {}", hex::encode(cbor2.encode_fragment().unwrap())); +} diff --git a/packages/whisky/src/builder/mod.rs b/packages/whisky/src/builder/mod.rs index b355b975..eb4a81f0 100644 --- a/packages/whisky/src/builder/mod.rs +++ b/packages/whisky/src/builder/mod.rs @@ -15,6 +15,7 @@ use crate::*; pub use data::*; pub use tx_eval::*; use whisky_common::data::*; +use whisky_pallas::{utils::get_min_utxo_value, WhiskyPallas}; pub struct TxBuilder { pub serializer: Box, @@ -96,7 +97,7 @@ impl TxBuilder { /// * `Self` - A new TxBuilder instance pub fn new_core() -> Self { Self::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, diff --git a/packages/whisky/src/builder/tx_eval.rs b/packages/whisky/src/builder/tx_eval.rs index 89641561..12029f99 100644 --- a/packages/whisky/src/builder/tx_eval.rs +++ b/packages/whisky/src/builder/tx_eval.rs @@ -2,6 +2,7 @@ use crate::*; use async_trait::async_trait; use uplc::tx::SlotConfig; use whisky_common::Evaluator; +use whisky_pallas::utils::evaluate_tx_scripts; use super::TxBuilder; diff --git a/packages/whisky/src/lib.rs b/packages/whisky/src/lib.rs index 394f0221..ad520f6d 100644 --- a/packages/whisky/src/lib.rs +++ b/packages/whisky/src/lib.rs @@ -72,35 +72,35 @@ extern crate self as whisky; // Data module is always available (uses whisky_common + whisky_macros) pub mod data; -// CSL-dependent modules -#[cfg(feature = "csl")] +// pallas-dependent modules +#[cfg(feature = "pallas")] pub mod builder; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub mod parser; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub mod transaction; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub mod utils; -// Services require both csl and provider -#[cfg(all(feature = "csl", feature = "provider"))] +// Services require both pallas and provider +#[cfg(all(feature = "pallas", feature = "provider"))] pub mod services; // Always re-export common and macros pub use whisky_common::*; pub use whisky_macros::*; -// CSL re-exports -#[cfg(feature = "csl")] +// Pallas re-exports +#[cfg(feature = "pallas")] pub use builder::*; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub use parser::*; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub use transaction::*; -#[cfg(feature = "csl")] +#[cfg(feature = "pallas")] pub use utils::*; -#[cfg(feature = "csl")] -pub use whisky_csl::*; +#[cfg(feature = "pallas")] +pub use whisky_pallas::*; // Wallet re-exports #[cfg(feature = "wallet")] diff --git a/packages/whisky/src/parser/mod.rs b/packages/whisky/src/parser/mod.rs index bc4a1446..085c4b0d 100644 --- a/packages/whisky/src/parser/mod.rs +++ b/packages/whisky/src/parser/mod.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use whisky_common::{Fetcher, TxBuilderBody, TxParsable, TxTester, UTxO, WError}; -use whisky_csl::WhiskyCSL; +use whisky_pallas::WhiskyPallas; pub struct TxParser { pub fetcher: Option>, @@ -12,7 +12,7 @@ impl TxParser { pub fn new(fetcher: Option>) -> Self { TxParser { fetcher: fetcher, - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), } } diff --git a/packages/whisky/src/transaction/inputs.rs b/packages/whisky/src/transaction/inputs.rs index 97395ec8..b96175b8 100644 --- a/packages/whisky/src/transaction/inputs.rs +++ b/packages/whisky/src/transaction/inputs.rs @@ -1,3 +1,5 @@ +use whisky_pallas::utils::get_script_hash; + use crate::*; use crate::builder::{WData, WRedeemer}; diff --git a/packages/whisky/src/utils/blueprint.rs b/packages/whisky/src/utils/blueprint.rs index 70215e2c..4614daef 100644 --- a/packages/whisky/src/utils/blueprint.rs +++ b/packages/whisky/src/utils/blueprint.rs @@ -1,6 +1,9 @@ use crate::*; use std::marker::PhantomData; use whisky_common::data::{ByteString, PlutusDataJson}; +use whisky_pallas::utils::{ + apply_params_to_script, get_script_hash, script_hash_to_stake_address, script_to_address, +}; #[derive(Debug, Clone)] pub struct MintingBlueprint

@@ -39,7 +42,7 @@ where let cbor = apply_params_to_script(compiled_code, params, params_type)?; let hash = get_script_hash(&cbor, self.version.clone())?; self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); Ok(self) } @@ -47,7 +50,7 @@ where let cbor = apply_params_to_script(compiled_code, &[], BuilderDataType::CBOR)?; let hash = get_script_hash(&cbor, self.version.clone())?; self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); Ok(self) } @@ -105,7 +108,7 @@ where let hash = get_script_hash(&cbor, self.version.clone()).unwrap(); self.address = script_hash_to_stake_address(&hash, self.network_id)?; self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); Ok(self) } @@ -114,7 +117,7 @@ where let hash = get_script_hash(&cbor, self.version.clone())?; self.address = script_hash_to_stake_address(&hash, self.network_id)?; self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); Ok(self) } @@ -187,7 +190,7 @@ where let address = script_to_address(self.network_id, &hash, stake_hash); self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); self.address = address; Ok(self) } @@ -201,7 +204,7 @@ where .map(|(hash, is_script)| (hash.as_str(), *is_script)); let address = script_to_address(self.network_id, &hash, stake_hash); self.hash = hash; - self.cbor = cbor; + self.cbor = cbor.to_string(); self.address = address; Ok(self) } diff --git a/packages/whisky/tests/csl_integration_tests.rs b/packages/whisky/tests/csl_integration_tests.rs deleted file mode 100644 index 802f0500..00000000 --- a/packages/whisky/tests/csl_integration_tests.rs +++ /dev/null @@ -1,987 +0,0 @@ -mod int_tests { - use serde_json::{json, to_string}; - use whisky::{Credential as TxBuilderCredential, *}; - use whisky_common::data::*; - - #[test] - fn test_complex_plutus_mint_spend_with_ref_tx() { - let cns_owner_addr = "addr_test1vr3vljjxan0hl6u28fle2l4ds6ugc9t08lwevpauk38t3agx7rtq6"; - let record_validator_addr = - "addr_test1wz97vqzhce0m4ek4cpnnlzvlaf5gdzck46axlur094lnzcgj0pq2u"; - let cns_policy_id = "baefdc6c5b191be372a794cd8d40d839ec0dbdd3c28957267dc81700"; - let record_token_policy_id = "19683f7853c85a7eb53615b580f15f89a1280f8fbd642edc4cb753e6"; - let cns_token_mp_script_ref_txhash = - "63210437b543c8a11afbbc6765aa205eb2733cb74e2805afd4c1c8cb72bd8e22"; - let cns_token_mp_script_ref_txid = "0"; - let record_validator_script_ref_txhash = - "bb712547a5abe3697f8aba72870e33a52fd2c0401715950197f9b7370d137998"; - let record_validator_script_ref_txid = "0"; - let cns_owner_pubkey = "e2cfca46ecdf7feb8a3a7f957ead86b88c156f3fdd9607bcb44eb8f5"; - - let wallet_address = "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x"; - let domain = "6d65736874657374696e67340a"; - let domain_with_ext = "6d65736874657374696e67342e6164610a"; - let metadata = json!({ - "baefdc6c5b191be372a794cd8d40d839ec0dbdd3c28957267dc81700": { - "tx_buildertesting4.ada": { - "cnsType": "Normal", - "description": "CNS, the digital social identity on Cardano.", - "expiry": "1731369600000", - "image": "ipfs://QmVEr6bkAek9Fibo7qotxfUWyXup2Bmav3SL9vB7t68Ngd", - "mediaType": "image/jpeg", - "name": "tx_buildertesting4.ada", - "origin": "Cardano Name Service", - "virtualSubdomainEnabled": "Disabled", - "virtualSubdomainLimits": 0, - }, - }, - "version": 1, - }); - - let record_token_name_hex = "434e53205265636f7264202834303029"; - let record_tx_hash = "aae2b8a5bf420c0d2fc785d54fe3eacc107145dee01b8c61beedcd13e6be9a71"; - let record_tx_id = 0; - - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let res = tx_builder - .tx_in( - "fc1c806abc9981f4bee2ce259f61578c3341012f3d04f22e82e7e40c7e7e3c3c", - 3, - &[Asset::new_from_str("lovelace", "9692479606")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .read_only_tx_in_reference( - "8b7ea04a142933b3d8005bf98be906bdba10978891593b383deac933497e2ea7", - 1, - None, - ) - .mint_plutus_script_v2() - .mint(1, cns_policy_id, domain_with_ext) - .mint_tx_in_reference( - cns_token_mp_script_ref_txhash, - cns_token_mp_script_ref_txid.parse::().unwrap(), - cns_policy_id, - 100, - ) - .mint_redeemer_value(&WRedeemer { - data: WData::JSON( - to_string(&json!({ - "constructor": 0, - "fields": [{ "bytes": domain }] - })) - .unwrap(), - ), - ex_units: Budget { - mem: 3386819, - steps: 1048170931, - }, - }) - .spending_plutus_script_v2() - .tx_in( - record_tx_hash, - record_tx_id, - &[Asset::new( - record_token_policy_id.to_string() + record_token_name_hex, - "1".to_string(), - )], - "addr_test1wz97vqzhce0m4ek4cpnnlzvlaf5gdzck46axlur094lnzcgj0pq2u", - ) - .spending_reference_tx_in_inline_datum_present() - .spending_reference_tx_in_redeemer_value(&WRedeemer { - data: WData::JSON( - to_string(&json!({ - "constructor": 0, - "fields": [{ "bytes": domain }], - })) - .unwrap(), - ), - ex_units: Budget { - mem: 9978951, - steps: 4541421719, - }, - }) - .spending_tx_in_reference( - record_validator_script_ref_txhash, - record_validator_script_ref_txid.parse::().unwrap(), - "8be60057c65fbae6d5c0673f899fea68868b16aeba6ff06f2d7f3161", - 100, - ) - .tx_out( - wallet_address, - &[ - Asset::new_from_str("lovelace", "2000000"), - Asset::new(cns_policy_id.to_string() + domain_with_ext, "1".to_string()), - ], - ) - .tx_out( - cns_owner_addr, - &[Asset::new_from_str("lovelace", "30000000")], - ) - .tx_out( - record_validator_addr, - &[ - Asset::new_from_str("lovelace", "20000000"), - Asset::new( - record_token_policy_id.to_string() + record_token_name_hex, - "1".to_string(), - ), - ], - ) - .tx_out_inline_datum_value(&WData::JSON( - to_string(&json!({ - "constructor": 0, - "fields": [{ "bytes": domain }], - })) - .unwrap(), - )) - .required_signer_hash(cns_owner_pubkey) - .metadata_value("721", to_string(&metadata).unwrap().as_str()) - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 6, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 7, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .change_address(wallet_address) - .change_output_datum(WData::JSON(constr0(json!([])).to_string())) - .complete_sync(None); - - match res { - Ok(_) => { - let signed_tx = tx_builder.complete_signing().unwrap(); - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - Err(e) => { - println!("error: {:?}", e); - // failing the test case - panic!() - } - } - println!("{}", tx_builder.serializer.tx_hex()); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_simple_spend() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .signing_key("51022b7e38be01d1cc581230e18030e6e1a3e949a1fdd2aeae5f5412154fe82b") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_simple_withdraw() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let signed_tx = tx_builder - .tx_in( - "fbd3e8091c9f0c5fb446be9e58d9235f548546a5a7d5f60ee56e389344db9c5e", - 0, - &[Asset::new_from_str("lovelace", "9496607660")], - "addr_test1qpjfsrjdr8kk5ffj4jnw02ht3y3td0y0zkcm52rc6w7z7flmy7vplnvz6a7dncss4q5quqwt48tv9dewuvdxqssur9jqc4x459", - ) - .change_address("addr_test1qpjfsrjdr8kk5ffj4jnw02ht3y3td0y0zkcm52rc6w7z7flmy7vplnvz6a7dncss4q5quqwt48tv9dewuvdxqssur9jqc4x459") - .withdrawal("stake_test1uraj0xqlekpdwlxeugg2s2qwq896n4kzkuhwxxnqggwpjeqe9s9k2", 0) - .required_signer_hash("fb27981fcd82d77cd9e210a8280e01cba9d6c2b72ee31a60421c1964") - .required_signer_hash("64980e4d19ed6a2532aca6e7aaeb8922b6bc8f15b1ba2878d3bc2f27") - .signing_key("58208d4cfa90e8bd0c48c52d2fb62c77ba3f6f5eb46f640d5f997390012928d670f7") - .signing_key("5820ba73019f1239fa47f8d9c0c42c5d05bf34f2b2f6ebd1c556f8f86e5bee1aac66") - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_plutus_withdraw() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let signed_tx = tx_builder - .tx_in( - "60b6a29a4c164bece283738abd57fa35c0b839f298f15836ee54a875ede87d37", - 0, - &[Asset::new_from_str("lovelace", "9999639476")], - "addr_test1yp8ezxpltlrus89uz8g7e07795w0cxn3a7w7nxdac8s4aj7cjpk2t3a6zf9qgpar9k4n0vkg9vfm8hxezy0y99qde6jq58zjfw", - ) - .tx_in_collateral( - "60b6a29a4c164bece283738abd57fa35c0b839f298f15836ee54a875ede87d37", - 0, - &[Asset::new_from_str("lovelace", "9999639476")], - "addr_test1yp8ezxpltlrus89uz8g7e07795w0cxn3a7w7nxdac8s4aj7cjpk2t3a6zf9qgpar9k4n0vkg9vfm8hxezy0y99qde6jq58zjfw", - ) - .change_address("addr_test1yp8ezxpltlrus89uz8g7e07795w0cxn3a7w7nxdac8s4aj7cjpk2t3a6zf9qgpar9k4n0vkg9vfm8hxezy0y99qde6jq58zjfw") - .withdrawal_plutus_script_v2() - .withdrawal("stake_test17rvfqm99c7apyjsyq73jm2ehktyzkyanmnv3z8jzjsxuafq5a6z2j", 0) - .withdrawal_script("5251010000322253330034a229309b2b2b9a01") - .withdrawal_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 2501, - steps: 617656, - }, - }) - .required_signer_hash("4f91183f5fc7c81cbc11d1ecbfde2d1cfc1a71ef9de999bdc1e15ecb") - .signing_key("5820c835cd2413c6330537c85e3d510b313dfdeee5708206e76ce8bd387cdd4b6bb2") - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_native_script_ref() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let _unsigned_tx = tx_builder - .tx_in( - "db0937db0e8a743e6e97e8cf29077af1e951b52e46f2e2c63ef12a3abaaf9052", - 80, - &[Asset::new_from_str("lovelace", "4633697637")], - "addr_test1qr3a9rrclgf9rx90lmll2qnfzfwgrw35ukvgjrk36pmlzu0jemqwylc286744g0tnqkrvu0dkl8r48k0upkfmg7mncpqf0672w", - ) - .change_address("addr_test1qr3a9rrclgf9rx90lmll2qnfzfwgrw35ukvgjrk36pmlzu0jemqwylc286744g0tnqkrvu0dkl8r48k0upkfmg7mncpqf0672w") - .tx_out("addr_test1qr3a9rrclgf9rx90lmll2qnfzfwgrw35ukvgjrk36pmlzu0jemqwylc286744g0tnqkrvu0dkl8r48k0upkfmg7mncpqf0672w", &[Asset::new_from_str("lovelace", "5000000")]) - .tx_out_reference_script("8200581ce3d28c78fa125198affefff50269125c81ba34e598890ed1d077f171", None) - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - // let signed_tx = merge_vkey_witnesses_to_transaction(unsigned_tx, "a10081825820096348a7a3640d8ecc89819abffc7ed89cde399346046d50444acbd6e467f9df5840111279e89d341c9ab51f9ee7d5bb3a8db068ca6d09b7d3d4aaa48940dc55162903fd8f194df5c048055c9ac869e95729273b4ebb752be8a998f3483fac5d6e05".to_string()); - // println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_plutus_script_cert_registration() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .tx_in("b3b05ac96e1eb4cd3b3cb8150cc48ee006d12683ed1b87ee57122d83235069df", - 0, - &[Asset::new_from_str("lovelace", "1488554147")], - "addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6",) - .tx_in_collateral("541e2c5e6af1661a08aedf53fc4fb66aee00885629100196abbe42b05121adff", 5, &[Asset::new_from_str("lovelace", "5000000")], "addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .register_stake_certificate("stake_test17rvfqm99c7apyjsyq73jm2ehktyzkyanmnv3z8jzjsxuafq5a6z2j") - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_plutus_script_cert_deregistration() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .tx_in("b3b05ac96e1eb4cd3b3cb8150cc48ee006d12683ed1b87ee57122d83235069df", - 0, - &[Asset::new_from_str("lovelace", "1488554147")], - "addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6",) - .tx_in_collateral("541e2c5e6af1661a08aedf53fc4fb66aee00885629100196abbe42b05121adff", 5, &[Asset::new_from_str("lovelace", "5000000")], "addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .deregister_stake_certificate("stake_test17rvfqm99c7apyjsyq73jm2ehktyzkyanmnv3z8jzjsxuafq5a6z2j") - .certificate_script("5251010000322253330034a229309b2b2b9a01", Some(LanguageVersion::V2)) - .certificate_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 7000000, - steps: 14000000 - }}) - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_mint_two_tokens_with_same_policy() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder. - tx_in("b68d2e8340d9454c66b0530de8fdeca5bc829c577217b12f0c0beeb7f42b6b18", 0, &[Asset::new_from_str("lovelace", "100000000000")], "addr_test1qrfkkp5dwgj07fljdum677pglfm5707hd8nwj5wgfqdhfp0m7kq4cxp4nznl6v9yp2wxvwl2vsh0mk7eq7g97vczj6uqse4e3j") - .tx_in_collateral("541e2c5e6af1661a08aedf53fc4fb66aee00885629100196abbe42b05121adff", 5, &[Asset::new_from_str("lovelace", "5000000")], "addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .mint_plutus_script_v2() - .mint(1, "d8906ca5c7ba124a0407a32dab37b2c82b13b3dcd9111e42940dcea4", "7465737431") - .mint_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 7000000, - steps: 14000000 - }}) - .minting_script("5251010000322253330034a229309b2b2b9a01") - .mint_plutus_script_v2() - .mint(1, "d8906ca5c7ba124a0407a32dab37b2c82b13b3dcd9111e42940dcea4", "7465737432") - .mint_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 7000000, - steps: 14000000 - }}) - .minting_script("5251010000322253330034a229309b2b2b9a01") - .change_address("addr_test1qrfkkp5dwgj07fljdum677pglfm5707hd8nwj5wgfqdhfp0m7kq4cxp4nznl6v9yp2wxvwl2vsh0mk7eq7g97vczj6uqse4e3j") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_spend_withdraw_and_unreg() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let reward_address = "stake_test17q3hjj9svuvmmj5untsrclvlwzs8q528tzj0k3g5hgkzajc23t4fh"; - - let unsigned_tx = tx_builder - .spending_plutus_script_v2() - .tx_in("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 0, &[Asset::new_from_str("lovelace", "6904620")], "addr_test1zrrpfzell3549ulhjwar3juz8dv8qcc99kfvlwrfzu2sw76u5ayjvx4rk9a29n2tqf4uv4nvfv2yy8tqs0kuue8luh9shn8fam") - .spending_tx_in_reference("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 1, "237948b06719bdca9c9ae03c7d9f70a070514758a4fb4514ba2c2ecb", 950) - .tx_in_inline_datum_present() - .spending_reference_tx_in_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 35588, - steps: 13042895 - }}) - .spending_plutus_script_v2() - .tx_in("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 1, &[Asset::new_from_str("lovelace", "5159070")], "addr_test1zrrpfzell3549ulhjwar3juz8dv8qcc99kfvlwrfzu2sw76u5ayjvx4rk9a29n2tqf4uv4nvfv2yy8tqs0kuue8luh9shn8fam") - .spending_tx_in_reference("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 1, "237948b06719bdca9c9ae03c7d9f70a070514758a4fb4514ba2c2ecb", 950) - .tx_in_inline_datum_present() - .spending_reference_tx_in_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 35588, - steps: 13042895 - }}) - .deregister_stake_certificate(reward_address) - .certificate_tx_in_reference("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 0, "237948b06719bdca9c9ae03c7d9f70a070514758a4fb4514ba2c2ecb", Some(LanguageVersion::V2), 953) - .certificate_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 120022, - steps: 44400485 - }}) - .withdrawal_plutus_script_v2() - .withdrawal(reward_address, 0) - .withdrawal_redeemer_value(&WRedeemer { - data: WData::JSON(constr0(json!([])).to_string()), - ex_units: Budget { - mem: 120022, - steps: 44400485 - }}) - .withdrawal_tx_in_reference("e4e94d4369b5a1b6366d468bf01bf4d332d29abd8061889e6d80fc5074248ed1", 0, "237948b06719bdca9c9ae03c7d9f70a070514758a4fb4514ba2c2ecb", 953) - .read_only_tx_in_reference("d3e7e43ec9c85cfdb90f98fb40bb4edd58fdd3d056e32f827739fe0b915c6eb7", 0, None) - .change_address("addr_test1qqjcvv7huxlf9epjq49j4952pez8l4zyrm6c4wrf2vtcym4jg6fd5d54p0k5mqy46ph5z3r59tkhnhjvsxx53dq5rvdsnaeh3a") - .tx_in_collateral("3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", 7, &[Asset::new_from_str("lovelace", "10000000")], "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x") - .required_signer_hash("258633d7e1be92e432054b2a968a0e447fd4441ef58ab8695317826e") - .required_signer_hash("5ca51b304b1f79d92eada8c58c513e969458dcd27ce4f5bc47823ffa") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_embedded_datum_output() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .tx_out( - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - &[Asset::new_from_str("lovelace", "2000000")], - ) - .tx_out_datum_embed_value(&WData::JSON( - json!({ - "constructor": 0, - "fields": [] - }) - .to_string(), - )) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .signing_key("51022b7e38be01d1cc581230e18030e6e1a3e949a1fdd2aeae5f5412154fe82b") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_register_drep() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .drep_registration("drep1j6257gz2swty9ut46lspyvujkt02pd82am2zq97p7p9pv2euzs7", 500000000, Some(Anchor { - anchor_url: "https://raw.githubusercontent.com/HinsonSIDAN/cardano-drep/main/HinsonSIDAN.jsonld".to_string(), - anchor_data_hash: "2aef51273a566e529a2d5958d981d7f0b3c7224fc2853b6c4922e019657b5060".to_string() - })) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_register_drep_cip129() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .drep_registration("drep1y2tf2neqf2pevsh3wht7qy3nj2edag95athdggqhc8cy59s6skxy4", 500000000, Some(Anchor { - anchor_url: "https://raw.githubusercontent.com/HinsonSIDAN/cardano-drep/main/HinsonSIDAN.jsonld".to_string(), - anchor_data_hash: "2aef51273a566e529a2d5958d981d7f0b3c7224fc2853b6c4922e019657b5060".to_string() - })) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_vote_delegation() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .vote_delegation_certificate("stake_test1uzdx8vwxvz5wy45fwdrwk2l85ax7j5wtr4cee6a8xc632cc3p6psh", &DRep::DRepId("drep1j6257gz2swty9ut46lspyvujkt02pd82am2zq97p7p9pv2euzs7".to_string())) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_drep_vote() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .vote(&Voter::DRepId("drep1j6257gz2swty9ut46lspyvujkt02pd82am2zq97p7p9pv2euzs7".to_string()), &RefTxIn { - tx_hash: "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85".to_string(), - tx_index: 2, - script_size: None, - }, &VotingProcedure { - vote_kind: VoteKind::Abstain, - anchor: Some(Anchor { - anchor_url: "https://raw.githubusercontent.com/HinsonSIDAN/cardano-drep/main/HinsonSIDAN.jsonld".to_string(), - anchor_data_hash: "2aef51273a566e529a2d5958d981d7f0b3c7224fc2853b6c4922e019657b5060".to_string() - }) - }) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_cc_vote() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .change_address("addr_test1qpsmz8q2xj43wg597pnpp0ffnlvr8fpfydff0wcsyzqyrxguk5v6wzdvfjyy8q5ysrh8wdxg9h0u4ncse4cxhd7qhqjqk8pse6") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .vote(&Voter::ConstitutionalCommitteeHotCred(TxBuilderCredential::KeyHash("e3a4c41d67592a1b8d87c62e5c5d73f7e8db836171945412d13f40f8".to_string())), &RefTxIn { - tx_hash: "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85".to_string(), - tx_index: 2, - script_size: None - }, &VotingProcedure { - vote_kind: VoteKind::Abstain, - anchor: Some(Anchor { - anchor_url: "https://raw.githubusercontent.com/HinsonSIDAN/cardano-drep/main/HinsonSIDAN.jsonld".to_string(), - anchor_data_hash: "2aef51273a566e529a2d5958d981d7f0b3c7224fc2853b6c4922e019657b5060".to_string() - }) - }) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_simple_spend_with_set_fee() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .signing_key("51022b7e38be01d1cc581230e18030e6e1a3e949a1fdd2aeae5f5412154fe82b") - .set_fee("500000") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_register_stake_with_custom_pp() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: Some(Protocol { - epoch: 0, - min_fee_a: 44, - min_fee_b: 155381, - max_block_size: 98304, - max_tx_size: 16384, - max_block_header_size: 1100, - key_deposit: 0, - pool_deposit: 500000000, - min_pool_cost: "340000000".to_string(), - price_mem: 0.0577, - price_step: 0.0000721, - max_tx_ex_mem: "16000000".to_string(), - max_tx_ex_steps: "10000000000".to_string(), - max_block_ex_mem: "80000000".to_string(), - max_block_ex_steps: "40000000000".to_string(), - max_val_size: 5000, - collateral_percent: 150.0, - max_collateral_inputs: 3, - coins_per_utxo_size: 4310, - min_fee_ref_script_cost_per_byte: 15, - decentralisation: 0.0, - }), - }); - let signed_tx = tx_builder - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .register_stake_certificate( - "stake_test17rvfqm99c7apyjsyq73jm2ehktyzkyanmnv3z8jzjsxuafq5a6z2j", - ) - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_balance() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .tx_out( - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - &[], - ) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn output_to_daedalus_address_test() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .tx_out( - "DdzFFzCqrhswh7xiYG8RE1TtcvWamhbExTXfsCYaF9PrGWHRLCwCsBH5JkeApUagvo4FZE3DJD3rn5hw8vaMBib2StKMJ77rJHt51jPt", - &[Asset::new_from_str("lovelace", "2000000")] - ) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn change_output_to_daedalus_address_test() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .tx_out( - "DdzFFzCqrhswh7xiYG8RE1TtcvWamhbExTXfsCYaF9PrGWHRLCwCsBH5JkeApUagvo4FZE3DJD3rn5hw8vaMBib2StKMJ77rJHt51jPt", - &[Asset::new_from_str("lovelace", "2000000")] - ) - .change_address("DdzFFzCqrhswh7xiYG8RE1TtcvWamhbExTXfsCYaF9PrGWHRLCwCsBH5JkeApUagvo4FZE3DJD3rn5hw8vaMBib2StKMJ77rJHt51jPt") - .complete_sync(None) - .unwrap() - .complete_signing().unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_min_output_with_datum() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - let signed_tx = tx_builder - .tx_in( - "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", - 3, - &[Asset::new_from_str("lovelace", "9891607895")], - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - ) - .tx_out( - "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", - &[], - ) - .tx_out_inline_datum_value(&WData::JSON( - json!({ - "constructor": 0, - "fields": [] - }) - .to_string(), - )) - .change_address("addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh") - .signing_key("51022b7e38be01d1cc581230e18030e6e1a3e949a1fdd2aeae5f5412154fe82b") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", signed_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[tokio::test] - async fn test_csl_tx_parser_round_trip() { - let utxo_1: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":0,\"txHash\":\"1a6157c0c9e170d716aee64b25384cad275770e2ef86df31eeebda4892980723\"},\"output\":{\"address\":\"addr_test1qrs3jlcsapdufgagzt35ug3nncwl26mlkcux49gs673sflmrjfm6y2eu7del3pprckzt4jaal9s7w9gq5kguqs5pf6fq542mmq\",\"amount\":[{\"quantity\":\"10000000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); - let utxo_2: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":5,\"txHash\":\"158a0bff150e9c6f68a14fdb1623c363f54e36cb22efc800911bffafa4e53442\"},\"output\":{\"address\":\"addr_test1qra9zdhfa8kteyr3mfe7adkf5nlh8jl5xcg9e7pcp5w9yhyf5tek6vpnha97yd5yw9pezm3wyd77fyrfs3ynftyg7njs5cfz2x\",\"amount\":[{\"quantity\":\"5000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); - - let utxos = vec![utxo_1, utxo_2]; - let tx_hex = "84a700d90102818258201a6157c0c9e170d716aee64b25384cad275770e2ef86df31eeebda4892980723000183a300581d70506245b8d10428549499ecfcd0435d5a0b9a3aac2c5bccc824441a7201821a001e8480a1581ceab3a1d125a3bf4cd941a6a0b5d7752af96fae7f5bcc641e8a0b6762a14001028201d818586ad8799fd8799fd8799f5041bfc7325343428683bbd0b94a4da41cd8799f581ce1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ffffd8799f581c7c87b6b5a0963af3eadb107da2ac4e1d34747a4df363858b649aa845ffffffa140a1401a00989680ff82581d70ba3efbd72650cbc7d5d7e6bede007cd3cb6730ba1972debf1c2c098f1a007a120082583900e1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ff639277a22b3cf373f88423c584bacbbdf961e71500a591c042814e921b0000000253704b3f021a0003024109a1581ceab3a1d125a3bf4cd941a6a0b5d7752af96fae7f5bcc641e8a0b6762a140010b5820d88d41dd788fcf7c3b1f15808e11b01d71e0413d57265ddb7fc5b5776ff16e720dd9010281825820158a0bff150e9c6f68a14fdb1623c363f54e36cb22efc800911bffafa4e53442050ed9010281581cfa5136e9e9ecbc9071da73eeb6c9a4ff73cbf436105cf8380d1c525ca207d901028158b558b30101009800aba2a6011e581cfa5136e9e9ecbc9071da73eeb6c9a4ff73cbf436105cf8380d1c525c00a6010746332d6d696e740048c8c8c8c88c88966002646464646464660020026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc030dd5180718061baa0072259800800c52844c96600266e3cdd71808001005c528c4cc00c00c00500d1808000a01c300c300d002300b001300b002300900130063754003149a26cac8028dd7000ab9a5573caae7d5d0905a182010082d87980821956861a0066ad1cf5f6"; - let mut tx_parser = TxParser::new(None); - let result = tx_parser.parse(tx_hex, &utxos).await; - - assert!(result.is_ok()); - let body = tx_parser.get_builder_body_without_change(); - - let mut new_tx_builder = TxBuilder::new_core(); - new_tx_builder.tx_builder_body = body.clone(); - - new_tx_builder.complete_sync(None).unwrap(); - - let tx_hex_round_trip = new_tx_builder.tx_hex(); - let decoded_by_csl_tx = csl::Transaction::from_hex(&tx_hex).unwrap(); - let decoded_by_csl_tx_after_round_trip = - csl::Transaction::from_hex(&tx_hex_round_trip).unwrap(); - assert_eq!(decoded_by_csl_tx, decoded_by_csl_tx_after_round_trip); - } - - #[test] - fn test_set_total_collateral() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .tx_in( - "fc1c806abc9981f4bee2ce259f61578c3341012f3d04f22e82e7e40c7e7e3c3c", - 3, - &[Asset::new_from_str("lovelace", "9692479606")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .change_address("addr_test1qqjcvv7huxlf9epjq49j4952pez8l4zyrm6c4wrf2vtcym4jg6fd5d54p0k5mqy46ph5z3r59tkhnhjvsxx53dq5rvdsnaeh3a") - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 6, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 7, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .set_total_collateral("5000000") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } - - #[test] - fn test_set_total_collateral_and_collateral_return_address() { - let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), - evaluator: None, - fetcher: None, - submitter: None, - params: None, - }); - - let unsigned_tx = tx_builder - .tx_in( - "fc1c806abc9981f4bee2ce259f61578c3341012f3d04f22e82e7e40c7e7e3c3c", - 3, - &[Asset::new_from_str("lovelace", "9692479606")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .change_address("addr_test1qqjcvv7huxlf9epjq49j4952pez8l4zyrm6c4wrf2vtcym4jg6fd5d54p0k5mqy46ph5z3r59tkhnhjvsxx53dq5rvdsnaeh3a") - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 6, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .tx_in_collateral( - "3fbdf2b0b4213855dd9b87f7c94a50cf352ba6edfdded85ecb22cf9ceb75f814", - 7, - &[Asset::new_from_str("lovelace", "10000000")], - "addr_test1vpw22xesfv0hnkfw4k5vtrz386tfgkxu6f7wfadug7prl7s6gt89x", - ) - .set_total_collateral("5000000") - .set_collateral_return_address("addr_test1qqjcvv7huxlf9epjq49j4952pez8l4zyrm6c4wrf2vtcym4jg6fd5d54p0k5mqy46ph5z3r59tkhnhjvsxx53dq5rvdsnaeh3a") - .complete_sync(None) - .unwrap() - .complete_signing() - .unwrap(); - - println!("{}", unsigned_tx); - assert!(tx_builder.serializer.tx_hex() != *""); - } -} diff --git a/packages/whisky/tests/pallas_integration_tests.rs b/packages/whisky/tests/pallas_integration_tests.rs index ad8df219..b0e06afa 100644 --- a/packages/whisky/tests/pallas_integration_tests.rs +++ b/packages/whisky/tests/pallas_integration_tests.rs @@ -824,31 +824,6 @@ mod int_tests { assert!(tx_builder.serializer.tx_hex() != *""); } - #[tokio::test] - async fn test_csl_tx_parser_round_trip() { - let utxo_1: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":0,\"txHash\":\"1a6157c0c9e170d716aee64b25384cad275770e2ef86df31eeebda4892980723\"},\"output\":{\"address\":\"addr_test1qrs3jlcsapdufgagzt35ug3nncwl26mlkcux49gs673sflmrjfm6y2eu7del3pprckzt4jaal9s7w9gq5kguqs5pf6fq542mmq\",\"amount\":[{\"quantity\":\"10000000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); - let utxo_2: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":5,\"txHash\":\"158a0bff150e9c6f68a14fdb1623c363f54e36cb22efc800911bffafa4e53442\"},\"output\":{\"address\":\"addr_test1qra9zdhfa8kteyr3mfe7adkf5nlh8jl5xcg9e7pcp5w9yhyf5tek6vpnha97yd5yw9pezm3wyd77fyrfs3ynftyg7njs5cfz2x\",\"amount\":[{\"quantity\":\"5000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); - - let utxos = vec![utxo_1, utxo_2]; - let tx_hex = "84a700d90102818258201a6157c0c9e170d716aee64b25384cad275770e2ef86df31eeebda4892980723000183a300581d70506245b8d10428549499ecfcd0435d5a0b9a3aac2c5bccc824441a7201821a001e8480a1581ceab3a1d125a3bf4cd941a6a0b5d7752af96fae7f5bcc641e8a0b6762a14001028201d818586ad8799fd8799fd8799f5041bfc7325343428683bbd0b94a4da41cd8799f581ce1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ffffd8799f581c7c87b6b5a0963af3eadb107da2ac4e1d34747a4df363858b649aa845ffffffa140a1401a00989680ff82581d70ba3efbd72650cbc7d5d7e6bede007cd3cb6730ba1972debf1c2c098f1a007a120082583900e1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ff639277a22b3cf373f88423c584bacbbdf961e71500a591c042814e921b0000000253704b3f021a0003024109a1581ceab3a1d125a3bf4cd941a6a0b5d7752af96fae7f5bcc641e8a0b6762a140010b5820d88d41dd788fcf7c3b1f15808e11b01d71e0413d57265ddb7fc5b5776ff16e720dd9010281825820158a0bff150e9c6f68a14fdb1623c363f54e36cb22efc800911bffafa4e53442050ed9010281581cfa5136e9e9ecbc9071da73eeb6c9a4ff73cbf436105cf8380d1c525ca207d901028158b558b30101009800aba2a6011e581cfa5136e9e9ecbc9071da73eeb6c9a4ff73cbf436105cf8380d1c525c00a6010746332d6d696e740048c8c8c8c88c88966002646464646464660020026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc030dd5180718061baa0072259800800c52844c96600266e3cdd71808001005c528c4cc00c00c00500d1808000a01c300c300d002300b001300b002300900130063754003149a26cac8028dd7000ab9a5573caae7d5d0905a182010082d87980821956861a0066ad1cf5f6"; - let mut tx_parser = TxParser::new(None); - let result = tx_parser.parse(tx_hex, &utxos).await; - - assert!(result.is_ok()); - let body = tx_parser.get_builder_body_without_change(); - - let mut new_tx_builder = TxBuilder::new_core(); - new_tx_builder.tx_builder_body = body.clone(); - - new_tx_builder.complete_sync(None).unwrap(); - - let tx_hex_round_trip = new_tx_builder.tx_hex(); - let decoded_by_csl_tx = csl::Transaction::from_hex(&tx_hex).unwrap(); - let decoded_by_csl_tx_after_round_trip = - csl::Transaction::from_hex(&tx_hex_round_trip).unwrap(); - assert_eq!(decoded_by_csl_tx, decoded_by_csl_tx_after_round_trip); - } - #[test] fn test_set_total_collateral() { let mut tx_builder = TxBuilder::new(TxBuilderParam { diff --git a/packages/whisky/tests/tx_builder.rs b/packages/whisky/tests/tx_builder.rs index ed6644a7..fb10e0ba 100644 --- a/packages/whisky/tests/tx_builder.rs +++ b/packages/whisky/tests/tx_builder.rs @@ -2,11 +2,12 @@ mod tx_builder_core_tests { use serde_json::{json, to_string}; use whisky::{data::*, *}; + use whisky_pallas::WhiskyPallas; #[test] fn test_tx_builder_tx_builder_core() { TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -17,7 +18,7 @@ mod tx_builder_core_tests { #[test] fn test_tx_in() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -35,7 +36,7 @@ mod tx_builder_core_tests { #[test] fn test_script_tx_in() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -70,7 +71,7 @@ mod tx_builder_core_tests { #[test] fn test_script_tx_in_with_datum_value() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -105,7 +106,7 @@ mod tx_builder_core_tests { #[test] fn test_script_tx_in_with_ref_script() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -146,7 +147,7 @@ mod tx_builder_core_tests { #[test] fn test_script_tx_in_with_script_value() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -183,7 +184,7 @@ mod tx_builder_core_tests { #[test] fn test_read_only_tx_in_reference() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -230,7 +231,7 @@ mod tx_builder_core_tests { #[test] fn test_mint() { let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, fetcher: None, submitter: None, @@ -269,7 +270,7 @@ mod tx_builder_core_tests { async fn test_wasm_request_with_offline_evaluation() { // This test builds a transaction from a WASM request JSON and triggers offline evaluation let mut tx_builder = TxBuilder::new(TxBuilderParam { - serializer: Box::new(WhiskyCSL::new(None).unwrap()), + serializer: Box::new(WhiskyPallas::new(None)), evaluator: None, // Uses OfflineTxEvaluator by default fetcher: None, submitter: None, diff --git a/packages/whisky/tests/tx_tester.rs b/packages/whisky/tests/tx_tester.rs index bbc03e6a..2a1dca51 100644 --- a/packages/whisky/tests/tx_tester.rs +++ b/packages/whisky/tests/tx_tester.rs @@ -4,8 +4,7 @@ mod tx_tester_tests { #[tokio::test] async fn test_tx_tester() { - let datum = "{\"constructor\":0,\"fields\":[{\"constructor\":0,\"fields\":[{\"constructor\":0,\"fields\":[{\"bytes\":\"41bfc7325343428683bbd0b94a4da41c\"},{\"constructor\":0,\"fields\":[{\"bytes\":\"e1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ff\"}]},{\"constructor\":0,\"fields\":[{\"bytes\":\"7c87b6b5a0963af3eadb107da2ac4e1d34747a4df363858b649aa845\"}]}]}]},{\"map\":[{\"k\":{\"bytes\":\"\"},\"v\":{\"map\":[{\"k\":{\"bytes\":\"\"},\"v\":{\"int\":10000000}}]}}]}]}"; - let datum_cbor = WData::JSON(datum.to_string()).to_cbor().unwrap(); + let datum_cbor = "d8799fd8799fd8799f5041bfc7325343428683bbd0b94a4da41cd8799f581ce1197f10e85bc4a3a812e34e22339e1df56b7fb6386a9510d7a304ffffd8799f581c7c87b6b5a0963af3eadb107da2ac4e1d34747a4df363858b649aa845ffffffa140a1401a00989680ff"; let utxo_1: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":0,\"txHash\":\"1a6157c0c9e170d716aee64b25384cad275770e2ef86df31eeebda4892980723\"},\"output\":{\"address\":\"addr_test1qrs3jlcsapdufgagzt35ug3nncwl26mlkcux49gs673sflmrjfm6y2eu7del3pprckzt4jaal9s7w9gq5kguqs5pf6fq542mmq\",\"amount\":[{\"quantity\":\"10000000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); let utxo_2: UTxO = serde_json::from_str("{\"input\":{\"outputIndex\":5,\"txHash\":\"158a0bff150e9c6f68a14fdb1623c363f54e36cb22efc800911bffafa4e53442\"},\"output\":{\"address\":\"addr_test1qra9zdhfa8kteyr3mfe7adkf5nlh8jl5xcg9e7pcp5w9yhyf5tek6vpnha97yd5yw9pezm3wyd77fyrfs3ynftyg7njs5cfz2x\",\"amount\":[{\"quantity\":\"5000000\",\"unit\":\"lovelace\"}],\"dataHash\":null,\"plutusData\":null,\"scriptHash\":null,\"scriptRef\":null}}").unwrap(); @@ -19,7 +18,7 @@ mod tx_tester_tests { let tx_parser = result.unwrap(); let mut tx_tester = tx_parser.to_tester(); - + println!("TxTesterBody: {:?}", tx_tester.tx_body); tx_tester .inputs_at("addr_test1qrs3jlcsapdufgagzt35ug3nncwl26mlkcux49gs673sflmrjfm6y2eu7del3pprckzt4jaal9s7w9gq5kguqs5pf6fq542mmq") .inputs_value(Value::from_asset(&Asset::new_from_str("lovelace", "10000000000"))) From 585a7fd2ba7744adc08972645073c871afa82433 Mon Sep 17 00:00:00 2001 From: SIDANPillow Date: Sun, 15 Mar 2026 22:05:11 +0800 Subject: [PATCH 2/5] add milestone 5 documentation and contribution materials --- CHANGELOG.md | 73 +++++++++++++++++++++++++++++++++ CONTRIBUTING => CONTRIBUTING.md | 0 MAINTAINERS.md | 26 ++++++++++++ SECURITY.md | 40 ++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 CHANGELOG.md rename CONTRIBUTING => CONTRIBUTING.md (100%) create mode 100644 MAINTAINERS.md create mode 100644 SECURITY.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3c9dc5bb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +All notable changes to the Whisky project under the Catalyst proposal will be documented in this file. + +## [v1.0.28-beta.1] - 2026-03-15 — Milestone 5: Documentation and Closeout Report + +### Added + +- SECURITY.md — security policy and responsible disclosure process +- MAINTAINERS.md — standalone maintainer list and responsibilities +- CHANGELOG.md — project changelog + +### Changed + +- Renamed CONTRIBUTING to CONTRIBUTING.md + +## [v1.0.27-beta.1] - 2026-02-26 — Milestone 4: Full Pallas Migration + +### Changed + +- Whisky main package fully migrated to Pallas over CSL + +### Added + +- Pallas evaluation utility +- Address utilities (address utils, script to address) +- Time convertor utility +- Check required signatures +- Extract UTXOs utility +- Data from_cbor support +- Plutus data from JSON +- serialize_address_obj utility +- MIGRATION.md — CSL to Pallas migration guide + +### Fixed + +- Redeemer duplicate add +- JSON round trip +- Send + Sync for TxBuildable trait + +## [v1.0.24] - 2026-01-22 — Milestone 3: Transaction Parser + +### Added + +- Transaction parser: parse CBOR hex into editable TxBuilderBody (whisky-pallas) +- Parse all tx components: inputs, outputs, mints, withdrawals, certs, metadata, validity range, votes, collaterals, reference inputs, required signers +- Transaction edit support and tests +- CI: add whisky-pallas to publish workflow + +## [v1.0.18-beta.1] - 2026-01-05 — Milestone 2: Transaction Builder with Pallas + +### Added + +- Pallas as alternative serialization library alongside CSL +- Full tx builder: inputs, outputs, mints, withdrawals, certs, collaterals, voting, required signers, witness set +- Transaction balancing, fee calculation, script data hash +- Feature flag support +- Cipher decrypt with salt +- Integration tests for Pallas +- End-to-end WhiskyPallas tx build documentation in README + +### Breaking + +- TxBuilderParam requires `serializer: Box` field + +## [v1.0.17] - 2025-12-17 — Milestone 1: Preparation and Organization Setup + +### Added + +- ConstrEnum wrapper on enum +- Maintainer list and CODE_OF_CONDUCT.md +- Discord server invite link +- Pallas transaction type prototyping diff --git a/CONTRIBUTING b/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING rename to CONTRIBUTING.md diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000..0cd876f0 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,26 @@ +# Maintainers + +This document lists the maintainers of the Whisky project. + +## Current Maintainers + +| Name | GitHub | Role | +|------|--------|------| +| Hinson Wong | [@HinsonSIDAN](https://github.com/HinsonSIDAN) | Maintainer | +| Tsz Wai | [@twwu123](https://github.com/twwu123) | Maintainer | +| Ken Lau | [@kenlau666](https://github.com/kenlau666) | Maintainer | +| Anson Chui | [@AnsonSIDAN](https://github.com/AnsonSIDAN) | Project Manager | + +## Responsibilities + +Maintainers are responsible for: + +- Reviewing and merging pull requests +- Triaging issues and bug reports +- Managing releases and versioning +- Enforcing the [Code of Conduct](CODE_OF_CONDUCT.md) +- Responding to [security reports](SECURITY.md) + +## Becoming a Maintainer + +If you are interested in becoming a maintainer, please reach out to any of the current maintainers. Active contributors who demonstrate a strong understanding of the codebase and a commitment to the project may be invited to join the maintainer team. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..37ebb41f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,40 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|---------|--------------------| +| 1.0.x | :white_check_mark: | +| < 1.0 | :x: | + +## Reporting a Vulnerability + +If you discover a security vulnerability in Whisky, please report it responsibly. **Do not open a public GitHub issue.** + +Instead, please contact one of the maintainers directly: + +- Hinson Wong ([@HinsonSIDAN](https://github.com/HinsonSIDAN)) +- Tsz Wai ([@twwu123](https://github.com/twwu123)) +- Ken Lau ([@kenlau666](https://github.com/kenlau666)) +- Anson Chui ([@AnsonSIDAN](https://github.com/AnsonSIDAN)) + +You can also reach us via our [Discord server](https://discord.gg/prJvB6b6p4). + +## What to Include + +When reporting a vulnerability, please include: + +- A description of the vulnerability +- Steps to reproduce the issue +- The potential impact +- Any suggested fixes (if applicable) + +## Response Timeline + +- We will acknowledge receipt of your report within **48 hours**. +- We will provide an initial assessment within **7 days**. +- We will work with you to understand and resolve the issue, and coordinate disclosure. + +## Disclosure Policy + +We follow a coordinated disclosure process. Please allow us reasonable time to address the vulnerability before making any public disclosure. From 7f7cfb4e986b81e6fc110bf5cc3ef1bcc4459d57 Mon Sep 17 00:00:00 2001 From: SIDANPillow Date: Sun, 15 Mar 2026 22:10:16 +0800 Subject: [PATCH 3/5] fix docs build and add link-check CI --- .github/workflows/rust-build-test.yml | 1 + .github/workflows/static.yml | 5 +++++ .lychee.toml | 23 +++++++++++++++++++++++ package.json | 2 +- packages/whisky/Cargo.toml | 24 +++++++----------------- packages/whisky/src/lib.rs | 15 +++------------ 6 files changed, 40 insertions(+), 30 deletions(-) create mode 100644 .lychee.toml diff --git a/.github/workflows/rust-build-test.yml b/.github/workflows/rust-build-test.yml index 5d5eb214..aed0bdf1 100644 --- a/.github/workflows/rust-build-test.yml +++ b/.github/workflows/rust-build-test.yml @@ -9,6 +9,7 @@ on: pull_request: branches: - master + - feature/pallas-migration jobs: build-test: diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 50c53332..2bf68b6d 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -5,6 +5,7 @@ on: push: branches: - master + - add-docs-for-m5 # temporary — remove after verification # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -36,6 +37,10 @@ jobs: - name: Build docs run: | npm run rust:doc + - name: Link check + uses: lycheeverse/lychee-action@v2 + with: + args: --no-progress --config .lychee.toml ./docs - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/.lychee.toml b/.lychee.toml new file mode 100644 index 00000000..c71990f1 --- /dev/null +++ b/.lychee.toml @@ -0,0 +1,23 @@ +# Lychee link checker configuration +# https://lychee.cli.rs/ + +# Exclude known false positives +exclude = [ + # Font license files reference external sites that may timeout + "adobe\\.com", + "nhncorp\\.com", + # Auto-generated tracing dispatcher links + "dispatcher#", + # Deprecated external documentation link + "meshjs\\.dev/apis/transaction/builderExample", + # Internal cross-references to whisky_csl/whisky_js docs not generated in this build + "whisky_csl/builder/trait\\.ITxBuilderCore", + "whisky_csl/core/builder/fn\\.js_serialize_tx_body", + "whisky_js/builder/trait\\.ITxBuilderCore", + "whisky_js/core/builder/fn\\.js_serialize_tx_body", +] + +# Exclude generated static files from Rust docs +exclude_path = [ + "docs/static.files", +] diff --git a/package.json b/package.json index 1baa2af8..050e8d90 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "js:publish-asm": "npm run core:build-asm && npm run js:prepublish && node ./scripts/publish-helper -asmjs && cd publish && npm publish --access public", "js:ts-json-gen": "cd packages/whisky-js/json-gen && cargo +stable run && cd ../../.. && node ./scripts/run-json2ts.js && node ./scripts/json-ts-types.js", "rust:doc-clear": "rm -rf ./docs && mkdir docs && echo '' > docs/index.html", - "rust:doc": "npm run rust:doc-clear && RUSTDOCFLAGS=\"--cfg docsrs\" cargo doc --workspace --manifest-path packages/Cargo.toml --no-deps && cp -r ./packages/target/doc/* ./docs", + "rust:doc": "npm run rust:doc-clear && RUSTDOCFLAGS=\"--cfg docsrs\" cargo doc --manifest-path packages/Cargo.toml --no-deps -p whisky -p whisky-pallas -p whisky-common -p whisky-macros -p whisky-provider -p whisky-wallet -p whisky-csl && cp -r ./packages/target/doc/* ./docs", "start-doc": "npm run rust:doc && npx http-server ./docs", "sh:bump-version": "./scripts/bump-version.sh", "sh:run-example": "./scripts/run-example.sh" diff --git a/packages/whisky/Cargo.toml b/packages/whisky/Cargo.toml index cd692d65..7e0928b8 100644 --- a/packages/whisky/Cargo.toml +++ b/packages/whisky/Cargo.toml @@ -12,11 +12,10 @@ crate-type = ["cdylib", "rlib"] [features] default = ["full"] -full = ["wallet", "provider", "pallas"] - +full = ["wallet", "provider"] # Wallet functionality -wallet = ["pallas", "dep:whisky-wallet"] +wallet = ["dep:whisky-wallet"] # Provider/API functionality provider = [ @@ -27,35 +26,28 @@ provider = [ "dep:futures", ] -# Pallas integration -pallas = ["dep:whisky-pallas", - "dep:pallas-codec", - "dep:pallas-primitives", - "dep:pallas-traverse", - "dep:uplc" - ] - [dependencies] # Always required whisky-common = { version = "1.0.25", path = "../whisky-common" } whisky-macros = { version = "1.0.25", path = "../whisky-macros" } +whisky-pallas = { version = "1.0.0", path = "../whisky-pallas" } hex = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.136" async-trait = "0.1.79" +uplc = { workspace = true } +pallas-codec = { workspace = true, features = ["num-bigint"] } +pallas-primitives = { workspace = true } +pallas-traverse = { workspace = true } # Optional - CSL feature whisky-csl = { version = "1.0.25", path = "../whisky-csl", optional = true } getrandom = { version = "0.2", features = ["js"], optional = true } cryptoxide = { version = "0.4.4", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } -uplc = { workspace = true, optional = true } wasm-bindgen = { version = "=0.2.92", features = ["serde-serialize"], optional = true } rand_os = { version = "0.1", optional = true } noop_proc_macro = { version = "0.3.0", optional = true } -pallas-codec = { workspace = true, features = ["num-bigint"], optional = true } -pallas-primitives = { workspace = true, optional = true } -pallas-traverse = { workspace = true, optional = true } # Optional - wallet feature whisky-wallet = { version = "1.0.25", path = "../whisky-wallet", optional = true } @@ -67,8 +59,6 @@ reqwest = { version = "0.12.5", optional = true } tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"], optional = true } futures = { version = "0.3.31", optional = true } -# Optional - pallas feature -whisky-pallas = { version = "1.0.0", path = "../whisky-pallas", optional = true } [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/packages/whisky/src/lib.rs b/packages/whisky/src/lib.rs index ad520f6d..81348abe 100644 --- a/packages/whisky/src/lib.rs +++ b/packages/whisky/src/lib.rs @@ -72,18 +72,14 @@ extern crate self as whisky; // Data module is always available (uses whisky_common + whisky_macros) pub mod data; -// pallas-dependent modules -#[cfg(feature = "pallas")] +// Pallas-based modules (always available after full migration) pub mod builder; -#[cfg(feature = "pallas")] pub mod parser; -#[cfg(feature = "pallas")] pub mod transaction; -#[cfg(feature = "pallas")] pub mod utils; -// Services require both pallas and provider -#[cfg(all(feature = "pallas", feature = "provider"))] +// Services require provider +#[cfg(feature = "provider")] pub mod services; // Always re-export common and macros @@ -91,15 +87,10 @@ pub use whisky_common::*; pub use whisky_macros::*; // Pallas re-exports -#[cfg(feature = "pallas")] pub use builder::*; -#[cfg(feature = "pallas")] pub use parser::*; -#[cfg(feature = "pallas")] pub use transaction::*; -#[cfg(feature = "pallas")] pub use utils::*; -#[cfg(feature = "pallas")] pub use whisky_pallas::*; // Wallet re-exports From 085bf73c588022f2d52e46376bfcd05c200f9ed9 Mon Sep 17 00:00:00 2001 From: SIDANPillow Date: Sun, 15 Mar 2026 22:26:33 +0800 Subject: [PATCH 4/5] bump version to 1.0.28-beta.1 --- packages/Cargo.lock | 18 +++++++++--------- packages/Cargo.toml | 2 +- packages/whisky-common/Cargo.toml | 4 ++-- packages/whisky-csl/Cargo.toml | 4 ++-- packages/whisky-examples/Cargo.toml | 4 ++-- packages/whisky-js/Cargo.toml | 6 +++--- packages/whisky-macros/Cargo.toml | 2 +- packages/whisky-pallas/Cargo.toml | 4 ++-- packages/whisky-provider/Cargo.toml | 6 +++--- packages/whisky-wallet/Cargo.toml | 6 +++--- packages/whisky/Cargo.toml | 14 +++++++------- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/Cargo.lock b/packages/Cargo.lock index c298f904..d7f5b45a 100644 --- a/packages/Cargo.lock +++ b/packages/Cargo.lock @@ -4376,7 +4376,7 @@ dependencies = [ [[package]] name = "whisky" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "async-trait", "cryptoxide", @@ -4407,7 +4407,7 @@ dependencies = [ [[package]] name = "whisky-common" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "async-trait", "hex", @@ -4420,7 +4420,7 @@ dependencies = [ [[package]] name = "whisky-csl" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "cardano-serialization-lib", "cquisitor-lib", @@ -4446,7 +4446,7 @@ dependencies = [ [[package]] name = "whisky-examples" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "actix-cors", "actix-web", @@ -4456,7 +4456,7 @@ dependencies = [ [[package]] name = "whisky-js" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "cquisitor-lib", "js-sys", @@ -4474,7 +4474,7 @@ dependencies = [ [[package]] name = "whisky-macros" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "proc-macro2", "quote", @@ -4483,7 +4483,7 @@ dependencies = [ [[package]] name = "whisky-pallas" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "bech32 0.11.1", "hex", @@ -4501,7 +4501,7 @@ dependencies = [ [[package]] name = "whisky-provider" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "async-trait", "dotenv", @@ -4519,7 +4519,7 @@ dependencies = [ [[package]] name = "whisky-wallet" -version = "1.0.25" +version = "1.0.28-beta.1" dependencies = [ "aes-gcm", "base64 0.22.1", diff --git a/packages/Cargo.toml b/packages/Cargo.toml index 007a7d02..9414509e 100644 --- a/packages/Cargo.toml +++ b/packages/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -version = "1.0.25" +version = "1.0.28-beta.1" resolver = "2" members = [ "whisky-common", diff --git a/packages/whisky-common/Cargo.toml b/packages/whisky-common/Cargo.toml index 2f3b76bc..5232410c 100644 --- a/packages/whisky-common/Cargo.toml +++ b/packages/whisky-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-common" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" @@ -14,4 +14,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.136" schemars = "0.8.8" async-trait = "0.1.79" -whisky-macros = { version = "1.0.25", path = "../whisky-macros" } \ No newline at end of file +whisky-macros = { version = "1.0.28-beta.1", path = "../whisky-macros" } \ No newline at end of file diff --git a/packages/whisky-csl/Cargo.toml b/packages/whisky-csl/Cargo.toml index 73236d28..2556c497 100644 --- a/packages/whisky-csl/Cargo.toml +++ b/packages/whisky-csl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-csl" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "Wrapper around the cardano-serialization-lib for easier transaction building, heavily inspired by cardano-cli APIs" @@ -19,7 +19,7 @@ serde_json = "1.0" cryptoxide = "0.4.4" serde-wasm-bindgen = "0.6.5" schemars = "0.8.8" -whisky-common = { version = "1.0.25", path = "../whisky-common" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } # non-wasm [target.'cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))'.dependencies] diff --git a/packages/whisky-examples/Cargo.toml b/packages/whisky-examples/Cargo.toml index 0f34e5e8..a75d6092 100644 --- a/packages/whisky-examples/Cargo.toml +++ b/packages/whisky-examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-examples" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" @@ -15,4 +15,4 @@ path = "src/server.rs" actix-cors = "0.7.0" actix-web = "4.9.0" serde = "1.0.209" -whisky = { version = "=1.0.25", path = "../whisky" } +whisky = { version = "=1.0.28-beta.1", path = "../whisky" } diff --git a/packages/whisky-js/Cargo.toml b/packages/whisky-js/Cargo.toml index 15b09374..2afdb526 100644 --- a/packages/whisky-js/Cargo.toml +++ b/packages/whisky-js/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-js" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "Wrapper around the cardano-serialization-lib for easier transaction building, heavily inspired by cardano-cli APIs" @@ -13,8 +13,8 @@ crate-type = ["cdylib", "rlib"] [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -whisky-csl = { version = "1.0.25", path = "../whisky-csl" } -whisky-common = { version = "1.0.25", path = "../whisky-common" } +whisky-csl = { version = "1.0.28-beta.1", path = "../whisky-csl" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } # non-wasm [target.'cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))'.dependencies] diff --git a/packages/whisky-macros/Cargo.toml b/packages/whisky-macros/Cargo.toml index 4c9426e8..58aa4157 100644 --- a/packages/whisky-macros/Cargo.toml +++ b/packages/whisky-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-macros" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" diff --git a/packages/whisky-pallas/Cargo.toml b/packages/whisky-pallas/Cargo.toml index 692add49..64d56ac4 100644 --- a/packages/whisky-pallas/Cargo.toml +++ b/packages/whisky-pallas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-pallas" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "Wrapper around the TxPipe's Pallas for easier transaction building, heavily inspired by cardano-cli APIs" @@ -15,7 +15,7 @@ serde = "1.0.219" ouroboros = "0.18" bech32 = "0.11.1" serde_json = "1.0" -whisky-common = { version = "1.0.25", path = "../whisky-common" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } uplc = "1.1.10" # Need pallas 0.31.0 for uplc compatibility pallas-primitives = "0.31.0" diff --git a/packages/whisky-provider/Cargo.toml b/packages/whisky-provider/Cargo.toml index 153ae1b2..f1d92498 100644 --- a/packages/whisky-provider/Cargo.toml +++ b/packages/whisky-provider/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-provider" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" @@ -17,8 +17,8 @@ serde_json = "1.0" async-trait = "0.1.79" uplc.workspace = true maestro-rust-sdk = "1.1.3" -whisky-csl = { version = "1.0.25", path = "../whisky-csl" } -whisky-common = { version = "1.0.25", path = "../whisky-common" } +whisky-csl = { version = "1.0.28-beta.1", path = "../whisky-csl" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } reqwest = "0.12.5" futures = "0.3.31" diff --git a/packages/whisky-wallet/Cargo.toml b/packages/whisky-wallet/Cargo.toml index 24f77e44..482056b6 100644 --- a/packages/whisky-wallet/Cargo.toml +++ b/packages/whisky-wallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky-wallet" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" @@ -15,8 +15,8 @@ hex = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.136" uplc.workspace = true -whisky-csl = { version = "1.0.25", path = "../whisky-csl" } -whisky-common = { version = "1.0.25", path = "../whisky-common" } +whisky-csl = { version = "1.0.28-beta.1", path = "../whisky-csl" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } tiny-bip39 = "2.0.0" rand = "0.8" aes-gcm = "0.10.3" diff --git a/packages/whisky/Cargo.toml b/packages/whisky/Cargo.toml index 7e0928b8..eb3c6ca7 100644 --- a/packages/whisky/Cargo.toml +++ b/packages/whisky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "whisky" -version = "1.0.25" +version = "1.0.28-beta.1" edition = "2021" license = "Apache-2.0" description = "The Cardano Rust SDK, inspired by MeshJS" @@ -28,9 +28,9 @@ provider = [ [dependencies] # Always required -whisky-common = { version = "1.0.25", path = "../whisky-common" } -whisky-macros = { version = "1.0.25", path = "../whisky-macros" } -whisky-pallas = { version = "1.0.0", path = "../whisky-pallas" } +whisky-common = { version = "1.0.28-beta.1", path = "../whisky-common" } +whisky-macros = { version = "1.0.28-beta.1", path = "../whisky-macros" } +whisky-pallas = { version = "1.0.28-beta.1", path = "../whisky-pallas" } hex = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.136" @@ -41,7 +41,7 @@ pallas-primitives = { workspace = true } pallas-traverse = { workspace = true } # Optional - CSL feature -whisky-csl = { version = "1.0.25", path = "../whisky-csl", optional = true } +whisky-csl = { version = "1.0.28-beta.1", path = "../whisky-csl", optional = true } getrandom = { version = "0.2", features = ["js"], optional = true } cryptoxide = { version = "0.4.4", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } @@ -50,10 +50,10 @@ rand_os = { version = "0.1", optional = true } noop_proc_macro = { version = "0.3.0", optional = true } # Optional - wallet feature -whisky-wallet = { version = "1.0.25", path = "../whisky-wallet", optional = true } +whisky-wallet = { version = "1.0.28-beta.1", path = "../whisky-wallet", optional = true } # Optional - provider feature -whisky-provider = { version = "1.0.25", path = "../whisky-provider", optional = true } +whisky-provider = { version = "1.0.28-beta.1", path = "../whisky-provider", optional = true } maestro-rust-sdk = { version = "1.1.3", optional = true } reqwest = { version = "0.12.5", optional = true } tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"], optional = true } From 9c0fb646f9e4c3ee2327f707b2c18d1b68ff8884 Mon Sep 17 00:00:00 2001 From: SIDANPillow Date: Tue, 17 Mar 2026 18:24:32 +0800 Subject: [PATCH 5/5] add mdBook for m5 --- .github/workflows/static.yml | 22 ++- .gitignore | 5 +- .lychee.toml | 15 +- README.md | 4 +- docs/guide/book.toml | 13 ++ docs/guide/src/SUMMARY.md | 26 +++ .../guide/src/getting-started/installation.md | 60 +++++++ docs/guide/src/getting-started/quickstart.md | 86 ++++++++++ docs/guide/src/guides/dependency-injection.md | 64 ++++++++ docs/guide/src/guides/di/providers.md | 138 ++++++++++++++++ docs/guide/src/guides/di/serializers.md | 120 ++++++++++++++ .../src/guides/migration-csl-to-pallas.md | 142 ++++++++++++++++ docs/guide/src/guides/tx-builder.md | 80 +++++++++ docs/guide/src/guides/tx-builder/minting.md | 155 ++++++++++++++++++ docs/guide/src/guides/tx-builder/plutus.md | 129 +++++++++++++++ docs/guide/src/guides/tx-builder/simple.md | 114 +++++++++++++ docs/guide/src/guides/tx-builder/staking.md | 107 ++++++++++++ docs/guide/src/guides/tx-parser.md | 110 +++++++++++++ docs/guide/src/introduction.md | 42 +++++ docs/guide/src/reference/api-docs.md | 31 ++++ docs/guide/src/reference/examples-server.md | 67 ++++++++ package.json | 7 +- 22 files changed, 1526 insertions(+), 11 deletions(-) create mode 100644 docs/guide/book.toml create mode 100644 docs/guide/src/SUMMARY.md create mode 100644 docs/guide/src/getting-started/installation.md create mode 100644 docs/guide/src/getting-started/quickstart.md create mode 100644 docs/guide/src/guides/dependency-injection.md create mode 100644 docs/guide/src/guides/di/providers.md create mode 100644 docs/guide/src/guides/di/serializers.md create mode 100644 docs/guide/src/guides/migration-csl-to-pallas.md create mode 100644 docs/guide/src/guides/tx-builder.md create mode 100644 docs/guide/src/guides/tx-builder/minting.md create mode 100644 docs/guide/src/guides/tx-builder/plutus.md create mode 100644 docs/guide/src/guides/tx-builder/simple.md create mode 100644 docs/guide/src/guides/tx-builder/staking.md create mode 100644 docs/guide/src/guides/tx-parser.md create mode 100644 docs/guide/src/introduction.md create mode 100644 docs/guide/src/reference/api-docs.md create mode 100644 docs/guide/src/reference/examples-server.md diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 2bf68b6d..92e4d268 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -5,7 +5,7 @@ on: push: branches: - master - - add-docs-for-m5 # temporary — remove after verification + - add-docs-for-m5 # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -34,18 +34,30 @@ jobs: uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v5 - - name: Build docs + - name: Install mdBook + uses: peaceiris/actions-mdbook@v2 + with: + mdbook-version: 'latest' + - name: Build API reference (cargo doc) run: | npm run rust:doc + - name: Build mdBook guide + run: | + mdbook build docs/guide + - name: Assemble site + run: | + mkdir -p _site + cp -r docs/guide/book/* _site/ + cp -r docs/ _site/api/ + rm -rf _site/api/guide - name: Link check uses: lycheeverse/lychee-action@v2 with: - args: --no-progress --config .lychee.toml ./docs + args: --no-progress --config .lychee.toml ./_site - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - # Upload entire repository - path: "./docs" + path: "./_site" - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 63ce4e3f..9a84e1ca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,12 @@ target */**/schemas */**/output rust/pkg -docs +docs/* +!docs/guide/ +docs/guide/book/ */**/.env publish +_site .claude \ No newline at end of file diff --git a/.lychee.toml b/.lychee.toml index c71990f1..0ad06d8c 100644 --- a/.lychee.toml +++ b/.lychee.toml @@ -15,9 +15,20 @@ exclude = [ "whisky_csl/core/builder/fn\\.js_serialize_tx_body", "whisky_js/builder/trait\\.ITxBuilderCore", "whisky_js/core/builder/fn\\.js_serialize_tx_body", + # mdBook relative ../api/ links resolve correctly over HTTP but not in lychee local file mode + "api/whisky/index\\.html", + "api/whisky_common/index\\.html", + "api/whisky_pallas/index\\.html", + "api/whisky_csl/index\\.html", + "api/whisky_provider/index\\.html", + "api/whisky_wallet/index\\.html", + "api/whisky_macros/index\\.html", ] -# Exclude generated static files from Rust docs +# Exclude generated static files from Rust docs and mdBook theme exclude_path = [ - "docs/static.files", + "_site/static.files", + "_site/api/static.files", + # mdBook 404 page has a root-relative "/" link that lychee can't resolve locally + "_site/404.html", ] diff --git a/README.md b/README.md index dcb92037..ea6d0c14 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ [![Crates.io](https://img.shields.io/crates/v/whisky?style=for-the-badge)](https://crates.io/crates/whisky) [![NPM](https://img.shields.io/npm/v/%40sidan-lab%2Fwhisky-js-nodejs?style=for-the-badge)](https://www.npmjs.com/package/@sidan-lab/whisky-js-nodejs) +**[Read the Guide](https://sidan-lab.github.io/whisky/)** | **[API Reference](https://sidan-lab.github.io/whisky/api/whisky/index.html)** +


@@ -308,7 +310,7 @@ Make sure llvm is installed ## APIs -Please refer to the [hosted documentation](https://sidan-lab.github.io/whisky/whisky/index.html) for the list of endpoints. +Please refer to the [Guide](https://sidan-lab.github.io/whisky/) and [API Reference](https://sidan-lab.github.io/whisky/api/whisky/index.html) for full documentation. ![Alt](https://repobeats.axiom.co/api/embed/2e35716a9dd3250972c06ca2b4c7f1846ef7c51e.svg "Repobeats analytics image") diff --git a/docs/guide/book.toml b/docs/guide/book.toml new file mode 100644 index 00000000..5436149b --- /dev/null +++ b/docs/guide/book.toml @@ -0,0 +1,13 @@ +[book] +authors = ["SIDAN Lab"] +language = "en" +src = "src" +title = "Whisky - Cardano Rust SDK Guide" + +[build] +build-dir = "book" + +[output.html] +git-repository-url = "https://github.com/sidan-lab/whisky" +edit-url-template = "https://github.com/sidan-lab/whisky/edit/master/docs/guide/src/{path}" +no-section-label = false diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md new file mode 100644 index 00000000..66e2288f --- /dev/null +++ b/docs/guide/src/SUMMARY.md @@ -0,0 +1,26 @@ +# Summary + +[Introduction](./introduction.md) + +# Getting Started + +- [Installation](./getting-started/installation.md) +- [Quick Start](./getting-started/quickstart.md) + +# Core Guides + +- [Transaction Builder](./guides/tx-builder.md) + - [Simple Transactions](./guides/tx-builder/simple.md) + - [Plutus Script Transactions](./guides/tx-builder/plutus.md) + - [Minting](./guides/tx-builder/minting.md) + - [Staking & Governance](./guides/tx-builder/staking.md) +- [Transaction Parser](./guides/tx-parser.md) +- [Dependency Injection](./guides/dependency-injection.md) + - [Serializer Backends](./guides/di/serializers.md) + - [Providers](./guides/di/providers.md) +- [Migration: CSL to Pallas](./guides/migration-csl-to-pallas.md) + +# Reference + +- [Examples Server](./reference/examples-server.md) +- [API Documentation](./reference/api-docs.md) diff --git a/docs/guide/src/getting-started/installation.md b/docs/guide/src/getting-started/installation.md new file mode 100644 index 00000000..584205a4 --- /dev/null +++ b/docs/guide/src/getting-started/installation.md @@ -0,0 +1,60 @@ +# Installation + +## Rust Library + +Add whisky to your project: + +```sh +cargo add whisky +``` + +Or add it directly to your `Cargo.toml`: + +```toml +[dependencies] +whisky = "1.0.28-beta.1" +``` + +### Feature Flags + +By default, all features are enabled (`full`). You can selectively enable only what you need: + +```toml +# Full (default) — includes wallet + provider +whisky = "1.0.28-beta.1" + +# Just common types (minimal, no serializer backends) +whisky = { version = "1.0.28-beta.1", default-features = false } + +# Wallet only (signing + key management) +whisky = { version = "1.0.28-beta.1", default-features = false, features = ["wallet"] } + +# Provider only (Blockfrost/Maestro integrations) +whisky = { version = "1.0.28-beta.1", default-features = false, features = ["provider"] } +``` + +| Feature | Includes | Use Case | +|---------|----------|----------| +| `full` (default) | wallet + provider | Full DApp backend | +| `wallet` | Signing, key encryption | Transaction signing only | +| `provider` | Blockfrost, Maestro | Blockchain data fetching | + +## JS / TS WASM Library + +For JavaScript or TypeScript projects, whisky is available as a WASM package: + +```sh +# For Node.js +yarn add @sidan-lab/whisky-js-nodejs + +# For browser +yarn add @sidan-lab/whisky-js-browser +``` + +## Prerequisites + +For building from source, make sure LLVM is installed on your system: + +- **macOS**: `brew install llvm` +- **Ubuntu/Debian**: `apt install llvm-dev libclang-dev` +- **Windows**: Install via [LLVM releases](https://releases.llvm.org/) diff --git a/docs/guide/src/getting-started/quickstart.md b/docs/guide/src/getting-started/quickstart.md new file mode 100644 index 00000000..c1d9c5e1 --- /dev/null +++ b/docs/guide/src/getting-started/quickstart.md @@ -0,0 +1,86 @@ +# Quick Start + +This guide shows you how to build your first Cardano transaction with whisky. + +## Your First Transaction + +The simplest transaction sends lovelace from one address to another. Here's the complete example: + +```rust,ignore +use whisky::*; + +pub fn send_lovelace( + recipient_address: &str, + my_address: &str, + inputs: &[UTxO], +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + tx_builder + .tx_out( + recipient_address, + &[Asset::new_from_str("lovelace", "1000000")], + ) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; + + Ok(tx_builder.tx_hex()) +} +``` + +Let's break this down: + +1. **`TxBuilder::new_core()`** — Creates a new transaction builder with the default Pallas serializer +2. **`.tx_out(address, assets)`** — Adds an output sending 1 ADA (1,000,000 lovelace) to the recipient +3. **`.change_address(address)`** — Sets where leftover funds go after paying fees +4. **`.select_utxos_from(inputs, threshold)`** — Automatically selects UTxOs from the provided set to cover outputs + fees. The threshold (5,000,000 lovelace) is extra headroom for fees and min UTxO +5. **`.complete_sync(None)`** — Balances the transaction, calculates fees, and serializes to CBOR +6. **`.tx_hex()`** — Returns the unsigned transaction as a hex-encoded CBOR string + +## Sync vs Async + +Whisky offers both synchronous and asynchronous completion: + +```rust,ignore +// Synchronous — no script evaluation, no provider calls +tx_builder.complete_sync(None)?; + +// Asynchronous — evaluates Plutus scripts, can fetch data from providers +tx_builder.complete(None).await?; +``` + +Use `complete_sync` for simple transactions without scripts. Use `complete` (async) when your transaction includes Plutus scripts that need execution unit evaluation. + +## Signing + +After building, sign the transaction with a private key: + +```rust,ignore +let signed_tx = tx_builder + .signing_key("your_signing_key_hex") + .complete_sync(None)? + .complete_signing()?; +``` + +The `signed_tx` string is ready for submission to the Cardano network. + +## Running the Tests + +Whisky includes comprehensive integration tests you can run to see these patterns in action: + +```sh +# Run all tests +cargo test + +# Run Pallas integration tests specifically +cargo test --package whisky --test pallas_integration_tests + +# Run a specific test with output +cargo test --package whisky --test pallas_integration_tests test_simple_spend -- --nocapture +``` + +## Next Steps + +- [Transaction Builder](../guides/tx-builder.md) — Explore all transaction building patterns +- [Transaction Parser](../guides/tx-parser.md) — Parse and edit existing transactions +- [Dependency Injection](../guides/dependency-injection.md) — Customize serializers and providers diff --git a/docs/guide/src/guides/dependency-injection.md b/docs/guide/src/guides/dependency-injection.md new file mode 100644 index 00000000..d6022d63 --- /dev/null +++ b/docs/guide/src/guides/dependency-injection.md @@ -0,0 +1,64 @@ +# Dependency Injection + +Whisky uses a trait-based dependency injection pattern that lets you swap out core components: the serializer backend, blockchain data fetcher, script evaluator, and transaction submitter. + +## TxBuilderParam + +When creating a `TxBuilder`, you can inject dependencies via `TxBuilderParam`: + +```rust,ignore +use whisky::*; +use whisky_pallas::WhiskyPallas; + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), // Required + evaluator: None, // Optional — defaults to OfflineTxEvaluator + fetcher: None, // Optional — for blockchain data + submitter: None, // Optional — for tx submission + params: None, // Optional — protocol parameters +}); +``` + +Or use the convenience constructor with all defaults: + +```rust,ignore +let mut tx_builder = TxBuilder::new_core(); +// Equivalent to: WhiskyPallas serializer, offline evaluator, no fetcher/submitter +``` + +## The TxBuilder Struct + +```rust,ignore +pub struct TxBuilder { + pub serializer: Box, // Serializes tx body to CBOR + pub fetcher: Option>, // Fetches blockchain data + pub evaluator: Option>, // Evaluates Plutus scripts + pub submitter: Option>, // Submits transactions + pub protocol_params: Option, // Network parameters + // ... internal state fields +} +``` + +## Why Dependency Injection? + +This design enables: + +- **Swappable serializers**: Use Pallas (recommended) or CSL backend without changing transaction logic +- **Testing**: Mock fetchers and evaluators in unit tests +- **Custom providers**: Implement your own blockchain data source +- **Offline mode**: Build transactions without any network dependency (the default) +- **Full pipeline**: Wire up fetcher + evaluator + submitter for end-to-end transaction handling + +## Trait Overview + +| Trait | Purpose | Built-in Implementations | +|-------|---------|------------------------| +| `TxBuildable` | Serialize transaction body to CBOR | `WhiskyPallas`, `WhiskyCSL` | +| `Fetcher` | Fetch UTxOs, protocol params, block info | `MaestroProvider`, `BlockfrostProvider` | +| `Evaluator` | Evaluate Plutus script execution units | `OfflineTxEvaluator` | +| `Submitter` | Submit signed transactions | `MaestroProvider`, `BlockfrostProvider` | + +## Chapters + +- [Serializer Backends](./di/serializers.md) — `TxBuildable` trait, WhiskyPallas vs WhiskyCSL +- [Providers](./di/providers.md) — Fetcher, Evaluator, Submitter traits and implementations diff --git a/docs/guide/src/guides/di/providers.md b/docs/guide/src/guides/di/providers.md new file mode 100644 index 00000000..6ecd46f3 --- /dev/null +++ b/docs/guide/src/guides/di/providers.md @@ -0,0 +1,138 @@ +# Providers + +Providers implement the `Fetcher`, `Evaluator`, and `Submitter` traits to connect whisky to the Cardano blockchain. + +## Fetcher + +The `Fetcher` trait provides blockchain data: + +```rust,ignore +#[async_trait] +pub trait Fetcher: Send + Sync { + async fn fetch_account_info(&self, address: &str) -> Result; + async fn fetch_address_utxos( + &self, + address: &str, + asset: Option<&str>, + ) -> Result, WError>; + async fn fetch_asset_addresses(&self, asset: &str) -> Result, WError>; + async fn fetch_asset_metadata( + &self, + asset: &str, + ) -> Result>, WError>; + async fn fetch_block_info(&self, hash: &str) -> Result; + async fn fetch_collection_assets( + &self, + policy_id: &str, + cursor: Option, + ) -> Result<(Vec<(String, String)>, Option), WError>; + async fn fetch_protocol_parameters(&self, epoch: Option) -> Result; + async fn fetch_tx_info(&self, hash: &str) -> Result; + async fn fetch_utxos(&self, hash: &str, index: Option) -> Result, WError>; + async fn get(&self, url: &str) -> Result; +} +``` + +## Evaluator + +The `Evaluator` trait runs Plutus scripts off-chain to determine execution units: + +```rust,ignore +#[async_trait] +pub trait Evaluator: Send { + async fn evaluate_tx( + &self, + tx_hex: &str, + inputs: &[UTxO], + additional_txs: &[String], + network: &Network, + slot_config: &SlotConfig, + ) -> Result, WError>; +} +``` + +By default, `TxBuilder` uses `OfflineTxEvaluator`, which evaluates scripts locally using TxPipe's `uplc` library — no network calls needed. + +## Submitter + +The `Submitter` trait submits signed transactions: + +```rust,ignore +#[async_trait] +pub trait Submitter: Send + Sync { + async fn submit_tx(&self, tx_hex: &str) -> Result; +} +``` + +Returns the transaction hash on success. + +## Built-in Providers + +### Maestro + +```rust,ignore +use whisky_provider::MaestroProvider; + +let provider = MaestroProvider::new("your_api_key", "preprod"); // or "mainnet" + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), + evaluator: None, + fetcher: Some(Box::new(provider.clone())), + submitter: Some(Box::new(provider)), + params: None, +}); +``` + +### Blockfrost + +```rust,ignore +use whisky_provider::BlockfrostProvider; + +let provider = BlockfrostProvider::new("your_project_id", "preprod"); // or "mainnet" + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), + evaluator: None, + fetcher: Some(Box::new(provider.clone())), + submitter: Some(Box::new(provider)), + params: None, +}); +``` + +> **Note**: Using providers requires the `provider` feature flag (enabled by default). + +## Wiring It All Together + +A fully-wired `TxBuilder` can fetch UTxOs, evaluate scripts, and submit — all in one flow: + +```rust,ignore +use whisky::*; +use whisky_pallas::WhiskyPallas; +use whisky_provider::MaestroProvider; + +let provider = MaestroProvider::new("api_key", "preprod"); + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), + evaluator: None, // Uses OfflineTxEvaluator by default + fetcher: Some(Box::new(provider.clone())), + submitter: Some(Box::new(provider)), + params: None, +}); + +// Build, evaluate, sign, and submit +tx_builder + .tx_out(recipient, &[Asset::new_from_str("lovelace", "5000000")]) + .change_address(my_address) + .select_utxos_from(&utxos, 5000000) + .signing_key(skey_hex) + .complete(None) + .await? + .complete_signing()?; + +// Submit +let tx_hash = tx_builder + .submit_tx(&tx_builder.tx_hex()) + .await?; +``` diff --git a/docs/guide/src/guides/di/serializers.md b/docs/guide/src/guides/di/serializers.md new file mode 100644 index 00000000..482a9648 --- /dev/null +++ b/docs/guide/src/guides/di/serializers.md @@ -0,0 +1,120 @@ +# Serializer Backends + +The `TxBuildable` trait abstracts transaction serialization. Whisky ships with two implementations: **WhiskyPallas** (recommended) and **WhiskyCSL** (legacy). + +## TxBuildable Trait + +```rust,ignore +pub trait TxBuildable: Debug + Send + Sync { + fn set_protocol_params(&mut self, protocol_params: Protocol); + fn set_tx_builder_body(&mut self, tx_builder: TxBuilderBody); + fn reset_builder(&mut self); + + fn serialize_tx_body(&mut self) -> Result; + fn unbalanced_serialize_tx_body(&mut self) -> Result; + fn complete_signing(&mut self) -> Result; + fn set_tx_hex(&mut self, tx_hex: String); + fn tx_hex(&mut self) -> String; + fn tx_evaluation_multiplier_percentage(&self) -> u64; + + fn add_tx_in(&mut self, input: PubKeyTxIn) -> Result<(), WError>; +} +``` + +The `TxBuilder` calls these methods internally — you interact with the high-level builder API, not the trait directly. + +## WhiskyPallas (Recommended) + +The Pallas-based serializer is the default and recommended backend. It uses [TxPipe's Pallas](https://github.com/txpipe/pallas) library for CBOR serialization. + +```rust,ignore +use whisky_pallas::WhiskyPallas; + +// With default protocol parameters +let serializer = WhiskyPallas::new(None); + +// With custom protocol parameters +let serializer = WhiskyPallas::new(Some(protocol_params)); +``` + +Use it with `TxBuilder`: + +```rust,ignore +use whisky::*; +use whisky_pallas::WhiskyPallas; + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), + evaluator: None, + fetcher: None, + submitter: None, + params: None, +}); +``` + +Or simply: + +```rust,ignore +let mut tx_builder = TxBuilder::new_core(); +// new_core() uses WhiskyPallas by default +``` + +**Why Pallas?** +- Pure Rust implementation — no C dependencies +- Actively maintained and updated for hard forks +- Better alignment with the Cardano Rust ecosystem + +## WhiskyCSL (Legacy) + +The CSL-based serializer uses `cardano-serialization-lib`. It's available for backward compatibility. + +```rust,ignore +use whisky_csl::WhiskyCSL; + +let serializer = WhiskyCSL::new(None); +``` + +> **Note**: The main `whisky` crate no longer includes CSL by default. To use it, add `whisky-csl` directly: +> +> ```toml +> [dependencies] +> whisky-csl = "1.0.28-beta.1" +> ``` + +## Implementing a Custom Serializer + +You can implement `TxBuildable` for your own serializer: + +```rust,ignore +use whisky_common::*; + +#[derive(Debug)] +struct MySerializer { + // your fields +} + +impl TxBuildable for MySerializer { + fn set_protocol_params(&mut self, protocol_params: Protocol) { /* ... */ } + fn set_tx_builder_body(&mut self, tx_builder: TxBuilderBody) { /* ... */ } + fn reset_builder(&mut self) { /* ... */ } + fn serialize_tx_body(&mut self) -> Result { /* ... */ } + fn unbalanced_serialize_tx_body(&mut self) -> Result { /* ... */ } + fn complete_signing(&mut self) -> Result { /* ... */ } + fn set_tx_hex(&mut self, tx_hex: String) { /* ... */ } + fn tx_hex(&mut self) -> String { /* ... */ } + fn tx_evaluation_multiplier_percentage(&self) -> u64 { 110 } + fn add_tx_in(&mut self, input: PubKeyTxIn) -> Result<(), WError> { /* ... */ } +} +``` + +Then inject it into `TxBuilder`: + +```rust,ignore +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(MySerializer { /* ... */ }), + evaluator: None, + fetcher: None, + submitter: None, + params: None, +}); +``` diff --git a/docs/guide/src/guides/migration-csl-to-pallas.md b/docs/guide/src/guides/migration-csl-to-pallas.md new file mode 100644 index 00000000..6e5323d2 --- /dev/null +++ b/docs/guide/src/guides/migration-csl-to-pallas.md @@ -0,0 +1,142 @@ +# Migration: CSL to Pallas + +This guide covers migrating from the legacy `cardano-serialization-lib` (CSL) backend to the `pallas`-based backend. + +## Why Migrate? + +Both serialization libraries have similar functionality, but **Pallas** offers: + +- **Better maintenance** — actively updated for Cardano hard forks +- **Pure Rust** — no C/WASM dependencies +- **Ecosystem alignment** — used widely in the Cardano Rust ecosystem + +The `whisky` main crate now defaults to Pallas. CSL has been removed from the default feature set. + +## Transaction Building + +### Before (CSL) + +```rust,ignore +use whisky::*; +use whisky_csl::WhiskyCSL; + +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyCSL::new(None)), + evaluator: None, + fetcher: None, + submitter: None, + params: None, +}); +``` + +### After (Pallas) + +```rust,ignore +use whisky::*; +use whisky_pallas::WhiskyPallas; + +// Option 1: Explicit +let mut tx_builder = TxBuilder::new(TxBuilderParam { + serializer: Box::new(WhiskyPallas::new(None)), + evaluator: None, + fetcher: None, + submitter: None, + params: None, +}); + +// Option 2: Use the convenience constructor (defaults to Pallas) +let mut tx_builder = TxBuilder::new_core(); +``` + +The transaction building API remains **identical** — only the serializer instantiation changes: + +```rust,ignore +let signed_tx = tx_builder + .tx_in(tx_hash, tx_index, amount, address) + .change_address(my_address) + .signing_key(skey_hex) + .complete_sync(None)? + .complete_signing()?; +``` + +## Transaction Parsing + +### Before (CSL) + +```rust,ignore +use whisky_csl::CSLParser; + +let mut parser = CSLParser::new(); +parser.parse(tx_hex, &utxos)?; +let body = parser.get_builder_body(); +``` + +### After (Pallas) + +```rust,ignore +use whisky_pallas::tx_parser::parse; + +let body = parse(tx_hex, &utxos)?; +``` + +Or using the trait-based approach: + +```rust,ignore +use whisky_pallas::tx_parser::PallasParser; +use whisky_common::TxParsable; + +let mut parser = PallasParser::new(); +parser.parse(tx_hex, &utxos)?; +let body = parser.get_builder_body(); +``` + +## Transaction Evaluation + +```rust,ignore +use uplc::tx::script_context::SlotConfig; +use whisky_common::{Network, UTxO}; +use whisky_pallas::utils::evaluate_tx_scripts; + +let result = evaluate_tx_scripts( + tx_hex, + &utxos, + &[], // additional chained transactions + &Network::Mainnet, + &SlotConfig::default(), +); +``` + +## Dependency Changes + +If you were depending on `whisky-csl` directly, update your `Cargo.toml`: + +### Before + +```toml +[dependencies] +whisky = "1.0.17" +# CSL was included by default +``` + +### After + +```toml +[dependencies] +whisky = "1.0.28-beta.1" +# Pallas is now the default — no extra dependency needed + +# Only if you still need CSL: +# whisky-csl = "1.0.28-beta.1" +``` + +## Summary of Changes + +| Component | CSL | Pallas | +|-----------|-----|--------| +| Serializer | `WhiskyCSL::new(None)` | `WhiskyPallas::new(None)` | +| Default constructor | N/A | `TxBuilder::new_core()` | +| Parser | `CSLParser::new()` | `parse(tx_hex, &utxos)` | +| Crate | `whisky-csl` | `whisky-pallas` (included via `whisky`) | +| Feature flag | `features = ["csl"]` | Included by default | + +The transaction building API (`tx_in`, `tx_out`, `change_address`, `complete`, etc.) is **unchanged** — migration only requires swapping the serializer backend. diff --git a/docs/guide/src/guides/tx-builder.md b/docs/guide/src/guides/tx-builder.md new file mode 100644 index 00000000..8d24ffa3 --- /dev/null +++ b/docs/guide/src/guides/tx-builder.md @@ -0,0 +1,80 @@ +# Transaction Builder + +The `TxBuilder` is the core API for constructing Cardano transactions in whisky. It uses a chainable builder pattern where you compose a transaction step by step, then finalize it. + +## Overview + +```rust,ignore +use whisky::*; + +let mut tx_builder = TxBuilder::new_core(); +tx_builder + .tx_in(tx_hash, tx_index, amount, address) // Add inputs + .tx_out(address, assets) // Add outputs + .change_address(my_address) // Set change address + .complete_sync(None)?; // Balance and serialize + +let tx_hex = tx_builder.tx_hex(); +``` + +## Key Concepts + +### Builder Pattern + +Every method returns `&mut Self`, allowing you to chain calls fluently. The builder accumulates state until you call `complete_sync()` or `complete()`. + +### Inputs and UTxO Selection + +You can specify inputs explicitly with `.tx_in()` or let whisky select them automatically: + +```rust,ignore +// Explicit input +tx_builder.tx_in(tx_hash, tx_index, amount, address); + +// Automatic selection from a pool of UTxOs +tx_builder.select_utxos_from(&available_utxos, 5000000); +``` + +The `select_utxos_from` threshold (e.g., 5,000,000 lovelace) tells the selector to pick enough UTxOs to cover all outputs plus this extra amount for fees and change output min UTxO. + +### Outputs + +```rust,ignore +// Send lovelace +tx_builder.tx_out(address, &[Asset::new_from_str("lovelace", "2000000")]); + +// Send native tokens +tx_builder.tx_out(address, &[ + Asset::new_from_str("lovelace", "2000000"), + Asset::new("policy_id_hex".to_string() + "token_name_hex", "1".to_string()), +]); +``` + +### Completion + +| Method | Sync/Async | Script Evaluation | Use Case | +|--------|-----------|-------------------|----------| +| `complete_sync(None)` | Sync | No | Simple transactions | +| `complete(None).await` | Async | Yes (offline) | Plutus script transactions | + +### Common Methods + +| Method | Purpose | +|--------|---------| +| `.tx_in()` | Add a specific UTxO as input | +| `.tx_out()` | Add an output | +| `.change_address()` | Set the change address | +| `.select_utxos_from()` | Auto-select UTxOs from a pool | +| `.signing_key()` | Add a signing key | +| `.required_signer_hash()` | Add a required signer | +| `.invalid_before()` | Set validity start slot | +| `.invalid_hereafter()` | Set validity end slot | +| `.metadata_value()` | Attach transaction metadata | +| `.complete_signing()` | Sign and return the final tx hex | + +## Chapters + +- [Simple Transactions](./tx-builder/simple.md) — Send lovelace, lock funds, delegate stake +- [Plutus Script Transactions](./tx-builder/plutus.md) — Unlock funds from scripts, handle datums and redeemers +- [Minting](./tx-builder/minting.md) — Mint and burn native tokens with Plutus scripts +- [Staking & Governance](./tx-builder/staking.md) — Stake registration, delegation, withdrawals, governance diff --git a/docs/guide/src/guides/tx-builder/minting.md b/docs/guide/src/guides/tx-builder/minting.md new file mode 100644 index 00000000..ff89f0e3 --- /dev/null +++ b/docs/guide/src/guides/tx-builder/minting.md @@ -0,0 +1,155 @@ +# Minting + +This chapter covers minting and burning native tokens using Plutus minting policies. + +## Mint Tokens + +Mint tokens using a Plutus V3 minting policy: + +```rust,ignore +use whisky::*; + +pub async fn mint_tokens( + to_mint_asset: &Asset, + redeemer: &str, + script: &ProvidedScriptSource, + my_address: &str, + inputs: &[UTxO], + collateral: &UTxO, +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + + tx_builder + .mint_plutus_script_v3() + .mint( + to_mint_asset.quantity_i128(), + &to_mint_asset.policy(), + &to_mint_asset.name(), + ) + .minting_script(&script.script_cbor) + .mint_redeemer_value(&WRedeemer { + data: WData::JSON(redeemer.to_string()), + ex_units: Budget { mem: 0, steps: 0 }, + }) + .change_address(my_address) + .tx_in_collateral( + &collateral.input.tx_hash, + collateral.input.output_index, + &collateral.output.amount, + &collateral.output.address, + ) + .select_utxos_from(inputs, 5000000) + .complete(None) + .await?; + + Ok(tx_builder.tx_hex()) +} +``` + +## The Minting Pattern + +Every Plutus mint follows this sequence: + +1. **Declare the script version**: `.mint_plutus_script_v3()` (or `_v2()`, `_v1()`) +2. **Specify the mint**: `.mint(quantity, policy_id, asset_name)` +3. **Provide the script**: `.minting_script(&script_cbor)` or use a reference script +4. **Provide the redeemer**: `.mint_redeemer_value(&redeemer)` + +## Reference Script Minting + +Instead of embedding the script, reference one already on-chain: + +```rust,ignore +tx_builder + .mint_plutus_script_v2() + .mint(1, policy_id, token_name_hex) + .mint_tx_in_reference( + "reference_tx_hash", + 0, // reference tx index + policy_id, // script hash to validate against + 100, // script size in bytes + ) + .mint_redeemer_value(&WRedeemer { + data: WData::JSON(r#"{"constructor": 0, "fields": []}"#.to_string()), + ex_units: Budget { mem: 3386819, steps: 1048170931 }, + }) +``` + +## Burning Tokens + +To burn tokens, use a negative quantity: + +```rust,ignore +tx_builder + .mint_plutus_script_v3() + .mint(-1, policy_id, token_name_hex) // Negative = burn + .minting_script(&script.script_cbor) + .mint_redeemer_value(&redeemer) +``` + +## Multiple Mints in One Transaction + +You can mint tokens from multiple policies in a single transaction by chaining mint blocks: + +```rust,ignore +use whisky::*; + +tx_builder + // First mint + .mint_plutus_script_v2() + .mint( + to_mint_asset_1.quantity_i128(), + &to_mint_asset_1.policy(), + &to_mint_asset_1.name(), + ) + .mint_redeemer_value(&WRedeemer { + data: WData::JSON(redeemer_1.to_string()), + ex_units: Budget { mem: 0, steps: 0 }, + }) + .minting_script(&script_1.script_cbor) + // Second mint + .mint_plutus_script_v2() + .mint( + to_mint_asset_2.quantity_i128(), + &to_mint_asset_2.policy(), + &to_mint_asset_2.name(), + ) + .mint_redeemer_value(&WRedeemer { + data: WData::JSON(redeemer_2.to_string()), + ex_units: Budget { mem: 0, steps: 0 }, + }) + .minting_script(&script_2.script_cbor) + .change_address(my_address) + .tx_in_collateral(/* ... */) + .select_utxos_from(inputs, 5000000) + .complete(None) + .await?; +``` + +Each mint block starts with `.mint_plutus_script_vN()` and is independent — you can mix V1, V2, and V3 policies in the same transaction. + +## Complex Transaction: Spend + Mint + +You can combine script spending and minting in one transaction: + +```rust,ignore +tx_builder + // Script spend + .spending_plutus_script_v2() + .tx_in(/* script UTxO */) + .tx_in_inline_datum_present() + .tx_in_redeemer_value(&spend_redeemer) + .tx_in_script(&spend_script_cbor) + // Mint + .mint_plutus_script_v2() + .mint(1, policy_id, token_name) + .mint_redeemer_value(&mint_redeemer) + .minting_script(&mint_script_cbor) + // Finalize + .change_address(my_address) + .tx_in_collateral(/* ... */) + .input_for_evaluation(script_utxo) + .select_utxos_from(inputs, 5000000) + .complete(None) + .await?; +``` diff --git a/docs/guide/src/guides/tx-builder/plutus.md b/docs/guide/src/guides/tx-builder/plutus.md new file mode 100644 index 00000000..581dfa6a --- /dev/null +++ b/docs/guide/src/guides/tx-builder/plutus.md @@ -0,0 +1,129 @@ +# Plutus Script Transactions + +This chapter covers spending from Plutus script addresses — unlocking funds guarded by validators. + +## Unlock Funds from a Script + +To spend a UTxO locked at a Plutus script address, you must provide the script, datum, and redeemer: + +```rust,ignore +use whisky::*; + +pub async fn unlock_fund( + script_utxo: &UTxO, + redeemer: &str, + script: &ProvidedScriptSource, + my_address: &str, + inputs: &[UTxO], + collateral: &UTxO, +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + let pub_key_hash = deserialize_address(my_address)?.pub_key_hash; + + tx_builder + .spending_plutus_script_v3() + .tx_in( + &script_utxo.input.tx_hash, + script_utxo.input.output_index, + &script_utxo.output.amount, + &script_utxo.output.address, + ) + .tx_in_inline_datum_present() + .tx_in_redeemer_value(&WRedeemer { + data: WData::JSON(redeemer.to_string()), + ex_units: Budget { mem: 0, steps: 0 }, + }) + .tx_in_script(&script.script_cbor) + .change_address(my_address) + .required_signer_hash(&pub_key_hash) + .tx_in_collateral( + &collateral.input.tx_hash, + collateral.input.output_index, + &collateral.output.amount, + &collateral.output.address, + ) + .input_for_evaluation(script_utxo) + .select_utxos_from(inputs, 5000000) + .complete(None) + .await?; + + Ok(tx_builder.tx_hex()) +} +``` + +## The Script Spending Pattern + +Every Plutus spend follows the same sequence: + +1. **Declare the script version**: `.spending_plutus_script_v3()` (or `_v2()`, `_v1()`) +2. **Add the script input**: `.tx_in(hash, index, amount, address)` +3. **Provide the datum**: `.tx_in_inline_datum_present()` or `.tx_in_datum_value(&datum)` +4. **Provide the redeemer**: `.tx_in_redeemer_value(&redeemer)` +5. **Provide the script**: `.tx_in_script(&script_cbor)` or use a reference script + +## Datum Handling + +Whisky supports two datum modes: + +```rust,ignore +// The UTxO already has an inline datum — just declare it's present +.tx_in_inline_datum_present() + +// Provide the datum value explicitly (e.g., when only the datum hash is on-chain) +.tx_in_datum_value(&WData::JSON(datum_json.to_string())) +``` + +## Redeemer Values + +Redeemers are Plutus data values passed to the validator. Set execution units to `0` and let the evaluator calculate them: + +```rust,ignore +.tx_in_redeemer_value(&WRedeemer { + data: WData::JSON(r#"{"constructor": 0, "fields": []}"#.to_string()), + ex_units: Budget { mem: 0, steps: 0 }, +}) +``` + +When you call `.complete(None).await`, the built-in offline evaluator runs the script and fills in the actual execution units. + +## Script Sources + +You can provide the script in two ways: + +```rust,ignore +// Embed the script CBOR directly in the transaction +.tx_in_script(&script_cbor) + +// Reference a script already on-chain (more efficient — saves tx size) +.spending_tx_in_reference(tx_hash, tx_index, script_hash, script_size) +``` + +## Collateral + +Plutus transactions require collateral — a UTxO that gets consumed if the script fails: + +```rust,ignore +.tx_in_collateral( + &collateral.input.tx_hash, + collateral.input.output_index, + &collateral.output.amount, + &collateral.output.address, +) +``` + +You can also set a specific total collateral amount: + +```rust,ignore +.tx_in_collateral(tx_hash, tx_index, amount, address) +.set_total_collateral("5000000") +``` + +## Input for Evaluation + +When using offline evaluation, provide the script UTxO context so the evaluator can resolve inputs: + +```rust,ignore +.input_for_evaluation(script_utxo) +``` + +This is required for the offline evaluator to correctly simulate script execution. diff --git a/docs/guide/src/guides/tx-builder/simple.md b/docs/guide/src/guides/tx-builder/simple.md new file mode 100644 index 00000000..07484220 --- /dev/null +++ b/docs/guide/src/guides/tx-builder/simple.md @@ -0,0 +1,114 @@ +# Simple Transactions + +These examples cover basic transaction patterns that don't involve Plutus scripts. + +## Send Lovelace + +The most basic transaction: send ADA from one address to another. + +```rust,ignore +use whisky::*; + +pub fn send_lovelace( + recipient_address: &str, + my_address: &str, + inputs: &[UTxO], +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + tx_builder + .tx_out( + recipient_address, + &[Asset::new_from_str("lovelace", "1000000")], + ) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; + + Ok(tx_builder.tx_hex()) +} +``` + +> **Note**: You don't need to manually add inputs — `select_utxos_from` automatically picks UTxOs to cover the output amount plus the threshold for fees. + +## Lock Funds at a Script Address + +Send funds to a script address with an inline datum attached: + +```rust,ignore +use whisky::*; + +pub fn lock_fund( + script_address: &str, + datum: &str, + my_address: &str, + inputs: &[UTxO], +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + tx_builder + .tx_out(script_address, &[]) + .tx_out_inline_datum_value(&WData::JSON(datum.to_string())) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; + + Ok(tx_builder.tx_hex()) +} +``` + +Key points: +- **`tx_out_inline_datum_value`** attaches an inline datum (stored on-chain) to the output +- The datum is provided as a JSON-encoded Plutus data string (e.g., `{"constructor": 0, "fields": []}`) +- Use `tx_out_datum_hash_value` instead if you only want to store the datum hash on-chain + +## Delegate Stake + +Register a stake key and delegate to a pool in a single transaction: + +```rust,ignore +use whisky::*; + +pub fn delegate_stake( + stake_key_hash: &str, + pool_id: &str, // e.g., "pool1..." + my_address: &str, + inputs: &[UTxO], +) -> Result { + let mut tx_builder = TxBuilder::new_core(); + tx_builder + .register_stake_certificate(stake_key_hash) + .delegate_stake_certificate(stake_key_hash, pool_id) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; + + Ok(tx_builder.tx_hex()) +} +``` + +## Validity Ranges and Metadata + +You can set time bounds and attach metadata to any transaction: + +```rust,ignore +tx_builder + .tx_out(address, &[Asset::new_from_str("lovelace", "2000000")]) + .invalid_before(100) // Transaction valid from slot 100 + .invalid_hereafter(200) // Transaction invalid after slot 200 + .metadata_value("674", "{\"msg\": [\"Hello, Cardano!\"]}") + .change_address(my_address) + .complete_sync(None)?; +``` + +## Signing and Submitting + +After building, sign with one or more keys: + +```rust,ignore +let signed_tx = tx_builder + .signing_key("ed25519_sk_hex_key_1") + .signing_key("ed25519_sk_hex_key_2") // Multiple signers + .complete_sync(None)? + .complete_signing()?; + +// signed_tx is ready for submission +``` diff --git a/docs/guide/src/guides/tx-builder/staking.md b/docs/guide/src/guides/tx-builder/staking.md new file mode 100644 index 00000000..fa088b81 --- /dev/null +++ b/docs/guide/src/guides/tx-builder/staking.md @@ -0,0 +1,107 @@ +# Staking & Governance + +This chapter covers stake operations and Conway-era governance actions. + +## Stake Registration + +Register a stake key on-chain: + +```rust,ignore +use whisky::*; + +let mut tx_builder = TxBuilder::new_core(); +tx_builder + .register_stake_certificate(stake_key_hash) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +## Stake Delegation + +Delegate to a stake pool (can be combined with registration): + +```rust,ignore +tx_builder + .register_stake_certificate(stake_key_hash) + .delegate_stake_certificate(stake_key_hash, pool_id) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +## Stake Deregistration + +Deregister a stake key to reclaim the deposit: + +```rust,ignore +tx_builder + .deregister_stake_certificate(stake_key_hash) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +## Withdrawals + +Withdraw staking rewards: + +```rust,ignore +tx_builder + .tx_in(tx_hash, tx_index, amount, address) + .change_address(my_address) + .withdrawal(stake_address, 0) // stake_address e.g., "stake_test1ur..." + .required_signer_hash(pub_key_hash) + .signing_key(signing_key_hex) + .complete_sync(None)? + .complete_signing()?; +``` + +## Governance: DRep Registration + +Register as a Delegated Representative (Conway era): + +```rust,ignore +tx_builder + .drep_registration(drep_id, deposit_amount) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +## Governance: Vote Delegation + +Delegate your voting power to a DRep: + +```rust,ignore +tx_builder + .vote_delegation(stake_key_hash, drep) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +## Governance: Voting + +Cast a governance vote: + +```rust,ignore +tx_builder + .vote(voter, ref_tx_hash, ref_tx_index, vote_kind) + .change_address(my_address) + .select_utxos_from(inputs, 5000000) + .complete_sync(None)?; +``` + +For script-based voting, use the Plutus vote pattern: + +```rust,ignore +tx_builder + .vote_plutus_script_v3() + .vote(voter, ref_tx_hash, ref_tx_index, vote_kind) + .vote_script(&script_cbor) + .vote_redeemer_value(&redeemer) + .tx_in_collateral(/* ... */) + .complete(None) + .await?; +``` diff --git a/docs/guide/src/guides/tx-parser.md b/docs/guide/src/guides/tx-parser.md new file mode 100644 index 00000000..642a1ea0 --- /dev/null +++ b/docs/guide/src/guides/tx-parser.md @@ -0,0 +1,110 @@ +# Transaction Parser + +The transaction parser lets you deserialize a raw CBOR transaction hex back into a `TxBuilderBody`, inspect it, edit it, and rebuild it into a new transaction. + +## Parsing a Transaction + +Use the `parse` function to deserialize a transaction: + +```rust,ignore +use whisky::*; +use whisky_pallas::tx_parser::parse; + +let tx_hex = "84a700d90102..."; // Raw transaction CBOR hex +let utxos = vec![/* resolved UTxOs referenced by the transaction */]; + +let body = parse(tx_hex, &utxos).unwrap(); +``` + +The `parse` function returns a `TxBuilderBody` containing all the transaction's inputs, outputs, mints, certificates, withdrawals, and metadata. + +> **Important**: You must provide the resolved UTxOs that the transaction references as inputs. The parser needs these to reconstruct the full input context. + +## The Parse-Edit-Rebuild Pattern + +A powerful pattern is to parse an existing transaction, modify it, and rebuild: + +```rust,ignore +use whisky::*; +use whisky_pallas::tx_parser::parse; + +// 1. Parse the original transaction +let utxos = vec![utxo_1, utxo_2, utxo_3]; +let tx_hex = "84a700d90102..."; +let mut body = parse(tx_hex, &utxos).unwrap(); + +// 2. Edit the body +body.outputs.pop(); // Remove last output +body.reference_inputs.pop(); // Remove a reference input + +// 3. Rebuild with a new TxBuilder +let mut tx_builder = TxBuilder::new_core(); +tx_builder.tx_builder_body = body; + +// 4. Add new elements and rebalance +let new_tx_hex = tx_builder + .tx_out( + "addr_test1zp...", + &[Asset::new_from_str("lovelace", "5000000")], + ) + .invalid_before(100) + .invalid_hereafter(200) + .required_signer_hash("3f1b5974f4f09f0974be655e4ce94f8a2d087df378b79ef3916c26b2") + .complete_sync(None) + .unwrap() + .tx_hex(); +``` + +The resulting transaction is automatically rebalanced with proper fee calculation and a new change output. + +## TxParsable Trait + +The parser implements the `TxParsable` trait, which provides: + +```rust,ignore +pub trait TxParsable { + fn parse(&mut self, tx_hex: &str, resolved_utxos: &[UTxO]) -> Result<(), WError>; + fn get_required_inputs(&mut self, tx_hex: &str) -> Result, WError>; + fn get_builder_body(&self) -> TxBuilderBody; + fn get_builder_body_without_change(&self) -> TxBuilderBody; + fn to_tester(&self) -> TxTester; +} +``` + +| Method | Purpose | +|--------|---------| +| `parse` | Deserialize tx hex into internal state | +| `get_required_inputs` | Extract input references without full parsing | +| `get_builder_body` | Get the full `TxBuilderBody` from parsed state | +| `get_builder_body_without_change` | Get the body excluding the change output | +| `to_tester` | Convert to a `TxTester` for making assertions | + +## Checking Required Signers + +You can also inspect a transaction's required signers: + +```rust,ignore +use whisky_pallas::tx_parser::check_tx_required_signers; + +let signers = check_tx_required_signers(tx_hex); +``` + +## Transaction Evaluation + +For parsed transactions that include Plutus scripts, you can evaluate execution units: + +```rust,ignore +use uplc::tx::script_context::SlotConfig; +use whisky_common::{Network, UTxO}; +use whisky_pallas::utils::evaluate_tx_scripts; + +let result = evaluate_tx_scripts( + tx_hex, + &utxos, + &[], // additional chained transactions + &Network::Mainnet, + &SlotConfig::default(), +); +``` + +This returns execution units (memory and CPU steps) for each script in the transaction. diff --git a/docs/guide/src/introduction.md b/docs/guide/src/introduction.md new file mode 100644 index 00000000..cd3ff00a --- /dev/null +++ b/docs/guide/src/introduction.md @@ -0,0 +1,42 @@ +# Introduction + +Whisky is an open-source Cardano Rust SDK built by [SIDAN Lab](https://github.com/sidan-lab). It provides a comprehensive set of tools for building Cardano DApps in Rust, with a chainable builder API inspired by [MeshJS](https://meshjs.dev/). + +## Modules + +Whisky is organized as a Rust workspace with the following crates: + +| Crate | Description | +|-------|-------------| +| **whisky** | The main crate — re-exports everything you need for DApp development | +| **whisky-common** | Shared types, interfaces, and utilities used across all crates | +| **whisky-pallas** | Transaction serializer built on [TxPipe's Pallas](https://github.com/txpipe/pallas) (recommended) | +| **whisky-csl** | Legacy serializer built on `cardano-serialization-lib` | +| **whisky-provider** | Provider integrations for Blockfrost and Maestro | +| **whisky-wallet** | Wallet signing and key management utilities | +| **whisky-macros** | Procedural macros for Plutus data encoding | +| **whisky-js** | WASM bindings for JavaScript/TypeScript usage | + +## What You Can Do + +With whisky, you can: + +- **Build transactions** with a chainable, cardano-cli-like API supporting complex DApp backends +- **Parse and edit transactions** from raw CBOR hex +- **Sign transactions** with key-based signing in Rust +- **Interact with the blockchain** via Maestro and Blockfrost providers +- **Evaluate scripts off-chain** using TxPipe's `uplc` for execution unit estimation +- **Swap serializer backends** between Pallas and CSL via dependency injection + +## Guide Overview + +This guide walks you through: + +1. **[Installation](./getting-started/installation.md)** — Adding whisky to your project +2. **[Quick Start](./getting-started/quickstart.md)** — Building your first transaction +3. **[Transaction Builder](./guides/tx-builder.md)** — Simple sends, Plutus scripts, minting, staking +4. **[Transaction Parser](./guides/tx-parser.md)** — Parsing and editing existing transactions +5. **[Dependency Injection](./guides/dependency-injection.md)** — Pluggable serializers and providers +6. **[Migration: CSL to Pallas](./guides/migration-csl-to-pallas.md)** — Upgrading from the legacy backend + +For full API reference, see the [generated Rust docs](../api/whisky/index.html). diff --git a/docs/guide/src/reference/api-docs.md b/docs/guide/src/reference/api-docs.md new file mode 100644 index 00000000..3ed696ac --- /dev/null +++ b/docs/guide/src/reference/api-docs.md @@ -0,0 +1,31 @@ +# API Documentation + +Full API reference documentation is auto-generated from Rust doc comments using `cargo doc`. + +## Browse the API Docs + +**[Open API Reference](../api/whisky/index.html)** + +## Key Entry Points + +| Crate | Description | Link | +|-------|-------------|------| +| **whisky** | Main crate — re-exports all public APIs | [docs](../api/whisky/index.html) | +| **whisky-common** | Shared types, traits, and utilities | [docs](../api/whisky_common/index.html) | +| **whisky-pallas** | Pallas-based serializer | [docs](../api/whisky_pallas/index.html) | +| **whisky-csl** | Legacy CSL-based serializer | [docs](../api/whisky_csl/index.html) | +| **whisky-provider** | Blockfrost and Maestro integrations | [docs](../api/whisky_provider/index.html) | +| **whisky-wallet** | Wallet signing and key management | [docs](../api/whisky_wallet/index.html) | +| **whisky-macros** | Procedural macros for Plutus data | [docs](../api/whisky_macros/index.html) | + +## Building Docs Locally + +```sh +# Generate API reference +npm run rust:doc + +# Serve locally +npx http-server ./docs +``` + +The docs are also published automatically to GitHub Pages on every push to the deployment branch. diff --git a/docs/guide/src/reference/examples-server.md b/docs/guide/src/reference/examples-server.md new file mode 100644 index 00000000..611b73b6 --- /dev/null +++ b/docs/guide/src/reference/examples-server.md @@ -0,0 +1,67 @@ +# Examples Server + +The `whisky-examples` crate provides runnable transaction examples and an HTTP server that exposes them as API endpoints. + +## Running the Server + +```sh +cargo run --package whisky-examples +``` + +The server starts on `http://127.0.0.1:8080` with CORS enabled. + +## Available Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/send_lovelace` | POST | Send ADA to a recipient | +| `/lock_fund` | POST | Lock funds at a script address with datum | +| `/unlock_fund` | POST | Unlock funds from a Plutus script | +| `/mint_tokens` | POST | Mint tokens with a Plutus minting policy | + +## Example: Send Lovelace + +```sh +curl -X POST http://127.0.0.1:8080/send_lovelace \ + -H "Content-Type: application/json" \ + -d '{ + "recipientAddress": "addr_test1...", + "myAddress": "addr_test1...", + "inputs": [ + { + "input": { + "txHash": "abcdef...", + "outputIndex": 0 + }, + "output": { + "address": "addr_test1...", + "amount": [{"unit": "lovelace", "quantity": "10000000"}] + } + } + ] + }' +``` + +Response: + +```json +{ + "txHex": "84a400..." +} +``` + +## Example Functions + +The transaction functions are in `packages/whisky-examples/src/tx/`: + +| File | Function | Type | +|------|----------|------| +| `send_lovelace.rs` | `send_lovelace` | Sync | +| `lock_fund.rs` | `lock_fund` | Sync | +| `unlock_fund.rs` | `unlock_fund` | Async | +| `mint_tokens.rs` | `mint_tokens` | Async | +| `delegate_stake.rs` | `delegate_stake` | Sync | +| `complex_transaction.rs` | `complex_transaction` | Async | +| `collateral_return.rs` | `collateral_return` | Async | + +These examples serve as both documentation and testable reference implementations. See the [Transaction Builder](../guides/tx-builder.md) guide for detailed explanations of each pattern. diff --git a/package.json b/package.json index 050e8d90..b19de092 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,12 @@ "js:publish-browser": "npm run core:build-browser && npm run js:prepublish && node ./scripts/publish-helper -browser && cd publish && npm publish --access public", "js:publish-asm": "npm run core:build-asm && npm run js:prepublish && node ./scripts/publish-helper -asmjs && cd publish && npm publish --access public", "js:ts-json-gen": "cd packages/whisky-js/json-gen && cargo +stable run && cd ../../.. && node ./scripts/run-json2ts.js && node ./scripts/json-ts-types.js", - "rust:doc-clear": "rm -rf ./docs && mkdir docs && echo '' > docs/index.html", + "rust:doc-clear": "find ./docs -maxdepth 1 -not -name guide -not -name docs -not -name '.' -exec rm -rf {} + && echo '' > docs/index.html", "rust:doc": "npm run rust:doc-clear && RUSTDOCFLAGS=\"--cfg docsrs\" cargo doc --manifest-path packages/Cargo.toml --no-deps -p whisky -p whisky-pallas -p whisky-common -p whisky-macros -p whisky-provider -p whisky-wallet -p whisky-csl && cp -r ./packages/target/doc/* ./docs", - "start-doc": "npm run rust:doc && npx http-server ./docs", + "guide:build": "mdbook build docs/guide", + "guide:serve": "mdbook serve docs/guide --open", + "docs:build": "npm run rust:doc && npm run guide:build && mkdir -p _site && cp -r docs/guide/book/* _site/ && cp -r docs/ _site/api/ && rm -rf _site/api/guide", + "start-doc": "npm run docs:build && npx http-server ./_site", "sh:bump-version": "./scripts/bump-version.sh", "sh:run-example": "./scripts/run-example.sh" },