Skip to main content

dpp_domain/transfer/
chain.rs

1//! [`TransferChain`] — the append-only history of responsibility transfers.
2
3use serde::{Deserialize, Serialize};
4
5use super::error::TransferError;
6use super::record::TransferRecord;
7use super::status::TransferStatus;
8use crate::operator::ResponsibleOperator;
9use crate::passport::PassportId;
10
11/// The complete history of responsibility transfers for a DPP.
12///
13/// Maintained as an append-only log. Once a transfer is completed,
14/// it cannot be modified or removed.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct TransferChain {
18    /// The passport this chain belongs to.
19    pub passport_id: PassportId,
20    /// The original responsible operator (at first publication).
21    pub original_operator: ResponsibleOperator,
22    /// Ordered list of transfer events (oldest first).
23    pub transfers: Vec<TransferRecord>,
24}
25
26impl TransferChain {
27    /// Create a new chain with an initial operator and no transfers.
28    #[must_use]
29    pub fn new(passport_id: PassportId, original_operator: ResponsibleOperator) -> Self {
30        Self {
31            passport_id,
32            original_operator,
33            transfers: Vec::new(),
34        }
35    }
36
37    /// Returns the current responsible operator.
38    ///
39    /// If no completed transfers exist, returns the original operator.
40    /// Otherwise, returns the `to_operator` of the most recent completed transfer.
41    pub fn current_operator(&self) -> &ResponsibleOperator {
42        self.transfers
43            .iter()
44            .rev()
45            .find(|t| t.is_complete())
46            .map(|t| &t.to_operator)
47            .unwrap_or(&self.original_operator)
48    }
49
50    /// Returns the total number of completed transfers.
51    pub fn transfer_count(&self) -> usize {
52        self.transfers.iter().filter(|t| t.is_complete()).count()
53    }
54
55    /// Append a new transfer record to the chain.
56    ///
57    /// Validates that:
58    /// - The `from_operator` matches the current responsible operator.
59    /// - No other transfer is currently pending (initiated but not completed/cancelled).
60    pub fn initiate_transfer(&mut self, record: TransferRecord) -> Result<(), TransferError> {
61        // Check that from_operator matches current
62        let current = self.current_operator();
63        if record.from_operator.did != current.did {
64            return Err(TransferError::OperatorMismatch {
65                expected: current.did.clone(),
66                got: record.from_operator.did.clone(),
67            });
68        }
69
70        // Check no pending transfer exists
71        let has_pending = self.transfers.iter().any(|t| {
72            t.status() == TransferStatus::Initiated || t.status() == TransferStatus::Accepted
73        });
74        if has_pending {
75            return Err(TransferError::TransferAlreadyPending);
76        }
77
78        self.transfers.push(record);
79        Ok(())
80    }
81}