1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use miden_protocol::Word;
5use miden_protocol::account::{Account, AccountId};
6use miden_protocol::note::{NoteDetailsCommitment, NoteTag};
7use miden_tx::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use tracing::warn;
15
16use crate::Client;
17use crate::errors::ClientError;
18use crate::store::{InputNoteRecord, NoteRecordError};
19
20impl<AUTH> Client<AUTH> {
22 pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
35 self.store.get_note_tags().await.map_err(Into::into)
36 }
37
38 pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
40 let added = self
41 .store
42 .add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
43 .await?;
44 if !added {
45 warn!("Tag {} is already being tracked", tag);
46 }
47 Ok(())
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(NoteDetailsCommitment),
80 User,
82 Subscription(Word),
87}
88
89impl NoteTagRecord {
90 pub fn with_note_source(tag: NoteTag, details_commitment: NoteDetailsCommitment) -> Self {
91 Self {
92 tag,
93 source: NoteTagSource::Note(details_commitment),
94 }
95 }
96
97 pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
98 Self {
99 tag,
100 source: NoteTagSource::Account(account_id),
101 }
102 }
103}
104
105impl Serializable for NoteTagRecord {
106 fn write_into<W: ByteWriter>(&self, target: &mut W) {
107 self.tag.write_into(target);
108 self.source.write_into(target);
109 }
110}
111
112impl Deserializable for NoteTagRecord {
113 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
114 let tag = NoteTag::read_from(source)?;
115 let source = NoteTagSource::read_from(source)?;
116 Ok(Self { tag, source })
117 }
118}
119
120impl Serializable for NoteTagSource {
121 fn write_into<W: ByteWriter>(&self, target: &mut W) {
122 match self {
123 NoteTagSource::Account(account_id) => {
124 target.write_u8(0);
125 account_id.write_into(target);
126 },
127 NoteTagSource::Note(details_commitment) => {
128 target.write_u8(1);
129 details_commitment.write_into(target);
130 },
131 NoteTagSource::User => target.write_u8(2),
132 NoteTagSource::Subscription(key) => {
133 target.write_u8(3);
135 key.write_into(target);
136 },
137 }
138 }
139}
140
141impl Deserializable for NoteTagSource {
142 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
143 match source.read_u8()? {
144 0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
145 1 => Ok(NoteTagSource::Note(NoteDetailsCommitment::read_from(source)?)),
146 2 => Ok(NoteTagSource::User),
147 3 => Ok(NoteTagSource::Subscription(Word::read_from(source)?)),
148 val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
149 }
150 }
151}
152
153impl PartialEq<NoteTag> for NoteTagRecord {
154 fn eq(&self, other: &NoteTag) -> bool {
155 self.tag == *other
156 }
157}
158
159impl From<&Account> for NoteTagRecord {
160 fn from(account: &Account) -> Self {
161 NoteTagRecord::with_account_source(NoteTag::with_account_target(account.id()), account.id())
162 }
163}
164
165impl TryInto<NoteTagRecord> for &InputNoteRecord {
166 type Error = NoteRecordError;
167
168 fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
169 match self.metadata() {
170 Some(metadata) => {
171 Ok(NoteTagRecord::with_note_source(metadata.tag(), self.details_commitment()))
172 },
173 None => Err(NoteRecordError::ConversionError(
174 "Input Note Record does not contain tag".to_string(),
175 )),
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use miden_protocol::{Felt, Word};
183 use miden_tx::utils::serde::{Deserializable, Serializable};
184
185 use super::NoteTagSource;
186
187 #[test]
188 fn subscription_note_tag_source_round_trips_with_stable_discriminant() {
189 let key: Word =
190 [Felt::from(1u32), Felt::from(2u32), Felt::from(3u32), Felt::from(4u32)].into();
191 let source = NoteTagSource::Subscription(key);
192
193 let bytes = source.to_bytes();
194 assert_eq!(bytes[0], 3, "Subscription discriminant must remain 3");
196 assert_eq!(NoteTagSource::read_from_bytes(&bytes).unwrap(), source);
197 }
198}