Transaction API
The transaction module provides a complete implementation of the Bitcoin UTXO model.
Data Structures
TxInput
A transaction input that references a previously unspent output (UTXO).
#![allow(unused)]
fn main() {
pub struct TxInput {
pub txid: String, // ID of the referenced transaction
pub vout: usize, // Output index
pub signature: String, // Digital signature
pub pub_key: String, // Public key
}
}
Methods:
new
#![allow(unused)]
fn main() {
pub fn new(
txid: String,
vout: usize,
signature: String,
pub_key: String
) -> Self
}
Creates a new transaction input.
Parameters:
txid- ID of the referenced transactionvout- Output index numbersignature- Signature generated with the private keypub_key- Corresponding public key
Example:
#![allow(unused)]
fn main() {
let input = TxInput::new(
"abc123...".to_string(),
0,
wallet.sign("data"),
wallet.public_key.clone()
);
}
TxOutput
A transaction output representing an unspent amount (UTXO).
#![allow(unused)]
fn main() {
pub struct TxOutput {
pub value: u64, // Amount (satoshi)
pub pub_key_hash: String, // Recipient address
}
}
Methods:
new
#![allow(unused)]
fn main() {
pub fn new(value: u64, address: String) -> Self
}
Creates a new transaction output.
Parameters:
value- Output amount (satoshi)address- Recipient address
Example:
#![allow(unused)]
fn main() {
let output = TxOutput::new(5000, bob_address);
}
can_be_unlocked_with
#![allow(unused)]
fn main() {
pub fn can_be_unlocked_with(&self, address: &str) -> bool
}
Checks whether this output can be unlocked by the specified address.
Parameters:
address- The address to check
Return value:
true- Address matchesfalse- Address does not match
Example:
#![allow(unused)]
fn main() {
if output.can_be_unlocked_with(&alice.address) {
println!("Alice can spend this output");
}
}
Transaction
The complete transaction structure.
#![allow(unused)]
fn main() {
pub struct Transaction {
pub id: String, // Transaction ID
pub inputs: Vec<TxInput>, // List of inputs
pub outputs: Vec<TxOutput>, // List of outputs
pub timestamp: u64, // Unix timestamp
pub fee: u64, // Transaction fee
}
}
Methods:
new
#![allow(unused)]
fn main() {
pub fn new(
inputs: Vec<TxInput>,
outputs: Vec<TxOutput>,
timestamp: u64,
fee: u64
) -> Self
}
Creates a new transaction.
Parameters:
inputs- List of transaction inputsoutputs- List of transaction outputstimestamp- Unix timestampfee- Transaction fee (satoshi)
Return value:
- A newly created transaction instance with an automatically computed ID
Example:
#![allow(unused)]
fn main() {
let tx = Transaction::new(
vec![input1, input2],
vec![output1, output2],
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
10
);
}
new_coinbase
#![allow(unused)]
fn main() {
pub fn new_coinbase(
to: String,
reward: u64,
timestamp: u64,
total_fees: u64
) -> Self
}
Creates a Coinbase transaction (mining reward).
Parameters:
to- Miner addressreward- Block reward (excluding fees)timestamp- Unix timestamptotal_fees- Sum of all transaction fees in the block
Return value:
- Coinbase transaction instance
Example:
#![allow(unused)]
fn main() {
let coinbase = Transaction::new_coinbase(
miner.address,
50,
timestamp,
total_fees
);
}
calculate_hash
#![allow(unused)]
fn main() {
pub fn calculate_hash(&self) -> String
}
Computes the transaction hash (transaction ID).
Return value:
- 64-character hexadecimal hash string
Notes:
- Uses the SHA256 algorithm
- Includes all transaction data (inputs, outputs, timestamp, fee)
- Any change in data will produce a completely different hash
Example:
#![allow(unused)]
fn main() {
let tx_id = tx.calculate_hash();
println!("Transaction ID: {}", tx_id);
}
is_coinbase
#![allow(unused)]
fn main() {
pub fn is_coinbase(&self) -> bool
}
Checks whether this is a Coinbase transaction.
Return value:
true- Coinbase transactionfalse- Regular transaction
Criteria:
- Exactly one input
- That input’s txid is empty
Example:
#![allow(unused)]
fn main() {
if tx.is_coinbase() {
println!("This is a mining reward transaction");
} else {
println!("This is a regular transaction");
}
}
verify
#![allow(unused)]
fn main() {
pub fn verify(&self) -> bool
}
Validates the transaction (simplified version).
Validation items:
- Coinbase transactions are always valid
- Checks for at least one input and one output
- Checks that the signature and public key are non-empty
Return value:
true- Transaction is validfalse- Transaction is invalid
Note: Real Bitcoin also requires:
- ECDSA signature correctness
- UTXO existence
- Amount balance
- Script execution
Example:
#![allow(unused)]
fn main() {
if tx.verify() {
blockchain.add_transaction(tx)?;
} else {
return Err("Invalid transaction".to_string());
}
}
size
#![allow(unused)]
fn main() {
pub fn size(&self) -> usize
}
Calculates the transaction size in bytes.
Return value:
- Byte size of the transaction
Use cases:
- Computing fee rate (sat/byte)
- Estimating block space usage
- Fee estimation
Example:
#![allow(unused)]
fn main() {
let size = tx.size();
println!("Transaction size: {} bytes", size);
}
fee_rate
#![allow(unused)]
fn main() {
pub fn fee_rate(&self) -> f64
}
Calculates the transaction fee rate (satoshi/byte).
Return value:
- Fee rate (sat/byte)
Formula:
fee_rate = fee / size
Fee rate reference:
- 1–5 sat/byte: Low priority
- 5–20 sat/byte: Medium priority
- 20–50 sat/byte: High priority
- 50+ sat/byte: Urgent
Example:
#![allow(unused)]
fn main() {
let rate = tx.fee_rate();
println!("Fee rate: {:.2} sat/byte", rate);
if rate < 5.0 {
println!("Warning: Low fee rate, confirmation may be slow");
}
}
output_sum
#![allow(unused)]
fn main() {
pub fn output_sum(&self) -> u64
}
Gets the total amount of all outputs.
Return value:
- Total output amount (satoshi)
Use cases:
- Verifying transaction balance
- Computing the actual fee
Formula:
fee = input_sum - output_sum
Example:
#![allow(unused)]
fn main() {
let output_total = tx.output_sum();
let fee = input_total - output_total;
println!("Fee: {}", fee);
}
Usage Examples
Creating a Simple Transaction
use bitcoin_simulation::{
blockchain::Blockchain,
wallet::Wallet,
};
fn main() -> Result<(), String> {
let mut blockchain = Blockchain::new();
let alice = Wallet::new();
let bob = Wallet::new();
// Alice receives initial funds
let init_tx = blockchain.create_transaction(
&Wallet::from_address("genesis".to_string()),
alice.address.clone(),
10000,
0,
)?;
blockchain.add_transaction(init_tx)?;
blockchain.mine_pending_transactions(alice.address.clone())?;
// Alice sends to Bob
let tx = blockchain.create_transaction(
&alice,
bob.address.clone(),
3000, // amount
10, // fee
)?;
// View transaction details
println!("Transaction ID: {}", tx.id);
println!("Number of inputs: {}", tx.inputs.len());
println!("Number of outputs: {}", tx.outputs.len());
println!("Fee: {}", tx.fee);
println!("Fee rate: {:.2} sat/byte", tx.fee_rate());
// Add to the blockchain
blockchain.add_transaction(tx)?;
blockchain.mine_pending_transactions(bob.address)?;
Ok(())
}
Batch Transactions
#![allow(unused)]
fn main() {
// Create multiple transactions to test fee priority
let transactions = vec![
(bob.address.clone(), 1000, 1), // Low fee
(charlie.address.clone(), 2000, 50), // High fee
(david.address.clone(), 3000, 5), // Medium fee
];
for (to, amount, fee) in transactions {
let tx = blockchain.create_transaction(&alice, to, amount, fee)?;
blockchain.add_transaction(tx)?;
}
// Mining sorts transactions by fee rate, highest first
blockchain.mine_pending_transactions(miner.address)?;
}
Manually Building a Transaction
#![allow(unused)]
fn main() {
use std::time::{SystemTime, UNIX_EPOCH};
use bitcoin_simulation::transaction::{Transaction, TxInput, TxOutput};
// 1. Create input (requires knowledge of the prior UTXO)
let input = TxInput::new(
"previous_tx_id".to_string(),
0, // vout
alice.sign("tx_data"),
alice.public_key.clone(),
);
// 2. Create outputs
let output1 = TxOutput::new(3000, bob.address); // To Bob
let output2 = TxOutput::new(6990, alice.address); // Change
// 3. Assemble transaction
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let tx = Transaction::new(
vec![input],
vec![output1, output2],
timestamp,
10, // fee
);
// 4. Verify and add
if tx.verify() {
blockchain.add_transaction(tx)?;
}
}
Error Handling
#![allow(unused)]
fn main() {
match blockchain.create_transaction(&alice, bob.address, 1000, 10) {
Ok(tx) => {
println!("✓ Transaction created successfully");
blockchain.add_transaction(tx)?;
}
Err(e) => {
eprintln!("❌ Transaction creation failed: {}", e);
// Common errors:
// - "Insufficient balance (including fee)"
// - "UTXO does not exist"
// - "Referenced transaction does not exist"
}
}
}
Best Practices
1. Setting Fees
#![allow(unused)]
fn main() {
// Set fee based on urgency
let size = estimate_tx_size(inputs_count, outputs_count);
let fee = match urgency {
Urgency::Low => size * 1, // 1 sat/byte
Urgency::Medium => size * 10, // 10 sat/byte
Urgency::High => size * 50, // 50 sat/byte
};
}
2. UTXO Selection
#![allow(unused)]
fn main() {
// Prefer small UTXOs to avoid fragmentation
let utxos = blockchain.utxo_set.find_spendable_outputs(&address, amount)?;
println!("Used {} UTXOs", utxos.1.len());
}
3. Transaction Validation
#![allow(unused)]
fn main() {
// Validate immediately after creating a transaction
let tx = Transaction::new(...);
assert!(tx.verify(), "Transaction validation failed");
assert!(tx.fee_rate() >= 1.0, "Fee rate too low");
}
References
- Blockchain API - Blockchain transaction management
- UTXO API - UTXO set operations
- Wallet API - Wallet and signing