algokit_transact/transactions/
mod.rsmod asset_transfer;
mod common;
mod payment;
use asset_transfer::AssetTransferTransactionBuilderError;
pub use asset_transfer::{AssetTransferTransactionBuilder, AssetTransferTransactionFields};
pub use common::{TransactionHeader, TransactionHeaderBuilder};
use payment::PaymentTransactionBuilderError;
pub use payment::{PaymentTransactionBuilder, PaymentTransactionFields};
use crate::constants::{
ALGORAND_SIGNATURE_BYTE_LENGTH, ALGORAND_SIGNATURE_ENCODING_INCR, HASH_BYTES_LENGTH,
MAX_TX_GROUP_SIZE,
};
use crate::error::AlgoKitTransactError;
use crate::traits::{AlgorandMsgpack, EstimateTransactionSize, TransactionId, Transactions};
use crate::utils::{compute_group_id, is_zero_addr_opt};
use crate::Address;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, Bytes};
use std::any::Any;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(tag = "type")]
pub enum Transaction {
#[serde(rename = "pay")]
Payment(PaymentTransactionFields),
#[serde(rename = "axfer")]
AssetTransfer(AssetTransferTransactionFields),
}
pub struct FeeParams {
pub fee_per_byte: u64,
pub min_fee: u64,
pub extra_fee: Option<u64>,
pub max_fee: Option<u64>,
}
impl Transaction {
pub fn header(&self) -> &TransactionHeader {
match self {
Transaction::Payment(p) => &p.header,
Transaction::AssetTransfer(a) => &a.header,
}
}
pub fn header_mut(&mut self) -> &mut TransactionHeader {
match self {
Transaction::Payment(p) => &mut p.header,
Transaction::AssetTransfer(a) => &mut a.header,
}
}
pub fn assign_fee(&self, request: FeeParams) -> Result<Transaction, AlgoKitTransactError> {
let mut tx = self.clone();
let mut calculated_fee: u64 = 0;
if request.fee_per_byte > 0 {
let estimated_size = tx.estimate_size()?;
calculated_fee = request.fee_per_byte * estimated_size as u64;
}
if calculated_fee < request.min_fee {
calculated_fee = request.min_fee;
}
if let Some(extra_fee) = request.extra_fee {
calculated_fee += extra_fee;
}
if let Some(max_fee) = request.max_fee {
if calculated_fee > max_fee {
return Err(AlgoKitTransactError::InputError(format!(
"Transaction fee {} µALGO is greater than max fee {} µALGO",
calculated_fee, max_fee
)));
}
}
let header = tx.header_mut();
header.fee = Some(calculated_fee);
return Ok(tx);
}
}
impl PaymentTransactionBuilder {
pub fn build(&self) -> Result<Transaction, PaymentTransactionBuilderError> {
self.build_fields().map(|d| Transaction::Payment(d))
}
}
impl AssetTransferTransactionBuilder {
pub fn build(&self) -> Result<Transaction, AssetTransferTransactionBuilderError> {
self.build_fields().map(|d| Transaction::AssetTransfer(d))
}
}
impl AlgorandMsgpack for Transaction {
const PREFIX: &'static [u8] = b"TX";
}
impl TransactionId for Transaction {}
impl EstimateTransactionSize for Transaction {
fn estimate_size(&self) -> Result<usize, AlgoKitTransactError> {
return Ok(self.encode_raw()?.len() + ALGORAND_SIGNATURE_ENCODING_INCR);
}
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct SignedTransaction {
#[serde(rename = "txn")]
pub transaction: Transaction,
#[serde(rename = "sig")]
#[serde_as(as = "Option<Bytes>")]
pub signature: Option<[u8; ALGORAND_SIGNATURE_BYTE_LENGTH]>,
#[serde(rename = "sgnr")]
#[serde(skip_serializing_if = "is_zero_addr_opt")]
#[serde(default)]
pub auth_address: Option<Address>,
}
impl AlgorandMsgpack for SignedTransaction {
fn decode(bytes: &[u8]) -> Result<Self, AlgoKitTransactError> {
let value: rmpv::Value = rmp_serde::from_slice(bytes)?;
match value {
rmpv::Value::Map(map) => {
let txn_value = &map
.iter()
.find(|(k, _)| k.as_str() == Some("txn"))
.unwrap()
.1;
let mut txn_buf = Vec::new();
rmpv::encode::write_value(&mut txn_buf, &txn_value)?;
let stxn = SignedTransaction {
transaction: Transaction::decode(&txn_buf)?,
..rmp_serde::from_slice(bytes)?
};
return Ok(stxn);
}
_ => {
return Err(AlgoKitTransactError::InputError(format!(
"expected signed transaction to be a map, but got a: {:#?}",
value.type_id()
)))
}
}
}
}
impl TransactionId for SignedTransaction {
fn id_raw(&self) -> Result<[u8; HASH_BYTES_LENGTH], AlgoKitTransactError> {
self.transaction.id_raw()
}
}
impl EstimateTransactionSize for SignedTransaction {
fn estimate_size(&self) -> Result<usize, AlgoKitTransactError> {
return Ok(self.encode()?.len());
}
}
impl Transactions for &[Transaction] {
fn assign_group(self) -> Result<Vec<Transaction>, AlgoKitTransactError> {
if self.len() > MAX_TX_GROUP_SIZE {
return Err(AlgoKitTransactError::InputError(format!(
"Transaction group size exceeds the max limit of {}",
MAX_TX_GROUP_SIZE
)));
}
if self.is_empty() {
return Err(AlgoKitTransactError::InputError(String::from(
"Transaction group size cannot be 0",
)));
}
let group_id = compute_group_id(self)?;
Ok(self
.iter()
.map(|tx| {
let mut tx = tx.clone();
tx.header_mut().group = Some(group_id);
tx
})
.collect())
}
}