miden_client/sync/
tag.rs

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