Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
636e163
feat: Add support for Asset Freeze transactions
lempira Jun 22, 2025
0426007
feat: add asset freeze transaction functionality to match app call
lempira Jul 2, 2025
4ce5c84
refactor: updated asset freeze txn to conform to other txns
lempira Jul 2, 2025
42296e8
refactor: formatted files
lempira Jul 2, 2025
3231cd6
fix: testing errors
lempira Jul 2, 2025
f090fe2
fix: defaultFrozen being kept in for false
lempira Jul 3, 2025
a02fc85
fix: encoding asset freeze
PatrickDinh Jul 4, 2025
33b5603
feat: consolidate asset freeze test data using real mainnet transaction
lempira Jul 9, 2025
d99960b
fix: remove remaining asset_freeze_real_data field after rebase
lempira Jul 9, 2025
bb59a49
fix: remove unreachable pattern in transaction type match
lempira Jul 9, 2025
936993b
chore: removed generic tests
lempira Jul 10, 2025
6a6ffcd
fix: allow clippy too_many_arguments for build_transaction utility
lempira Jul 10, 2025
df1b387
fix(tests): handle missing 'frozen' field in python test data
lempira Jul 10, 2025
35e5ce4
fix: consolidate asset freeze transaction data changes
lempira Jul 10, 2025
9349959
wip
PatrickDinh Jul 10, 2025
67d165e
wip
PatrickDinh Jul 10, 2025
3ad6724
cargo test done
PatrickDinh Jul 10, 2025
372233d
chore: merged imports
lempira Jul 11, 2025
7d901d0
fix: rebuilt packages
lempira Jul 11, 2025
afd4f7e
chore(tests): updated asset frz test to standalone file
PatrickDinh Jul 10, 2025
c452f1f
chore: updated test_data
PatrickDinh Jul 10, 2025
ba3bae3
cargo test done
PatrickDinh Jul 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions crates/algokit_transact/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ pub use multisig::*;
pub use traits::{AlgorandMsgpack, EstimateTransactionSize, TransactionId, Transactions, Validate};
pub use transactions::{
ApplicationCallTransactionBuilder, ApplicationCallTransactionFields,
AssetConfigTransactionBuilder, AssetConfigTransactionFields, AssetTransferTransactionBuilder,
AssetTransferTransactionFields, BoxReference, FeeParams, KeyRegistrationTransactionBuilder,
KeyRegistrationTransactionFields, OnApplicationComplete, PaymentTransactionBuilder,
PaymentTransactionFields, SignedTransaction, StateSchema, Transaction, TransactionHeader,
TransactionHeaderBuilder,
AssetConfigTransactionBuilder, AssetConfigTransactionFields, AssetFreezeTransactionBuilder,
AssetFreezeTransactionFields, AssetTransferTransactionBuilder, AssetTransferTransactionFields,
BoxReference, FeeParams, KeyRegistrationTransactionBuilder, KeyRegistrationTransactionFields,
OnApplicationComplete, PaymentTransactionBuilder, PaymentTransactionFields, SignedTransaction,
StateSchema, Transaction, TransactionHeader, TransactionHeaderBuilder,
};

#[cfg(test)]
Expand Down
81 changes: 81 additions & 0 deletions crates/algokit_transact/src/test_utils/asset_freeze.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::{Address, AssetFreezeTransactionBuilder, Byte32, TransactionHeaderBuilder};
use base64::{prelude::BASE64_STANDARD, Engine};

pub struct AssetFreezeTransactionMother {}

impl AssetFreezeTransactionMother {
pub fn asset_freeze() -> AssetFreezeTransactionBuilder {
// mainnet-2XFGVOHMFYLAWBHOSIOI67PBT5LDRHBTD3VLX5EYBDTFNVKMCJIA
let sender = "E4A6FVIHXSZ3F7QXRCOTYDDILVQYEBFH56HYDIIYX4SVXS2QX5GUTBVZHY"
.parse::<Address>()
.unwrap();
let freeze_address = "ZJU3X2B2QN3BUBIJ64JZ565V363ANGBUDOLXAJHDXGIIMYK6WV3NSNCBQQ"
.parse::<Address>()
.unwrap();
let genesis_hash: Byte32 = BASE64_STANDARD
.decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")
.unwrap()
.try_into()
.unwrap();
let note = BASE64_STANDARD
.decode("TkZUIGZyZWV6ZWQgYnkgbG9mdHkuYWk=")
.unwrap();
let group = BASE64_STANDARD
.decode("xERjxVTlNb8jeHa16qmpxDMh4+dcDCokO69QnNESbFk=")
.unwrap()
.try_into()
.unwrap();

AssetFreezeTransactionBuilder::default()
.header(
TransactionHeaderBuilder::default()
.sender(sender)
.fee(1000)
.first_valid(37463562)
.last_valid(37464562)
.genesis_hash(genesis_hash)
.genesis_id("mainnet-v1.0".to_string())
.note(note)
.group(group)
.build()
.unwrap(),
)
.asset_id(1707148495)
.freeze_target(freeze_address)
.frozen(true)
.to_owned()
}

pub fn asset_unfreeze() -> AssetFreezeTransactionBuilder {
// testnet-LZ2ODDAT4ATAVJUEQW34DIKMPCMBXCCHOSIYKMWGBPEVNHLSEV2A
let sender = "WLH5LELVSEVQL45LBRQYCLJAX6KQPGWUY5WHJXVRV2NPYZUBQAFPH22Q7A"
.parse::<Address>()
.unwrap();
let freeze_address = "ZYQX7BZ6LGTD7UCS7J5RVEAKHUJPK3FNJFZV2GPUYS2TFIADVFHDBKTN7I"
.parse::<Address>()
.unwrap();
let genesis_hash: Byte32 = BASE64_STANDARD
.decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=")
.unwrap()
.try_into()
.unwrap();
let note = BASE64_STANDARD.decode("th4JDxFROQw=").unwrap();

AssetFreezeTransactionBuilder::default()
.header(
TransactionHeaderBuilder::default()
.sender(sender)
.fee(1000)
.first_valid(3277583)
.last_valid(3278583)
.genesis_hash(genesis_hash)
.note(note)
.build()
.unwrap(),
)
.asset_id(185)
.freeze_target(freeze_address)
.frozen(false)
.to_owned()
}
}
44 changes: 44 additions & 0 deletions crates/algokit_transact/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod application_call;
mod asset_config;
mod asset_freeze;
mod key_registration;

use crate::{
Expand All @@ -18,6 +19,7 @@ use std::{fs::File, str::FromStr};

pub use application_call::ApplicationCallTransactionMother;
pub use asset_config::AssetConfigTransactionMother;
pub use asset_freeze::AssetFreezeTransactionMother;
pub use key_registration::KeyRegistrationTransactionMother;

pub struct TransactionHeaderMother {}
Expand Down Expand Up @@ -451,6 +453,28 @@ 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 = AssetFreezeTransactionMother::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 = AssetFreezeTransactionMother::asset_unfreeze()
.build()
.unwrap();
TransactionTestData::new(transaction, signing_private_key)
}

pub fn export<F, T>(path: &std::path::Path, transform: Option<F>)
where
F: Fn(&TransactionTestData) -> T,
Expand All @@ -473,6 +497,8 @@ impl TestDataMother {
"online_key_registration": Self::online_key_registration().as_json(&transform),
"offline_key_registration": Self::offline_key_registration().as_json(&transform),
"non_participation_key_registration": Self::non_participation_key_registration().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");
Expand Down Expand Up @@ -658,4 +684,22 @@ mod tests {
String::from("ACAP6ZGMGNTLUO3IQ26P22SRKYWTQQO3MF64GX7QO6NICDUFPM5A")
);
}

#[test]
fn test_asset_freeze_snapshot() {
let data = TestDataMother::asset_freeze();
assert_eq!(
data.id,
String::from("2XFGVOHMFYLAWBHOSIOI67PBT5LDRHBTD3VLX5EYBDTFNVKMCJIA")
);
}

#[test]
fn test_asset_unfreeze_snapshot() {
let data = TestDataMother::asset_unfreeze();
assert_eq!(
data.id,
String::from("LZ2ODDAT4ATAVJUEQW34DIKMPCMBXCCHOSIYKMWGBPEVNHLSEV2A")
);
}
}
58 changes: 58 additions & 0 deletions crates/algokit_transact/src/transactions/asset_freeze.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! 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 crate::Transaction;
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)]
#[serde(skip_serializing_if = "std::ops::Not::not")]
#[builder(default)]
pub frozen: bool,
}

impl AssetFreezeTransactionBuilder {
pub fn build(&self) -> Result<Transaction, AssetFreezeTransactionBuilderError> {
self.build_fields().map(Transaction::AssetFreeze)
}
}
7 changes: 7 additions & 0 deletions crates/algokit_transact/src/transactions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

mod application_call;
mod asset_config;
mod asset_freeze;
mod asset_transfer;
mod common;
mod key_registration;
Expand All @@ -20,6 +21,7 @@ pub use asset_config::{
asset_config_deserializer, asset_config_serializer, AssetConfigTransactionBuilder,
AssetConfigTransactionFields,
};
pub use asset_freeze::{AssetFreezeTransactionBuilder, AssetFreezeTransactionFields};
pub use asset_transfer::{AssetTransferTransactionBuilder, AssetTransferTransactionFields};
pub use common::{TransactionHeader, TransactionHeaderBuilder};
pub use key_registration::{KeyRegistrationTransactionBuilder, KeyRegistrationTransactionFields};
Expand Down Expand Up @@ -57,6 +59,9 @@ pub enum Transaction {
#[serde(rename = "appl")]
ApplicationCall(ApplicationCallTransactionFields),

#[serde(rename = "afrz")]
AssetFreeze(AssetFreezeTransactionFields),

#[serde(rename = "keyreg")]
KeyRegistration(KeyRegistrationTransactionFields),
}
Expand All @@ -76,6 +81,7 @@ impl Transaction {
Transaction::AssetConfig(a) => &a.header,
Transaction::ApplicationCall(a) => &a.header,
Transaction::KeyRegistration(k) => &k.header,
Transaction::AssetFreeze(f) => &f.header,
}
}

Expand All @@ -86,6 +92,7 @@ impl Transaction {
Transaction::AssetConfig(a) => &mut a.header,
Transaction::ApplicationCall(a) => &mut a.header,
Transaction::KeyRegistration(k) => &mut k.header,
Transaction::AssetFreeze(f) => &mut f.header,
}
}

Expand Down
Loading
Loading