General shape apparent

Many things now broke and need fixing.
This commit is contained in:
waalge 2025-10-01 17:23:42 +00:00
parent 0befe1a40a
commit 7163525fb1
67 changed files with 1006 additions and 557 deletions

52
Cargo.lock generated
View File

@ -229,6 +229,47 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cardano-connect"
version = "0.0.0"
dependencies = [
"anyhow",
"cardano-tx-builder",
]
[[package]]
name = "cardano-connect-blockfrost"
version = "0.0.0"
dependencies = [
"anyhow",
"blockfrost",
"blockfrost-openapi",
"cardano-connect",
"cardano-tx-builder",
"hex",
"minicbor",
"reqwest",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "cardano-tx-builder"
version = "0.0.0"
dependencies = [
"anyhow",
"bech32 0.11.0",
"cryptoxide 0.5.1",
"hex",
"minicbor",
"pallas-addresses",
"pallas-codec",
"pallas-crypto",
"pallas-primitives",
"uplc",
]
[[package]]
name = "cc"
version = "1.2.38"
@ -1206,6 +1247,17 @@ dependencies = [
"uplc",
]
[[package]]
name = "konduit-tx"
version = "0.0.0"
dependencies = [
"anyhow",
"cardano-tx-builder",
"hex",
"minicbor",
"rand",
]
[[package]]
name = "libc"
version = "0.2.176"

View File

@ -1,30 +1,21 @@
[package]
name = "konduit-cli"
[workspace]
members = ["crates/*"]
resolver = "2"
[workspace.package]
description = "Konduit-tools"
documentation = "https://github.com/cardano-lightning/konduit"
version = "0.0.0"
edition = "2021"
publish = false
rust-version = "1.86.0"
# license = "MIT" -- set to apache
edition = "2024"
repository = "https://github.com/cardano-lightning/konduit"
homepage = "https://github.com/cardano-lightning/konduit"
# license = "Apache-2.0" // TBC
authors = [
"waalge",
"KtorZ <matthias.benkort@gmail.com>",
]
rust-version = "1.90.0"
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
bech32 = "0.11.0"
blockfrost = "1.1.0"
blockfrost-openapi = "0.1.75"
clap = { version = "4.5.18", features = ["cargo", "derive"] }
cryptoxide = "0.5.1"
hex = "0.4.3"
minicbor = { version = "0.25.1", features = ["alloc", "derive"] }
pallas-addresses = "0.33.0"
pallas-crypto = "0.33.0"
pallas-codec = "0.33.0"
pallas-primitives = "0.33.0"
rand = "0.9.2"
reqwest = { version = "0.12.23", features = ["json"] }
serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.138"
tokio = { version = "1.47.1", features = ["full"] }
uplc = "1.1.19"
[workspace.metadata.release]
shared-version = true
tag-name = "v{{version}}"

View File

@ -1,17 +1,17 @@
# Konduit cli
# Konduit tools
> A first stab at the konduit cli
> A collection to crates to run konduit in the wild
## Workspace
- [ ] cardano-tx-builder - Pure cardano transaction builder utils
- [ ] cardano-connect - Traits for cardano connector
- [ ] cardano-connect-blockfrost - Impl for blockfrost
- [ ] konduit-core - Pure konduit transaction builders.
- cardano-tx-builder - Pure cardano transaction builder utils
- cardano-connect - Traits for cardano connector
- cardano-connect-blockfrost - Impl for blockfrost
- konduit-core - Pure konduit transaction builders.
Depends only on cardano-tx-builder.
- [ ] test round trip data
- [ ] compiles to wasm
- [ ] konduit-cli - cli wrapping of core
- test round trip data
- compiles to wasm
- konduit-cli - cli wrapping of core
## TODOs

View File

@ -0,0 +1,27 @@
[package]
name = "cardano-connect-blockfrost"
version.workspace = true
edition.workspace = true
description.workspace = true
# license.workspace = true // TBC
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
blockfrost = "1.1.0"
blockfrost-openapi = "0.1.75"
hex = "0.4.3"
minicbor = { version = "0.25.1", features = ["alloc", "derive"] }
reqwest = { version = "0.12.23", features = ["json"] }
serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.138"
tokio = { version = "1.47.1", features = ["full"] }
cardano-tx-builder = { path = "../cardano-tx-builder" }
cardano-connect = { path = "../cardano-connect" }

View File

@ -0,0 +1,3 @@
# Cardano connect blockfrost
> An implementation of Cardano connect for blockfrost

View File

@ -0,0 +1,337 @@
// ORIGINAL SOURCE : https://github.com/CardanoSolutions/zhuli/blob/main/cli/src/cardano.rs
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::collections::{BTreeMap, HashMap};
use anyhow::{Result, anyhow};
use blockfrost::{BlockfrostAPI, Pagination};
use blockfrost_openapi::models::tx_content_output_amount_inner::TxContentOutputAmountInner;
use cardano_connect::CardanoConnect;
use cardano_tx_builder::{
Address, BuildParameters, Credential, DatumOrHash, Input, Network, Output, PlutusData, Utxo,
Value,
};
pub struct Config {
pub project_id: String,
}
impl Config {
pub fn from_env(env: &HashMap<String, String>) -> Self {
let project_id = env
.get("project_id")
.expect("Blockfrost requires `project_id`")
.clone();
Self { project_id }
}
}
pub struct Blockfrost {
api: BlockfrostAPI,
base_url: String,
client: reqwest::Client,
network: Network,
project_id: String,
}
const UNIT_LOVELACE: &str = "lovelace";
const MAINNET_PREFIX: &str = "mainnet";
const PREPROD_PREFIX: &str = "preprod";
const PREVIEW_PREFIX: &str = "preview";
#[derive(Debug)]
pub struct ProtocolParameters {
pub collateral_percent: f64,
pub cost_model_v3: Vec<i64>,
pub drep_deposit: u64,
pub fee_constant: u64,
pub fee_coefficient: u64,
pub min_utxo_deposit_coefficient: u64,
pub price_mem: f64,
pub price_steps: f64,
}
impl From<&ProtocolParameters> for BuildParameters {
fn from(params: &ProtocolParameters) -> BuildParameters {
BuildParameters {
fee_constant: params.fee_constant,
fee_coefficient: params.fee_coefficient,
price_mem: params.price_mem,
price_steps: params.price_steps,
}
}
}
impl Blockfrost {
pub fn new(project_id: String) -> Self {
let network_prefix = if project_id.starts_with(MAINNET_PREFIX) {
MAINNET_PREFIX.to_string()
} else if project_id.starts_with(PREPROD_PREFIX) {
PREPROD_PREFIX.to_string()
} else if project_id.starts_with(PREVIEW_PREFIX) {
PREVIEW_PREFIX.to_string()
} else {
panic!("unexpected project id prefix")
};
let base_url = format!("https://cardano-{}.blockfrost.io/api/v0", network_prefix,);
let api = BlockfrostAPI::new(project_id.as_str(), Default::default());
Self {
api,
base_url,
client: reqwest::Client::new(),
network: if project_id.starts_with(MAINNET_PREFIX) {
Network::Mainnet
} else {
Network::Testnet
},
project_id,
}
}
pub async fn plutus_data_from_hash(&self, datum_hash: &str) -> Result<PlutusData> {
let x = self.api.scripts_datum_hash_cbor(datum_hash).await?;
let data = x
.as_object()
.expect("Expect an object")
.get("cbor")
.expect("Expect key `cbor`")
.as_str()
.expect("Expect value to be string");
plutus_data_from_inline(&data)
}
pub async fn resolve_datum(
&self,
datum_hash: &Option<String>,
inline_datum: &Option<String>,
) -> Result<Option<DatumOrHash>> {
if let Some(inline_datum) = inline_datum {
Ok(Some(DatumOrHash::Data(plutus_data_from_inline(
inline_datum,
)?)))
} else {
if let Some(datum_hash) = datum_hash {
Ok(Some(DatumOrHash::Data(
self.plutus_data_from_hash(&datum_hash).await?,
)))
} else {
Ok(None)
}
}
}
/// Blockfrost client has the wrong type.
pub async fn scripts_hash_cbor(&self, script_hash: &str) -> Result<Vec<u8>> {
let response = self
.client
.get(&format!("{}/scripts/{}", self.base_url, script_hash))
.header("Accept", "application/json")
.header("project_id", self.project_id.as_str())
.send()
.await
.unwrap();
match response.status() {
reqwest::StatusCode::OK => {
let ResponseCbor { cbor } = response.json::<ResponseCbor>().await.unwrap();
let bytes = hex::decode(cbor)?;
Ok(bytes)
}
_ => Err(anyhow!("No script found")),
}
}
// /// Blockfrost client has incomplete type
// pub async fn script_type(&self, script_hash : &str) -> Result<PlutusVersion>{
// let response = self
// .client
// .get(&format!( "{}/scripts/{}/cbor", self.base_url, script_hash))
// .header("Accept", "application/json")
// .header("project_id", self.project_id.as_str())
// .send()
// .await
// .unwrap();
// match response.status() {
// reqwest::StatusCode::OK => {
// let ResponseScript { plutus_type,.. } = response.json::<ResponseScript>().await.unwrap();
// }
// _ => Err(anyhow!("No script found")),
// }
// }
}
impl CardanoConnect for Blockfrost {
async fn build_parameters(&self) -> BuildParameters {
let params = self
.api
.epochs_latest_parameters()
.await
.expect("failed to fetch protocol parameters");
let pp = ProtocolParameters {
collateral_percent: (params
.collateral_percent
.expect("protocol parameters are missing collateral percent")
as f64)
/ 1e2,
// NOTE: Blockfrost returns cost models out of order. They must be ordered by their
// "ParamName" according to how Plutus defines it, but they are usually found ordered
// by ascending keys, unfortunately. Given that they are unlikely to change anytime
// soon, I am going to bundle them as-is.
cost_model_v3: vec![
100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189,
769, 4, 2, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 1, 1000, 42921,
4, 2, 24548, 29498, 38, 1, 898148, 27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1,
141895, 32, 83150, 32, 15299, 32, 76049, 1, 13169, 4, 22100, 10, 28999, 74, 1,
28999, 74, 1, 43285, 552, 1, 44749, 541, 1, 33852, 32, 68246, 32, 72362, 32, 7243,
32, 7391, 32, 11546, 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1,
90434, 519, 0, 1, 74433, 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1,
1, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 955506, 213312, 0, 2,
270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0, 141992, 32, 100788, 420, 1, 1,
81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32, 25933, 32, 24623, 32,
43053543, 10, 53384111, 14333, 10, 43574283, 26308, 10, 16000, 100, 16000, 100,
962335, 18, 2780678, 6, 442008, 1, 52538055, 3756, 18, 267929, 18, 76433006, 8868,
18, 52948122, 18, 1995836, 36, 3227919, 12, 901022, 1, 166917843, 4307, 36, 284546,
36, 158221314, 26549, 36, 74698472, 36, 333849714, 1, 254006273, 72, 2174038, 72,
2261318, 64571, 4, 207616, 8310, 4, 1293828, 28716, 63, 0, 1, 1006041, 43623, 251,
0, 1,
],
drep_deposit: 500_000_000, // NOTE: Missing from Blockfrost
fee_constant: params.min_fee_b as u64,
fee_coefficient: params.min_fee_a as u64,
min_utxo_deposit_coefficient: params
.coins_per_utxo_size
.expect("protocol parameters are missing min utxo deposit coefficient")
.parse()
.unwrap(),
price_mem: params
.price_mem
.expect("protocol parameters are missing price mem") as f64,
price_steps: params
.price_step
.expect("protocol parameters are missing price step")
as f64,
};
(&pp).into()
}
async fn utxos_at(
&self,
payment: &Credential,
delegation: &Option<Credential>,
) -> Result<Vec<Utxo>> {
let addr = Address::new(self.network_id(), payment.clone(), delegation.clone());
let response = self
.api
.addresses_utxos(&addr.to_bech32()?, Pagination::all())
.await?;
response
.iter()
.map(|o| {
// FIXME: This should pull the datum and script reference
let datum = None;
let script_ref = None;
Ok(Utxo {
input: Input {
transaction_id: v2a(hex::decode(&o.tx_hash)?)?.into(),
index: o.tx_index as u64,
},
output: Output {
address: Address::from_bech32(&o.address).into(),
value: from_tx_content_output_amounts(&o.amount[..]),
datum,
script_ref,
},
})
})
.collect()
}
async fn submit(&self, tx: Vec<u8>) -> Result<String> {
let tx_hash = self.api.transactions_submit(tx).await?;
Ok(tx_hash)
}
fn network(&self) -> Network {
self.network
}
fn health(&self) -> impl std::future::Future<Output = Result<String, String>> + Send {
todo!()
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct ResponseCbor {
cbor: String,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct ResponseScript {
script_hash: String,
#[serde(rename = "type")]
plutus_type: String,
serialised_size: u64,
}
fn from_tx_content_output_amounts(xs: &[TxContentOutputAmountInner]) -> Value {
/// FIXME : Using the sane version of value
let mut lovelaces = 0;
let mut assets = BTreeMap::new();
for asset in xs {
let quantity: u64 = asset.quantity.parse().unwrap();
if asset.unit == UNIT_LOVELACE {
lovelaces += quantity;
} else {
let policy_id: PolicyId = asset.unit[0..56].parse().unwrap();
let asset_name: AssetName = hex::decode(&asset.unit[56..]).unwrap().into();
assets
.entry(policy_id)
.and_modify(|m: &mut BTreeMap<AssetName, u64>| {
m.entry(asset_name.clone())
.and_modify(|q| *q += quantity)
.or_insert(quantity);
})
.or_insert_with(|| BTreeMap::from([(asset_name, quantity)]));
}
}
if assets.is_empty() {
Value::Coin(lovelaces)
} else {
Value::Multiasset(
lovelaces,
NonEmptyKeyValuePairs::Def(
assets
.into_iter()
.map(|(policy_id, policies)| {
(
policy_id,
NonEmptyKeyValuePairs::Def(
policies
.into_iter()
.map(|(asset_name, quantity)| {
(asset_name, quantity.try_into().unwrap())
})
.collect::<Vec<_>>(),
),
)
})
.collect::<Vec<_>>(),
),
)
}
}
pub fn plutus_data_from_inline(inline_datum: &str) -> Result<PlutusData> {
Ok(minicbor::decode(&hex::decode(inline_datum)?)?)
}

View File

@ -0,0 +1,18 @@
[package]
name = "cardano-connect"
version.workspace = true
edition.workspace = true
description.workspace = true
# license.workspace = true // TBC
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
cardano-tx-builder = { path = "../cardano-tx-builder" }

View File

@ -0,0 +1,5 @@
# Cardano connect
> No frills interface to Cardano
Just enough to get konduit running

View File

@ -0,0 +1,20 @@
use anyhow::Result;
use cardano_tx_builder::{
Credential,
Network,
Utxo,
BuildParameters,
};
pub trait CardanoConnect {
fn network(&self) -> Network;
fn build_parameters(&self) -> impl std::future::Future<Output = BuildParameters> + Send;
fn utxos_at(
&self,
payment: &Credential,
delegation: Option<&Credential>,
) -> impl std::future::Future<Output = Result<Vec<Utxo>>> + Send;
fn health(&self) -> impl std::future::Future<Output = Result<String, String>> + Send;
fn submit(&self, tx: Vec<u8>) -> impl std::future::Future<Output = Result<String>> + Send;
}

View File

@ -0,0 +1,26 @@
[package]
name = "cardano-tx-builder"
version.workspace = true
edition.workspace = true
description.workspace = true
# license.workspace = true // TBC
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
bech32 = "0.11.0"
cryptoxide = "0.5.1"
hex = "0.4.3"
minicbor = { version = "0.25.1", features = ["alloc", "derive"] }
pallas-addresses = "0.33.0"
pallas-crypto = "0.33.0"
pallas-codec = "0.33.0"
pallas-primitives = "0.33.0"
uplc = "1.1.19"

View File

@ -0,0 +1,9 @@
# Cardano tx builder
> The bits of cardano we need for konduit. No more, no less.
## Aims
- Provide an ergonomic interface for handling cardano data.
- Contain nothing konduit specific.
- Downstream Konduit tools depend on this crate, and not any pallas crate.

View File

@ -1,7 +1,8 @@
/// Seems like quite a lot of code just to pullout `from_bech32` on address
/// This is mostly extracted from private function of pallas_addresses
use anyhow::{anyhow, Result};
use pallas_addresses::{self, Network};
use anyhow::{Result, anyhow};
use pallas_addresses;
pub use pallas_addresses::Network;
use pallas_crypto::hash::Hash;
use pallas_primitives::NetworkId;
@ -26,7 +27,6 @@ fn parse_network(header: u8) -> Network {
}
}
/// Credential
#[derive(Debug, PartialEq)]
@ -65,12 +65,15 @@ pub struct Address {
}
impl Address {
pub fn new(network: Network, payment: Credential, delegation: Option<Credential>) -> Self {
Self { network, payment, delegation }
Self {
network,
payment,
delegation,
}
}
pub fn from_bech32(s : &str) -> Result<Self> {
pub fn from_bech32(s: &str) -> Result<Self> {
let (_, bytes) = bech32::decode(s)?;
let x = Address::try_from(bytes)?;
Ok(x)
@ -78,16 +81,32 @@ impl Address {
}
impl TryFrom<Vec<u8>> for Address {
fn try_from(bytes : Vec<u8>) -> std::result::Result<Self, Self::Error> {
fn try_from(bytes: Vec<u8>) -> std::result::Result<Self, Self::Error> {
let header = *bytes.first().expect("Missing bytes");
let network = parse_network(header);
let payment : Hash<28> = v2a(bytes[1..=28].to_vec())?.into();
let payment: Hash<28> = v2a(bytes[1..=28].to_vec())?.into();
let delegation = v2a(bytes[28..].to_vec());
match header & 0b1111_0000 {
0b0000_0000 => Ok(Address::new(network, Credential::Key(payment), Some(Credential::Key(delegation?.into())))),
0b0001_0000 => Ok(Address::new(network, Credential::Script(payment),Some(Credential::Key(delegation?.into())))),
0b0010_0000 => Ok(Address::new(network, Credential::Key(payment), Some(Credential::Script(delegation?.into())))),
0b0011_0000 => Ok(Address::new(network, Credential::Script(payment), Some(Credential::Script(delegation?.into())))),
0b0000_0000 => Ok(Address::new(
network,
Credential::Key(payment),
Some(Credential::Key(delegation?.into())),
)),
0b0001_0000 => Ok(Address::new(
network,
Credential::Script(payment),
Some(Credential::Key(delegation?.into())),
)),
0b0010_0000 => Ok(Address::new(
network,
Credential::Key(payment),
Some(Credential::Script(delegation?.into())),
)),
0b0011_0000 => Ok(Address::new(
network,
Credential::Script(payment),
Some(Credential::Script(delegation?.into())),
)),
0b0110_0000 => Ok(Address::new(network, Credential::Key(payment), None)),
0b0111_0000 => Ok(Address::new(network, Credential::Script(payment), None)),
_ => Err(anyhow!("Header not supported")),
@ -109,7 +128,6 @@ impl Into<pallas_addresses::ShelleyAddress> for Address {
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -120,10 +138,10 @@ mod tests {
let addr0 = Address::from_bech32(testnet).unwrap();
let mainnet = "addr1vypymjsk49r6p9f2f6d3gm04ther0ka7ts8jsv8dukg4nvgr9x6tq";
let addr1 = Address::from_bech32(mainnet).unwrap();
assert_eq!(addr0.delegation,addr1.delegation, "oops");
let sa0 : pallas_addresses::ShelleyAddress = addr0.into();
assert_eq!(addr0.delegation, addr1.delegation, "oops");
let sa0: pallas_addresses::ShelleyAddress = addr0.into();
assert_eq!(sa0.to_bech32().unwrap(), testnet, "oops");
let sa1 : pallas_addresses::ShelleyAddress = addr1.into();
let sa1: pallas_addresses::ShelleyAddress = addr1.into();
assert_eq!(sa1.to_bech32().unwrap(), mainnet, "oops");
}
}

View File

@ -0,0 +1,24 @@
use pallas_codec::utils::CborWrap;
use super::pallas::era;
use super::plutus_data::PlutusData;
/// Datum
/// We are not being too clever with this.
/// We make use of rust's `Optional` for the no datum case.
/// We are yet to distinguish cases where we need the data,
/// in place of the hash.
/// It should be documented.
pub enum DatumOrHash {
Hash([u8; 32]), // Check this
Data(PlutusData),
}
impl Into<era::DatumOption> for DatumOrHash {
fn into(self) -> era::DatumOption {
match self {
DatumOrHash::Hash(hash) => era::DatumOption::Hash(hash.into()),
DatumOrHash::Data(plutus_data) => era::DatumOption::Data(CborWrap(plutus_data)),
}
}
}

View File

@ -0,0 +1,3 @@
/// Input
/// No change from pallas primitives here.
pub type Input = pallas_primitives::TransactionInput;

View File

@ -0,0 +1,29 @@
mod pallas;
mod utils;
pub mod plutus_data;
pub use plutus_data::PlutusData;
pub mod input;
pub use input::Input;
pub mod address;
pub use address::{Address, Credential, Network};
pub mod value;
pub use value::{MultiAsset, Value};
pub mod datum;
pub use datum::DatumOrHash;
pub mod script;
pub use script::Script;
pub mod output;
pub use output::Output;
pub mod utxo;
pub use utxo::Utxo;
pub mod tx;
pub use tx::{BuildParameters, Tx};

View File

@ -0,0 +1,32 @@
use super::pallas::era;
use super::address::Address;
use super::datum::DatumOrHash;
use super::script::Script;
use super::value::Value;
/// Output
pub struct Output {
pub address: Address,
pub value: Value,
pub datum: Option<DatumOrHash>,
pub script_ref: Option<Script>,
}
impl Into<era::PostAlonzoTransactionOutput> for Output {
fn into(self) -> era::PostAlonzoTransactionOutput {
let address: pallas_addresses::ShelleyAddress = self.address.into();
era::PostAlonzoTransactionOutput {
address: address.to_vec().into(),
datum_option: self.datum.map(|x| x.into()),
value: self.value.into(),
script_ref: self.script_ref.map(|x| x.into()),
}
}
}
impl Into<era::PseudoTransactionOutput<era::PostAlonzoTransactionOutput>> for Output {
fn into(self) -> era::PseudoTransactionOutput<era::PostAlonzoTransactionOutput> {
era::PseudoTransactionOutput::PostAlonzo(self.into())
}
}

View File

@ -0,0 +1 @@
pub use pallas_primitives::conway as era;

View File

@ -0,0 +1,93 @@
use anyhow::{Result, anyhow};
pub use pallas_primitives::PlutusData;
pub fn bytes(b: &[u8]) -> PlutusData {
PlutusData::BoundedBytes(pallas_primitives::BoundedBytes::from(
Into::<Vec<u8>>::into(b),
))
}
pub fn unbytes(u: &PlutusData) -> Result<Vec<u8>, anyhow::Error> {
match u {
PlutusData::BoundedBytes(b) => Ok(b.to_vec()),
_ => Err(anyhow!("not int")),
}
}
pub fn int(i: i128) -> PlutusData {
PlutusData::BigInt(pallas_primitives::BigInt::Int(
pallas_primitives::Int::try_from(i).unwrap(),
))
}
pub fn unint(u: &PlutusData) -> Result<i128> {
// Pattern match to ensure we have the correct variant.
match u {
PlutusData::BigInt(pallas_primitives::BigInt::Int(x)) => {
// Access the inner `i128` value from the newtype `Int(i128)`.
let y = x.0.try_into()?;
Ok(y)
}
_ => Err(anyhow!("not an integer PlutusData")),
}
}
pub fn list(v: &Vec<PlutusData>) -> PlutusData {
PlutusData::Array(pallas_primitives::MaybeIndefArray::Indef(v.clone()))
}
pub fn unlist(u: &PlutusData) -> Result<Vec<PlutusData>> {
match u {
PlutusData::Array(pallas_primitives::MaybeIndefArray::Indef(v)) => Ok(v.clone()),
_ => Err(anyhow!("not list")),
}
}
pub fn constr(constr_index: u64, v: Vec<PlutusData>) -> PlutusData {
let fields = pallas_primitives::MaybeIndefArray::Indef(v.clone());
constr_arr(constr_index, fields)
}
pub fn constr_arr(
constr_index: u64,
fields: pallas_primitives::MaybeIndefArray<PlutusData>,
) -> PlutusData {
let tag = if constr_index < 8 {
121 + constr_index
} else {
panic!("I don't know what the number is");
};
// FIXME :: What is this?
let any_constructor: Option<u64> = None;
PlutusData::Constr(pallas_primitives::Constr {
tag,
any_constructor,
fields,
})
}
pub fn unconstr(u: &PlutusData) -> Result<(u64, Vec<PlutusData>)> {
match u {
PlutusData::Constr(pallas_primitives::Constr { tag, fields, .. }) => {
let constr_index = if *tag < 128 {
tag - 121
} else {
panic!("I don't know what the number is");
};
Ok((constr_index, fields.clone().to_vec()))
}
_ => Err(anyhow!("not list")),
}
}
// FIXME :: These require fixing the error traits
//
// pub fn to_cbor<T: Into<PlutusData>>(x: T) -> Result<Vec<u8>> {
// let v = minicbor::to_vec(PlutusData::from(x))?;
// Ok(v)
// }
//
// pub fn from_cbor<T: TryFrom<PlutusData>>(x: &[u8]) -> Result<T> {
// let d: PlutusData = minicbor::decode(x)?;
// T::try_from(&d)?
// }

View File

@ -0,0 +1,28 @@
use super::pallas::era;
use pallas_codec::utils::CborWrap;
use pallas_primitives::PlutusScript;
/// Script.
/// There is no support for native scripts,
/// and we only really care about V3.
pub enum Script {
V1(Vec<u8>),
V2(Vec<u8>),
V3(Vec<u8>),
}
impl Into<era::PseudoScript<era::NativeScript>> for Script {
fn into(self) -> era::PseudoScript<era::NativeScript> {
match self {
Script::V1(items) => era::PseudoScript::PlutusV1Script(PlutusScript(items.into())),
Script::V2(items) => era::PseudoScript::PlutusV2Script(PlutusScript(items.into())),
Script::V3(items) => era::PseudoScript::PlutusV3Script(PlutusScript(items.into())),
}
}
}
impl Into<CborWrap<era::PseudoScript<era::NativeScript>>> for Script {
fn into(self) -> CborWrap<era::PseudoScript<era::NativeScript>> {
CborWrap(self.into())
}
}

View File

@ -4,33 +4,32 @@
// SOURCE : github.com/cardanoSolutions/zhuli
/// FIXME :: Refactor to:
/// - depend on local primitives, and less on pallas primitives
/// - move stuff from here to the suitable module
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
use pallas_codec::{
minicbor as cbor,
utils::{NonEmptyKeyValuePairs, NonEmptySet, Set},
};
use pallas_crypto::hash::{Hash, Hasher};
use pallas_primitives::{
conway::{
AssetName, Constr, ExUnits, Language, Multiasset, NetworkId, PlutusData,
PostAlonzoTransactionOutput, PseudoTransactionOutput, RedeemerTag, RedeemersKey,
RedeemersValue, TransactionBody, TransactionInput, TransactionOutput, Tx, Value,
WitnessSet,
},
MaybeIndefArray,
};
use pallas_primitives::MaybeIndefArray;
use super::pallas::era;
use std::{cmp::Ordering, str::FromStr};
use uplc::tx::{eval_phase_two, ResolvedInput, SlotConfig};
use uplc::tx::{ResolvedInput, SlotConfig, eval_phase_two};
pub use pallas_primitives::conway::Tx;
#[derive(Debug)]
pub struct BuildParams {
pub struct BuildParameters {
pub fee_constant: u64,
pub fee_coefficient: u64,
pub price_mem: f64,
pub price_steps: f64,
}
pub struct OutputReference(pub TransactionInput);
pub struct OutputReference(pub era::TransactionInput);
impl FromStr for OutputReference {
type Err = String;
@ -43,7 +42,7 @@ impl FromStr for OutputReference {
let index: u64 = ix_str
.parse()
.map_err(|e| format!("failed to decode output index: {e:?}"))?;
Ok(OutputReference(TransactionInput {
Ok(OutputReference(era::TransactionInput {
transaction_id,
index,
}))
@ -56,24 +55,28 @@ impl FromStr for OutputReference {
pub struct Redeemer {}
impl Redeemer {
pub fn mint(index: u32, data: PlutusData, ex_units: ExUnits) -> (RedeemersKey, RedeemersValue) {
pub fn mint(
index: u32,
data: era::PlutusData,
ex_units: era::ExUnits,
) -> (era::RedeemersKey, era::RedeemersValue) {
(
RedeemersKey {
tag: RedeemerTag::Mint,
era::RedeemersKey {
tag: era::RedeemerTag::Mint,
index,
},
RedeemersValue { data, ex_units },
era::RedeemersValue { data, ex_units },
)
}
pub fn spend(
(inputs, target): (&[TransactionInput], &TransactionInput),
data: PlutusData,
ex_units: ExUnits,
) -> (RedeemersKey, RedeemersValue) {
(inputs, target): (&[era::TransactionInput], &era::TransactionInput),
data: era::PlutusData,
ex_units: era::ExUnits,
) -> (era::RedeemersKey, era::RedeemersValue) {
(
RedeemersKey {
tag: RedeemerTag::Spend,
era::RedeemersKey {
tag: era::RedeemerTag::Spend,
index: inputs
.iter()
.enumerate()
@ -81,47 +84,51 @@ impl Redeemer {
.unwrap()
.0 as u32,
},
RedeemersValue { data, ex_units },
era::RedeemersValue { data, ex_units },
)
}
pub fn publish(
index: u32,
data: PlutusData,
ex_units: ExUnits,
) -> (RedeemersKey, RedeemersValue) {
data: era::PlutusData,
ex_units: era::ExUnits,
) -> (era::RedeemersKey, era::RedeemersValue) {
(
RedeemersKey {
tag: RedeemerTag::Cert,
era::RedeemersKey {
tag: era::RedeemerTag::Cert,
index,
},
RedeemersValue { data, ex_units },
era::RedeemersValue { data, ex_units },
)
}
pub fn vote(index: u32, data: PlutusData, ex_units: ExUnits) -> (RedeemersKey, RedeemersValue) {
pub fn vote(
index: u32,
data: era::PlutusData,
ex_units: era::ExUnits,
) -> (era::RedeemersKey, era::RedeemersValue) {
(
RedeemersKey {
tag: RedeemerTag::Vote,
era::RedeemersKey {
tag: era::RedeemerTag::Vote,
index,
},
RedeemersValue { data, ex_units },
era::RedeemersValue { data, ex_units },
)
}
}
pub fn void() -> PlutusData {
PlutusData::Constr(Constr {
pub fn void() -> era::PlutusData {
era::PlutusData::Constr(era::Constr {
tag: 121,
any_constructor: None,
fields: MaybeIndefArray::Indef(vec![]),
})
}
pub fn from_network(network: Network) -> NetworkId {
pub fn from_network(network: Network) -> era::NetworkId {
match network {
Network::Mainnet => NetworkId::Mainnet,
_ => NetworkId::Testnet,
Network::Mainnet => era::NetworkId::Mainnet,
_ => era::NetworkId::Testnet,
}
}
@ -148,17 +155,17 @@ where
}
}
pub fn into_outputs(outputs: Vec<PostAlonzoTransactionOutput>) -> Vec<TransactionOutput> {
pub fn into_outputs(outputs: Vec<era::PostAlonzoTransactionOutput>) -> Vec<era::TransactionOutput> {
outputs
.into_iter()
.map(PseudoTransactionOutput::PostAlonzo)
.map(era::PseudoTransactionOutput::PostAlonzo)
.collect()
}
pub fn singleton_assets<T: Clone>(
validator_hash: Hash<28>,
assets: &[(AssetName, T)],
) -> Multiasset<T> {
assets: &[(era::AssetName, T)],
) -> era::Multiasset<T> {
NonEmptyKeyValuePairs::Def(vec![(
validator_hash,
NonEmptyKeyValuePairs::Def(assets.to_vec()),
@ -176,32 +183,32 @@ pub fn from_validator(validator: &[u8], network_id: Network) -> (Hash<28>, Shell
(validator_hash, validator_address)
}
pub fn value_subtract_lovelace(value: Value, lovelace: u64) -> Option<Value> {
pub fn value_subtract_lovelace(value: era::Value, lovelace: u64) -> Option<era::Value> {
match value {
Value::Coin(total) if total > lovelace => Some(Value::Coin(total - lovelace)),
Value::Multiasset(total, assets) if total > lovelace => {
Some(Value::Multiasset(total - lovelace, assets))
era::Value::Coin(total) if total > lovelace => Some(era::Value::Coin(total - lovelace)),
era::Value::Multiasset(total, assets) if total > lovelace => {
Some(era::Value::Multiasset(total - lovelace, assets))
}
_ => None,
}
}
pub fn value_add_lovelace(value: Value, lovelace: u64) -> Value {
pub fn value_add_lovelace(value: era::Value, lovelace: u64) -> era::Value {
match value {
Value::Coin(total) => Value::Coin(total + lovelace),
Value::Multiasset(total, assets) => Value::Multiasset(total + lovelace, assets),
era::Value::Coin(total) => era::Value::Coin(total + lovelace),
era::Value::Multiasset(total, assets) => era::Value::Multiasset(total + lovelace, assets),
}
}
pub fn lovelace_of(value: &Value) -> u64 {
pub fn lovelace_of(value: &era::Value) -> u64 {
match value {
Value::Coin(lovelace) | Value::Multiasset(lovelace, _) => *lovelace,
era::Value::Coin(lovelace) | era::Value::Multiasset(lovelace, _) => *lovelace,
}
}
pub fn new_min_value_output<F>(per_byte: u64, build: F) -> PostAlonzoTransactionOutput
pub fn new_min_value_output<F>(per_byte: u64, build: F) -> era::PostAlonzoTransactionOutput
where
F: Fn(u64) -> PostAlonzoTransactionOutput,
F: Fn(u64) -> era::PostAlonzoTransactionOutput,
{
let value = build(1);
let mut buffer: Vec<u8> = Vec::new();
@ -213,7 +220,7 @@ where
build((buffer.len() as u64 + 164) * per_byte)
}
pub fn total_execution_cost(params: &BuildParams, redeemers: &[ExUnits]) -> u64 {
pub fn total_execution_cost(params: &BuildParameters, redeemers: &[era::ExUnits]) -> u64 {
redeemers.iter().fold(0, |acc, ex_units| {
acc + ((params.price_mem * ex_units.mem as f64).ceil() as u64)
+ ((params.price_steps * ex_units.steps as f64).ceil() as u64)
@ -221,9 +228,9 @@ pub fn total_execution_cost(params: &BuildParams, redeemers: &[ExUnits]) -> u64
}
pub fn script_integrity_hash(
redeemers: Option<&NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>>,
datums: Option<&NonEmptyKeyValuePairs<Hash<32>, PlutusData>>,
language_views: &[(Language, &[i64])],
redeemers: Option<&NonEmptyKeyValuePairs<era::RedeemersKey, era::RedeemersValue>>,
datums: Option<&NonEmptyKeyValuePairs<Hash<32>, era::PlutusData>>,
language_views: &[(era::Language, &[i64])],
) -> Option<Hash<32>> {
if redeemers.is_none() && language_views.is_empty() && datums.is_none() {
return None;
@ -243,15 +250,15 @@ pub fn script_integrity_hash(
let mut views = language_views.to_vec();
// TODO: Derive an Ord instance in Pallas.
views.sort_by(|(a, _), (b, _)| match (a, b) {
(Language::PlutusV3, Language::PlutusV3) => Ordering::Equal,
(Language::PlutusV3, _) => Ordering::Greater,
(_, Language::PlutusV3) => Ordering::Less,
(era::Language::PlutusV3, era::Language::PlutusV3) => Ordering::Equal,
(era::Language::PlutusV3, _) => Ordering::Greater,
(_, era::Language::PlutusV3) => Ordering::Less,
(Language::PlutusV2, Language::PlutusV2) => Ordering::Equal,
(Language::PlutusV2, _) => Ordering::Greater,
(_, Language::PlutusV2) => Ordering::Less,
(era::Language::PlutusV2, era::Language::PlutusV2) => Ordering::Equal,
(era::Language::PlutusV2, _) => Ordering::Greater,
(_, era::Language::PlutusV2) => Ordering::Less,
(Language::PlutusV1, Language::PlutusV1) => Ordering::Equal,
(era::Language::PlutusV1, era::Language::PlutusV1) => Ordering::Equal,
});
cbor::encode(NonEmptyKeyValuePairs::Def(views), &mut preimage).unwrap()
}
@ -259,8 +266,8 @@ pub fn script_integrity_hash(
Some(Hasher::<256>::hash(&preimage))
}
pub fn default_transaction_body() -> TransactionBody {
TransactionBody {
pub fn default_transaction_body() -> era::TransactionBody {
era::TransactionBody {
auxiliary_data_hash: None,
certificates: None,
collateral: None,
@ -284,8 +291,8 @@ pub fn default_transaction_body() -> TransactionBody {
}
}
pub fn default_witness_set() -> WitnessSet {
WitnessSet {
pub fn default_witness_set() -> era::WitnessSet {
era::WitnessSet {
bootstrap_witness: None,
native_script: None,
plutus_data: None,
@ -300,16 +307,20 @@ pub fn default_witness_set() -> WitnessSet {
// Build a transaction by repeatedly executing some building logic with different fee and execution
// units settings. Stops when a fixed point is reached. The final transaction has corresponding
// fees and execution units.
pub fn build_transaction<F>(params: &BuildParams, resolved_inputs: &[ResolvedInput], with: F) -> Tx
pub fn build_transaction<F>(
params: &BuildParameters,
resolved_inputs: &[ResolvedInput],
with: F,
) -> Tx
where
F: Fn(u64, &[ExUnits]) -> Tx,
F: Fn(u64, &[era::ExUnits]) -> Tx,
{
let empty_ex_units = || {
vec![
ExUnits { mem: 0, steps: 0 },
ExUnits { mem: 0, steps: 0 },
ExUnits { mem: 0, steps: 0 },
ExUnits { mem: 0, steps: 0 },
era::ExUnits { mem: 0, steps: 0 },
era::ExUnits { mem: 0, steps: 0 },
era::ExUnits { mem: 0, steps: 0 },
era::ExUnits { mem: 0, steps: 0 },
]
};
@ -390,8 +401,8 @@ where
tx
}
pub fn expect_post_alonzo(output: &TransactionOutput) -> &PostAlonzoTransactionOutput {
if let TransactionOutput::PostAlonzo(ref o) = output {
pub fn expect_post_alonzo(output: &era::TransactionOutput) -> &era::PostAlonzoTransactionOutput {
if let era::TransactionOutput::PostAlonzo(o) = output {
o
} else {
panic!("expected PostAlonzo output but got a legacy one.")

View File

@ -0,0 +1,7 @@
use anyhow::{Result, anyhow};
/// Handles the map error
pub fn v2a<T, const N: usize>(v: Vec<T>) -> Result<[T; N]> {
<[T; N]>::try_from(v)
.map_err(|v: Vec<T>| anyhow!("Expected a Vec of length {}, but got {}", N, v.len()))
}

View File

@ -0,0 +1,18 @@
use super::input::Input;
use super::output::Output;
/// Utxo
/// Aka resolved input.
pub struct Utxo {
pub input: Input,
pub output: Output,
}
impl Into<uplc::tx::ResolvedInput> for Utxo {
fn into(self) -> uplc::tx::ResolvedInput {
uplc::tx::ResolvedInput {
input: self.input,
output: self.output.into(),
}
}
}

View File

@ -0,0 +1,31 @@
// TODO: Write some sane value handling fuctions:
// - add
// - sub
// - prune
// - split (negative, positive)
// - is_positive
use super::pallas::era;
use std::collections::HashMap;
/// Naive version of value
pub type MultiAsset = HashMap<[u8; 28], HashMap<Vec<u8>, u64>>;
#[derive(Debug)]
pub struct Value(pub u64, pub MultiAsset);
impl Value {
pub fn add(self, _other: Value) -> Self {
todo!()
}
pub fn sub(self, _other: Value) -> Self {
todo!()
}
}
impl Into<era::Value> for Value {
fn into(self) -> era::Value {
todo!()
}
}

View File

@ -0,0 +1,27 @@
[package]
name = "konduit-cli"
version.workspace = true
edition.workspace = true
description.workspace = true
# license.workspace = true // TBC
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
bech32 = "0.11.0"
clap = { version = "4.5.18", features = ["cargo", "derive"] }
cryptoxide = "0.5.1"
hex = "0.4.3"
minicbor = { version = "0.25.1", features = ["alloc", "derive"] }
rand = "0.9.2"
reqwest = { version = "0.12.23", features = ["json"] }
serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.138"
tokio = { version = "1.47.1", features = ["full"] }

View File

@ -5,10 +5,7 @@ use crate::cardano::cardano::Cardano;
use crate::wallet;
pub mod context;
mod open;
pub mod plutus;
pub mod send;
mod torso;
pub fn from_env(env: &HashMap<String, String>) -> context::TxContext<impl Cardano> {
context::TxContext {

View File

@ -0,0 +1,24 @@
[package]
name = "konduit-tx"
version.workspace = true
edition.workspace = true
description.workspace = true
# license.workspace = true // TBC
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
[package.metadata.release]
release = false
[dependencies]
anyhow = "1.0.100"
minicbor = { version = "0.25.1", features = ["alloc", "derive"] }
cardano-tx-builder = { path = "../cardano-tx-builder" }
# Maybe for pretty printing??
hex = "0.4.3"
# Maybe for testing
rand = "0.9.2"

View File

@ -0,0 +1,8 @@
# Konduit tx
> Pure tx builders
## Aims
- Agnostic to excution context
- Compiles to wasm

View File

@ -0,0 +1,3 @@
pub mod data;
pub mod tx;
pub mod utils;

View File

@ -0,0 +1 @@
pub mod open;

View File

@ -0,0 +1,21 @@
use anyhow::{anyhow, Result};
pub fn v2a<T, const N: usize>(v: Vec<T>) -> Result<[T; N]> {
<[T;N]>::try_from(v).map_err(|v: Vec<T>| anyhow!("Expected a Vec of length {}, but got {}", N, v.len()))
}
pub fn concat<T: Clone>(l: &[T], r: &[T]) -> Vec<T> {
let mut n = l.to_vec();
n.extend(r.iter().cloned());
return n;
}
pub fn unzip<A, B>(zipped: Vec<(A, B)>) -> (Vec<A>, Vec<B>) {
let mut va: Vec<A> = Vec::with_capacity(zipped.len());
let mut vb: Vec<B> = Vec::with_capacity(zipped.len());
for (a, b) in zipped.into_iter() {
va.push(a);
vb.push(b);
}
(va, vb)
}

View File

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1757068644,
"narHash": "sha256-NOrUtIhTkIIumj1E/Rsv1J37Yi3xGStISEo8tZm3KW4=",
"lastModified": 1759036355,
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8eb28adfa3dc4de28e792e3bf49fcf9007ca8ac9",
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
"type": "github"
},
"original": {
@ -48,11 +48,11 @@
]
},
"locked": {
"lastModified": 1757298987,
"narHash": "sha256-yuFSw6fpfjPtVMmym51ozHYpJQ7SzVOTkk7tUv2JA0U=",
"lastModified": 1759286284,
"narHash": "sha256-JLdGGc4XDutzSD1L65Ni6Ye+oTm8kWfm0KTPMcyl7Y4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "cfd63776bde44438ff2936f0c9194c79dd407a5f",
"rev": "f6f2da475176bb7cff51faae8b3fe879cd393545",
"type": "github"
},
"original": {

View File

@ -30,8 +30,8 @@
cargoTomlContents = builtins.readFile ./Cargo.toml;
version = (builtins.fromTOML cargoTomlContents).package.version;
rustVersion = (builtins.fromTOML cargoTomlContents).package."rust-version";
version = (builtins.fromTOML cargoTomlContents).workspace.package.version;
rustVersion = (builtins.fromTOML cargoTomlContents).workspace.package."rust-version";
rustToolchain = pkgs.rust-bin.stable.${rustVersion}.default;
@ -48,7 +48,7 @@
buildInputs = with pkgs; [openssl] ++ osxDependencies;
nativeBuildInputs = with pkgs; [pkg-config openssl.dev];
src = pkgs.lib.cleanSourceWith {src = self;};
src = pkgs.lib.cleanSourceWith {src = ./crates/cardano-tx-builder;};
doCheck = false; # dont run cargo test
CARGO_BUILD_TESTS = "false"; # dont even compile test binaries

View File

@ -1,32 +0,0 @@
use anyhow::Result;
use pallas_addresses::{Network, ShelleyPaymentPart};
use pallas_primitives::conway::Tx;
use uplc::{tx::ResolvedInput, TransactionInput};
use crate::cardano_types::{Utxo};
use crate::tx::plutus::BuildParams;
pub trait Cardano {
fn network_id(&self) -> Network;
fn build_parameters(&self) -> impl std::future::Future<Output = BuildParams> + Send;
fn resolve_many(
&self,
inputs: Vec<TransactionInput>,
) -> impl std::future::Future<Output = Vec<Utxo>> + Send;
fn resolve(
&self,
input: TransactionInput,
) -> impl std::future::Future<Output = Result<Utxo>> + Send;
fn transaction_by_hash(
&self,
tx_hash: &str,
) -> impl std::future::Future<Output = Result<Tx>> + Send;
/// This should return all utxos available to be spent,
/// ie regardelss of the staking part.
fn utxos_at(
&self,
payment_credential: &ShelleyPaymentPart,
) -> impl std::future::Future<Output = Result<Vec<ResolvedInput>>> + Send;
fn health(&self) -> impl std::future::Future<Output = Result<String, String>> + Send;
fn submit(&self, tx: Vec<u8>) -> impl std::future::Future<Output = Result<String>> + Send;
}

View File

@ -1,114 +0,0 @@
use pallas_addresses;
use pallas_codec::utils::CborWrap;
use pallas_crypto::hash::Hash;
use pallas_primitives::{
conway::{DatumOption, NativeScript, PostAlonzoTransactionOutput, PseudoScript, PseudoTransactionOutput, Value},
PlutusScript,
};
use uplc::PlutusData;
pub mod address;
use address::Address;
pub mod value;
/// This is a sad file: yet another set of wrapping.
///
/// The pallas types are too confusing.
/// This is surely a consequence of supporting all eras.
/// At least in part.
/// Another part may also be that this support has simply grown,
/// rather than be rationalised.
///
/// Script.
/// There is no support for native scripts,
/// and we only really care about V3.
pub enum Script {
V1(Vec<u8>),
V2(Vec<u8>),
V3(Vec<u8>),
}
impl Into<PseudoScript<NativeScript>> for Script {
fn into(self) -> PseudoScript<NativeScript> {
match self {
Script::V1(items) => PseudoScript::PlutusV1Script(PlutusScript(items.into())),
Script::V2(items) => PseudoScript::PlutusV2Script(PlutusScript(items.into())),
Script::V3(items) => PseudoScript::PlutusV3Script(PlutusScript(items.into())),
}
}
}
impl Into<CborWrap<PseudoScript<NativeScript>>> for Script {
fn into(self) -> CborWrap<PseudoScript<NativeScript>> {
CborWrap(self.into())
}
}
/// Datum
/// We are not being too clever with this.
/// We make use of rust's `Optional` for the no datum case.
/// We are yet to distinguish cases where we need the data,
/// in place of the hash.
/// It should be documented.
pub enum DatumOrHash {
Hash(Hash<32>),// Check this
Data(PlutusData),
}
impl Into<DatumOption> for DatumOrHash {
fn into(self) -> DatumOption {
match self {
DatumOrHash::Hash(hash) => DatumOption::Hash(hash),
DatumOrHash::Data(plutus_data) => DatumOption::Data(CborWrap(plutus_data)),
}
}
}
/// Output
pub struct Output {
pub address: Address,
pub value: Value,
pub datum: Option<DatumOrHash>,
pub script_ref: Option<Script>,
}
impl Into<PostAlonzoTransactionOutput> for Output {
fn into(self) -> PostAlonzoTransactionOutput {
let address : pallas_addresses::ShelleyAddress = self.address.into();
PostAlonzoTransactionOutput {
address: address.to_vec().into(),
value: self.value,
datum_option: self.datum.map(|x| x.into()),
script_ref: self.script_ref.map(|x| x.into()),
}
}
}
impl Into<pallas_primitives::conway::PseudoTransactionOutput<PostAlonzoTransactionOutput>> for Output {
fn into(self) -> PseudoTransactionOutput<PostAlonzoTransactionOutput> {
PseudoTransactionOutput::PostAlonzo(self.into())
}
}
/// Input
/// No change from pallas primitives here.
pub type Input = pallas_primitives::TransactionInput;
/// Utxo
/// Aka resolved input.
pub struct Utxo {
pub input: Input,
pub output: Output,
}
impl Into<uplc::tx::ResolvedInput> for Utxo {
fn into(self) -> uplc::tx::ResolvedInput {
uplc::tx::ResolvedInput {
input: self.input,
output: self.output.into(),
}
}
}

View File

@ -1,17 +0,0 @@
// TODO: Write some sane value handling fuctions:
// - add
// - sub
// - prune
// - split (negative, positive)
// - is_positive
use std::collections::HashMap;
use pallas_crypto::hash::Hash;
/// Naive version of value
pub type MultiAsset = HashMap<Hash<28>, HashMap<Vec<u8>, u64>>;
#[derive(Debug)]
pub struct Value(u64, MultiAsset);

View File

@ -1,182 +0,0 @@
use anyhow::{anyhow, Result};
use pallas_primitives::PlutusData;
use crate::utils::{concat, v2a};
pub fn bytes(b: &[u8]) -> PlutusData {
PlutusData::BoundedBytes(pallas_primitives::BoundedBytes::from(
Into::<Vec<u8>>::into(b),
))
}
pub fn unbytes(u: &PlutusData) -> Result<Vec<u8>, anyhow::Error> {
match u {
PlutusData::BoundedBytes(b) => Ok(b.to_vec()),
_ => Err(anyhow!("not int")),
}
}
pub fn int(i: i128) -> PlutusData {
PlutusData::BigInt(pallas_primitives::BigInt::Int(
pallas_primitives::Int::try_from(i).unwrap(),
))
}
pub fn unint(u: &PlutusData) -> Result<i128> {
// Pattern match to ensure we have the correct variant.
match u {
PlutusData::BigInt(pallas_primitives::BigInt::Int(x)) => {
// Access the inner `i128` value from the newtype `Int(i128)`.
let y = x.0.try_into()?;
Ok(y)
}
_ => Err(anyhow!("not an integer PlutusData")),
}
}
pub fn list(v: &Vec<PlutusData>) -> PlutusData {
PlutusData::Array(pallas_primitives::MaybeIndefArray::Indef(v.clone()))
}
pub fn unlist(u: &PlutusData) -> Result<Vec<PlutusData>> {
match u {
PlutusData::Array(pallas_primitives::MaybeIndefArray::Indef(v)) => Ok(v.clone()),
_ => Err(anyhow!("not list")),
}
}
pub fn constr(constr_index: u64, v: Vec<PlutusData>) -> PlutusData {
let fields = pallas_primitives::MaybeIndefArray::Indef(v.clone());
constr_arr(constr_index, fields)
}
pub fn constr_arr(
constr_index: u64,
fields: pallas_primitives::MaybeIndefArray<PlutusData>,
) -> PlutusData {
let tag = if constr_index < 8 {
121 + constr_index
} else {
panic!("I don't know what the number is");
};
// FIXME :: What is this?
let any_constructor: Option<u64> = None;
PlutusData::Constr(pallas_primitives::Constr {
tag,
any_constructor,
fields,
})
}
pub fn unconstr(u: &PlutusData) -> Result<(u64, Vec<PlutusData>)> {
match u {
PlutusData::Constr(pallas_primitives::Constr { tag, fields, .. }) => {
let constr_index = if *tag < 128 {
tag - 121
} else {
panic!("I don't know what the number is");
};
Ok((constr_index, fields.clone().to_vec()))
}
_ => Err(anyhow!("not list")),
}
}
// Define the `PData` trait.
// It requires any implementing type to be able to convert to and from `PlutusData`.
pub trait PData {
// Converts the current instance into a `PlutusData` representation.
fn to_plutus_data(&self) -> PlutusData;
// Creates a new instance of the implementing type from a `PlutusData` object.
// It returns a `Result` to handle potential conversion errors.
fn from_plutus_data(data: &PlutusData) -> Result<Self>
where
Self: Sized;
}
impl PData for u64 {
fn to_plutus_data(&self) -> PlutusData {
int(*self as i128)
}
fn from_plutus_data(d: &PlutusData) -> Result<Self>
where
Self: Sized,
{
let n: i128 = unint(d)?;
let m: u64 = u64::try_from(n)?;
Ok(m)
}
}
impl PData for i128 {
fn to_plutus_data(&self) -> PlutusData {
int(*self)
}
fn from_plutus_data(d: &PlutusData) -> Result<Self>
where
Self: Sized,
{
let n: i128 = unint(d)?;
Ok(n)
}
}
impl PData for Vec<u8> {
fn to_plutus_data(&self) -> PlutusData {
bytes(self)
}
fn from_plutus_data(data: &PlutusData) -> Result<Self> {
unbytes(data)
}
}
impl<T> PData for Vec<T>
where
T: PData,
{
fn to_plutus_data(&self) -> PlutusData {
list(
&self
.iter()
.map(|x| x.to_plutus_data())
.collect::<Vec<PlutusData>>(),
)
}
fn from_plutus_data(data: &PlutusData) -> Result<Self> {
unlist(data)?
.iter()
.map(|x| PData::from_plutus_data(x))
.collect::<Result<Self>>()
}
}
impl<const N: usize> PData for [u8; N] {
fn to_plutus_data(&self) -> PlutusData {
self.to_vec().to_plutus_data()
}
fn from_plutus_data(data: &PlutusData) -> Result<Self> {
let v = Vec::<u8>::from_plutus_data(data)?;
v2a(v)
}
}
pub fn to_cbor<T: PData>(x: &T) -> Result<Vec<u8>> {
let v = minicbor::to_vec(x.to_plutus_data())?;
Ok(v)
}
pub fn from_cbor<T: PData>(x: &[u8]) -> Result<T> {
let d: PlutusData = minicbor::decode(x)?;
T::from_plutus_data(&d)
}
pub fn mk_msg<T: PData>(tag: &Vec<u8>, body: &T) -> Result<Vec<u8>> {
Ok(concat(tag, &to_cbor(body)?))
}

View File

@ -1,19 +0,0 @@
use pallas_codec::utils::CborWrap;
use pallas_primitives::conway::{DatumOption, NativeScript, PostAlonzoTransactionOutput, PseudoScript, Value};
/// Address needs to be a Vec<u8>
///
pub fn output(
address: Vec<u8>,
value: Value,
datum_option: Option<DatumOption>,
script_ref: PseudoScript<NativeScript>,
) -> PostAlonzoTransactionOutput
{
PostAlonzoTransactionOutput {
address: address.into(),
value,
datum_option,
script_ref : Some(CborWrap(script_ref.into())),
}
}

View File

@ -1,51 +0,0 @@
// /// Torso: the main part of the body relevant to tx building.
// use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
// use pallas_codec::{
// minicbor as cbor,
// utils::{NonEmptyKeyValuePairs, NonEmptySet, Set},
// };
// use pallas_crypto::hash::{Hash, Hasher};
// use pallas_primitives::{conway::{
// AssetName, Constr, ExUnits, Language, Multiasset, NetworkId, PlutusData,
// PostAlonzoTransactionOutput, PseudoTransactionOutput, RedeemerTag, RedeemersKey,
// RedeemersValue, TransactionBody, TransactionInput, TransactionOutput, Tx, Value, WitnessSet,
// }, MaybeIndefArray, PolicyId};
// use std::{cmp::Ordering, collections::HashMap, str::FromStr};
// use uplc::tx::{eval_phase_two, ResolvedInput, SlotConfig};
// struct TransactionBody {
// inputs: Set<TransactionInput>,
// outputs: Vec<TransactionOutput>,
// fee: Coin,
// ttl: Option<u64>,
// certificates: Option<NonEmptySet<Certificate>>,
// withdrawals: Option<NonEmptyKeyValuePairs<RewardAccount, Coin>>,
// auxiliary_data_hash: Option<Bytes>,
// validity_interval_start: Option<u64>,
// mint: Option<Multiasset<NonZeroInt>>,
// script_data_hash: Option<Hash<32>>,
// collateral: Option<NonEmptySet<TransactionInput>>,
// required_signers: Option<RequiredSigners>,
// network_id: Option<NetworkId>,
// collateral_return: Option<T1>,
// total_collateral: Option<Coin>,
// reference_inputs: Option<NonEmptySet<TransactionInput>>,
// voting_procedures: Option<VotingProcedures>,
// proposal_procedures: Option<NonEmptySet<ProposalProcedure>>,
// treasury_value: Option<Coin>,
// donation: Option<PositiveCoin>,
// }
// pub struct Torso {
// inputs: Vec<ResolvedInput>,
// reference_inputs: Vec<ResolvedInput>,
// outputs: Vec<TransactionOutput>,
// mint: HashMap<PolicyId, HashMap<AssetName, i64>>,
// voting_procedures: Vec<Votes>
// collateral: Vec<ResolvedInput>,
// required_signers: Vec<VerificationKeyHash>,
// }
//
// pub struct WitnessPart {
// }