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;