1pub mod adapters;
3pub mod asset;
4pub mod balance;
5pub mod error;
6pub mod holding;
7pub mod money;
8pub mod transaction;
9pub mod value_object;
10
11pub use asset::Asset;
12pub use balance::Balance;
13use chrono::{DateTime, Utc};
14pub use error::MoneyError;
15pub use holding::{Holding, Portfolio};
16pub use money::{ExecutionPlan, LedgerContext, Money, MoneySlice, Operation, TransactionContext};
17pub use transaction::Transaction;
18pub use value_object::{ValueObject, ValueObjectState};
19
20use async_trait::async_trait;
21use std::sync::Arc;
22use uuid::Uuid;
23
24pub(crate) fn hash_idempotency_key(key: &str) -> String {
25 blake3::hash(key.as_bytes()).to_hex().to_string()
26}
27
28#[async_trait]
30pub trait LedgerAdapter: Send + Sync {
31 async fn execute_plan(
39 &self,
40 plan: &ExecutionPlan,
41 locks: &[(Uuid, Uuid, u64)],
42 ) -> Result<(), MoneyError>;
43
44 async fn get_balance(&self, asset_id: Uuid, owner: Uuid) -> Result<Balance, MoneyError>;
46 async fn get_transaction(&self, tx_id: Uuid) -> Result<Transaction, MoneyError>;
47 async fn get_transactions_for_owner(
48 &self,
49 owner: Uuid,
50 timespan: &[DateTime<Utc>; 2],
51 ) -> Result<Vec<Transaction>, MoneyError>;
52 async fn check_idempotency_key(&self, key: &str) -> Result<(), MoneyError>;
53 async fn get_transaction_by_idempotency_key(
54 &self,
55 key: &str,
56 ) -> Result<Transaction, MoneyError>;
57 async fn get_asset(&self, code: &str) -> Result<Asset, MoneyError>;
58 async fn create_asset(&self, asset: Asset) -> Result<(), MoneyError>;
59
60 async fn get_holdings(&self, owner: Uuid) -> Result<Vec<Holding>, MoneyError>;
62
63 async fn get_transactions_for_asset(
65 &self,
66 asset_id: Uuid,
67 timespan: &[DateTime<Utc>; 2],
68 ) -> Result<Vec<Transaction>, MoneyError>;
69}
70
71pub struct LedgerSystem {
73 adapter: Arc<dyn LedgerAdapter>,
74}
75
76impl LedgerSystem {
77 pub fn new(adapter: Box<dyn LedgerAdapter>) -> Self {
78 Self {
79 adapter: adapter.into(),
80 }
81 }
82
83 pub fn adapter(&self) -> &dyn LedgerAdapter {
85 self.adapter.as_ref()
86 }
87
88 pub fn adapter_arc(&self) -> Arc<dyn LedgerAdapter> {
90 Arc::clone(&self.adapter)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_asset_conversion() {
100 let usd = Asset::new("USD", 10_000, 2);
101 assert_eq!(usd.to_internal(100.50), 10050);
102 assert_eq!(usd.to_display(10050), 100.50);
103
104 let eth = Asset::new("ETH", 1_000_000_000_000_000_000u64, 18);
105 let one_eth = 1_000_000_000_000_000_000u64;
106 assert_eq!(eth.to_display(one_eth), 1.0);
107 }
108
109 #[test]
110 fn test_value_object_states() {
111 assert!(matches!(ValueObjectState::Alive, ValueObjectState::Alive));
112 assert!(matches!(
113 ValueObjectState::Reserved,
114 ValueObjectState::Reserved
115 ));
116 assert!(matches!(ValueObjectState::Burned, ValueObjectState::Burned));
117 }
118}