miden_client/sync/
tag.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use miden_objects::account::{Account, AccountId};
5use miden_objects::note::{NoteId, NoteTag};
6use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
7use tracing::warn;
8
9use crate::Client;
10use crate::errors::ClientError;
11use crate::store::{InputNoteRecord, NoteRecordError};
12
13/// Tag management methods
14impl<AUTH> Client<AUTH> {
15    /// Returns the list of note tags tracked by the client along with their source.
16    ///
17    /// When syncing the state with the node, these tags will be added to the sync request and
18    /// note-related information will be retrieved for notes that have matching tags.
19    ///  The source of the tag indicates its origin. It helps distinguish between:
20    ///  - Tags added manually by the user.
21    ///  - Tags automatically added by the client to track notes.
22    ///  - Tags added for accounts tracked by the client.
23    ///
24    /// Note: Tags for accounts that are being tracked by the client are managed automatically by
25    /// the client and don't need to be added here. That is, notes for managed accounts will be
26    /// retrieved automatically by the client when syncing.
27    pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
28        self.store.get_note_tags().await.map_err(Into::into)
29    }
30
31    /// Adds a note tag for the client to track. This tag's source will be marked as `User`.
32    pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
33        match self
34            .store
35            .add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
36            .await
37            .map_err(Into::into)
38        {
39            Ok(true) => Ok(()),
40            Ok(false) => {
41                warn!("Tag {} is already being tracked", tag);
42                Ok(())
43            },
44            Err(err) => Err(err),
45        }
46    }
47
48    /// Removes a note tag for the client to track. Only tags added by the user can be removed.
49    pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
50        if self
51            .store
52            .remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
53            .await?
54            == 0
55        {
56            warn!("Tag {} wasn't being tracked", tag);
57        }
58
59        Ok(())
60    }
61}
62
63/// Represents a note tag of which the Store can keep track and retrieve.
64#[derive(Debug, PartialEq, Eq, Clone, Copy)]
65pub struct NoteTagRecord {
66    pub tag: NoteTag,
67    pub source: NoteTagSource,
68}
69
70/// Represents the source of the tag. This is used to differentiate between tags that are added by
71/// the user and tags that are added automatically by the client to track notes .
72#[derive(Debug, PartialEq, Eq, Clone, Copy)]
73pub enum NoteTagSource {
74    /// Tag for notes directed to a tracked account.
75    Account(AccountId),
76    /// Tag for tracked expected notes.
77    Note(NoteId),
78    /// Tag manually added by the user.
79    User,
80}
81
82impl NoteTagRecord {
83    pub fn with_note_source(tag: NoteTag, note_id: NoteId) -> Self {
84        Self {
85            tag,
86            source: NoteTagSource::Note(note_id),
87        }
88    }
89
90    pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
91        Self {
92            tag,
93            source: NoteTagSource::Account(account_id),
94        }
95    }
96}
97
98impl Serializable for NoteTagSource {
99    fn write_into<W: ByteWriter>(&self, target: &mut W) {
100        match self {
101            NoteTagSource::Account(account_id) => {
102                target.write_u8(0);
103                account_id.write_into(target);
104            },
105            NoteTagSource::Note(note_id) => {
106                target.write_u8(1);
107                note_id.write_into(target);
108            },
109            NoteTagSource::User => target.write_u8(2),
110        }
111    }
112}
113
114impl Deserializable for NoteTagSource {
115    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
116        match source.read_u8()? {
117            0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
118            1 => Ok(NoteTagSource::Note(NoteId::read_from(source)?)),
119            2 => Ok(NoteTagSource::User),
120            val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
121        }
122    }
123}
124
125impl PartialEq<NoteTag> for NoteTagRecord {
126    fn eq(&self, other: &NoteTag) -> bool {
127        self.tag == *other
128    }
129}
130
131impl TryInto<NoteTagRecord> for &InputNoteRecord {
132    type Error = NoteRecordError;
133
134    fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
135        match self.metadata() {
136            Some(metadata) => Ok(NoteTagRecord::with_note_source(metadata.tag(), self.id())),
137            None => Err(NoteRecordError::ConversionError(
138                "Input Note Record does not contain tag".to_string(),
139            )),
140        }
141    }
142}
143
144impl From<&Account> for NoteTagRecord {
145    fn from(account: &Account) -> Self {
146        NoteTagRecord::with_account_source(NoteTag::from_account_id(account.id()), account.id())
147    }
148}