The Transaction State Tracker is a comprehensive system for managing and tracking the lifecycle of transactions within the AnchorKit smart contract. It provides a robust mechanism to track transactions through four distinct states: Pending, In-Progress, Completed, and Failed.
The Transaction State Tracker supports four transaction states:
- Pending - Initial state when a transaction is created
- In-Progress - State when the transaction processing has started
- Completed - State when the transaction has been successfully processed
- Failed - State when the transaction has failed with an error message
┌─────────────────┐
│ PENDING │
└────────┬────────┘
│
┌────────▼────────┐
│ IN_PROGRESS │
└────┬────────┬───┘
│ │
┌───────────────┘ └───────────────┐
│ │
┌────▼────────┐ ┌───────▼──────┐
│ COMPLETED │ │ FAILED │
└─────────────┘ └──────────────┘
- Development Mode: Uses in-memory cache (
Vec<TransactionStateRecord>) - Production Mode: Designed for database persistence (implementation ready for DB integration)
#[contracttype]
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum TransactionState {
Pending = 1,
InProgress = 2,
Completed = 3,
Failed = 4,
}#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TransactionStateRecord {
pub transaction_id: u64,
pub state: TransactionState,
pub initiator: Address,
pub timestamp: u64,
pub last_updated: u64,
pub error_message: Option<String>,
}let mut tracker = TransactionStateTracker::new(is_dev_mode);tracker.create_transaction(transaction_id, initiator, env)?;- Creates a new transaction in Pending state
- Returns:
TransactionStateRecord
// Move to In-Progress
tracker.start_transaction(transaction_id, env)?;
// Mark as Completed
tracker.complete_transaction(transaction_id, env)?;
// Mark as Failed with error message
tracker.fail_transaction(transaction_id, error_message, env)?;// Get specific transaction state
tracker.get_transaction_state(transaction_id, env)?;
// Get all transactions in a specific state
tracker.get_transactions_by_state(state)?;
// Get all transactions
tracker.get_all_transactions()?;
// Get cache size (dev mode only)
tracker.cache_size();
// Clear cache (dev mode only)
tracker.clear_cache()?;use anchorkit::{TransactionStateTracker, TransactionState};
use soroban_sdk::Env;
fn handle_transaction(env: &Env, user: Address) {
let mut tracker = TransactionStateTracker::new(true); // dev mode
// Create a transaction
let record = tracker.create_transaction(1, user, env)
.expect("Failed to create transaction");
assert_eq!(record.state, TransactionState::Pending);
// Start processing
let record = tracker.start_transaction(1, env)
.expect("Failed to start transaction");
assert_eq!(record.state, TransactionState::InProgress);
// Process and complete
let record = tracker.complete_transaction(1, env)
.expect("Failed to complete transaction");
assert_eq!(record.state, TransactionState::Completed);
}fn handle_failed_transaction(env: &Env, user: Address) {
let mut tracker = TransactionStateTracker::new(true);
tracker.create_transaction(1, user, env).ok();
tracker.start_transaction(1, env).ok();
// Handle failure
let error_msg = String::from_slice(env, "Insufficient balance".as_bytes());
let record = tracker.fail_transaction(1, error_msg, env)
.expect("Failed to mark transaction as failed");
assert_eq!(record.state, TransactionState::Failed);
assert!(record.error_message.is_some());
}fn monitor_transactions(tracker: &TransactionStateTracker) {
// Get all pending transactions
let pending = tracker.get_transactions_by_state(TransactionState::Pending)
.expect("Failed to query pending transactions");
// Get all in-progress transactions
let in_progress = tracker.get_transactions_by_state(TransactionState::InProgress)
.expect("Failed to query in-progress transactions");
// Get all failed transactions for error handling
let failed = tracker.get_transactions_by_state(TransactionState::Failed)
.expect("Failed to query failed transactions");
}- Transactions are stored in a
Vec<TransactionStateRecord> - Useful for development and testing
- Can be cleared with
clear_cache() - Access with O(n) complexity for lookups
- Framework prepared for database integration
- In production mode, data would be persisted to permanent storage
- Supports error handling for database operations
- Ready to implement with:
- Soroban persistent storage
- External database integration
- Distributed cache (Redis, etc.)
- timestamp: Initial creation time
- last_updated: Latest state change time
- Automatically tracked for audit trails
- Failed transactions can store detailed error messages
- Error messages preserved in state record
- Useful for debugging and user notification
- Every transaction tracks the initiator address
- Enables per-user transaction filtering
- Supports multi-actor scenarios
The implementation includes comprehensive tests covering:
- State transitions (Pending → In-Progress → Completed/Failed)
- Error cases (transaction not found, production mode constraints)
- State queries by single ID and batch by state
- Cache management and lifecycle
- Timestamp accuracy
- Multi-transaction isolation
Run tests with:
cargo test --lib transaction_state_trackerThe Transaction State Tracker integrates with:
- Storage Module: Extended with transaction state storage keys
- Types Module: Defines shared state enums and structures
- Error Handling: Uses AnchorKit error types
- Session Management: Can be used to track session-level transactions
- Database Persistence: Implement Soroban persistent storage backend
- Event Emission: Emit events on state transitions
- TTL Management: Auto-cleanup of old transactions
- Batch Operations: Bulk state updates for efficiency
- Advanced Queries: Complex filtering and aggregation
- Rate Limiting: Throttle transaction creation
- History Tracking: Full audit trail of state changes
To add Transaction State Tracker to your AnchorKit deployment:
- Update to the latest AnchorKit version
- Import the module:
use anchorkit::TransactionStateTracker; - Initialize tracker:
let tracker = TransactionStateTracker::new(is_dev_mode); - Use the API for transaction lifecycle management
- Dev Mode: O(n) for lookups, O(1) for append
- Production Mode: Depends on storage backend implementation
- Cache Size: No built-in limits; clear periodically if needed
- Memory Usage: ~100-200 bytes per transaction record
- Transactions are immutable once created (state transitions only)
- Initiator address prevents spoofing
- Error messages are logged but should not contain sensitive data
- Production mode with database backend should use encryption at rest