use alloc::string::ToString;
use alloc::vec::Vec;
use miden_protocol::Word;
use miden_protocol::account::{Account, AccountId};
use miden_protocol::note::{NoteDetailsCommitment, NoteTag};
use miden_tx::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use tracing::warn;
use crate::Client;
use crate::errors::ClientError;
use crate::store::{InputNoteRecord, NoteRecordError};
impl<AUTH> Client<AUTH> {
pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
self.store.get_note_tags().await.map_err(Into::into)
}
pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
let added = self
.store
.add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
.await?;
if !added {
warn!("Tag {} is already being tracked", tag);
}
Ok(())
}
pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
if self
.store
.remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
.await?
== 0
{
warn!("Tag {} wasn't being tracked", tag);
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct NoteTagRecord {
pub tag: NoteTag,
pub source: NoteTagSource,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum NoteTagSource {
Account(AccountId),
Note(NoteDetailsCommitment),
User,
Subscription(Word),
}
impl NoteTagRecord {
pub fn with_note_source(tag: NoteTag, details_commitment: NoteDetailsCommitment) -> Self {
Self {
tag,
source: NoteTagSource::Note(details_commitment),
}
}
pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
Self {
tag,
source: NoteTagSource::Account(account_id),
}
}
}
impl Serializable for NoteTagRecord {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.tag.write_into(target);
self.source.write_into(target);
}
}
impl Deserializable for NoteTagRecord {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let tag = NoteTag::read_from(source)?;
let source = NoteTagSource::read_from(source)?;
Ok(Self { tag, source })
}
}
impl Serializable for NoteTagSource {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
NoteTagSource::Account(account_id) => {
target.write_u8(0);
account_id.write_into(target);
},
NoteTagSource::Note(details_commitment) => {
target.write_u8(1);
details_commitment.write_into(target);
},
NoteTagSource::User => target.write_u8(2),
NoteTagSource::Subscription(key) => {
target.write_u8(3);
key.write_into(target);
},
}
}
}
impl Deserializable for NoteTagSource {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
1 => Ok(NoteTagSource::Note(NoteDetailsCommitment::read_from(source)?)),
2 => Ok(NoteTagSource::User),
3 => Ok(NoteTagSource::Subscription(Word::read_from(source)?)),
val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
}
}
}
impl PartialEq<NoteTag> for NoteTagRecord {
fn eq(&self, other: &NoteTag) -> bool {
self.tag == *other
}
}
impl From<&Account> for NoteTagRecord {
fn from(account: &Account) -> Self {
NoteTagRecord::with_account_source(NoteTag::with_account_target(account.id()), account.id())
}
}
impl TryInto<NoteTagRecord> for &InputNoteRecord {
type Error = NoteRecordError;
fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
match self.metadata() {
Some(metadata) => {
Ok(NoteTagRecord::with_note_source(metadata.tag(), self.details_commitment()))
},
None => Err(NoteRecordError::ConversionError(
"Input Note Record does not contain tag".to_string(),
)),
}
}
}
#[cfg(test)]
mod tests {
use miden_protocol::{Felt, Word};
use miden_tx::utils::serde::{Deserializable, Serializable};
use super::NoteTagSource;
#[test]
fn subscription_note_tag_source_round_trips_with_stable_discriminant() {
let key: Word =
[Felt::from(1u32), Felt::from(2u32), Felt::from(3u32), Felt::from(4u32)].into();
let source = NoteTagSource::Subscription(key);
let bytes = source.to_bytes();
assert_eq!(bytes[0], 3, "Subscription discriminant must remain 3");
assert_eq!(NoteTagSource::read_from_bytes(&bytes).unwrap(), source);
}
}