Skip to main content

ic_query/icrc/model/reports/account_history/
mod.rs

1//! Module: icrc::model::reports::account_history
2//!
3//! Responsibility: serialized ICRC account-history page, cache, and row contracts.
4//! Does not own: ledger-wide history, requests, live transport, cache mechanics, or rendering.
5//! Boundary: owns the stable JSON and persisted snapshot shapes for indexed account history.
6
7use crate::cache::{CacheCollectionCompleteness, CacheValidationStatus};
8use serde::{Deserialize as SerdeDeserialize, Serialize};
9use serde_json::Value as JsonValue;
10
11///
12/// IcrcAccountTransactionPageData
13///
14/// Source-layer account-history page returned by an ICRC index canister.
15///
16
17#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
18pub struct IcrcAccountTransactionPageData {
19    /// Index canister that answered the query.
20    pub index_canister_id: String,
21    /// Account balance reported by the index.
22    pub balance: String,
23    /// Oldest transaction id known for the account.
24    pub oldest_transaction_id: Option<String>,
25    /// Cursor for requesting the next older page.
26    pub next_start: Option<String>,
27    /// Ledger token symbol.
28    pub token_symbol: String,
29    /// Ledger token decimals.
30    pub decimals: u8,
31    /// Account transactions in index response order.
32    pub transactions: Vec<IcrcAccountTransactionRow>,
33}
34
35///
36/// IcrcAccountTransactionPageReport
37///
38/// Serializable report for a backward page of ICRC index account transactions.
39///
40
41#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
42pub struct IcrcAccountTransactionPageReport {
43    /// Shared record, flattened in serialized output.
44    #[serde(flatten)]
45    pub provenance: super::IcrcReportProvenance,
46    /// Queried account owner principal.
47    pub account_owner: String,
48    /// Queried subaccount as normalized hex.
49    pub subaccount_hex: Option<String>,
50    /// Exclusive block-index cursor supplied by the caller.
51    pub requested_start: Option<String>,
52    /// Maximum number of transactions requested.
53    pub requested_limit: u32,
54    /// Validated index response and ledger display metadata.
55    #[serde(flatten)]
56    pub page: IcrcAccountTransactionPageData,
57}
58
59///
60/// IcrcAccountTransactionContext
61///
62/// Collected account identity and acquisition metadata shared by complete-history views.
63///
64
65#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
66pub struct IcrcAccountTransactionContext {
67    /// Ledger canister whose history was collected.
68    pub ledger_canister_id: String,
69    /// Verified index canister used for every page.
70    pub index_canister_id: String,
71    /// Queried account owner principal.
72    pub account_owner: String,
73    /// Queried subaccount as normalized hex.
74    pub subaccount_hex: Option<String>,
75    /// Explicit IC API endpoint used for collection.
76    pub source_endpoint: String,
77    /// Complete collection start timestamp.
78    pub collection_started_at: String,
79    /// Complete collection finish timestamp.
80    pub collection_completed_at: String,
81    /// Collector identity.
82    pub fetched_by: String,
83}
84
85///
86/// IcrcAccountTransactionSnapshot
87///
88/// Complete persisted account-history snapshot collected by exhausting the index API.
89///
90
91#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
92#[serde(deny_unknown_fields)]
93pub struct IcrcAccountTransactionSnapshot {
94    /// Shared identity and collection metadata, flattened in JSON.
95    #[serde(flatten)]
96    pub context: IcrcAccountTransactionContext,
97    /// Cache schema version.
98    pub schema_version: u32,
99    /// Account balance reported by the first index page.
100    pub balance: String,
101    /// Ledger token symbol used for text rendering.
102    pub token_symbol: String,
103    /// Ledger token decimals used for text rendering.
104    pub decimals: u8,
105    /// Complete-collection evidence.
106    pub completeness: CacheCollectionCompleteness,
107    /// Canonical newest-first account transactions.
108    pub transactions: Vec<IcrcAccountTransactionRow>,
109}
110
111///
112/// IcrcAccountTransactionRefreshReport
113///
114/// Serializable forced-refresh outcome for one complete account-history cache.
115///
116
117#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
118pub struct IcrcAccountTransactionRefreshReport {
119    /// Shared identity and collection metadata, flattened in JSON.
120    #[serde(flatten)]
121    pub context: IcrcAccountTransactionContext,
122    /// Report schema version.
123    pub schema_version: u32,
124    /// Number of unique transactions published.
125    pub transaction_count: usize,
126    /// Highest published transaction id.
127    pub newest_transaction_id: Option<String>,
128    /// Lowest published transaction id.
129    pub oldest_transaction_id: Option<String>,
130    /// Maximum transactions requested per source page.
131    pub page_size: u32,
132    /// Number of source pages collected.
133    pub page_count: u32,
134    /// Whether the source guarantees one point-in-time snapshot.
135    pub point_in_time_guaranteed: bool,
136    /// Whether a prior complete cache existed.
137    pub replaced_existing_cache: bool,
138    /// Non-fatal error encountered finalizing the refresh-attempt sidecar.
139    pub attempt_finalization_error: Option<String>,
140    /// Published complete-cache path.
141    pub cache_path: String,
142    /// Refresh-attempt sidecar path.
143    pub refresh_attempt_path: String,
144    /// Refresh lock path.
145    pub refresh_lock_path: String,
146}
147
148///
149/// IcrcAccountTransactionListReport
150///
151/// Serializable cache-only view over a complete account-history snapshot.
152///
153
154#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
155pub struct IcrcAccountTransactionListReport {
156    /// Shared identity and collection metadata, flattened in JSON.
157    #[serde(flatten)]
158    pub context: IcrcAccountTransactionContext,
159    /// Report schema version.
160    pub schema_version: u32,
161    /// Maximum cached rows requested by this view.
162    pub requested_limit: u32,
163    /// Stable requested ordering name.
164    pub sort: String,
165    /// Total rows in the complete cache.
166    pub total_transaction_count: usize,
167    /// Highest transaction id in the complete cache.
168    pub newest_transaction_id: Option<String>,
169    /// Lowest transaction id in the complete cache.
170    pub oldest_transaction_id: Option<String>,
171    /// Account balance captured from the first index page.
172    pub balance: String,
173    /// Ledger token symbol used for text rendering.
174    pub token_symbol: String,
175    /// Ledger token decimals used for text rendering.
176    pub decimals: u8,
177    /// Whether source exhaustion was proven.
178    pub complete: bool,
179    /// Whether the source guaranteed one point-in-time snapshot.
180    pub point_in_time_guaranteed: bool,
181    /// Maximum transactions requested per source page.
182    pub page_size: u32,
183    /// Number of source pages collected.
184    pub page_count: u32,
185    /// Complete-cache path read by this view.
186    pub cache_path: String,
187    /// Selected cached rows in requested order.
188    pub transactions: Vec<IcrcAccountTransactionRow>,
189}
190
191///
192/// IcrcAccountTransactionCacheStatusReport
193///
194/// Serializable local cache and latest-refresh status.
195///
196
197#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
198pub struct IcrcAccountTransactionCacheStatusReport {
199    /// Report schema version.
200    pub schema_version: u32,
201    /// Ledger canister in the requested cache identity.
202    pub ledger_canister_id: String,
203    /// Account owner in the requested cache identity.
204    pub account_owner: String,
205    /// Subaccount in the requested cache identity.
206    pub subaccount_hex: Option<String>,
207    /// IC API endpoint in the requested cache identity.
208    pub source_endpoint: String,
209    /// Whether a cache file exists at the expected path.
210    pub found: bool,
211    /// Validation summary when a cache file exists.
212    pub cache: Option<IcrcAccountTransactionCacheSummary>,
213    /// Expected complete-cache path.
214    pub expected_cache_path: String,
215    /// Refresh-attempt sidecar path.
216    pub refresh_attempt_path: String,
217    /// Refresh lock path.
218    pub refresh_lock_path: String,
219    /// Latest refresh-attempt state when present.
220    pub latest_attempt: Option<IcrcAccountTransactionRefreshAttemptStatus>,
221}
222
223///
224/// IcrcAccountTransactionCacheSummary
225///
226/// Existing-file admission outcome; invalid files carry no invented collection metadata.
227///
228
229#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
230#[serde(tag = "cache_status", rename_all = "snake_case")]
231pub enum IcrcAccountTransactionCacheSummary {
232    /// One admitted complete snapshot.
233    Valid {
234        /// Verified collection facts, flattened in JSON.
235        #[serde(flatten)]
236        data: IcrcAccountTransactionValidCacheSummary,
237    },
238    /// Existing file that failed admission.
239    Invalid {
240        /// Existing file path.
241        cache_path: String,
242        /// Original validation failure.
243        cache_error: String,
244    },
245}
246
247impl IcrcAccountTransactionCacheSummary {
248    /// Return the typed admission status.
249    #[must_use]
250    pub const fn cache_status(&self) -> CacheValidationStatus {
251        match self {
252            Self::Valid { .. } => CacheValidationStatus::Valid,
253            Self::Invalid { .. } => CacheValidationStatus::Invalid,
254        }
255    }
256}
257
258///
259/// IcrcAccountTransactionValidCacheSummary
260///
261/// Facts read only from an admitted complete account-history snapshot.
262///
263
264#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
265pub struct IcrcAccountTransactionValidCacheSummary {
266    /// Verified index canister.
267    pub index_canister_id: String,
268    /// Number of cached transaction rows.
269    pub transaction_count: usize,
270    /// Highest cached transaction id.
271    pub newest_transaction_id: Option<String>,
272    /// Lowest cached transaction id.
273    pub oldest_transaction_id: Option<String>,
274    /// Maximum transactions requested per source page.
275    pub page_size: u32,
276    /// Number of source pages collected.
277    pub page_count: u32,
278    /// Whether the source guaranteed one point-in-time snapshot.
279    pub point_in_time_guaranteed: bool,
280    /// Complete collection start timestamp.
281    pub collection_started_at: String,
282    /// Complete collection finish timestamp.
283    pub collection_completed_at: String,
284    /// Complete-cache path.
285    pub cache_path: String,
286}
287
288///
289/// IcrcAccountTransactionRefreshAttemptStatus
290///
291/// Serializable status of the latest complete-history refresh attempt.
292///
293
294#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
295pub struct IcrcAccountTransactionRefreshAttemptStatus {
296    /// Explicit or resolved index canister recorded by the attempt.
297    pub index_canister_id: Option<String>,
298    /// Validated shared lifecycle evidence.
299    #[serde(flatten)]
300    pub lifecycle: crate::cache::SnapshotRefreshAttemptStatus,
301}
302
303///
304/// IcrcAccountRow
305///
306/// Structured ledger account or legacy ICP account identifier in transaction rows.
307///
308
309#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
310#[serde(tag = "account_kind", rename_all = "snake_case", deny_unknown_fields)]
311pub enum IcrcAccountRow {
312    /// ICRC ledger account identity.
313    Structured {
314        /// Account owner principal.
315        owner: String,
316        /// Optional 32-byte subaccount as lowercase hex.
317        subaccount_hex: Option<String>,
318    },
319    /// Legacy ICP account identifier retained exactly as returned by the index.
320    Legacy {
321        /// Legacy account identifier text.
322        account_identifier: String,
323    },
324}
325
326///
327/// IcrcAccountTransactionRow
328///
329/// Serializable projected and lossless JSON representation of one index transaction.
330///
331
332#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
333pub struct IcrcAccountTransactionRow {
334    /// Ledger block index of the transaction.
335    pub id: String,
336    /// Index-reported transaction kind.
337    pub kind: String,
338    /// Ledger transaction timestamp as Unix nanoseconds when present.
339    pub timestamp_unix_nanos: Option<String>,
340    /// Operation amount in ledger base units when the operation carries one.
341    pub amount_base_units: Option<String>,
342    /// Operation fee in ledger base units when the operation carries one.
343    pub fee_base_units: Option<String>,
344    /// Source account when present.
345    pub from: Option<IcrcAccountRow>,
346    /// Destination account when present.
347    pub to: Option<IcrcAccountRow>,
348    /// Spender account when present.
349    pub spender: Option<IcrcAccountRow>,
350    /// Operation memo as lowercase hex when present.
351    pub memo_hex: Option<String>,
352    /// Caller-supplied creation time as Unix nanoseconds when present.
353    pub created_at_time_unix_nanos: Option<String>,
354    /// Approval expiry as Unix nanoseconds when present.
355    pub expires_at_unix_nanos: Option<String>,
356    /// Expected prior allowance in base units when present.
357    pub expected_allowance_base_units: Option<String>,
358    /// Lossless JSON projection of every typed transaction field returned by the index.
359    pub raw_transaction: JsonValue,
360}
361
362#[cfg(test)]
363mod tests;