tycho_common/storage.rs
1//! Storage traits used by Tycho
2use std::{collections::HashMap, fmt::Display};
3
4use async_trait::async_trait;
5use chrono::NaiveDateTime;
6use thiserror::Error;
7
8use crate::{
9 dto,
10 models::{
11 blockchain::{Block, Transaction},
12 contract::{Account, AccountBalance, AccountDelta},
13 protocol::{
14 ComponentBalance, ProtocolComponent, ProtocolComponentState,
15 ProtocolComponentStateDelta, QualityRange,
16 },
17 token::CurrencyToken,
18 Address, BlockHash, Chain, ComponentId, ContractId, ExtractionState, PaginationParams,
19 ProtocolType, TxHash,
20 },
21 Bytes,
22};
23
24/// Identifies a block in storage.
25#[derive(Debug, Clone, PartialEq, Hash, Eq)]
26pub enum BlockIdentifier {
27 /// Identifies the block by its position on a specified chain.
28 ///
29 /// This form of identification has potential risks as it may become
30 /// ambiguous in certain situations. For example, if the block has not been
31 /// finalised, there exists a possibility of forks occurring. As a result,
32 /// the same number could refer to different blocks on different forks.
33 Number((Chain, i64)),
34
35 /// Identifies a block by its hash.
36 ///
37 /// The hash should be unique across multiple chains. Preferred method if
38 /// the block is very recent.
39 Hash(BlockHash),
40
41 /// Latest stored block for the target chain
42 ///
43 /// Returns the block with the highest block number on the target chain.
44 Latest(Chain),
45}
46
47impl Display for BlockIdentifier {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{self:?}")
50 }
51}
52
53#[derive(Error, Debug, PartialEq)]
54pub enum StorageError {
55 #[error("Could not find {0} with id `{1}`!")]
56 NotFound(String, String),
57 #[error("The entity {0} with id {1} was already present!")]
58 DuplicateEntry(String, String),
59 #[error("Could not find related {0} for {1} with id `{2}`!")]
60 NoRelatedEntity(String, String, String),
61 #[error("DecodeError: {0}")]
62 DecodeError(String),
63 #[error("Unexpected storage error: {0}")]
64 Unexpected(String),
65 #[error("Currently unsupported operation: {0}")]
66 Unsupported(String),
67 #[error("Write cache unexpectedly dropped notification channel!")]
68 WriteCacheGoneAway(),
69 #[error("Invalid block range encountered")]
70 InvalidBlockRange(),
71}
72
73/// Storage methods for chain specific objects.
74///
75/// This trait abstracts the specific implementation details of a blockchain's
76/// entities, allowing the user to add and retrieve blocks and transactions in a
77/// generic way.
78///
79/// For traceability protocol components and contracts changes are linked to
80/// blocks of their respective chain if applicable. This means while indexing we
81/// need to keep a lightweight and cross chain compatible representation of
82/// blocks and transactions in storage.
83///
84/// It's defined generically over two associated types:
85///
86/// * `Block`: represents a block in the blockchain.
87/// * `Transaction`: represents a transaction within a block.
88#[async_trait]
89pub trait ChainGateway {
90 /// Upserts a new block to the blockchain's storage.
91 ///
92 /// Ignores any existing tx, if the new entry has different attributes
93 /// no error is raised and the old entry is kept.
94 ///
95 /// # Parameters
96 /// - `new`: An instance of `Self::Block`, representing the new block to be stored.
97 ///
98 /// # Returns
99 /// - Empty ok result indicates success. Failure might occur if the block is already present.
100 async fn upsert_block(&self, new: &[Block]) -> Result<(), StorageError>;
101 /// Retrieves a block from storage.
102 ///
103 /// # Parameters
104 /// - `id`: Block's unique identifier of type `BlockIdentifier`.
105 ///
106 /// # Returns
107 /// - An Ok result containing the block. Might fail if the block does not exist yet.
108 async fn get_block(&self, id: &BlockIdentifier) -> Result<Block, StorageError>;
109 /// Upserts a transaction to storage.
110 ///
111 /// Ignores any existing tx, if the new entry has different attributes
112 /// no error is raised and the old entry is kept.
113 ///
114 /// # Parameters
115 /// - `new`: An instance of `Self::Transaction`, representing the new transaction to be stored.
116 ///
117 /// # Returns
118 /// - Empty ok result indicates success. Failure might occur if the
119 /// corresponding block does not exists yet, or if the transaction already
120 /// exists.
121 async fn upsert_tx(&self, new: &[Transaction]) -> Result<(), StorageError>;
122
123 /// Tries to retrieve a transaction from the blockchain's storage using its
124 /// hash.
125 ///
126 /// # Parameters
127 /// - `hash`: The byte slice representing the hash of the transaction to be retrieved.
128 ///
129 /// # Returns
130 /// - An Ok result containing the transaction. Might fail if the transaction does not exist yet.
131 async fn get_tx(&self, hash: &TxHash) -> Result<Transaction, StorageError>;
132
133 /// Reverts the blockchain storage to a previous version.
134 ///
135 /// Reverting state signifies deleting database history. Only the main branch will be kept.
136 ///
137 /// Blocks that are greater than the provided block (`to`) are deleted and any versioned rows
138 /// which were invalidated in the deleted blocks are updated to be valid again.
139 ///
140 /// # Parameters
141 /// - `to` The version to revert to. Given a block uses VersionKind::Last behaviour.
142 /// - `db` The database gateway.
143 ///
144 /// # Returns
145 /// - An Ok if the revert is successful, or a `StorageError` if not.
146 async fn revert_state(&self, to: &BlockIdentifier) -> Result<(), StorageError>;
147}
148
149/// Store and retrieve state of Extractors.
150///
151/// Sometimes extractors may wish to persist their state across restart. E.g.
152/// substreams based extractors need to store the cursor, so they can continue
153/// processing where they left off.
154///
155/// Extractors are uniquely identified by a name and the respective chain which
156/// they are indexing.
157#[async_trait]
158pub trait ExtractionStateGateway {
159 /// Retrieves the state of an extractor instance from a storage.
160 ///
161 /// # Parameters
162 /// - `name` A unique name for the extractor instance.
163 /// - `chain` The chain this extractor is indexing.
164 ///
165 /// # Returns
166 /// Ok if the corrsponding state was retrieved successfully, Err in
167 /// case the state was not found.
168 async fn get_state(&self, name: &str, chain: &Chain) -> Result<ExtractionState, StorageError>;
169
170 /// Saves the state of an extractor instance to a storage.
171 ///
172 /// Creates an entry if not present yet, or updates an already existing
173 /// entry.
174 ///
175 /// # Parameters
176 /// - `state` The state of the extractor that needs to be saved.
177 ///
178 /// # Returns
179 /// Ok, if state was stored successfully, Err if the state is not valid.
180 async fn save_state(&self, state: &ExtractionState) -> Result<(), StorageError>;
181}
182
183/// Point in time as either block or timestamp. If a block is chosen it
184/// timestamp attribute is used.
185#[derive(Debug, Clone, PartialEq, Hash, Eq)]
186pub enum BlockOrTimestamp {
187 Block(BlockIdentifier),
188 Timestamp(NaiveDateTime),
189}
190
191// TODO: remove once deprecated chain field is removed from VersionParam
192#[allow(deprecated)]
193impl TryFrom<&dto::VersionParam> for BlockOrTimestamp {
194 type Error = anyhow::Error;
195
196 fn try_from(version: &dto::VersionParam) -> Result<Self, Self::Error> {
197 match (&version.timestamp, &version.block) {
198 (_, Some(block)) => {
199 // If a full block is provided, we prioritize hash over number and chain
200 let block_identifier = match (&block.hash, &block.chain, &block.number) {
201 (Some(hash), _, _) => BlockIdentifier::Hash(hash.clone()),
202 (_, Some(chain), Some(number)) => {
203 BlockIdentifier::Number((Chain::from(*chain), *number))
204 }
205 _ => {
206 return Err(anyhow::format_err!("Insufficient block information".to_owned()))
207 }
208 };
209 Ok(BlockOrTimestamp::Block(block_identifier))
210 }
211 (Some(timestamp), None) => Ok(BlockOrTimestamp::Timestamp(*timestamp)),
212 (None, None) => {
213 Err(anyhow::format_err!("Missing timestamp or block identifier".to_owned()))
214 }
215 }
216 }
217}
218
219/// References certain states within a single block.
220///
221/// **Note:** Not all methods that take a version will support all version kinds,
222/// the versions here are included for completeness and to document the
223/// retrieval behaviour that is possible with the storage layout. Please refer
224/// to the individual implementation for information about which version kinds
225/// it supports.
226#[derive(Debug, Clone, Default)]
227pub enum VersionKind {
228 /// Represents the final state within a specific block. Essentially, it
229 /// retrieves the state subsequent to the execution of the last transaction
230 /// executed in that block.
231 #[default]
232 Last,
233 /// Represents the initial state of a specific block. In other words,
234 /// it is the state before any transaction has been executed within that block.
235 First,
236 /// Represents a specific transactions indexed position within a block.
237 /// It includes the state after executing the transaction at that index.
238 Index(i64),
239}
240
241/// A version desribes the state of the DB at a exact point in time.
242/// See the module level docs for more information on how versioning works.
243#[derive(Debug, Clone)]
244pub struct Version(pub BlockOrTimestamp, pub VersionKind);
245
246impl Version {
247 pub fn from_block_number(chain: Chain, number: i64) -> Self {
248 Self(BlockOrTimestamp::Block(BlockIdentifier::Number((chain, number))), VersionKind::Last)
249 }
250 pub fn from_ts(ts: NaiveDateTime) -> Self {
251 Self(BlockOrTimestamp::Timestamp(ts), VersionKind::Last)
252 }
253}
254
255// Helper type to retrieve entities with their total retrievable count.
256#[derive(Debug)]
257pub struct WithTotal<T> {
258 pub entity: T,
259 pub total: Option<i64>,
260}
261
262/// Store and retrieve protocol related structs.
263///
264/// This trait defines how to retrieve protocol components, state as well as
265/// tokens from storage.
266#[async_trait]
267pub trait ProtocolGateway {
268 /// Retrieve ProtocolComponent from the db
269 ///
270 /// # Parameters
271 /// - `chain` The chain of the component
272 /// - `system` Allows to optionally filter by system.
273 /// - `id` Allows to optionally filter by id.
274 ///
275 /// # Returns
276 /// Ok, if found else Err
277 async fn get_protocol_components(
278 &self,
279 chain: &Chain,
280 system: Option<String>,
281 ids: Option<&[&str]>,
282 min_tvl: Option<f64>,
283 pagination_params: Option<&PaginationParams>,
284 ) -> Result<WithTotal<Vec<ProtocolComponent>>, StorageError>;
285
286 /// Retrieves owners of tokens
287 ///
288 /// Queries for owners (protocol components) of tokens that have a certain minimum
289 /// balance and returns a maximum aggregate of those in case there are multiple
290 /// owners.
291 ///
292 /// # Parameters
293 /// - `chain` The chain of the component
294 /// - `tokens` The tokens to query for, any component with at least one of these tokens is
295 /// returned.
296 /// - `min_balance` A minimum balance we expect the component to have on any of the tokens
297 /// mentioned in `tokens`.
298 async fn get_token_owners(
299 &self,
300 chain: &Chain,
301 tokens: &[Address],
302 min_balance: Option<f64>,
303 ) -> Result<HashMap<Address, (ComponentId, Bytes)>, StorageError>;
304
305 async fn add_protocol_components(&self, new: &[ProtocolComponent]) -> Result<(), StorageError>;
306
307 async fn delete_protocol_components(
308 &self,
309 to_delete: &[ProtocolComponent],
310 block_ts: NaiveDateTime,
311 ) -> Result<(), StorageError>;
312
313 /// Stores new found ProtocolTypes.
314 ///
315 /// # Parameters
316 /// - `new` The new protocol types.
317 ///
318 /// # Returns
319 /// Ok if stored successfully.
320 async fn add_protocol_types(
321 &self,
322 new_protocol_types: &[ProtocolType],
323 ) -> Result<(), StorageError>;
324
325 /// Retrieve protocol component states
326 ///
327 /// This resource is versioned, the version can be specified by either block
328 /// or timestamp, for off-chain components, a block version will error.
329 ///
330 /// As the state is retained on a transaction basis on blockchain systems, a
331 /// single version may relate to more than one state. In these cases a
332 /// versioned result is returned, if requesting `Version:All` with the
333 /// latest entry being the state at the end of the block and the first entry
334 /// represents the first change to the state within the block.
335 ///
336 /// # Parameters
337 /// - `chain` The chain of the component
338 /// - `system` The protocol system this component belongs to
339 /// - `ids` The external ids of the components e.g. addresses, or the pairs
340 /// - `at` The version at which the state is valid at.
341 async fn get_protocol_states(
342 &self,
343 chain: &Chain,
344 at: Option<Version>,
345 system: Option<String>,
346 ids: Option<&[&str]>,
347 retrieve_balances: bool,
348 pagination_params: Option<&PaginationParams>,
349 ) -> Result<WithTotal<Vec<ProtocolComponentState>>, StorageError>;
350
351 async fn update_protocol_states(
352 &self,
353 new: &[(TxHash, ProtocolComponentStateDelta)],
354 ) -> Result<(), StorageError>;
355
356 /// Retrieves a tokens from storage
357 ///
358 /// # Parameters
359 /// - `chain` The chain this token is implemented on.
360 /// - `address` The address for the token within the chain.
361 ///
362 /// # Returns
363 /// Ok if the results could be retrieved from the storage, else errors.
364 async fn get_tokens(
365 &self,
366 chain: Chain,
367 address: Option<&[&Address]>,
368 quality: QualityRange,
369 traded_n_days_ago: Option<NaiveDateTime>,
370 pagination_params: Option<&PaginationParams>,
371 ) -> Result<WithTotal<Vec<CurrencyToken>>, StorageError>;
372
373 /// Saves multiple component balances to storage.
374 ///
375 /// # Parameters
376 /// - `component_balances` The component balances to insert.
377 /// - `chain` The chain of the component balances to be inserted.
378 /// - `block_ts` The timestamp of the block that the balances are associated with.
379 ///
380 /// # Return
381 /// Ok if all component balances could be inserted, Err if at least one token failed to
382 /// insert.
383 async fn add_component_balances(
384 &self,
385 component_balances: &[ComponentBalance],
386 ) -> Result<(), StorageError>;
387
388 /// Saves multiple tokens to storage.
389 ///
390 /// Inserts token into storage. Tokens and their properties are assumed to
391 /// be immutable.
392 ///
393 /// # Parameters
394 /// - `token` The tokens to insert.
395 ///
396 /// # Return
397 /// Ok if all tokens could be inserted, Err if at least one token failed to
398 /// insert.
399 async fn add_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
400
401 /// Updates multiple tokens in storage.
402 ///
403 /// Updates token in storage. Will warn if one of the tokens does not exist in the
404 /// database. Currently assumes that token addresses are unique across chains.
405 /// -
406 ///
407 /// # Parameters
408 /// - `token` The tokens to update.
409 ///
410 /// # Return
411 /// Ok if all tokens could be inserted, Err if at least one token failed to
412 /// insert.
413 async fn update_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
414
415 /// Retrieve protocol state changes
416 ///
417 /// Fetches all state changes that occurred for the given chain
418 ///
419 /// # Parameters
420 /// - `chain` The chain of the component
421 /// - `start_version` The version at which to start looking for changes at.
422 /// - `end_version` The version at which to stop looking for changes.
423 ///
424 /// # Return
425 /// A list of ProtocolStateDeltas containing all state changes, Err if no changes were found.
426 async fn get_protocol_states_delta(
427 &self,
428 chain: &Chain,
429 start_version: Option<&BlockOrTimestamp>,
430 end_version: &BlockOrTimestamp,
431 ) -> Result<Vec<ProtocolComponentStateDelta>, StorageError>;
432
433 /// Retrieve protocol component balance changes
434 ///
435 /// Fetches all balance changes that occurred for the given protocol system
436 ///
437 /// # Parameters
438 /// - `chain` The chain of the component
439 /// - `start_version` The version at which to start looking for changes at.
440 /// - `target_version` The version at which to stop looking for changes.
441 ///
442 /// # Return
443 /// A vec containing ComponentBalance objects for changed components.
444 async fn get_balance_deltas(
445 &self,
446 chain: &Chain,
447 start_version: Option<&BlockOrTimestamp>,
448 target_version: &BlockOrTimestamp,
449 ) -> Result<Vec<ComponentBalance>, StorageError>;
450
451 async fn get_component_balances(
452 &self,
453 chain: &Chain,
454 ids: Option<&[&str]>,
455 version: Option<&Version>,
456 ) -> Result<HashMap<String, HashMap<Bytes, ComponentBalance>>, StorageError>;
457
458 async fn get_token_prices(&self, chain: &Chain) -> Result<HashMap<Bytes, f64>, StorageError>;
459
460 async fn upsert_component_tvl(
461 &self,
462 chain: &Chain,
463 tvl_values: &HashMap<String, f64>,
464 ) -> Result<(), StorageError>;
465
466 /// Retrieve a list of actively supported protocol systems
467 ///
468 /// Fetches the list of protocol systems supported by the Tycho indexing service.
469 ///
470 /// # Parameters
471 /// - `chain` The chain for which to retrieve supported protocol systems.
472 /// - `pagination_params` Optional pagination parameters to control the number of results.
473 ///
474 /// # Return
475 /// A paginated list of supported protocol systems, along with the total count.
476 async fn get_protocol_systems(
477 &self,
478 chain: &Chain,
479 pagination_params: Option<&PaginationParams>,
480 ) -> Result<WithTotal<Vec<String>>, StorageError>;
481}
482
483/// Manage contracts and their state in storage.
484///
485/// Specifies how to retrieve, add and update contracts in storage.
486#[async_trait]
487pub trait ContractStateGateway {
488 /// Get a contracts state from storage
489 ///
490 /// This method retrieves a single contract from the database.
491 ///
492 /// # Parameters
493 /// - `id` The identifier for the contract.
494 /// - `version` Version at which to retrieve state for. None retrieves the latest state.
495 /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
496 /// includes storage slot.
497 /// - `db`: Database session reference.
498 async fn get_contract(
499 &self,
500 id: &ContractId,
501 version: Option<&Version>,
502 include_slots: bool,
503 ) -> Result<Account, StorageError>;
504
505 /// Get multiple contracts' states from storage.
506 ///
507 /// This method retrieves balance and code, and optionally storage, of
508 /// multiple contracts in a chain. It can optionally filter by given
509 /// addresses and retrieve state for specific versions.
510 ///
511 /// # Parameters:
512 /// - `chain`: The blockchain where the contracts reside.
513 /// - `addresses`: Filter for specific addresses. If set to `None`, it retrieves all indexed
514 /// contracts in the chain.
515 /// - `version`: Version at which to retrieve state for. If set to `None`, it retrieves the
516 /// latest state.
517 /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
518 /// includes storage slot.
519 /// - `db`: Database session reference.
520 ///
521 /// # Returns:
522 /// A `Result` with a list of contract states if the operation is
523 /// successful, or a `StorageError` if the operation fails.
524 async fn get_contracts(
525 &self,
526 chain: &Chain,
527 addresses: Option<&[Address]>,
528 version: Option<&Version>,
529 include_slots: bool,
530 pagination_params: Option<&PaginationParams>,
531 ) -> Result<WithTotal<Vec<Account>>, StorageError>;
532
533 /// Inserts a new contract into the database.
534 ///
535 /// If it the creation transaction is known, the contract will have slots, balance and code
536 /// inserted alongside with the new account else it won't.
537 ///
538 /// # Arguments
539 /// - `new`: A reference to the new contract state to be inserted.
540 /// - `db`: Database session reference.
541 ///
542 /// # Returns
543 /// - A Result with Ok if the operation was successful, and an Err containing `StorageError` if
544 /// there was an issue inserting the contract into the database. E.g. if the contract already
545 /// existed.
546 async fn upsert_contract(&self, new: &Account) -> Result<(), StorageError>;
547
548 /// Update multiple contracts
549 ///
550 /// Given contract deltas, this method will batch all updates to contracts across a single
551 /// chain.
552 ///
553 /// As changes are versioned by transaction, each changeset needs to be associated with a
554 /// transaction hash. All references transaction are assumed to be already persisted.
555 ///
556 /// # Arguments
557 ///
558 /// - `chain`: The blockchain which the contracts belong to.
559 /// - `new`: A reference to a slice of tuples where each tuple has a transaction hash (`TxHash`)
560 /// and a reference to the state delta (`&Self::Delta`) for that transaction.
561 /// - `db`: A mutable reference to the connected database where the updated contracts will be
562 /// stored.
563 ///
564 /// # Returns
565 ///
566 /// A Result with `Ok` if the operation was successful, and an `Err` containing
567 /// `StorageError` if there was an issue updating the contracts in the database. E.g. if a
568 /// transaction can't be located by it's reference or accounts refer to a different chain then
569 /// the one specified.
570 async fn update_contracts(&self, new: &[(TxHash, AccountDelta)]) -> Result<(), StorageError>;
571
572 /// Mark a contract as deleted
573 ///
574 /// Issues a soft delete of the contract.
575 ///
576 /// # Parameters
577 /// - `id` The identifier for the contract.
578 /// - `at_tx` The transaction hash which deleted the contract. This transaction is assumed to be
579 /// in storage already. None retrieves the latest state.
580 /// - `db` The database handle or connection.
581 ///
582 /// # Returns
583 /// Ok if the deletion was successful, might Err if:
584 /// - Contract is not present in storage.
585 /// - Deletion transaction is not present in storage.
586 /// - Contract was already deleted.
587 async fn delete_contract(&self, id: &ContractId, at_tx: &TxHash) -> Result<(), StorageError>;
588
589 /// Retrieve a account delta between two versions.
590 ///
591 /// Given start version V1 and end version V2, this method will return the
592 /// changes necessary to move from V1 to V2. So if V1 < V2, it will contain
593 /// the changes of all accounts that changed between the two versions with the
594 /// values corresponding to V2. If V2 < V1 then it will contain all the
595 /// slots that changed between the two versions with the values corresponding to V1.
596 ///
597 /// This method is mainly meant to handle reverts, but can also be used to create delta changes
598 /// between two historical version thus providing the basis for creating a backtestable stream
599 /// of messages.
600 ///
601 /// # Parameters
602 ///
603 /// - `chain` The chain for which to generate the delta changes.
604 /// - `start_version` The deltas start version, given a block uses VersionKind::Last behaviour.
605 /// If None the latest version is assumed.
606 /// - `end_version` The deltas end version, given a block uses VersionKind::Last behaviour.
607 ///
608 /// # Note
609 ///
610 /// A choice to utilize `BlockOrTimestamp` has been made intentionally in
611 /// this scenario as passing a `Version` by user isn't quite logical.
612 /// Support for deltas is limited to the states at the start or end of
613 /// blocks because blockchain reorganization at the transaction level is not
614 /// common.
615 ///
616 /// The decision to use either the beginning or end state of a block is
617 /// automatically determined by the underlying logic. For example, if we are
618 /// tracing back, `VersionKind::First` retrieval mode will be used.
619 /// Conversely, if we're progressing forward, we would apply the
620 /// `VersionKind::Last` semantics.
621 ///
622 /// # Returns
623 /// A map containing the necessary changes to update a state from start_version to end_version.
624 /// Errors if:
625 /// - The versions can't be located in storage.
626 /// - There was an error with the database
627 async fn get_accounts_delta(
628 &self,
629 chain: &Chain,
630 start_version: Option<&BlockOrTimestamp>,
631 end_version: &BlockOrTimestamp,
632 ) -> Result<Vec<AccountDelta>, StorageError>;
633
634 /// Saves multiple account balances to storage.
635 ///
636 /// # Parameters
637 /// - `account_balances` The account balances to insert.
638 /// - `chain` The chain of the account balances to be inserted.
639 /// - `block_ts` The timestamp of the block that the balances are associated with.
640 ///
641 /// # Return
642 /// Ok if all account balances could be inserted, Err if at least one token failed to insert.
643 async fn add_account_balances(
644 &self,
645 account_balances: &[AccountBalance],
646 ) -> Result<(), StorageError>;
647
648 /// Retrieve account balances
649 ///
650 /// # Parameters
651 /// - `chain` The chain of the account balances
652 /// - `accounts` The accounts to query for. If set to `None`, it retrieves balances for all
653 /// indexed
654 /// accounts in the chain.
655 /// - `version` Version at which to retrieve balances for. If set to `None`, it retrieves the
656 /// latest balances.
657 async fn get_account_balances(
658 &self,
659 chain: &Chain,
660 accounts: Option<&[Address]>,
661 version: Option<&Version>,
662 ) -> Result<HashMap<Address, HashMap<Address, AccountBalance>>, StorageError>;
663}
664
665pub trait Gateway:
666 ChainGateway
667 + ContractStateGateway
668 + ExtractionStateGateway
669 + ProtocolGateway
670 + ContractStateGateway
671 + Send
672 + Sync
673{
674}