From e1793c888e88ad4224886827fdaf38f680fb473d Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Fri, 23 Feb 2024 23:30:47 -0800 Subject: [PATCH] SubCoin:from_base10_f32() + Config docs + Plutus builder dos + misc docs --- chain/rust/src/utils.rs | 16 +- chain/wasm/src/utils.rs | 13 +- docs/docs/index.md | 14 +- .../builders/generating_transactions.mdx | 116 ++++----------- docs/docs/modules/builders/index.mdx | 79 ++++++++++ .../modules/builders/plutus_contracts.mdx | 139 ++++++++++++++++++ docs/docs/modules/multi-era/_category_.json | 7 + docs/docs/modules/multi-era/index.md | 7 + docs/docs/modules/wasm.mdx | 15 ++ 9 files changed, 314 insertions(+), 92 deletions(-) create mode 100644 docs/docs/modules/builders/index.mdx create mode 100644 docs/docs/modules/builders/plutus_contracts.mdx create mode 100644 docs/docs/modules/multi-era/_category_.json create mode 100644 docs/docs/modules/multi-era/index.md create mode 100644 docs/docs/modules/wasm.mdx diff --git a/chain/rust/src/utils.rs b/chain/rust/src/utils.rs index bd20c90d..c1a356ca 100644 --- a/chain/rust/src/utils.rs +++ b/chain/rust/src/utils.rs @@ -11,7 +11,7 @@ use std::io::{BufRead, Seek, Write}; use crate::{ crypto::hash::{hash_script, ScriptHashNamespace}, plutus::{Language, PlutusScript, PlutusV1Script, PlutusV2Script}, - NativeScript, Script, + NativeScript, Script, SubCoin, }; impl Script { @@ -569,6 +569,20 @@ impl Deserialize for NetworkId { } } +impl SubCoin { + /// Converts base 10 floats to SubCoin. + /// This is the format used by blockfrost for ex units + /// Warning: If the passed in float was not meant to be base 10 + /// this might result in a slightly inaccurate fraction. + pub fn from_base10_f32(f: f32) -> Self { + let mut denom = 1u64; + while (f * (denom as f32)).fract().abs() > f32::EPSILON { + denom *= 10; + } + Self::new((f * (denom as f32)).ceil() as u64, denom) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/chain/wasm/src/utils.rs b/chain/wasm/src/utils.rs index be2c0811..fafed94f 100644 --- a/chain/wasm/src/utils.rs +++ b/chain/wasm/src/utils.rs @@ -1,4 +1,4 @@ -use super::{Int, Script, ScriptHash}; +use super::{Int, Script, ScriptHash, SubCoin}; use cml_chain::plutus::Language; use wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue}; @@ -85,3 +85,14 @@ impl NetworkId { self.0.network } } + +#[wasm_bindgen] +impl SubCoin { + /// Converts base 10 floats to SubCoin. + /// This is the format used by blockfrost for ex units + /// Warning: If the passed in float was not meant to be base 10 + /// this might result in a slightly inaccurate fraction. + pub fn from_base10_f32(f: f32) -> Self { + cml_chain::SubCoin::from_base10_f32(f).into() + } +} diff --git a/docs/docs/index.md b/docs/docs/index.md index abc65997..51d3d29b 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -13,16 +13,26 @@ This is a library, written in Rust, that can be deployed to multiple platforms ( ##### NPM packages -TODO: update these once we publish new versions: - browser: [link](https://www.npmjs.com/package/@dcspark/cardano-multiplatform-lib-browser) - nodejs: [link](https://www.npmjs.com/package/@dcspark/cardano-multiplatform-lib-nodejs) + +There is also an outdated asm.js . It is strongly discouraged from using this as it is out of date and asm.js results in incredibly slow cryptographic operations. - asm.js (strongly discouraged): [link](https://www.npmjs.com/package/@dcspark/cardano-multiplatform-lib-asmjs) Note: If you are using WebPack, you must use version 5 or later for CML to work. ##### Rust crates -- crates: [link](https://crates.io/crates/cardano-multiplatform-lib) +The rust crates are split up by functionality. + +- core: [link](https://crates.io/crates/cml-core) +- crypto: [link](https://crates.io/crates/cml-crypto) +- chain: [link](https://crates.io/crates/cml-chain) +- multi-era: [link](https://crates.io/crates/cml-multi-era) +- cip25: [link](https://crates.io/crates/cml-cip25) +- cip36: [link](https://crates.io/crates/cml-cip36) + +Most users will likely be using primarily `cml-chain` for general uses, `cml-multi-era` if they need historical (pre-babbage eras) chain-parsing and `cip25` or `cip36` if they need those specific metadata standards. ##### Mobile bindings diff --git a/docs/docs/modules/builders/generating_transactions.mdx b/docs/docs/modules/builders/generating_transactions.mdx index f0555970..c9b88f58 100644 --- a/docs/docs/modules/builders/generating_transactions.mdx +++ b/docs/docs/modules/builders/generating_transactions.mdx @@ -2,21 +2,6 @@ sidebar_position: 4 --- -# TransactionBuilder - -In order to simplify transaction creation, we provide a `TransactionBuilder` struct that manages witnesses, fee calculation, change addresses and such. Assume we have instantiated an instance under the variable `builder` for this explanation. The `TransactionBuilder` requires several protocol parameters governing Cardano to be created which is shown in the following section. These are specified initially in the genesis file for Cardano nodes. - -The minimum required for a valid transaction is to add inputs, outputs, time-to-live and either set the fee explicitly with `builder.set_fee(fee)`, or calculate it implicitly using `builder.add_change_if_needed(address)`. -Optionally a transaction can also have certificates, reward withdrawals, and metadata added to it. -Any change made to the builder can impact the size and thus the fee so the fee should be the last thing set. -If implicitly setting the fee any extra ADA (`inputs + withdrawals - outputs + refund - deposit - min fee`) is sent to the provided change address. -Fees must be sufficient, i.e. `inputs + withdrawals + refund >= outputs + deposit + fee` which must be manually ensured if you explicitly set the fee. Any extra fee is not necessary and the extra ADA beyond that will be burned. -Once the transaction is ready, `const body = builder.build()` can be called to return a ready `TransactionBody`. - -Withdrawals are ADA withdrawn as part of the rewards generated by staking and deposits are refundable ADA locked while resources such as stake certificates or pool registrations exist on the blockchain. They are returned as refunds when these resources are deregistered/retired. - -To get to a transaction ready to post on the blockchain, we must create a `Transaction` from that, which consists of the `TransactionBody`, a matching `TransactionWitnessSet` and optionally a `TransactionMetadata`. -The witnesses and optional metadata must match those provided to the builder. The witnesses must sign the hash of the transaction body returned by `hash_transaction(body)`. In addition to the witnesses for inputs, withdrawals and some certificates require witnesses as well. For example, staking address registration does not require a witness while stake address de-registration requires one. For any questions or doubts about the rules governing fees, deposits, rewards, certificates or which witness types are required refer to the [shelley specs](https://github.com/input-output-hk/cardano-ledger-specs#cardano-ledger), specifically the Shelley design specification for general design. The formal specification could be useful for specific details as well. The design spec contains details about which certificates require which type of witnesses in the Certificates and Registrations section. ## Example code @@ -27,95 +12,50 @@ Fees are automatically calculated and sent to a change address in the example. ```javascript // instantiate the tx builder with the Cardano protocol parameters - these may change later on -const linearFee = CardanoWasm.LinearFee.new( - CardanoWasm.BigNum.from_str('44'), - CardanoWasm.BigNum.from_str('155381') -); -const txBuilderCfg = CardanoWasm.TransactionBuilderConfigBuilder.new() - .fee_algo(linearFee) - .pool_deposit(CardanoWasm.BigNum.from_str('500000000')) - .key_deposit(CardanoWasm.BigNum.from_str('2000000')) - .max_value_size(4000) - .max_tx_size(8000) - .coins_per_utxo_word(CardanoWasm.BigNum.from_str('34482')) - .build(); -const txBuilder = CardanoWasm.TransactionBuilder.new(txBuilderCfg); - +const txBuilder = makeTxBuilder(); +const testnetId = 0; // add a keyhash input - for ADA held in a Shelley-era normal address (Base, Enterprise, Pointer) -const prvKey = CardanoWasm.PrivateKey.from_bech32("ed25519e_sk16rl5fqqf4mg27syjzjrq8h3vq44jnnv52mvyzdttldszjj7a64xtmjwgjtfy25lu0xmv40306lj9pcqpa6slry9eh3mtlqvfjz93vuq0grl80"); -txBuilder.add_key_input( - prvKey.to_public().hash(), - CardanoWasm.TransactionInput.new( - CardanoWasm.TransactionHash.from_bytes( - Buffer.from("8561258e210352fba2ac0488afed67b3427a27ccf1d41ec030c98a8199bc22ec", "hex") - ), // tx hash +const prvKey = CML.PrivateKey.from_bech32("ed25519e_sk16rl5fqqf4mg27syjzjrq8h3vq44jnnv52mvyzdttldszjj7a64xtmjwgjtfy25lu0xmv40306lj9pcqpa6slry9eh3mtlqvfjz93vuq0grl80"); +const inputAddr = CML.EnterpriseAddress.new(testnetId, CML.StakeCredential.new_key(prvKey.to_public().hash())).to_address(); +txBuilder.add_input(CML.SingleInputBuilder.new( + CML.TransactionInput.new( + CML.TransactionHash.from_hex("8561258e210352fba2ac0488afed67b3427a27ccf1d41ec030c98a8199bc22ec"), // tx hash 0, // index ), - CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('3000000')) -); - -// add a bootstrap input - for ADA held in a Byron-era address -const byronAddress = CardanoWasm.ByronAddress.from_base58("Ae2tdPwUPEZLs4HtbuNey7tK4hTKrwNwYtGqp7bDfCy2WdR3P6735W5Yfpe"); -txBuilder.add_bootstrap_input( - byronAddress, - CardanoWasm.TransactionInput.new( - CardanoWasm.TransactionHash.from_bytes( - Buffer.from("488afed67b342d41ec08561258e210352fba2ac030c98a8199bc22ec7a27ccf1", "hex"), - ), // tx hash - 0, // index - ), - CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('3000000')) + CML.TransactionOutput.new( + inputAddr, + CML.Value.from_coin(BigInt(6000000)), + ) ); // base address -const shelleyOutputAddress = CardanoWasm.Address.from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w"); +const outputAddress = CML.Address.from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w"); // pointer address -const shelleyChangeAddress = CardanoWasm.Address.from_bech32("addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpsqe70et"); +const changeAddress = CML.Address.from_bech32("addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpsqe70et"); // add output to the tx txBuilder.add_output( - CardanoWasm.TransactionOutput.new( - shelleyOutputAddress, - CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('1000000')) - ), + CML.TransactionOutputBuilder() + .with_address(outputAddress) + .next() + .with_value(CML.Value.from_coin(BigInt(1000000))) + .build() ); -// set the time to live - the absolute slot value before the tx becomes invalid -txBuilder.set_ttl(410021); - // calculate the min fee required and send any change to an address -txBuilder.add_change_if_needed(shelleyChangeAddress); - -// once the transaction is ready, we build it to get the tx body without witnesses -const txBody = txBuilder.build(); -const txHash = CardanoWasm.hash_transaction(txBody); -const witnesses = CardanoWasm.TransactionWitnessSet.new(); - -// add keyhash witnesses -const vkeyWitnesses = CardanoWasm.Vkeywitnesses.new(); -const vkeyWitness = CardanoWasm.make_vkey_witness(txHash, prvKey); -vkeyWitnesses.add(vkeyWitness); -witnesses.set_vkeys(vkeyWitnesses); - -// add bootstrap (Byron-era) witnesses -const cip1852Account = CardanoWasm.Bip32PrivateKey.from_bech32('xprv1hretan5mml3tq2p0twkhq4tz4jvka7m2l94kfr6yghkyfar6m9wppc7h9unw6p65y23kakzct3695rs32z7vaw3r2lg9scmfj8ec5du3ufydu5yuquxcz24jlkjhsc9vsa4ufzge9s00fn398svhacse5su2awrw'); -const bootstrapWitnesses = CardanoWasm.BootstrapWitnesses.new(); -const bootstrapWitness = CardanoWasm.make_icarus_bootstrap_witness( - txHash, - byronAddress, - cip1852Account, +// this moves onto the next step of building the transaction: providing witnesses +const signedTxBuilder = tx_builder.build( + changeAddress, + CML.ChangeSelectionAlgo.Default ); -bootstrapWitnesses.add(bootstrapWitness); -witnesses.set_bootstraps(bootstrapWitnesses); -// create the finalized transaction with witnesses -const transaction = CardanoWasm.Transaction.new( - txBody, - witnesses, - undefined, // transaction metadata -); +// sign with the key that owns the input used +signedTxBuilder.add_vkey(CML.make_vkey_witness(txHash, prvKey)); + +const tx = signedTxBuilder.build_checked(); +// ready to submit, can be converted to CBOR via tx.to_cbor_bytes() or to_cbor_hex() for hex ``` ## A note on fees -Fees is Cardano Shelley are based directly on the size of the final encoded transaction. It is important to note that a transaction created by this library potentially can vary in size compared to one built with other tools. This is because transactions, as well as other Cardano Shelley structures, are encoded using [CBOR](https://cbor.io/) a binary JSON-like encoding. Due to arrays and maps allowing both definite or indefinite length encoding in the encoded transaction created by the library, the size can vary. This is because definite encoding consists of a tag containing the size of the array/map which can be 1 or more bytes long depending on the number of elements the size of the encoded structure, while indefinite length encoding consists of a 1 byte starting tag and after all elements are listed, a 1 byte ending tag. These variances should should only be a couple bytes and cardano-multiplatform-lib uses definite encoding which is the same length or smaller for any reasonable sized transaction. \ No newline at end of file +Fees in Cardano are based directly on the size of the final encoded transaction. It is important to note that a transaction created by this library potentially can vary in size compared to one built with other tools. This is because transactions, as well as other Cardano structures, are encoded using [CBOR](https://cbor.io/) a binary JSON-like encoding. Due to arrays and maps allowing both definite or indefinite length encoding in the encoded transaction created by the library, the size can vary. This is because definite encoding consists of a tag containing the size of the array/map which can be 1 or more bytes long depending on the number of elements the size of the encoded structure, while indefinite length encoding consists of a 1 byte starting tag and after all elements are listed, a 1 byte ending tag. These variances should should only be a couple bytes and cardano-multiplatform-lib uses definite encoding by default which is the same length or smaller for any reasonable sized transaction. \ No newline at end of file diff --git a/docs/docs/modules/builders/index.mdx b/docs/docs/modules/builders/index.mdx new file mode 100644 index 00000000..851258bf --- /dev/null +++ b/docs/docs/modules/builders/index.mdx @@ -0,0 +1,79 @@ +# TransactionBuilder + +In order to simplify transaction creation, we provide a `TransactionBuilder` struct that manages witnesses, fee calculation, change addresses and such. Assume we have instantiated an instance under the variable `builder` for this explanation. The `TransactionBuilder` requires several protocol parameters governing Cardano to be created which is shown in the following section. These are specified initially in the genesis file for Cardano nodes. + +The minimum required for a valid transaction is to add inputs, outputs, and either set the fee explicitly with `builder.set_fee(fee)`, or calculate it implicitly using `builder.add_change_if_needed(address)`. +Optionally a transaction can also have certificates, reward withdrawals, metadata, and minting added to it. +Any change made to the builder can impact the size and thus the fee so the fee should be the last thing set. +If implicitly setting the fee any extra ADA (`inputs + withdrawals - outputs + refund - deposit - min fee`) is sent to the provided change address. +Fees must be sufficient, i.e. `inputs + withdrawals + refund >= outputs + deposit + fee` which must be manually ensured if you explicitly set the fee. Any extra fee is not necessary and the extra ADA beyond that will be burned. +Once the transaction is ready, `const body = builder.build()` can be called to return a ready `TransactionBody`. + +Withdrawals are ADA withdrawn as part of the rewards generated by staking and deposits are refundable ADA locked while resources such as stake certificates or pool registrations exist on the blockchain. They are returned as refunds when these resources are deregistered/retired. + +To get to a transaction ready to post on the blockchain, we must create a `Transaction` from that, which consists of the `TransactionBody`, a matching `TransactionWitnessSet` and optionally an `AuxiliaryData`. +The witnesses and optional metadata must match those provided to the builder. The witnesses must sign the hash of the transaction body returned by `hash_transaction(body)`. In addition to the witnesses for inputs, withdrawals and some certificates require witnesses as well. For example, staking address registration does not require a witness while stake address de-registration requires one. For any questions or doubts about the rules governing fees, deposits, rewards, certificates or which witness types are required refer to the [specs for the relevant era](https://github.com/input-output-hk/cardano-ledger-specs#cardano-ledger), specifically the Shelley design specification for general design for non-governance certificates. Refer to the Conway specs for those. The formal specification could be useful for specific details as well. The design spec contains details about which certificates require which type of witnesses in the Certificates and Registrations section. + +# TransactionBuilderConfig + +To correctly make transactions the builder must know some on-chain parameters such as the current fee costs, key deposits, etc. These can all potentially change, even if some have largely been static for large periods of time. We pass these into the builder via the `TransactionBuilderConfigBuilder`. For test things out hard-coding them might suffice, but these parameters should ideally be fetched from the current blockchain head or your transactions could fail to be accepted by the network or will end up paying higher fees. The cost models parameter is optional if you are not building a transaction that utilizes Plutus smart contracts. + +Code examples for the builders will assume you have a `make_tx_builder()` function that creates a `TransactionBuilder` with the appropriate config. + +# Blockfrost + +One way of getting this information is via the `epochs/latest/parameters` endpoint of blockfrost. This can be automated from rust using the `cml-blockfrost` crate's `make_tx_builder_cfg()`. Blockfrost is by no means necessary but it can be convenient. It is possible to get this information by other means as well e.g. having a synced cardano node. + +Using `cml-blockfrost` (rust): + +```rust +let cfg = cml_blockfrost::make_tx_builder_cfg(&api).await.unwrap(); +let mut tx_builder = TransactionBuilder::new(cfg); +``` + +This could also be done manually similar to below (or reference `cml-blockfrost`'s code) + +Manually using WASM: + +```javascript +let params = await blockfrost.epochsLatestParameters(); + +// cost order is based on lex ordering of keys +let costModels = CML.CostModels.new(); +let v1Costs = params.cost_models['PlutusV1']; +if (v1Costs != null) { + let v1CMLCosts = CML.IntList.new(); + for (key in Object.keys(v1Costs).toSorted()) { + v1CMLCosts.add(CML.Int.new(v1Costs[key])); + } + costModels.set_plutus_v1(v1CMLCosts); +} +// cost order is based on lex ordering of keys +let v2Costs = params.cost_models['PlutusV2']; +if (v2Costs != null) { + let v2CMLCosts = CML.IntList.new(); + for (key in Object.keys(v2Costs).toSorted()) { + v2CMLCosts.add(CML.Int.new(v2Costs[key])); + } + costModels.set_plutus_v2(v2CMLCosts); +} +// note: as of writing this the sancho testnet format is different for v3 +// compared to v1/v2. this may remain true once mainnet switches over so +// please inspect the object you are getting for cost models from blockfrost + +let configBuilder = CML.TransactionBuilderConfigBuilder.new() + .fee_algo(CML.LinearFee.new(params.min_fee_a, params.min_fee_b)) + .coins_per_utxo_byte(BigNum(params.coins_per_utxo_size)) + .pool_deposit(BigNum(params.pool_deposit)) + .key_deposit(BigNum(params.key_deposit)) + .max_value_size(Number(params.max_val_size)) + .max_tx_size(params.max_tx_size) + .ex_unit_prices(CML.ExUnitPrices.new( + CML.SubCoin.from_base10_f32(params.price_mem), + CML.SubCoin.from_base10_f32(params.price_step) + )) + .cost_models(costModels) + .collateral_percentage(params.collateral_percent) + max_collateral_inputs(params.max_collateral_inputs); +let mut txBuilder = CML.TransactionBuilder.new(configBuilder.build()); +``` \ No newline at end of file diff --git a/docs/docs/modules/builders/plutus_contracts.mdx b/docs/docs/modules/builders/plutus_contracts.mdx new file mode 100644 index 00000000..2084331b --- /dev/null +++ b/docs/docs/modules/builders/plutus_contracts.mdx @@ -0,0 +1,139 @@ +--- +sidebar_position: 2 +--- + +# Aiken's Hello World Example + +Using Aiken's [hello world](https://aiken-lang.org/example--hello-world/basics) example we can see how to use datums and redeemers with the transaction builder. + +To deploy the contract datum we need to create an output to the contract with the datum attached like so: + +```rust +let change_addr: Address = todo!("Add your change address here"); +let sk1: PrivateKey = todo!("Add your own private key here that controls the input"); +let sk2: PrivateKey = todo!("Add your own private key here for the contract's datum"); + +let mut tx_builder = make_tx_builder(); + +// input needed to pay for the tx +tx_builder.add_input(SingleInputBuilder::new( + TransactionInput::new( + TransactionHash::from_hex("1665fc34e312445884d752a557e6b3499e1fc10228de77ca712b6bda9078ced7").unwrap(), + 0 + ), + TransactionOutput::new( + addr.clone(), + Value::from(10000000000), + Some(DatumOption::new_hash(contract_datum.hash())), + None + ), +).payment_key().unwrap())?; + +// contract created from bytes from the Plutus.json generated by Aiken +let contract = PlutusV2Script::from_cbor_bytes(&hex::decode("58f2010000323232323232323222232325333008323232533300b002100114a06644646600200200644a66602200229404c8c94ccc040cdc78010028a511330040040013014002375c60240026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc020c008c020014dd71801180400399b8f375c6002600e00a91010d48656c6c6f2c20576f726c6421002300d00114984d958c94ccc020cdc3a400000226464a66601a601e0042930b1bae300d00130060041630060033253330073370e900000089919299980618070010a4c2c6eb8c030004c01401058c01400c8c014dd5000918019baa0015734aae7555cf2ab9f5742ae881").unwrap()).unwrap(); +let contract_addr = EnterpriseAddress::new( + change_addr.network_id().unwrap(), + StakeCredential::new_script(contract.hash()), +).to_address(); + +// contract datum +let contract_datum = PlutusData::new_constr_plutus_data( + ConstrPlutusData::new(0, vec![PlutusData::new_bytes(sk2.to_public().hash().to_raw_bytes().to_vec())]) +); + +// send funds to the contract +tx_builder.add_output(SingleOutputBuilderResult::new( + TransactionOutput::new(contract_addr.clone(), Value::from(5000000000), None, None) +)); + +let mut signed_tx_builder = tx_builder.build( + ChangeSelectionAlgo::Default, + &addr +)?; + +let tx_hash = hash_transaction(&signed_tx_builder.body()); +signed_tx_builder.add_vkey(make_vkey_witness(&tx_hash, &sk1)); +let tx = signed_tx_builder.build_checked().unwrap(); +``` + +After we've deployed this contract it is time to redeem it. + +```rust +// contract / tx_builder / contract_datum are same as above + +let redeemer_datum = PlutusData::new_constr_plutus_data( + ConstrPlutusData::new(0, vec![PlutusData::new_bytes("Hello, World!".as_bytes().to_vec())]) +); + +tx_builder.add_input(SingleInputBuilder::new( + TransactionInput::new( + TransactionHash::from_hex("6079e89a1eeba9ef1d6a334f8edbf6029ff5299315a3fd55fce732da6a72fd9b").unwrap(), + 0 + ), + TransactionOutput::new( + addr.clone(), + Value::from(7500000), + None, + None + ), +).plutus_script( + PartialPlutusWitness::new( + PlutusScriptWitness::Script(contract.into()), + redeemer_datum.clone() + ), + vec![sk1.to_public().hash(), sk2.to_public().hash()], + contract_datum.clone(), +).unwrap())?; + +// In order to run plutus contracts we must supply collateral which will only be spent if the contract does +// not provide enough ADA specified via exunits to execute it. +tx_builder.add_collateral(SingleInputBuilder::new( + TransactionInput::new( + TransactionHash::from_hex("5acebd1bc82df3d6c8f50908c1a182d5fb2ff0525066fa5a3ec44fe8df80f005").unwrap(), + 0, + ), + TransactionOutput::new( + addr.clone(), + Value::from(5000000), + None, + None, + ), +).payment_key().unwrap())?; +let mut redeemer_builder = tx_builder.build_for_evaluation( + ChangeSelectionAlgo::Default, + &addr, +)?; + +// We must then send this draft tx for evaluation via ogmios, blockfrost, etc +// in order to figure out how many exunits are necessary to evaluate it +println!("draft tx: {}", hex::encode(redeemer_builder.draft_tx()?.to_cbor_bytes())); + +// once we get the exunits required back we can set it and be ready to finalize the signing of the tx +redeemer_builder.set_exunits( + RedeemerWitnessKey::new(RedeemerTag::Spend, 0), + ExUnits::new(5000000, 2000000000), +); +let redeemers = redeemer_builder.build()?; +tx_builder.set_exunits( + RedeemerWitnessKey::new(RedeemerTag::Spend, 0), + ExUnits::new(5000000, 2000000000), +); + + +let mut signed_tx_builder = tx_builder.build( + ChangeSelectionAlgo::Default, + &addr +)?; +let tx_hash = hash_transaction(&signed_tx_builder.body()); +signed_tx_builder + .add_vkey(make_vkey_witness( + &tx_hash, + &sk1, + )); +signed_tx_builder + .add_vkey(make_vkey_witness( + &tx_hash, + &sk2, + )); +let tx = signed_tx_builder.build_checked().unwrap(); +``` \ No newline at end of file diff --git a/docs/docs/modules/multi-era/_category_.json b/docs/docs/modules/multi-era/_category_.json new file mode 100644 index 00000000..35a0d5b7 --- /dev/null +++ b/docs/docs/modules/multi-era/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Multi-Era", + "position": 2, + "link": { + "type": "generated-index" + } +} diff --git a/docs/docs/modules/multi-era/index.md b/docs/docs/modules/multi-era/index.md new file mode 100644 index 00000000..b037f89c --- /dev/null +++ b/docs/docs/modules/multi-era/index.md @@ -0,0 +1,7 @@ +# Multi-Era + +This crate contains all the on-chain types for previous eras (Byron, Shelley, Alonzo, Babbage, etc). There are also wrappers around this era if you need era-agnostic types e.g. parsing all blocks from genesis. The wrappers support the current era as well. + +## Parsing blocks across eras + +`MultiEraBlock` can be used for this. Take care about the format you are giving it. Some tools (e.g. Pallas/Oura) won't give you the block format from the binary spec directly, but will instead have it wrapped in some network wrapper array containing the explicit era tag. If your CBOR looks like `[uint, ]` (likely starting with `82` in hex e.g. `8201`, `8204`, `8207`, etc) then you should use `MultiEraBlock.from_explicit_network_cbor_bytes()` instead of `MultiEraBlock.from_cbor_bytes()`. \ No newline at end of file diff --git a/docs/docs/modules/wasm.mdx b/docs/docs/modules/wasm.mdx new file mode 100644 index 00000000..680a2337 --- /dev/null +++ b/docs/docs/modules/wasm.mdx @@ -0,0 +1,15 @@ +--- +sidebar_position: 6 +--- + +# WASM Usage + +## Memory Management + +If you are using CML from the browser this section is likely irrelevant for you. +Using CML from a javascript environment with weakrefs enabled should have automatic memory cleanup. +If this is not the case (e.g. non-javascript/typescript WASM environment), or you are using CML inside of a very tight loop that is executed hundreds of thousands of times in a short period it might be advisable to explicitly call `.free()` on any CML types after they are used. +This is because while from an environment with weakrefs the types will eventually be freed automatically, +it is still possible to use excessive memory or run out if, for example, large CML types are created in a constant loop that runs many times (e.g. hundreds of thousands of times without a break), as the automatic cleanup will not be run in time. Do not worry about this for normal CML usage. +Do not call `.free()` on a type or use it after `.free()` has been called on it already. +WASM types passed into other CML APIs will be done so by reference and will not have their `.free()` method called just by doing so, but will still eventually be cleaned up if weakrefs are available. \ No newline at end of file