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