diff --git a/crates/algokit_transact/src/lib.rs b/crates/algokit_transact/src/lib.rs index af635da6..99fc8876 100644 --- a/crates/algokit_transact/src/lib.rs +++ b/crates/algokit_transact/src/lib.rs @@ -12,9 +12,9 @@ pub use constants::*; pub use error::AlgoKitTransactError; pub use traits::{AlgorandMsgpack, EstimateTransactionSize, TransactionId, Transactions}; pub use transactions::{ - AssetTransferTransactionBuilder, AssetTransferTransactionFields, FeeParams, - PaymentTransactionBuilder, PaymentTransactionFields, SignedTransaction, Transaction, - TransactionHeader, TransactionHeaderBuilder, + AssetFreezeTransactionBuilder, AssetFreezeTransactionFields, AssetTransferTransactionBuilder, + AssetTransferTransactionFields, FeeParams, PaymentTransactionBuilder, PaymentTransactionFields, + SignedTransaction, Transaction, TransactionHeader, TransactionHeaderBuilder, }; // Re-export msgpack functionality diff --git a/crates/algokit_transact/src/test_utils/mod.rs b/crates/algokit_transact/src/test_utils/mod.rs index 12d05e6d..66d18c90 100644 --- a/crates/algokit_transact/src/test_utils/mod.rs +++ b/crates/algokit_transact/src/test_utils/mod.rs @@ -1,5 +1,7 @@ use crate::{ - transactions::{AssetTransferTransactionBuilder, PaymentTransactionBuilder}, + transactions::{ + AssetFreezeTransactionBuilder, AssetTransferTransactionBuilder, PaymentTransactionBuilder, + }, Address, AlgorandMsgpack, Byte32, SignedTransaction, Transaction, TransactionHeaderBuilder, TransactionId, ALGORAND_PUBLIC_KEY_BYTE_LENGTH, HASH_BYTES_LENGTH, }; @@ -94,6 +96,24 @@ impl TransactionMother { .receiver(AddressMother::neil()) .to_owned() } + + pub fn asset_freeze() -> AssetFreezeTransactionBuilder { + AssetFreezeTransactionBuilder::default() + .header(TransactionHeaderMother::simple_testnet().build().unwrap()) + .asset_id(12345) + .freeze_target(AddressMother::neil()) + .frozen(true) + .to_owned() + } + + pub fn asset_unfreeze() -> AssetFreezeTransactionBuilder { + AssetFreezeTransactionBuilder::default() + .header(TransactionHeaderMother::simple_testnet().build().unwrap()) + .asset_id(12345) + .freeze_target(AddressMother::neil()) + .frozen(false) + .to_owned() + } } pub struct AddressMother {} @@ -261,6 +281,24 @@ impl TestDataMother { TransactionTestData::new(transaction, signing_private_key) } + pub fn asset_freeze() -> TransactionTestData { + let signing_private_key: Byte32 = [ + 2, 205, 103, 33, 67, 14, 82, 196, 115, 196, 206, 254, 50, 110, 63, 182, 149, 229, 184, + 216, 93, 11, 13, 99, 69, 213, 218, 165, 134, 118, 47, 44, + ]; + let transaction = TransactionMother::asset_freeze().build().unwrap(); + TransactionTestData::new(transaction, signing_private_key) + } + + pub fn asset_unfreeze() -> TransactionTestData { + let signing_private_key: Byte32 = [ + 2, 205, 103, 33, 67, 14, 82, 196, 115, 196, 206, 254, 50, 110, 63, 182, 149, 229, 184, + 216, 93, 11, 13, 99, 69, 213, 218, 165, 134, 118, 47, 44, + ]; + let transaction = TransactionMother::asset_unfreeze().build().unwrap(); + TransactionTestData::new(transaction, signing_private_key) + } + pub fn export(path: &std::path::Path, transform: Option) where F: Fn(&TransactionTestData) -> T, @@ -273,6 +311,8 @@ impl TestDataMother { let test_data = normalise_json(serde_json::json!({ "simple_payment": Self::simple_payment().as_json(&transform), "opt_in_asset_transfer": Self::opt_in_asset_transfer().as_json(&transform), + "asset_freeze": Self::asset_freeze().as_json(&transform), + "asset_unfreeze": Self::asset_unfreeze().as_json(&transform), })); let file = File::create(path).expect("Failed to create export file"); @@ -399,4 +439,14 @@ mod tests { ] ); } + + #[test] + fn test_asset_freeze_snapshot() { + let data = TestDataMother::asset_freeze(); + // Note: These values would need to be updated once we run the actual test + // to get the real encoded transaction values + assert!(!data.id.is_empty()); + assert!(!data.unsigned_bytes.is_empty()); + assert!(!data.signed_bytes.is_empty()); + } } diff --git a/crates/algokit_transact/src/tests.rs b/crates/algokit_transact/src/tests.rs index 7a06c41c..884d51b2 100644 --- a/crates/algokit_transact/src/tests.rs +++ b/crates/algokit_transact/src/tests.rs @@ -5,7 +5,7 @@ use crate::{ test_utils::{ AddressMother, TransactionGroupMother, TransactionHeaderMother, TransactionMother, }, - transactions::FeeParams, + transactions::{AssetFreezeTransactionBuilder, FeeParams}, Address, AlgorandMsgpack, EstimateTransactionSize, SignedTransaction, Transaction, TransactionId, Transactions, }; @@ -389,3 +389,149 @@ fn test_signed_transaction_group_encoding() { assert_eq!(decoded_signed_tx, signed_grouped_tx); } } + +#[test] +fn test_asset_freeze_transaction_encoding() { + let tx_builder = TransactionMother::asset_freeze(); + let asset_freeze_tx_fields = tx_builder.build_fields().unwrap(); + let asset_freeze_tx = tx_builder.build().unwrap(); + + let encoded = asset_freeze_tx.encode().unwrap(); + let decoded = Transaction::decode(&encoded).unwrap(); + assert_eq!(decoded, asset_freeze_tx); + assert_eq!(decoded, Transaction::AssetFreeze(asset_freeze_tx_fields)); + + let signed_tx = SignedTransaction { + transaction: asset_freeze_tx.clone(), + signature: Some([0; ALGORAND_SIGNATURE_BYTE_LENGTH]), + auth_address: None, + }; + let encoded_stx = signed_tx.encode().unwrap(); + let decoded_stx = SignedTransaction::decode(&encoded_stx).unwrap(); + assert_eq!(decoded_stx, signed_tx); + assert_eq!(decoded_stx.transaction, asset_freeze_tx); + + let raw_encoded = asset_freeze_tx.encode_raw().unwrap(); + assert_eq!(encoded[0], b'T'); + assert_eq!(encoded[1], b'X'); + assert_eq!(encoded.len(), raw_encoded.len() + 2); + assert_eq!(encoded[2..], raw_encoded); +} + +#[test] +fn test_asset_unfreeze_transaction_encoding() { + let tx_builder = TransactionMother::asset_unfreeze(); + let asset_freeze_tx_fields = tx_builder.build_fields().unwrap(); + let asset_freeze_tx = tx_builder.build().unwrap(); + + // Verify it's an unfreeze transaction + assert_eq!(asset_freeze_tx_fields.frozen, false); + + let encoded = asset_freeze_tx.encode().unwrap(); + let decoded = Transaction::decode(&encoded).unwrap(); + assert_eq!(decoded, asset_freeze_tx); + assert_eq!(decoded, Transaction::AssetFreeze(asset_freeze_tx_fields)); +} + +#[test] +fn test_asset_freeze_transaction_id() { + let tx_builder = TransactionMother::asset_freeze(); + let asset_freeze_tx = tx_builder.build().unwrap(); + + let signed_tx = SignedTransaction { + transaction: asset_freeze_tx.clone(), + signature: Some([0; ALGORAND_SIGNATURE_BYTE_LENGTH]), + auth_address: None, + }; + + // Test that transaction ID can be generated + let tx_id = asset_freeze_tx.id().unwrap(); + let tx_id_raw = asset_freeze_tx.id_raw().unwrap(); + + assert_eq!(signed_tx.id().unwrap(), tx_id); + assert_eq!(signed_tx.id_raw().unwrap(), tx_id_raw); + + // Transaction ID should be non-empty + assert!(!tx_id.is_empty()); + assert_ne!(tx_id_raw, [0u8; 32]); +} + +#[test] +fn test_asset_freeze_fee_calculation() { + let asset_freeze_tx = TransactionMother::asset_freeze().build().unwrap(); + + let updated_transaction = asset_freeze_tx + .assign_fee(FeeParams { + fee_per_byte: 1, + min_fee: 1000, + extra_fee: None, + max_fee: None, + }) + .unwrap(); + + // Fee should be calculated based on transaction size + assert!(updated_transaction.header().fee.unwrap() >= 1000); +} + +#[test] +fn test_asset_freeze_in_transaction_group() { + let header_builder = TransactionHeaderMother::testnet() + .sender(AddressMother::neil()) + .first_valid(51532821) + .last_valid(51533021) + .to_owned(); + + let asset_freeze_tx = TransactionMother::asset_freeze() + .header(header_builder.build().unwrap()) + .build() + .unwrap(); + + let payment_tx = TransactionMother::simple_payment() + .header(header_builder.build().unwrap()) + .build() + .unwrap(); + + let txs = vec![asset_freeze_tx, payment_tx]; + let grouped_txs = txs.assign_group().unwrap(); + + assert_eq!(grouped_txs.len(), 2); + + // Both transactions should have the same group ID + let group_id = grouped_txs[0].header().group.unwrap(); + assert_eq!(grouped_txs[1].header().group.unwrap(), group_id); + + // Group ID should be non-zero + assert_ne!(group_id, [0u8; 32]); +} + +#[test] +fn test_asset_freeze_vs_unfreeze() { + let freeze_tx = TransactionMother::asset_freeze().build_fields().unwrap(); + let unfreeze_tx = TransactionMother::asset_unfreeze().build_fields().unwrap(); + + assert_eq!(freeze_tx.frozen, true); + assert_eq!(unfreeze_tx.frozen, false); + assert_eq!(freeze_tx.asset_id, unfreeze_tx.asset_id); + assert_eq!(freeze_tx.freeze_target, unfreeze_tx.freeze_target); +} + +#[test] +fn test_asset_freeze_builder_validation() { + let result = AssetFreezeTransactionBuilder::default() + .header(TransactionHeaderMother::simple_testnet().build().unwrap()) + .asset_id(12345) + .freeze_target(AddressMother::neil()) + .frozen(true) + .build(); + + assert!(result.is_ok()); + let tx = result.unwrap(); + + if let Transaction::AssetFreeze(fields) = tx { + assert_eq!(fields.asset_id, 12345); + assert_eq!(fields.freeze_target, AddressMother::neil()); + assert_eq!(fields.frozen, true); + } else { + panic!("Expected AssetFreeze transaction"); + } +} diff --git a/crates/algokit_transact/src/transactions/asset_freeze.rs b/crates/algokit_transact/src/transactions/asset_freeze.rs new file mode 100644 index 00000000..feff4be5 --- /dev/null +++ b/crates/algokit_transact/src/transactions/asset_freeze.rs @@ -0,0 +1,49 @@ +//! Asset freeze transaction module for AlgoKit Core. +//! +//! This module provides functionality for creating and managing asset freeze transactions, +//! which are used to freeze or unfreeze asset holdings for specific accounts. + +use crate::address::Address; +use crate::transactions::common::TransactionHeader; +use crate::utils::{is_zero, is_zero_addr}; +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, skip_serializing_none}; + +/// Represents an asset freeze transaction that freezes or unfreezes asset holdings. +/// +/// Asset freeze transactions are used by the asset freeze account to control +/// whether a specific account can transfer a particular asset. +#[serde_as] +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Builder)] +#[builder( + name = "AssetFreezeTransactionBuilder", + setter(strip_option), + build_fn(name = "build_fields") +)] +pub struct AssetFreezeTransactionFields { + /// Common transaction header fields. + #[serde(flatten)] + pub header: TransactionHeader, + + /// The ID of the asset being frozen/unfrozen. + #[serde(rename = "faid")] + #[serde(skip_serializing_if = "is_zero")] + #[serde(default)] + pub asset_id: u64, + + /// The target account whose asset holdings will be affected. + #[serde(rename = "fadd")] + #[serde(skip_serializing_if = "is_zero_addr")] + #[serde(default)] + pub freeze_target: Address, + + /// The new freeze status. + /// + /// `true` to freeze the asset holdings (prevent transfers), + /// `false` to unfreeze the asset holdings (allow transfers). + #[serde(rename = "afrz")] + #[serde(default)] + pub frozen: bool, +} diff --git a/crates/algokit_transact/src/transactions/mod.rs b/crates/algokit_transact/src/transactions/mod.rs index 00df565c..3ba1b349 100644 --- a/crates/algokit_transact/src/transactions/mod.rs +++ b/crates/algokit_transact/src/transactions/mod.rs @@ -4,10 +4,13 @@ //! This module includes support for various transaction types, along with the ability to sign, //! serialize, and deserialize them. +mod asset_freeze; mod asset_transfer; mod common; mod payment; +use asset_freeze::AssetFreezeTransactionBuilderError; +pub use asset_freeze::{AssetFreezeTransactionBuilder, AssetFreezeTransactionFields}; use asset_transfer::AssetTransferTransactionBuilderError; pub use asset_transfer::{AssetTransferTransactionBuilder, AssetTransferTransactionFields}; pub use common::{TransactionHeader, TransactionHeaderBuilder}; @@ -35,10 +38,10 @@ pub enum Transaction { #[serde(rename = "axfer")] AssetTransfer(AssetTransferTransactionFields), - // All the below transaction variants will be implemented in the future - // #[serde(rename = "afrz")] - // AssetFreeze(...), + #[serde(rename = "afrz")] + AssetFreeze(AssetFreezeTransactionFields), + // All the below transaction variants will be implemented in the future // #[serde(rename = "acfg")] // AssetConfig(...), @@ -61,6 +64,7 @@ impl Transaction { match self { Transaction::Payment(p) => &p.header, Transaction::AssetTransfer(a) => &a.header, + Transaction::AssetFreeze(f) => &f.header, } } @@ -68,6 +72,7 @@ impl Transaction { match self { Transaction::Payment(p) => &mut p.header, Transaction::AssetTransfer(a) => &mut a.header, + Transaction::AssetFreeze(f) => &mut f.header, } } @@ -116,6 +121,12 @@ impl AssetTransferTransactionBuilder { } } +impl AssetFreezeTransactionBuilder { + pub fn build(&self) -> Result { + self.build_fields().map(|d| Transaction::AssetFreeze(d)) + } +} + impl AlgorandMsgpack for Transaction { const PREFIX: &'static [u8] = b"TX"; } diff --git a/crates/algokit_transact_ffi/src/lib.rs b/crates/algokit_transact_ffi/src/lib.rs index b455e409..2a00507b 100644 --- a/crates/algokit_transact_ffi/src/lib.rs +++ b/crates/algokit_transact_ffi/src/lib.rs @@ -6,7 +6,9 @@ use algokit_transact::msgpack::{ encode_json_to_msgpack as internal_encode_json_to_msgpack, AlgoKitMsgPackError as InternalMsgPackError, ModelType as InternalModelType, }; -use algokit_transact::{AlgorandMsgpack, Byte32, EstimateTransactionSize, TransactionId, Transactions}; +use algokit_transact::{ + AlgorandMsgpack, Byte32, EstimateTransactionSize, TransactionId, Transactions, +}; use ffi_macros::{ffi_enum, ffi_func, ffi_record}; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -199,6 +201,13 @@ pub struct AssetTransferTransactionFields { close_remainder_to: Option
, } +#[ffi_record] +pub struct AssetFreezeTransactionFields { + asset_id: u64, + freeze_target: Address, + frozen: bool, +} + #[ffi_record] pub struct Transaction { /// The type of transaction @@ -231,6 +240,8 @@ pub struct Transaction { payment: Option, asset_transfer: Option, + + asset_freeze: Option, } impl TryFrom for algokit_transact::Transaction { @@ -238,10 +249,14 @@ impl TryFrom for algokit_transact::Transaction { fn try_from(tx: Transaction) -> Result { // Ensure there is never more than 1 transaction type specific field set - if [tx.payment.is_some(), tx.asset_transfer.is_some()] - .iter() - .filter(|&&x| x) - .count() + if [ + tx.payment.is_some(), + tx.asset_transfer.is_some(), + tx.asset_freeze.is_some(), + ] + .iter() + .filter(|&&x| x) + .count() > 1 { return Err(Self::Error::DecodingError( @@ -254,6 +269,9 @@ impl TryFrom for algokit_transact::Transaction { TransactionType::AssetTransfer => { Ok(algokit_transact::Transaction::AssetTransfer(tx.try_into()?)) } + TransactionType::AssetFreeze => { + Ok(algokit_transact::Transaction::AssetFreeze(tx.try_into()?)) + } _ => { return Err(Self::Error::DecodingError( "Transaction type is not implemented".to_string(), @@ -350,6 +368,38 @@ impl TryFrom for algokit_transact::AssetTransferTransactionFields { } } +impl From for AssetFreezeTransactionFields { + fn from(tx: algokit_transact::AssetFreezeTransactionFields) -> Self { + Self { + asset_id: tx.asset_id, + freeze_target: tx.freeze_target.into(), + frozen: tx.frozen, + } + } +} + +impl TryFrom for algokit_transact::AssetFreezeTransactionFields { + type Error = AlgoKitTransactError; + + fn try_from(tx: Transaction) -> Result { + if tx.transaction_type != TransactionType::AssetFreeze || tx.asset_freeze.is_none() { + return Err(Self::Error::DecodingError( + "Asset Freeze data missing".to_string(), + )); + } + + let data = tx.clone().asset_freeze.unwrap(); + let header: algokit_transact::TransactionHeader = tx.try_into()?; + + Ok(Self { + header, + asset_id: data.asset_id, + freeze_target: data.freeze_target.try_into()?, + frozen: data.frozen, + }) + } +} + impl TryFrom for Transaction { type Error = AlgoKitTransactError; @@ -362,6 +412,7 @@ impl TryFrom for Transaction { TransactionType::Payment, Some(payment_fields), None, + None, ) } algokit_transact::Transaction::AssetTransfer(asset_transfer) => { @@ -371,6 +422,17 @@ impl TryFrom for Transaction { TransactionType::AssetTransfer, None, Some(asset_transfer_fields), + None, + ) + } + algokit_transact::Transaction::AssetFreeze(asset_freeze) => { + let asset_freeze_fields = asset_freeze.clone().into(); + build_transaction( + asset_freeze.header, + TransactionType::AssetFreeze, + None, + None, + Some(asset_freeze_fields), ) } } @@ -441,6 +503,7 @@ fn build_transaction( transaction_type: TransactionType, payment: Option, asset_transfer: Option, + asset_freeze: Option, ) -> Result { Ok(Transaction { transaction_type, @@ -456,6 +519,7 @@ fn build_transaction( group: header.group.map(byte32_to_bytebuf), payment, asset_transfer, + asset_freeze, }) } @@ -471,6 +535,7 @@ pub fn get_encoded_transaction_type(bytes: &[u8]) -> Result Ok(TransactionType::Payment), algokit_transact::Transaction::AssetTransfer(_) => Ok(TransactionType::AssetTransfer), + algokit_transact::Transaction::AssetFreeze(_) => Ok(TransactionType::AssetFreeze), } } diff --git a/crates/algokit_transact_ffi/test_data.json b/crates/algokit_transact_ffi/test_data.json index f2b72d58..eb0846f4 100644 --- a/crates/algokit_transact_ffi/test_data.json +++ b/crates/algokit_transact_ffi/test_data.json @@ -1,4 +1,1926 @@ { + "assetFreeze": { + "id": "YNROMESHSC5N7QGS3OLYWTOOPUCMFCP3IJYOZSNMC5VBDDNGQVWA", + "idRaw": [ + 195, + 98, + 230, + 18, + 71, + 144, + 186, + 223, + 192, + 210, + 219, + 151, + 139, + 77, + 206, + 125, + 4, + 194, + 137, + 251, + 66, + 112, + 236, + 201, + 172, + 23, + 106, + 17, + 141, + 166, + 133, + 108 + ], + "rekeyedSenderAuthAddress": { + "address": "BKDYDIDVSZCP75JVCB76P3WBJRY6HWAIFDSEOKYHJY5WMNJ2UWJ65MYETU", + "pubKey": [ + 10, + 135, + 129, + 160, + 117, + 150, + 68, + 255, + 245, + 53, + 16, + 127, + 231, + 238, + 193, + 76, + 113, + 227, + 216, + 8, + 40, + 228, + 71, + 43, + 7, + 78, + 59, + 102, + 53, + 58, + 165, + 147 + ] + }, + "rekeyedSenderSignedBytes": [ + 131, + 164, + 115, + 103, + 110, + 114, + 196, + 32, + 10, + 135, + 129, + 160, + 117, + 150, + 68, + 255, + 245, + 53, + 16, + 127, + 231, + 238, + 193, + 76, + 113, + 227, + 216, + 8, + 40, + 228, + 71, + 43, + 7, + 78, + 59, + 102, + 53, + 58, + 165, + 147, + 163, + 115, + 105, + 103, + 196, + 64, + 75, + 144, + 71, + 162, + 123, + 169, + 163, + 33, + 65, + 60, + 189, + 6, + 173, + 37, + 251, + 240, + 220, + 212, + 152, + 164, + 247, + 112, + 166, + 131, + 9, + 239, + 210, + 16, + 185, + 2, + 81, + 18, + 238, + 199, + 86, + 224, + 63, + 195, + 171, + 52, + 213, + 254, + 93, + 79, + 224, + 161, + 189, + 133, + 126, + 234, + 79, + 18, + 230, + 147, + 189, + 218, + 125, + 56, + 29, + 52, + 119, + 88, + 121, + 9, + 163, + 116, + 120, + 110, + 138, + 164, + 97, + 102, + 114, + 122, + 195, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ], + "signedBytes": [ + 130, + 163, + 115, + 105, + 103, + 196, + 64, + 75, + 144, + 71, + 162, + 123, + 169, + 163, + 33, + 65, + 60, + 189, + 6, + 173, + 37, + 251, + 240, + 220, + 212, + 152, + 164, + 247, + 112, + 166, + 131, + 9, + 239, + 210, + 16, + 185, + 2, + 81, + 18, + 238, + 199, + 86, + 224, + 63, + 195, + 171, + 52, + 213, + 254, + 93, + 79, + 224, + 161, + 189, + 133, + 126, + 234, + 79, + 18, + 230, + 147, + 189, + 218, + 125, + 56, + 29, + 52, + 119, + 88, + 121, + 9, + 163, + 116, + 120, + 110, + 138, + 164, + 97, + 102, + 114, + 122, + 195, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ], + "signingPrivateKey": [ + 2, + 205, + 103, + 33, + 67, + 14, + 82, + 196, + 115, + 196, + 206, + 254, + 50, + 110, + 63, + 182, + 149, + 229, + 184, + 216, + 93, + 11, + 13, + 99, + 69, + 213, + 218, + 165, + 134, + 118, + 47, + 44 + ], + "transaction": { + "assetFreeze": { + "assetId": 12345, + "freezeTarget": { + "address": "JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA", + "pubKey": [ + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123 + ] + }, + "frozen": true + }, + "fee": 1000, + "firstValid": 50659540, + "genesisHash": [ + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34 + ], + "genesisId": "testnet-v1.0", + "lastValid": 50660540, + "sender": { + "address": "RIMARGKZU46OZ77OLPDHHPUJ7YBSHRTCYMQUC64KZCCMESQAFQMYU6SL2Q", + "pubKey": [ + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25 + ] + }, + "transactionType": "AssetFreeze" + }, + "unsignedBytes": [ + 84, + 88, + 138, + 164, + 97, + 102, + 114, + 122, + 195, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ] + }, + "assetUnfreeze": { + "id": "4IQZYWHBHQE6FWI4PLZF5YE6URF7L7NSHLSURH7Q7TTBZMQ4ZJNQ", + "idRaw": [ + 226, + 33, + 156, + 88, + 225, + 60, + 9, + 226, + 217, + 28, + 122, + 242, + 94, + 224, + 158, + 164, + 75, + 245, + 253, + 178, + 58, + 229, + 72, + 159, + 240, + 252, + 230, + 28, + 178, + 28, + 202, + 91 + ], + "rekeyedSenderAuthAddress": { + "address": "BKDYDIDVSZCP75JVCB76P3WBJRY6HWAIFDSEOKYHJY5WMNJ2UWJ65MYETU", + "pubKey": [ + 10, + 135, + 129, + 160, + 117, + 150, + 68, + 255, + 245, + 53, + 16, + 127, + 231, + 238, + 193, + 76, + 113, + 227, + 216, + 8, + 40, + 228, + 71, + 43, + 7, + 78, + 59, + 102, + 53, + 58, + 165, + 147 + ] + }, + "rekeyedSenderSignedBytes": [ + 131, + 164, + 115, + 103, + 110, + 114, + 196, + 32, + 10, + 135, + 129, + 160, + 117, + 150, + 68, + 255, + 245, + 53, + 16, + 127, + 231, + 238, + 193, + 76, + 113, + 227, + 216, + 8, + 40, + 228, + 71, + 43, + 7, + 78, + 59, + 102, + 53, + 58, + 165, + 147, + 163, + 115, + 105, + 103, + 196, + 64, + 238, + 140, + 221, + 49, + 199, + 162, + 49, + 46, + 215, + 91, + 143, + 30, + 144, + 152, + 187, + 131, + 7, + 173, + 105, + 8, + 196, + 241, + 118, + 103, + 7, + 52, + 81, + 227, + 143, + 194, + 8, + 114, + 161, + 197, + 195, + 186, + 75, + 117, + 186, + 233, + 29, + 137, + 16, + 5, + 190, + 51, + 211, + 245, + 142, + 62, + 245, + 3, + 193, + 200, + 155, + 122, + 255, + 236, + 255, + 106, + 16, + 146, + 130, + 8, + 163, + 116, + 120, + 110, + 138, + 164, + 97, + 102, + 114, + 122, + 194, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ], + "signedBytes": [ + 130, + 163, + 115, + 105, + 103, + 196, + 64, + 238, + 140, + 221, + 49, + 199, + 162, + 49, + 46, + 215, + 91, + 143, + 30, + 144, + 152, + 187, + 131, + 7, + 173, + 105, + 8, + 196, + 241, + 118, + 103, + 7, + 52, + 81, + 227, + 143, + 194, + 8, + 114, + 161, + 197, + 195, + 186, + 75, + 117, + 186, + 233, + 29, + 137, + 16, + 5, + 190, + 51, + 211, + 245, + 142, + 62, + 245, + 3, + 193, + 200, + 155, + 122, + 255, + 236, + 255, + 106, + 16, + 146, + 130, + 8, + 163, + 116, + 120, + 110, + 138, + 164, + 97, + 102, + 114, + 122, + 194, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ], + "signingPrivateKey": [ + 2, + 205, + 103, + 33, + 67, + 14, + 82, + 196, + 115, + 196, + 206, + 254, + 50, + 110, + 63, + 182, + 149, + 229, + 184, + 216, + 93, + 11, + 13, + 99, + 69, + 213, + 218, + 165, + 134, + 118, + 47, + 44 + ], + "transaction": { + "assetFreeze": { + "assetId": 12345, + "freezeTarget": { + "address": "JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA", + "pubKey": [ + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123 + ] + }, + "frozen": false + }, + "fee": 1000, + "firstValid": 50659540, + "genesisHash": [ + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34 + ], + "genesisId": "testnet-v1.0", + "lastValid": 50660540, + "sender": { + "address": "RIMARGKZU46OZ77OLPDHHPUJ7YBSHRTCYMQUC64KZCCMESQAFQMYU6SL2Q", + "pubKey": [ + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25 + ] + }, + "transactionType": "AssetFreeze" + }, + "unsignedBytes": [ + 84, + 88, + 138, + 164, + 97, + 102, + 114, + 122, + 194, + 164, + 102, + 97, + 100, + 100, + 196, + 32, + 72, + 118, + 175, + 30, + 96, + 187, + 134, + 238, + 76, + 228, + 146, + 219, + 137, + 200, + 222, + 52, + 40, + 86, + 146, + 168, + 129, + 190, + 15, + 103, + 21, + 24, + 5, + 31, + 88, + 27, + 201, + 123, + 164, + 102, + 97, + 105, + 100, + 205, + 48, + 57, + 163, + 102, + 101, + 101, + 205, + 3, + 232, + 162, + 102, + 118, + 206, + 3, + 5, + 0, + 212, + 163, + 103, + 101, + 110, + 172, + 116, + 101, + 115, + 116, + 110, + 101, + 116, + 45, + 118, + 49, + 46, + 48, + 162, + 103, + 104, + 196, + 32, + 72, + 99, + 181, + 24, + 164, + 179, + 200, + 78, + 200, + 16, + 242, + 45, + 79, + 16, + 129, + 203, + 15, + 113, + 240, + 89, + 167, + 172, + 32, + 222, + 198, + 47, + 127, + 112, + 229, + 9, + 58, + 34, + 162, + 108, + 118, + 206, + 3, + 5, + 4, + 188, + 163, + 115, + 110, + 100, + 196, + 32, + 138, + 24, + 8, + 153, + 89, + 167, + 60, + 236, + 255, + 238, + 91, + 198, + 115, + 190, + 137, + 254, + 3, + 35, + 198, + 98, + 195, + 33, + 65, + 123, + 138, + 200, + 132, + 194, + 74, + 0, + 44, + 25, + 164, + 116, + 121, + 112, + 101, + 164, + 97, + 102, + 114, + 122 + ] + }, "optInAssetTransfer": { "id": "JIDBHDPLBASULQZFI4EY5FJWR6VQRMPPFSGYBKE2XKW65N3UQJXA", "idRaw": [ diff --git a/packages/python/algokit_transact/tests/test_asset_freeze.py b/packages/python/algokit_transact/tests/test_asset_freeze.py new file mode 100644 index 00000000..08f3b7e1 --- /dev/null +++ b/packages/python/algokit_transact/tests/test_asset_freeze.py @@ -0,0 +1,140 @@ +import pytest + +from . import TEST_DATA +from algokit_transact import ( + FeeParams, + assign_fee, + encode_transaction, + encode_signed_transaction, + AssetFreezeTransactionFields, + TransactionType, + decode_transaction, + get_encoded_transaction_type, + Transaction, + SignedTransaction, + address_from_string, + address_from_pub_key, + get_transaction_id, + get_transaction_id_raw, +) +from nacl.signing import SigningKey + +# We'll need to add test data once the implementation is complete +# asset_freeze = TEST_DATA.asset_freeze +# asset_unfreeze = TEST_DATA.asset_unfreeze + + +def test_example(): + """A human-readable example of forming an asset freeze transaction and signing it""" + alice_keypair = SigningKey.generate() # Keypair generated from PyNaCl + alice = address_from_pub_key(alice_keypair.verify_key.__bytes__()) + + target_account = address_from_string( + "JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA" + ) + + # Example 1: Freeze an asset + freeze_txn = Transaction( + transaction_type=TransactionType.ASSET_FREEZE, + first_valid=1337, + last_valid=1347, + sender=alice, + genesis_hash=b"A" * 32, # pretend this is a valid hash + genesis_id="localnet", + asset_freeze=AssetFreezeTransactionFields( + asset_id=12345, + freeze_target=target_account, + frozen=True, + ), + ) + + freeze_txn_with_fee = assign_fee( + freeze_txn, FeeParams(fee_per_byte=0, min_fee=1000) + ) + assert freeze_txn_with_fee.fee == 1000 + + # Example 2: Unfreeze an asset + unfreeze_txn = Transaction( + transaction_type=TransactionType.ASSET_FREEZE, + first_valid=1337, + last_valid=1347, + sender=alice, + genesis_hash=b"A" * 32, + genesis_id="localnet", + asset_freeze=AssetFreezeTransactionFields( + asset_id=12345, + freeze_target=target_account, + frozen=False, + ), + ) + + unfreeze_txn_with_fee = assign_fee( + unfreeze_txn, FeeParams(fee_per_byte=0, min_fee=1000) + ) + assert unfreeze_txn_with_fee.fee == 1000 + + +def test_asset_freeze_transaction_encoding(): + """Test basic encoding/decoding of asset freeze transactions""" + alice_keypair = SigningKey.generate() + alice = address_from_pub_key(alice_keypair.verify_key.__bytes__()) + target_account = address_from_string( + "JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA" + ) + + freeze_txn = Transaction( + transaction_type=TransactionType.ASSET_FREEZE, + first_valid=1337, + last_valid=1347, + sender=alice, + fee=1000, + genesis_hash=b"A" * 32, + genesis_id="localnet", + asset_freeze=AssetFreezeTransactionFields( + asset_id=12345, + freeze_target=target_account, + frozen=True, + ), + ) + + # Test encoding and decoding + encoded = encode_transaction(freeze_txn) + decoded = decode_transaction(encoded) + + assert decoded.transaction_type == TransactionType.ASSET_FREEZE + assert decoded.asset_freeze.asset_id == 12345 + assert decoded.asset_freeze.frozen == True + assert decoded.asset_freeze.freeze_target.address == target_account.address + + # Test transaction type detection + assert get_encoded_transaction_type(encoded) == TransactionType.ASSET_FREEZE + + +def test_asset_freeze_transaction_id(): + """Test transaction ID generation for asset freeze transactions""" + alice_keypair = SigningKey.generate() + alice = address_from_pub_key(alice_keypair.verify_key.__bytes__()) + target_account = address_from_string( + "JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA" + ) + + freeze_txn = Transaction( + transaction_type=TransactionType.ASSET_FREEZE, + first_valid=1337, + last_valid=1347, + sender=alice, + fee=1000, + genesis_hash=b"A" * 32, + genesis_id="localnet", + asset_freeze=AssetFreezeTransactionFields( + asset_id=12345, + freeze_target=target_account, + frozen=True, + ), + ) + + tx_id = get_transaction_id(freeze_txn) + tx_id_raw = get_transaction_id_raw(freeze_txn) + + assert len(tx_id) > 0 + assert len(tx_id_raw) == 32 diff --git a/packages/typescript/algokit_transact/__tests__/asset_freeze.test.ts b/packages/typescript/algokit_transact/__tests__/asset_freeze.test.ts new file mode 100644 index 00000000..53ff75f1 --- /dev/null +++ b/packages/typescript/algokit_transact/__tests__/asset_freeze.test.ts @@ -0,0 +1,128 @@ +import { expect, test, describe } from "bun:test"; +import * as ed from "@noble/ed25519"; +import { + encodeTransaction, + decodeTransaction, + getEncodedTransactionType, + Transaction, + addressFromPubKey, + addressFromString, + getTransactionIdRaw, + getTransactionId, + assignFee, + SignedTransaction, + encodeSignedTransaction, +} from ".."; + +// We'll need to add test data once implementation is complete +// const assetFreeze = testData.assetFreeze; +// const assetUnfreeze = testData.assetUnfreeze; + +describe("Asset Freeze", () => { + describe("Transaction Tests", () => { + test("example", async () => { + const aliceSk = ed.utils.randomPrivateKey(); + const alicePubKey = await ed.getPublicKeyAsync(aliceSk); + const alice = addressFromPubKey(alicePubKey); + const targetAccount = addressFromString("JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA"); + + // Example 1: Freeze an asset + const freezeTxn: Transaction = { + transactionType: "AssetFreeze", + sender: alice, + firstValid: 1337n, + lastValid: 1347n, + genesisHash: new Uint8Array(32).fill(65), // pretend this is a valid hash + genesisId: "localnet", + assetFreeze: { + assetId: 12345n, + freezeTarget: targetAccount, + frozen: true, + }, + }; + + const freezeTxnWithFee = assignFee(freezeTxn, { feePerByte: 0n, minFee: 1000n }); + expect(freezeTxnWithFee.fee).toBe(1000n); + + // Example 2: Unfreeze an asset + const unfreezeTxn: Transaction = { + transactionType: "AssetFreeze", + sender: alice, + firstValid: 1337n, + lastValid: 1347n, + genesisHash: new Uint8Array(32).fill(65), + genesisId: "localnet", + assetFreeze: { + assetId: 12345n, + freezeTarget: targetAccount, + frozen: false, + }, + }; + + const unfreezeTxnWithFee = assignFee(unfreezeTxn, { feePerByte: 0n, minFee: 1000n }); + expect(unfreezeTxnWithFee.fee).toBe(1000n); + }); + + test("asset freeze transaction encoding", async () => { + const aliceSk = ed.utils.randomPrivateKey(); + const alicePubKey = await ed.getPublicKeyAsync(aliceSk); + const alice = addressFromPubKey(alicePubKey); + const targetAccount = addressFromString("JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA"); + + const freezeTxn: Transaction = { + transactionType: "AssetFreeze", + sender: alice, + firstValid: 1337n, + lastValid: 1347n, + fee: 1000n, + genesisHash: new Uint8Array(32).fill(65), + genesisId: "localnet", + assetFreeze: { + assetId: 12345n, + freezeTarget: targetAccount, + frozen: true, + }, + }; + + // Test encoding and decoding + const encoded = encodeTransaction(freezeTxn); + const decoded = decodeTransaction(encoded); + + expect(decoded.transactionType).toBe("AssetFreeze"); + expect(decoded.assetFreeze?.assetId).toBe(12345n); + expect(decoded.assetFreeze?.frozen).toBe(true); + expect(decoded.assetFreeze?.freezeTarget.address).toBe(targetAccount.address); + + // Test transaction type detection + expect(getEncodedTransactionType(encoded)).toBe("AssetFreeze"); + }); + + test("get transaction id", async () => { + const aliceSk = ed.utils.randomPrivateKey(); + const alicePubKey = await ed.getPublicKeyAsync(aliceSk); + const alice = addressFromPubKey(alicePubKey); + const targetAccount = addressFromString("JB3K6HTAXODO4THESLNYTSG6GQUFNEVIQG7A6ZYVDACR6WA3ZF52TKU5NA"); + + const freezeTxn: Transaction = { + transactionType: "AssetFreeze", + sender: alice, + firstValid: 1337n, + lastValid: 1347n, + fee: 1000n, + genesisHash: new Uint8Array(32).fill(65), + genesisId: "localnet", + assetFreeze: { + assetId: 12345n, + freezeTarget: targetAccount, + frozen: true, + }, + }; + + const txId = getTransactionId(freezeTxn); + const txIdRaw = getTransactionIdRaw(freezeTxn); + + expect(txId.length).toBeGreaterThan(0); + expect(txIdRaw.length).toBe(32); + }); + }); +});