use crate::error::AptosResult;
use crate::transaction::authenticator::TransactionAuthenticator;
use crate::transaction::payload::TransactionPayload;
use crate::types::{AccountAddress, ChainId, HashValue};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawTransaction {
pub sender: AccountAddress,
pub sequence_number: u64,
pub payload: TransactionPayload,
pub max_gas_amount: u64,
pub gas_unit_price: u64,
pub expiration_timestamp_secs: u64,
pub chain_id: ChainId,
}
impl RawTransaction {
pub fn new(
sender: AccountAddress,
sequence_number: u64,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
Self {
sender,
sequence_number,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransaction");
let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RawTransactionOrderless {
pub sender: AccountAddress,
pub nonce: Vec<u8>,
pub payload: TransactionPayload,
pub max_gas_amount: u64,
pub gas_unit_price: u64,
pub expiration_timestamp_secs: u64,
pub chain_id: ChainId,
}
impl RawTransactionOrderless {
pub fn new(
sender: AccountAddress,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
let mut nonce = vec![0u8; 32];
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut nonce);
Self {
sender,
nonce,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn with_nonce(
sender: AccountAddress,
nonce: Vec<u8>,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
Self {
sender,
nonce,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionOrderless");
let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignedTransactionOrderless {
pub raw_txn: RawTransactionOrderless,
pub authenticator: TransactionAuthenticator,
}
impl SignedTransactionOrderless {
pub fn new(raw_txn: RawTransactionOrderless, authenticator: TransactionAuthenticator) -> Self {
Self {
raw_txn,
authenticator,
}
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
pub fn sender(&self) -> AccountAddress {
self.raw_txn.sender
}
pub fn nonce(&self) -> &[u8] {
&self.raw_txn.nonce
}
pub fn hash(&self) -> AptosResult<HashValue> {
let bcs_bytes = self.to_bcs()?;
let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
data.extend_from_slice(&prefix);
data.push(2);
data.extend_from_slice(&bcs_bytes);
Ok(HashValue::new(crate::crypto::sha3_256(&data)))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignedTransaction {
pub raw_txn: RawTransaction,
pub authenticator: TransactionAuthenticator,
}
impl SignedTransaction {
pub fn new(raw_txn: RawTransaction, authenticator: TransactionAuthenticator) -> Self {
Self {
raw_txn,
authenticator,
}
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
pub fn sender(&self) -> AccountAddress {
self.raw_txn.sender
}
pub fn sequence_number(&self) -> u64 {
self.raw_txn.sequence_number
}
pub fn hash(&self) -> AptosResult<HashValue> {
let bcs_bytes = self.to_bcs()?;
let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
data.extend_from_slice(&prefix);
data.push(0); data.extend_from_slice(&bcs_bytes);
Ok(HashValue::new(crate::crypto::sha3_256(&data)))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransactionInfo {
pub hash: HashValue,
#[serde(default)]
pub version: Option<u64>,
#[serde(default)]
pub success: Option<bool>,
#[serde(default)]
pub vm_status: Option<String>,
#[serde(default)]
pub gas_used: Option<u64>,
}
impl TransactionInfo {
pub fn is_success(&self) -> bool {
self.success.unwrap_or(false)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MultiAgentRawTransaction {
pub raw_txn: RawTransaction,
pub secondary_signer_addresses: Vec<AccountAddress>,
}
impl MultiAgentRawTransaction {
pub fn new(raw_txn: RawTransaction, secondary_signer_addresses: Vec<AccountAddress>) -> Self {
Self {
raw_txn,
secondary_signer_addresses,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
#[derive(Serialize)]
enum RawTransactionWithData<'a> {
MultiAgent {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
},
}
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
let data = RawTransactionWithData::MultiAgent {
raw_txn: &self.raw_txn,
secondary_signer_addresses: &self.secondary_signer_addresses,
};
let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeePayerRawTransaction {
pub raw_txn: RawTransaction,
pub secondary_signer_addresses: Vec<AccountAddress>,
pub fee_payer_address: AccountAddress,
}
impl FeePayerRawTransaction {
pub fn new(
raw_txn: RawTransaction,
secondary_signer_addresses: Vec<AccountAddress>,
fee_payer_address: AccountAddress,
) -> Self {
Self {
raw_txn,
secondary_signer_addresses,
fee_payer_address,
}
}
pub fn new_simple(raw_txn: RawTransaction, fee_payer_address: AccountAddress) -> Self {
Self {
raw_txn,
secondary_signer_addresses: vec![],
fee_payer_address,
}
}
pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
#[derive(Serialize)]
enum RawTransactionWithData<'a> {
#[allow(dead_code)]
MultiAgent {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
},
MultiAgentWithFeePayer {
raw_txn: &'a RawTransaction,
secondary_signer_addresses: &'a Vec<AccountAddress>,
fee_payer_address: &'a AccountAddress,
},
}
let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
let data = RawTransactionWithData::MultiAgentWithFeePayer {
raw_txn: &self.raw_txn,
secondary_signer_addresses: &self.secondary_signer_addresses,
fee_payer_address: &self.fee_payer_address,
};
let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
message.extend_from_slice(&prefix);
message.extend_from_slice(&bcs_bytes);
Ok(message)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::payload::EntryFunction;
use crate::types::MoveModuleId;
fn create_test_raw_transaction() -> RawTransaction {
RawTransaction::new(
AccountAddress::ONE,
0,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
)
}
#[test]
fn test_raw_transaction_signing_message() {
let txn = create_test_raw_transaction();
let message = txn.signing_message().unwrap();
assert!(!message.is_empty());
assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
}
#[test]
fn test_raw_transaction_fields() {
let txn = create_test_raw_transaction();
assert_eq!(txn.sender, AccountAddress::ONE);
assert_eq!(txn.sequence_number, 0);
assert_eq!(txn.max_gas_amount, 100_000);
assert_eq!(txn.gas_unit_price, 100);
assert_eq!(txn.expiration_timestamp_secs, 1_000_000_000);
assert_eq!(txn.chain_id, ChainId::testnet());
}
#[test]
fn test_raw_transaction_bcs_serialization() {
let txn = create_test_raw_transaction();
let bcs = txn.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_transaction() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
assert_eq!(signed.sender(), AccountAddress::ONE);
}
#[test]
fn test_signed_transaction_bcs() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
let bcs = signed.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_authenticator_bcs_format() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0xab; 32]),
signature: Ed25519Signature([0xcd; 64]),
};
let bcs = aptos_bcs::to_bytes(&auth).unwrap();
println!("Authenticator BCS bytes: {}", const_hex::encode(&bcs));
println!("First byte (variant index): {}", bcs[0]);
println!("Second byte (length prefix): {}", bcs[1]);
println!("Third byte (first pubkey byte): {}", bcs[2]);
assert_eq!(bcs[0], 0, "Ed25519 variant index should be 0");
assert_eq!(bcs[1], 32, "Pubkey length prefix should be 32");
assert_eq!(bcs[2], 0xab, "First pubkey byte should be 0xab");
assert_eq!(bcs[34], 64, "Signature length prefix should be 64");
assert_eq!(bcs[35], 0xcd, "First signature byte should be 0xcd");
assert_eq!(bcs.len(), 99, "BCS length should be 99");
}
#[test]
fn test_transaction_info_deserialization() {
let json = r#"{
"version": 12345,
"hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
"gas_used": 100,
"success": true,
"vm_status": "Executed successfully"
}"#;
let info: TransactionInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.version, Some(12345));
assert_eq!(info.gas_used, Some(100));
assert_eq!(info.success, Some(true));
assert_eq!(info.vm_status, Some("Executed successfully".to_string()));
}
#[test]
fn test_fee_payer_raw_transaction_new() {
let raw_txn = create_test_raw_transaction();
let secondary_addr = AccountAddress::from_hex("0x2").unwrap();
let fee_payer_addr = AccountAddress::THREE;
let fee_payer = FeePayerRawTransaction::new(raw_txn, vec![secondary_addr], fee_payer_addr);
assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
assert_eq!(fee_payer.secondary_signer_addresses.len(), 1);
}
#[test]
fn test_fee_payer_raw_transaction_new_simple() {
let raw_txn = create_test_raw_transaction();
let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
assert!(fee_payer.secondary_signer_addresses.is_empty());
}
#[test]
fn test_fee_payer_signing_message() {
let raw_txn = create_test_raw_transaction();
let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
let message = fee_payer.signing_message().unwrap();
assert!(!message.is_empty());
}
#[test]
fn test_signed_transaction_hash() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
let hash = signed.hash().unwrap();
assert_eq!(hash.as_bytes().len(), 32);
let hash2 = signed.hash().unwrap();
assert_eq!(hash, hash2);
}
#[test]
fn test_signed_transaction_sequence_number() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_raw_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransaction::new(txn, auth);
assert_eq!(signed.sequence_number(), 0);
}
#[test]
fn test_transaction_info_is_success() {
let info_success = TransactionInfo {
hash: HashValue::new([0; 32]),
version: Some(1),
success: Some(true),
vm_status: None,
gas_used: Some(100),
};
assert!(info_success.is_success());
let info_failed = TransactionInfo {
hash: HashValue::new([0; 32]),
version: Some(1),
success: Some(false),
vm_status: Some("Failed".to_string()),
gas_used: Some(100),
};
assert!(!info_failed.is_success());
let info_unknown = TransactionInfo {
hash: HashValue::new([0; 32]),
version: None,
success: None,
vm_status: None,
gas_used: None,
};
assert!(!info_unknown.is_success());
}
fn create_test_orderless_transaction() -> RawTransactionOrderless {
RawTransactionOrderless::with_nonce(
AccountAddress::ONE,
vec![
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,
],
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
)
}
#[test]
fn test_orderless_transaction_with_nonce() {
let nonce = vec![0xab; 32];
let txn = RawTransactionOrderless::with_nonce(
AccountAddress::ONE,
nonce.clone(),
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
assert_eq!(txn.sender, AccountAddress::ONE);
assert_eq!(txn.nonce, nonce);
assert_eq!(txn.max_gas_amount, 100_000);
assert_eq!(txn.gas_unit_price, 100);
}
#[test]
fn test_orderless_transaction_new_generates_random_nonce() {
let txn1 = RawTransactionOrderless::new(
AccountAddress::ONE,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
let txn2 = RawTransactionOrderless::new(
AccountAddress::ONE,
TransactionPayload::EntryFunction(EntryFunction {
module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
function: "transfer".to_string(),
type_args: vec![],
args: vec![],
}),
100_000,
100,
1_000_000_000,
ChainId::testnet(),
);
assert_ne!(txn1.nonce, txn2.nonce);
assert_eq!(txn1.nonce.len(), 32);
assert_eq!(txn2.nonce.len(), 32);
}
#[test]
fn test_orderless_transaction_signing_message() {
let txn = create_test_orderless_transaction();
let message = txn.signing_message().unwrap();
assert!(!message.is_empty());
assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
}
#[test]
fn test_orderless_transaction_bcs() {
let txn = create_test_orderless_transaction();
let bcs = txn.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_orderless_transaction() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
assert_eq!(signed.sender(), AccountAddress::ONE);
assert_eq!(signed.nonce().len(), 32);
}
#[test]
fn test_signed_orderless_transaction_bcs() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
let bcs = signed.to_bcs().unwrap();
assert!(!bcs.is_empty());
}
#[test]
fn test_signed_orderless_transaction_hash() {
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
let txn = create_test_orderless_transaction();
let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
let signed = SignedTransactionOrderless::new(txn, auth);
let hash = signed.hash().unwrap();
assert_eq!(hash.as_bytes().len(), 32);
let hash2 = signed.hash().unwrap();
assert_eq!(hash, hash2);
}
#[test]
fn test_multi_agent_raw_transaction() {
let raw_txn = create_test_raw_transaction();
let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary.clone());
assert_eq!(multi_agent.secondary_signer_addresses, secondary);
}
#[test]
fn test_multi_agent_signing_message() {
let raw_txn = create_test_raw_transaction();
let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary);
let message = multi_agent.signing_message().unwrap();
assert!(!message.is_empty());
}
}