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};
14
15use crate::Client;
16use crate::errors::ClientError;
17use crate::store::{InputNoteRecord, NoteRecordError};
18
19impl<AUTH> Client<AUTH> {
21 pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
34 self.store.get_note_tags().await.map_err(Into::into)
35 }
36
37 pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<bool, ClientError> {
41 self.store
42 .add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
43 .await
44 .map_err(Into::into)
45 }
46
47 pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<bool, ClientError> {
51 let removed = self
52 .store
53 .remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
54 .await?;
55
56 Ok(removed > 0)
57 }
58}
59
60#[derive(Debug, PartialEq, Eq, Clone, Copy)]
62pub struct NoteTagRecord {
63 pub tag: NoteTag,
64 pub source: NoteTagSource,
65}
66
67#[derive(Debug, PartialEq, Eq, Clone, Copy)]
70pub enum NoteTagSource {
71 Account(AccountId),
73 Note(NoteDetailsCommitment),
75 User,
77 Subscription(Word),
82}
83
84impl NoteTagRecord {
85 pub fn with_note_source(tag: NoteTag, details_commitment: NoteDetailsCommitment) -> Self {
86 Self {
87 tag,
88 source: NoteTagSource::Note(details_commitment),
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 NoteTagRecord {
101 fn write_into<W: ByteWriter>(&self, target: &mut W) {
102 self.tag.write_into(target);
103 self.source.write_into(target);
104 }
105}
106
107impl Deserializable for NoteTagRecord {
108 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
109 let tag = NoteTag::read_from(source)?;
110 let source = NoteTagSource::read_from(source)?;
111 Ok(Self { tag, source })
112 }
113}
114
115impl Serializable for NoteTagSource {
116 fn write_into<W: ByteWriter>(&self, target: &mut W) {
117 match self {
118 NoteTagSource::Account(account_id) => {
119 target.write_u8(0);
120 account_id.write_into(target);
121 },
122 NoteTagSource::Note(details_commitment) => {
123 target.write_u8(1);
124 details_commitment.write_into(target);
125 },
126 NoteTagSource::User => target.write_u8(2),
127 NoteTagSource::Subscription(key) => {
128 target.write_u8(3);
130 key.write_into(target);
131 },
132 }
133 }
134}
135
136impl Deserializable for NoteTagSource {
137 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
138 match source.read_u8()? {
139 0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
140 1 => Ok(NoteTagSource::Note(NoteDetailsCommitment::read_from(source)?)),
141 2 => Ok(NoteTagSource::User),
142 3 => Ok(NoteTagSource::Subscription(Word::read_from(source)?)),
143 val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
144 }
145 }
146}
147
148impl PartialEq<NoteTag> for NoteTagRecord {
149 fn eq(&self, other: &NoteTag) -> bool {
150 self.tag == *other
151 }
152}
153
154impl From<&Account> for NoteTagRecord {
155 fn from(account: &Account) -> Self {
156 NoteTagRecord::with_account_source(NoteTag::with_account_target(account.id()), account.id())
157 }
158}
159
160impl TryInto<NoteTagRecord> for &InputNoteRecord {
161 type Error = NoteRecordError;
162
163 fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
164 match self.metadata() {
165 Some(metadata) => {
166 Ok(NoteTagRecord::with_note_source(metadata.tag(), self.details_commitment()))
167 },
168 None => Err(NoteRecordError::ConversionError(
169 "Input Note Record does not contain tag".to_string(),
170 )),
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use miden_protocol::{Felt, Word};
178 use miden_tx::utils::serde::{Deserializable, Serializable};
179
180 use super::NoteTagSource;
181
182 #[test]
183 fn subscription_note_tag_source_round_trips_with_stable_discriminant() {
184 let key: Word =
185 [Felt::from(1u32), Felt::from(2u32), Felt::from(3u32), Felt::from(4u32)].into();
186 let source = NoteTagSource::Subscription(key);
187
188 let bytes = source.to_bytes();
189 assert_eq!(bytes[0], 3, "Subscription discriminant must remain 3");
191 assert_eq!(NoteTagSource::read_from_bytes(&bytes).unwrap(), source);
192 }
193}