Compare commits
3 Commits
main
...
w/as-works
| Author | SHA1 | Date |
|---|---|---|
|
|
28b7e67277 | |
|
|
7d96313386 | |
|
|
7163525fb1 |
|
|
@ -229,6 +229,48 @@ 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",
|
||||
"futures",
|
||||
"hex",
|
||||
"minicbor 0.25.1",
|
||||
"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 0.25.1",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
"pallas-primitives",
|
||||
"uplc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.38"
|
||||
|
|
@ -1188,22 +1230,30 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bech32 0.11.0",
|
||||
"blockfrost",
|
||||
"blockfrost-openapi",
|
||||
"cardano-connect",
|
||||
"cardano-connect-blockfrost",
|
||||
"cardano-tx-builder",
|
||||
"clap",
|
||||
"cryptoxide 0.5.1",
|
||||
"hex",
|
||||
"minicbor",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"minicbor 2.1.1",
|
||||
"pallas-crypto",
|
||||
"pallas-primitives",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"uplc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "konduit-tx"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cardano-tx-builder",
|
||||
"hex",
|
||||
"minicbor 0.25.1",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1295,6 +1345,12 @@ dependencies = [
|
|||
"minicbor-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f182275033b808ede9427884caa8e05fa7db930801759524ca7925bd8aa7a82"
|
||||
|
||||
[[package]]
|
||||
name = "minicbor-derive"
|
||||
version = "0.15.3"
|
||||
|
|
@ -1469,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8b2737b05f0dbb6d197feeb26ef15d2567e54833184bd469f5655a0537da89fa"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"minicbor",
|
||||
"minicbor 0.25.1",
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
|
|
|
|||
47
Cargo.toml
47
Cargo.toml
|
|
@ -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}}"
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
[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" }
|
||||
futures = "0.3.31"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Cardano connect blockfrost
|
||||
|
||||
> An implementation of Cardano connect for blockfrost
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
// 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::HashMap;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use futures::stream::{self, StreamExt};
|
||||
|
||||
use blockfrost::{BlockfrostAPI, Pagination};
|
||||
use blockfrost_openapi::models::{address_utxo_content_inner::AddressUtxoContentInner, tx_content_output_amount_inner::TxContentOutputAmountInner};
|
||||
use cardano_connect::CardanoConnect;
|
||||
use cardano_tx_builder::{
|
||||
Address, BuildParameters, Credential, Datum, Input, Network, Output, PlutusData, Script, 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<Datum> {
|
||||
match (inline_datum, datum_hash) {
|
||||
(None, None) => Ok(Datum::None),
|
||||
(Some(inline_datum), _) => Ok(Datum::Data(plutus_data_from_inline(inline_datum)?)),
|
||||
(_, Some(datum_hash)) => {
|
||||
Ok(Datum::Data(self.plutus_data_from_hash(&datum_hash).await?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_utxo(
|
||||
&self,
|
||||
bf_utxo: AddressUtxoContentInner,
|
||||
) -> Result<Utxo> {
|
||||
let datum = self
|
||||
.resolve_datum(&bf_utxo.data_hash, &bf_utxo.inline_datum)
|
||||
.await?;
|
||||
let script_ref = match &bf_utxo.reference_script_hash {
|
||||
None => None,
|
||||
Some(hash) => Some(self.resolve_script(&hash).await?),
|
||||
};
|
||||
Ok(Utxo {
|
||||
input: Input {
|
||||
transaction_id: v2a(hex::decode(&bf_utxo.tx_hash)?)?.into(),
|
||||
index: bf_utxo.tx_index as u64,
|
||||
},
|
||||
output: Output {
|
||||
address: Address::from_bech32(&bf_utxo.address)?,
|
||||
value: from_tx_content_output_amounts(&bf_utxo.amount[..])?,
|
||||
datum,
|
||||
script_ref,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Blockfrost client has the wrong type.
|
||||
pub async fn resolve_script(&self, script_hash: &str) -> Result<Script> {
|
||||
let plutus_version = self.plutus_version(script_hash);
|
||||
let bytes = self.scripts_hash_cbor(script_hash);
|
||||
match plutus_version.await? {
|
||||
1 => Ok(Script::V1(bytes.await?)),
|
||||
2 => Ok(Script::V2(bytes.await?)),
|
||||
3 => Ok(Script::V3(bytes.await?)),
|
||||
_ => Err(anyhow!("Unknown script")),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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 plutus_version(&self, script_hash: &str) -> Result<u8> {
|
||||
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();
|
||||
match plutus_type.as_str() {
|
||||
"plutusV1" => Ok(1),
|
||||
"plutusV2" => Ok(2),
|
||||
"plutusV3" => Ok(3),
|
||||
"plutusV4" => Ok(4),
|
||||
_ => Err(anyhow!("Unknown plutus version")),
|
||||
}
|
||||
}
|
||||
_ => Err(anyhow!("No script found")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CardanoConnect for Blockfrost {
|
||||
fn network(&self) -> Network {
|
||||
self.network
|
||||
}
|
||||
|
||||
async fn health(&self) -> Result<String> {
|
||||
Ok(format!("{:?}", self.api.health().await?))
|
||||
}
|
||||
|
||||
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(), payment.clone(), delegation.clone());
|
||||
let response = self
|
||||
.api
|
||||
.addresses_utxos(&addr.to_bech32(), Pagination::all())
|
||||
.await?;
|
||||
let s = stream::iter(response)
|
||||
.map(move |bf_utxo| self.resolve_utxo(bf_utxo))
|
||||
.buffer_unordered(10)
|
||||
.collect::<Vec<Result<Utxo>>>()
|
||||
.await;
|
||||
s.into_iter().collect::<Result<Vec<Utxo>>>()
|
||||
}
|
||||
|
||||
async fn submit(&self, tx: Vec<u8>) -> Result<String> {
|
||||
Ok(self.api.transactions_submit(tx).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[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]) -> Result<Value> {
|
||||
let mut v = Value::zero();
|
||||
for asset in xs {
|
||||
let amount: i128 = asset.quantity.parse()?;
|
||||
if asset.unit == UNIT_LOVELACE {
|
||||
v.add_lovelace(amount);
|
||||
} else {
|
||||
let hash: [u8; 28] = v2a(hex::decode(&asset.unit[0..56])?)?;
|
||||
let name: Vec<u8> = hex::decode(&asset.unit[56..])?;
|
||||
v.add_asset(hash, name, amount);
|
||||
}
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
pub fn plutus_data_from_inline(inline_datum: &str) -> Result<PlutusData> {
|
||||
Ok(minicbor::decode(&hex::decode(inline_datum)?)?)
|
||||
}
|
||||
|
||||
/// Handles the map error
|
||||
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()))
|
||||
}
|
||||
|
|
@ -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" }
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Cardano connect
|
||||
|
||||
> No frills interface to Cardano
|
||||
|
||||
Just enough to get konduit running
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use cardano_tx_builder::{
|
||||
Credential,
|
||||
Network,
|
||||
Utxo,
|
||||
BuildParameters,
|
||||
};
|
||||
|
||||
pub trait CardanoConnect {
|
||||
fn network(&self) -> Network;
|
||||
fn health(&self) -> impl std::future::Future<Output = Result<String>> + Send;
|
||||
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 submit(&self, tx: Vec<u8>) -> impl std::future::Future<Output = Result<String>> + Send;
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,8 +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 pallas_crypto::hash::Hash;
|
||||
use anyhow::{Result, anyhow};
|
||||
use pallas_addresses;
|
||||
pub use pallas_addresses::Network;
|
||||
use pallas_primitives::NetworkId;
|
||||
|
||||
use crate::utils::v2a;
|
||||
|
|
@ -26,13 +26,12 @@ fn parse_network(header: u8) -> Network {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Credential
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Credential {
|
||||
Key(Hash<28>),
|
||||
Script(Hash<28>),
|
||||
Key([u8; 28]),
|
||||
Script([u8; 28]),
|
||||
}
|
||||
|
||||
impl Into<pallas_addresses::ShelleyPaymentPart> for Credential {
|
||||
|
|
@ -65,29 +64,54 @@ 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)
|
||||
}
|
||||
|
||||
pub fn to_bech32(self) -> String {
|
||||
Into::<pallas_addresses::ShelleyAddress>::into(self)
|
||||
.to_bech32()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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: [u8; 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 +133,6 @@ impl Into<pallas_addresses::ShelleyAddress> for Address {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -120,10 +143,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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
use cryptoxide::digest::Digest;
|
||||
use cryptoxide::sha2::Sha256;
|
||||
use cryptoxide::blake2b::Blake2b;
|
||||
|
||||
pub fn sha2_256(msg: &[u8]) -> [u8;32] {
|
||||
let mut output = [0;32];
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(msg);
|
||||
hasher.result(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub fn blake2b_224(msg: &[u8]) -> [u8;28] {
|
||||
let mut output = [0;28];
|
||||
let mut hasher = Blake2b::new(28);
|
||||
hasher.input(msg);
|
||||
hasher.result(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub fn blake2b_256(msg: &[u8]) -> [u8;32] {
|
||||
let mut output = [0;32];
|
||||
let mut hasher = Blake2b::new(32);
|
||||
hasher.input(msg);
|
||||
hasher.result(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
use pallas_codec::utils::CborWrap;
|
||||
|
||||
use super::pallas::era;
|
||||
use super::plutus_data::PlutusData;
|
||||
|
||||
/// Datum
|
||||
/// As it may appear on a utxo
|
||||
#[derive(Debug)]
|
||||
pub enum Datum {
|
||||
None,
|
||||
Hash([u8; 32]), // Check this
|
||||
Data(PlutusData),
|
||||
}
|
||||
|
||||
impl Into<Option<era::DatumOption>> for Datum {
|
||||
fn into(self) -> Option<era::DatumOption> {
|
||||
match self {
|
||||
Datum::None => None,
|
||||
Datum::Hash(hash) => Some(era::DatumOption::Hash(hash.into())),
|
||||
Datum::Data(plutus_data) => Some(era::DatumOption::Data(CborWrap(plutus_data))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
/// Input
|
||||
/// No change from pallas primitives here.
|
||||
pub type Input = pallas_primitives::TransactionInput;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
use cryptoxide::ed25519;
|
||||
|
||||
use super::crypto::blake2b_224;
|
||||
use super::address::{Credential};
|
||||
|
||||
pub struct Skey(pub [u8; 32]);
|
||||
pub struct Vkey(pub [u8; 32]);
|
||||
|
||||
impl Skey {
|
||||
pub fn vkey(&self) -> Vkey {
|
||||
Vkey(ed25519::keypair(&self.0).1)
|
||||
}
|
||||
|
||||
pub fn sign(&self, msg: &[u8]) -> [u8;64] {
|
||||
ed25519::signature(msg.as_ref(), &ed25519::keypair(&self.0).0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Vkey {
|
||||
pub fn hash(&self) -> [u8;28] {
|
||||
blake2b_224(&self.0)
|
||||
}
|
||||
|
||||
pub fn verify(&self, msg: &[u8], sig: &[u8;64]) -> bool {
|
||||
ed25519::verify(msg, &self.0, sig)
|
||||
}
|
||||
|
||||
pub fn credential(&self) -> Credential {
|
||||
Credential::Key(self.hash())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
mod pallas;
|
||||
mod utils;
|
||||
|
||||
pub mod crypto;
|
||||
pub use crypto::{blake2b_224, blake2b_256, sha2_256};
|
||||
|
||||
pub mod key;
|
||||
pub use key::{Skey, Vkey};
|
||||
|
||||
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::Datum;
|
||||
|
||||
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};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
use super::pallas::era;
|
||||
|
||||
use super::address::Address;
|
||||
use super::datum::Datum;
|
||||
use super::script::Script;
|
||||
use super::value::Value;
|
||||
|
||||
/// Output
|
||||
#[derive(Debug)]
|
||||
pub struct Output {
|
||||
pub address: Address,
|
||||
pub value: Value,
|
||||
pub datum: Datum,
|
||||
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.into(),
|
||||
value: self.value.try_into().expect("Failed to coerce value"),
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub use pallas_primitives::conway as era;
|
||||
|
|
@ -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)?
|
||||
// }
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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.
|
||||
#[derive(Debug)]
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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.")
|
||||
|
|
@ -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()))
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
use super::input::Input;
|
||||
use super::output::Output;
|
||||
|
||||
/// Utxo
|
||||
/// Aka resolved input.
|
||||
#[derive(Debug)]
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// TODO: Write some sane value handling fuctions:
|
||||
// - prune
|
||||
// - split (negative, positive)
|
||||
// - is_positive
|
||||
|
||||
use super::pallas::era;
|
||||
use anyhow::{Error, Result};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Naive version of value
|
||||
pub type MultiAsset = BTreeMap<[u8; 28], BTreeMap<Vec<u8>, i128>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Value(pub i128, pub MultiAsset);
|
||||
|
||||
impl Value {
|
||||
pub fn zero() -> Self {
|
||||
Value(0, BTreeMap::new())
|
||||
}
|
||||
|
||||
pub fn add_lovelace(&mut self, amount: i128) {
|
||||
self.0 = self.0 + amount;
|
||||
}
|
||||
|
||||
pub fn add_asset(&mut self, hash: [u8; 28], name: Vec<u8>, amount: i128) {
|
||||
self.1
|
||||
.entry(hash)
|
||||
.and_modify(|tree| {
|
||||
tree.entry(name.clone())
|
||||
.and_modify(|curr| *curr += amount)
|
||||
.or_insert(amount);
|
||||
})
|
||||
.or_insert(vec![(name, amount)].into_iter().collect());
|
||||
}
|
||||
|
||||
pub fn add(&mut self, other: Value) {
|
||||
self.0 += other.0;
|
||||
for (k0, v0) in other.1.iter() {
|
||||
self.1
|
||||
.entry(k0.clone())
|
||||
.and_modify(|curr| {
|
||||
for (k1, v1) in v0.iter() {
|
||||
curr.entry(k1.clone())
|
||||
.and_modify(|quantity| *quantity += v1)
|
||||
.or_insert(v1.clone());
|
||||
}
|
||||
})
|
||||
.or_insert(v0.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn neg(&mut self) {
|
||||
self.0 = -self.0;
|
||||
for class in self.1.values_mut() {
|
||||
for quantity in class.values_mut() {
|
||||
*quantity = 0 - *quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<era::Value> for Value {
|
||||
fn try_into(self) -> Result<era::Value> {
|
||||
if self.1.is_empty() {
|
||||
Ok(era::Value::Coin(self.0.try_into()?))
|
||||
} else {
|
||||
panic!("Not yet implemented")
|
||||
}
|
||||
}
|
||||
type Error = Error;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
[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"
|
||||
pallas-crypto = "0.33.0"
|
||||
# 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"] }
|
||||
cardano-tx-builder = { path = "../cardano-tx-builder" }
|
||||
cardano-connect = { path = "../cardano-connect" }
|
||||
cardano-connect-blockfrost = { path = "../cardano-connect-blockfrost" }
|
||||
minicbor = "2.1.1"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub use cardano_connect::CardanoConnect;
|
||||
use cardano_connect_blockfrost::{Blockfrost, Config};
|
||||
|
||||
const PREFIX: &str = "cardano_";
|
||||
|
||||
pub fn from_env(env: &HashMap<String, String>) -> impl CardanoConnect {
|
||||
let cardano_env: HashMap<String, String> = env
|
||||
.iter()
|
||||
.filter_map(|(k, v)| k.strip_prefix(PREFIX).map(|k| (k.to_string(), v.clone())))
|
||||
.collect();
|
||||
match env.get("cardano") {
|
||||
None => panic!("Expect cardano connection details in env"),
|
||||
Some(s) if s == "blockfrost" => {
|
||||
let config = Config::from_env(&cardano_env);
|
||||
Blockfrost::new(config.project_id)
|
||||
}
|
||||
Some(_s) => panic!("Unkown cardano connection"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
use clap::Subcommand;
|
||||
|
||||
mod cardano;
|
||||
mod data;
|
||||
mod tx;
|
||||
// mod data;
|
||||
// mod tx;
|
||||
mod wallet;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Cmd {
|
||||
/// Txs
|
||||
#[command(subcommand)]
|
||||
Tx(tx::Cmd),
|
||||
#[command(subcommand)]
|
||||
Data(data::Cmd),
|
||||
// #[command(subcommand)]
|
||||
// Tx(tx::Cmd),
|
||||
// #[command(subcommand)]
|
||||
// Data(data::Cmd),
|
||||
#[command(subcommand)]
|
||||
Cardano(cardano::Cmd),
|
||||
#[command(subcommand)]
|
||||
|
|
@ -20,8 +20,8 @@ pub enum Cmd {
|
|||
|
||||
pub fn handle(cmd: Cmd) {
|
||||
match cmd {
|
||||
Cmd::Tx(inner) => tx::handle(inner),
|
||||
Cmd::Data(inner) => data::handle(inner),
|
||||
// Cmd::Tx(inner) => tx::handle(inner),
|
||||
// Cmd::Data(inner) => data::handle(inner),
|
||||
Cmd::Cardano(inner) => cardano::handle(inner),
|
||||
Cmd::Wallet(inner) => wallet::handle(inner),
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use clap::Subcommand;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::cardano::{cardano::Cardano, from_env};
|
||||
use cardano_connect::CardanoConnect;
|
||||
use crate::cardano_connect;
|
||||
use crate::env::get_env;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
|
|
@ -15,7 +16,7 @@ pub fn handle(cmd: Cmd) {
|
|||
match cmd {
|
||||
Cmd::Health => {
|
||||
let env = get_env();
|
||||
let conn = from_env(&env);
|
||||
let conn = cardano_connect::from_env(&env);
|
||||
let rt = Runtime::new().expect("Failed to create Tokio runtime");
|
||||
rt.block_on(async {
|
||||
println!("{:?}", conn.health().await);
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
use clap::Subcommand;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use cardano_connect::CardanoConnect;
|
||||
use cardano_tx_builder::{Address, Network};
|
||||
use crate::cardano_connect;
|
||||
|
||||
use crate::env::get_env;
|
||||
use crate::wallet::{from_env, generate};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
/// Wallet Api
|
||||
pub enum Cmd {
|
||||
/// Gen new skey
|
||||
Gen,
|
||||
/// Show
|
||||
Show,
|
||||
/// Utxos at address. Requires `Cardano` connection.
|
||||
Utxos,
|
||||
}
|
||||
|
||||
pub fn handle(cmd: Cmd) {
|
||||
match cmd {
|
||||
Cmd::Gen => {
|
||||
println!("KONDUIT_WALLET_SKEY={}", hex::encode(generate()));
|
||||
}
|
||||
Cmd::Show => {
|
||||
let env = get_env();
|
||||
let w = from_env(&env);
|
||||
let cred = w.credential();
|
||||
let addr_main = Address::new(Network::Mainnet, cred.clone(), None);
|
||||
let addr_test = Address::new(Network::Testnet, cred.clone(), None);
|
||||
println!("VKEY={}", hex::encode(w.skey.vkey().0));
|
||||
// println!("PAYMENT_CRED={:?}", cred.into().to_bech32());
|
||||
println!("ADDRESS_MAINNET={:?}", addr_main.to_bech32());
|
||||
println!("ADDRESS_TESTNET={:?}", addr_test.to_bech32());
|
||||
}
|
||||
Cmd::Utxos => {
|
||||
let env = get_env();
|
||||
let w = from_env(&env);
|
||||
let w = from_env(&env);
|
||||
let cred = w.credential();
|
||||
let conn = cardano_connect::from_env(&env);
|
||||
let rt = Runtime::new().expect("Failed to create Tokio runtime");
|
||||
rt.block_on(async {
|
||||
println!("{:?}", conn.utxos_at(&w.credential(), &None).await);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
pub mod cardano;
|
||||
pub mod cardano_connect;
|
||||
pub mod cmd;
|
||||
pub mod data;
|
||||
pub mod env;
|
||||
pub mod tx;
|
||||
pub mod utils;
|
||||
pub mod wallet;
|
||||
pub mod cardano_types;
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::cardano;
|
||||
use crate::cardano::cardano::Cardano;
|
||||
use ::cardano_connect::CardanoConnect;
|
||||
|
||||
use crate::cardano_connect;
|
||||
use crate::wallet;
|
||||
|
||||
pub mod context;
|
||||
mod open;
|
||||
pub mod plutus;
|
||||
pub mod send;
|
||||
mod torso;
|
||||
// pub mod send;
|
||||
|
||||
pub fn from_env(env: &HashMap<String, String>) -> context::TxContext<impl Cardano> {
|
||||
pub fn from_env(env: &HashMap<String, String>) -> context::TxContext<impl CardanoConnect> {
|
||||
context::TxContext {
|
||||
cardano: cardano::from_env(env),
|
||||
cardano: cardano_connect::from_env(env),
|
||||
wallet: wallet::from_env(env),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
use anyhow::Result;
|
||||
use cardano_connect::CardanoConnect;
|
||||
use cardano_tx_builder::{Address, BuildParameters, Utxo};
|
||||
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
pub struct TxContext<CardanoT> {
|
||||
pub cardano: CardanoT,
|
||||
pub wallet: Wallet,
|
||||
}
|
||||
|
||||
impl<T: CardanoConnect> TxContext<T> {
|
||||
pub async fn build_parameters(&self) -> BuildParameters {
|
||||
self.cardano.build_parameters().await
|
||||
}
|
||||
|
||||
pub async fn available_utxos(&self) -> Result<Vec<Utxo>> {
|
||||
self.cardano
|
||||
.utxos_at(&self.wallet.credential(), &None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn wallet_address(&self) -> Address {
|
||||
Address::new(
|
||||
self.cardano.network(),
|
||||
self.wallet.credential(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,13 @@
|
|||
use anyhow::Result;
|
||||
use minicbor::encode;
|
||||
use pallas_addresses::{Address, ShelleyAddress, ShelleyDelegationPart};
|
||||
use pallas_primitives::{
|
||||
conway::{
|
||||
PostAlonzoTransactionOutput, TransactionBody, TransactionInput, TransactionOutput, Tx,
|
||||
Value, WitnessSet,
|
||||
},
|
||||
Coin, Nullable, Set,
|
||||
};
|
||||
|
||||
use crate::{cardano::cardano::Cardano, wallet::Wallet};
|
||||
use crate::{
|
||||
, wallet::Wallet
|
||||
};
|
||||
|
||||
use super::{
|
||||
context::TxContext,
|
||||
plutus::{
|
||||
build_transaction, default_transaction_body, default_witness_set, expect_post_alonzo,
|
||||
from_network, into_outputs, value_subtract_lovelace, BuildParams,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::utils::v2a;
|
||||
|
||||
use cardano_tx_builder::{Credential, Skey};
|
||||
use rand::{TryRngCore, rngs::OsRng};
|
||||
|
||||
const PREFIX: &str = "wallet_";
|
||||
|
||||
pub struct Wallet {
|
||||
pub skey: Skey,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn credential(&self) -> Credential {
|
||||
self.skey.vkey().credential()
|
||||
}
|
||||
// pub fn sign(&self, tx: &mut Tx) {
|
||||
// let mut msg = Vec::new();
|
||||
// encode(&tx.transaction_body, &mut msg).unwrap();
|
||||
// let tx_hash = blake2b_256(&msg);
|
||||
// let sig = self.sign_hash(&tx_hash);
|
||||
// tx.transaction_witness_set.vkeywitness = non_empty_set(vec![VKeyWitness {
|
||||
// vkey: self.vkey().to_vec().into(),
|
||||
// signature: sig.as_ref().to_vec().into(),
|
||||
// }])
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn from_env(env: &HashMap<String, String>) -> Wallet {
|
||||
let wallet_env: HashMap<String, String> = env
|
||||
.iter()
|
||||
.filter_map(|(k, v)| k.strip_prefix(PREFIX).map(|k| (k.to_string(), v.clone())))
|
||||
.collect();
|
||||
let raw = wallet_env.get("skey").expect("wallet `skey` not found");
|
||||
let skey = Skey(parse_raw_skey(raw));
|
||||
Wallet { skey }
|
||||
}
|
||||
|
||||
pub fn generate() -> [u8; 32] {
|
||||
let mut key = [0u8; 32];
|
||||
OsRng.try_fill_bytes(&mut key).unwrap();
|
||||
key
|
||||
}
|
||||
|
||||
fn parse_raw_skey(raw: &str) -> [u8; 32] {
|
||||
// FIXME :: Not tested
|
||||
if raw.len() == 64 {
|
||||
// Assume hex
|
||||
v2a(hex::decode(raw).expect("expected hex")).expect("wrong length")
|
||||
} else if raw.len() == 70 {
|
||||
// Assume Bech
|
||||
v2a(bech32::decode(raw).unwrap().1).expect("wrong length")
|
||||
} else {
|
||||
panic!("Not supported")
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Konduit tx
|
||||
|
||||
> Pure tx builders
|
||||
|
||||
## Aims
|
||||
|
||||
- Agnostic to excution context
|
||||
- Compiles to wasm
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
pub mod data;
|
||||
pub mod tx;
|
||||
pub mod utils;
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub mod open;
|
||||
12
flake.lock
12
flake.lock
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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; # don’t run cargo test
|
||||
CARGO_BUILD_TESTS = "false"; # don’t even compile test binaries
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
use clap::Subcommand;
|
||||
use pallas_addresses::{
|
||||
Address, Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart,
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::cardano;
|
||||
use crate::cardano::cardano::Cardano;
|
||||
use crate::env::get_env;
|
||||
use crate::wallet::{from_env, generate, Wallet};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
/// Wallet Api
|
||||
pub enum Cmd {
|
||||
/// Gen new skey
|
||||
Gen,
|
||||
/// Show
|
||||
Show,
|
||||
/// Utxos at address. Requires `Cardano` connection.
|
||||
Utxos,
|
||||
}
|
||||
|
||||
pub fn handle(cmd: Cmd) {
|
||||
match cmd {
|
||||
Cmd::Gen => {
|
||||
println!("KONDUIT_WALLET_KEY={}", hex::encode(generate()));
|
||||
}
|
||||
Cmd::Show => {
|
||||
let env = get_env();
|
||||
let w = from_env(&env);
|
||||
let payment_cred = ShelleyPaymentPart::Key(w.key_hash());
|
||||
let stake_cred = ShelleyDelegationPart::Null;
|
||||
let addr_main =
|
||||
ShelleyAddress::new(Network::Mainnet, payment_cred.clone(), stake_cred.clone());
|
||||
let addr_test =
|
||||
ShelleyAddress::new(Network::Testnet, payment_cred.clone(), stake_cred.clone());
|
||||
println!("VKEY={}", hex::encode(w.vkey()));
|
||||
println!("PAYMENT_CRED={:?}", payment_cred.to_bech32());
|
||||
println!("ADDRESS_MAINNET={:?}", addr_main.to_bech32());
|
||||
println!("ADDRESS_TESTNET={:?}", addr_test.to_bech32());
|
||||
}
|
||||
Cmd::Utxos => {
|
||||
let env = get_env();
|
||||
let w = from_env(&env);
|
||||
let payment_cred = w.payment_credential();
|
||||
let stake_cred = ShelleyDelegationPart::Null;
|
||||
let conn = cardano::from_env(&env);
|
||||
let rt = Runtime::new().expect("Failed to create Tokio runtime");
|
||||
rt.block_on(async {
|
||||
let addr = ShelleyAddress::new(
|
||||
conn.network_id(),
|
||||
payment_cred.clone(),
|
||||
stake_cred.clone(),
|
||||
);
|
||||
println!("{:?}", conn.utxos_at(&w.payment_credential()).await);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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)?))
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
use crate::{cardano::cardano::Cardano, wallet::Wallet};
|
||||
use anyhow::Result;
|
||||
use pallas_addresses::{ShelleyAddress, ShelleyDelegationPart};
|
||||
use uplc::tx::ResolvedInput;
|
||||
|
||||
use super::plutus::BuildParams;
|
||||
|
||||
pub struct TxContext<CardanoT> {
|
||||
pub cardano: CardanoT,
|
||||
pub wallet: Wallet,
|
||||
}
|
||||
|
||||
impl<T: Cardano> TxContext<T> {
|
||||
pub async fn build_parameters(&self) -> BuildParams {
|
||||
self.cardano.build_parameters().await
|
||||
}
|
||||
|
||||
pub async fn available_utxos(&self) -> Result<Vec<ResolvedInput>> {
|
||||
self.cardano
|
||||
.utxos_at(&self.wallet.payment_credential())
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn wallet_address(&self) -> ShelleyAddress {
|
||||
ShelleyAddress::new(
|
||||
self.cardano.network_id(),
|
||||
self.wallet.payment_credential(),
|
||||
ShelleyDelegationPart::Null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
// }
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cryptoxide::hashing::blake2b_256;
|
||||
use minicbor::encode;
|
||||
use pallas_addresses::ShelleyPaymentPart;
|
||||
use pallas_crypto::hash::{Hash, Hasher};
|
||||
use pallas_crypto::key::ed25519::Signature;
|
||||
use pallas_crypto::{self, key::ed25519::SecretKey};
|
||||
use pallas_primitives::conway::{Tx, VKeyWitness};
|
||||
|
||||
use crate::tx::plutus::non_empty_set;
|
||||
use crate::utils::v2a;
|
||||
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
|
||||
const PREFIX: &str = "wallet_";
|
||||
|
||||
pub struct Wallet {
|
||||
pub skey: [u8; 32],
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn vkey(&self) -> [u8; 32] {
|
||||
SecretKey::from(self.skey).public_key().into()
|
||||
}
|
||||
|
||||
pub fn key_hash(&self) -> Hash<28> {
|
||||
Hasher::<224>::hash(SecretKey::from(self.skey).public_key().as_ref())
|
||||
}
|
||||
|
||||
pub fn payment_credential(&self) -> ShelleyPaymentPart {
|
||||
ShelleyPaymentPart::Key(self.key_hash())
|
||||
}
|
||||
|
||||
pub fn sign_hash(&self, h: &[u8; 32]) -> Signature {
|
||||
SecretKey::from(self.skey).sign(h)
|
||||
}
|
||||
|
||||
pub fn sign(&self, tx: &mut Tx) {
|
||||
let mut msg = Vec::new();
|
||||
encode(&tx.transaction_body, &mut msg).unwrap();
|
||||
let tx_hash = blake2b_256(&msg);
|
||||
let sig = self.sign_hash(&tx_hash);
|
||||
tx.transaction_witness_set.vkeywitness = non_empty_set(vec![VKeyWitness {
|
||||
vkey: self.vkey().to_vec().into(),
|
||||
signature: sig.as_ref().to_vec().into(),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_env(env: &HashMap<String, String>) -> Wallet {
|
||||
let wallet_env: HashMap<String, String> = env
|
||||
.iter()
|
||||
.filter_map(|(k, v)| k.strip_prefix(PREFIX).map(|k| (k.to_string(), v.clone())))
|
||||
.collect();
|
||||
let raw = wallet_env.get("key").expect("wallet key not found");
|
||||
let skey = parse_raw_skey(raw);
|
||||
Wallet { skey }
|
||||
}
|
||||
|
||||
pub fn generate() -> [u8; 32] {
|
||||
let mut key = [0u8; 32];
|
||||
OsRng.try_fill_bytes(&mut key).unwrap();
|
||||
key
|
||||
}
|
||||
|
||||
fn parse_raw_skey(raw: &str) -> [u8; 32] {
|
||||
// FIXME :: Not tested
|
||||
if raw.len() == 64 {
|
||||
// Assume hex
|
||||
v2a(hex::decode(raw).expect("expected hex")).expect("wrong length")
|
||||
} else if raw.len() == 70 {
|
||||
// Assume Bech
|
||||
v2a(bech32::decode(raw).unwrap().1).expect("wrong length")
|
||||
} else {
|
||||
panic!("Not supported")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue