1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use miden_protocol::account::{Account, AccountId};
5use miden_protocol::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
13impl<AUTH> Client<AUTH> {
15 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 pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
33 match self.insert_note_tag(NoteTagRecord { tag, source: NoteTagSource::User }).await {
34 Ok(true) => Ok(()),
35 Ok(false) => {
36 warn!("Tag {} is already being tracked", tag);
37 Ok(())
38 },
39 Err(err) => Err(err),
40 }
41 }
42
43 pub async fn insert_note_tag(&self, tag_record: NoteTagRecord) -> Result<bool, ClientError> {
46 self.check_note_tag_limit().await?;
47 self.store.add_note_tag(tag_record).await.map_err(Into::into)
48 }
49
50 pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
52 if self
53 .store
54 .remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
55 .await?
56 == 0
57 {
58 warn!("Tag {} wasn't being tracked", tag);
59 }
60
61 Ok(())
62 }
63}
64
65#[derive(Debug, PartialEq, Eq, Clone, Copy)]
67pub struct NoteTagRecord {
68 pub tag: NoteTag,
69 pub source: NoteTagSource,
70}
71
72#[derive(Debug, PartialEq, Eq, Clone, Copy)]
75pub enum NoteTagSource {
76 Account(AccountId),
78 Note(NoteId),
80 User,
82}
83
84impl NoteTagRecord {
85 pub fn with_note_source(tag: NoteTag, note_id: NoteId) -> Self {
86 Self {
87 tag,
88 source: NoteTagSource::Note(note_id),
89 }
90 }
91
92 pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
93 Self {
94 tag,
95 source: NoteTagSource::Account(account_id),
96 }
97 }
98}
99
100impl Serializable for NoteTagSource {
101 fn write_into<W: ByteWriter>(&self, target: &mut W) {
102 match self {
103 NoteTagSource::Account(account_id) => {
104 target.write_u8(0);
105 account_id.write_into(target);
106 },
107 NoteTagSource::Note(note_id) => {
108 target.write_u8(1);
109 note_id.write_into(target);
110 },
111 NoteTagSource::User => target.write_u8(2),
112 }
113 }
114}
115
116impl Deserializable for NoteTagSource {
117 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
118 match source.read_u8()? {
119 0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
120 1 => Ok(NoteTagSource::Note(NoteId::read_from(source)?)),
121 2 => Ok(NoteTagSource::User),
122 val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
123 }
124 }
125}
126
127impl PartialEq<NoteTag> for NoteTagRecord {
128 fn eq(&self, other: &NoteTag) -> bool {
129 self.tag == *other
130 }
131}
132
133impl From<&Account> for NoteTagRecord {
134 fn from(account: &Account) -> Self {
135 NoteTagRecord::with_account_source(NoteTag::with_account_target(account.id()), account.id())
136 }
137}
138
139impl TryInto<NoteTagRecord> for &InputNoteRecord {
140 type Error = NoteRecordError;
141
142 fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
143 match self.metadata() {
144 Some(metadata) => Ok(NoteTagRecord::with_note_source(metadata.tag(), self.id())),
145 None => Err(NoteRecordError::ConversionError(
146 "Input Note Record does not contain tag".to_string(),
147 )),
148 }
149 }
150}