algokit_transact/transactions/common.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
//! Common types and structures for Algorand transactions.
//!
//! This module provides the fundamental transaction types and headers used
//! across different transaction types.
use crate::address::Address;
use crate::constants::Byte32;
use crate::utils::{
is_empty_bytes32_opt, is_empty_string_opt, is_empty_vec_opt, is_zero, is_zero_addr,
is_zero_addr_opt, is_zero_opt,
};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, Bytes};
/// Common header fields shared by all transaction types.
///
/// This structure contains the fields that are present in every transaction,
/// regardless of transaction type.
#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Builder)]
#[builder(setter(strip_option))]
pub struct TransactionHeader {
/// The account that authorized the transaction.
///
/// Fees are deducted from this account.
#[serde(rename = "snd")]
#[serde(skip_serializing_if = "is_zero_addr")]
#[serde(default)]
pub sender: Address,
/// Optional transaction fee in microALGO.
///
/// When None, the fee will be interpreted as 0 by the network.
#[serde(skip_serializing_if = "is_zero_opt")]
#[serde(default)]
#[builder(default)]
pub fee: Option<u64>,
/// First round for when the transaction is valid.
#[serde(rename = "fv")]
#[serde(skip_serializing_if = "is_zero")]
#[serde(default)]
pub first_valid: u64,
/// Last round for when the transaction is valid.
///
/// After this round, the transaction will be expired.
#[serde(rename = "lv")]
#[serde(skip_serializing_if = "is_zero")]
#[serde(default)]
pub last_valid: u64,
/// Hash of the genesis block of the network.
///
/// Used to identify which network the transaction is for.
#[serde(rename = "gh")]
#[serde_as(as = "Option<Bytes>")]
#[serde(skip_serializing_if = "is_empty_bytes32_opt")]
#[serde(default)]
#[builder(default)]
pub genesis_hash: Option<Byte32>,
/// Genesis ID of the network.
///
/// A human-readable string used alongside genesis hash to identify the network.
#[serde(rename = "gen")]
#[serde(skip_serializing_if = "is_empty_string_opt")]
#[serde(default)]
#[builder(default)]
pub genesis_id: Option<String>,
/// Optional user-defined note field.
///
/// Can contain arbitrary data up to 1KB in size.
#[serde_as(as = "Option<Bytes>")]
#[serde(skip_serializing_if = "is_empty_vec_opt")]
#[serde(default)]
#[builder(default)]
pub note: Option<Vec<u8>>,
/// Optional authorized account for future transactions.
///
/// If set, only this account will be used for transaction authorization going forward.
/// Reverting back control to the original address must be done by setting this field to
/// the original address.
#[serde(rename = "rekey")]
#[serde(skip_serializing_if = "is_zero_addr_opt")]
#[serde(default)]
#[builder(default)]
pub rekey_to: Option<Address>,
/// Optional lease value to enforce mutual transaction exclusion.
///
/// When a transaction with a non-empty lease field is confirmed, the lease is acquired.
/// A lease X is acquired by the sender, generating the (sender, X) lease.
/// The lease is kept active until the last_valid round of the transaction has elapsed.
/// No other transaction sent by the same sender can be confirmed until the lease expires.
#[serde(rename = "lx")]
#[serde_as(as = "Option<Bytes>")]
#[serde(skip_serializing_if = "is_empty_bytes32_opt")]
#[serde(default)]
#[builder(default)]
pub lease: Option<Byte32>,
/// Optional group ID for atomic transaction grouping.
///
/// Transactions with the same group ID must execute together or not at all.
#[serde(rename = "grp")]
#[serde_as(as = "Option<Bytes>")]
#[serde(skip_serializing_if = "is_empty_bytes32_opt")]
#[serde(default)]
#[builder(default)]
pub group: Option<Byte32>,
}
/// Validation errors for asset configuration transactions.
#[derive(Debug, Clone, PartialEq)]
pub enum TransactionValidationError {
RequiredField(String),
FieldTooLong {
field: String,
actual: usize,
max: usize,
unit: String,
},
ImmutableField(String),
}
impl std::fmt::Display for TransactionValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransactionValidationError::RequiredField(field) => {
write!(f, "{} is required", field)
}
TransactionValidationError::FieldTooLong {
field,
actual,
max,
unit,
} => {
write!(
f,
"{} cannot exceed {} {}, got {}",
field, max, unit, actual
)
}
TransactionValidationError::ImmutableField(field) => {
write!(f, "Field '{}' is immutable and cannot be changed", field)
}
}
}
}
impl std::error::Error for TransactionValidationError {}