miden-client-web 0.15.0

Web Client library that facilitates interaction with the Miden network
use js_export_macro::js_export;
use miden_client::note::{NoteDetails as NativeNoteDetails, NoteId as NativeNoteId};
use miden_client::notes::NoteFile as NativeNoteFile;
use miden_client::{Deserializable, Serializable};
#[cfg(feature = "nodejs")]
use napi_derive::napi;
#[cfg(feature = "browser")]
use wasm_bindgen::prelude::*;

use super::input_note::InputNote;
use super::note::Note;
use super::output_note::OutputNote;
use crate::js_error_with_context;
use crate::models::note_details::NoteDetails;
use crate::models::note_id::NoteId;
use crate::models::note_inclusion_proof::NoteInclusionProof;
use crate::models::note_tag::NoteTag;
use crate::platform::JsErr;

/// A serialized representation of a note.
#[derive(Clone)]
#[cfg_attr(feature = "browser", wasm_bindgen(inspectable))]
#[cfg_attr(feature = "nodejs", napi)]
pub struct NoteFile {
    pub(crate) inner: NativeNoteFile,
}

#[js_export]
impl NoteFile {
    /// Returns this `NoteFile`'s types.
    #[js_export(js_name = noteType)]
    pub fn note_type(&self) -> String {
        match &self.inner {
            NativeNoteFile::NoteId(_) => "NoteId".to_owned(),
            NativeNoteFile::NoteDetails { .. } => "NoteDetails".to_owned(),
            NativeNoteFile::NoteWithProof(..) => "NoteWithProof".to_owned(),
        }
    }

    /// Returns the note ID when the file carries one.
    ///
    /// Migration note (miden-client PR #2214): `NoteDetails::id()` was
    /// removed (computing the ID now requires `NoteMetadata`), so the
    /// `NoteDetails`-only variant cannot synthesize one without extra
    /// information. Returns `None` in that case.
    #[js_export(js_name = "noteId")]
    pub fn note_id(&self) -> Option<NoteId> {
        match &self.inner {
            NativeNoteFile::NoteId(note_id) => Some((*note_id).into()),
            NativeNoteFile::NoteWithProof(note, _) => Some(note.id().into()),
            NativeNoteFile::NoteDetails { .. } => None,
        }
    }

    /// Returns the note details if present.
    #[js_export(js_name = "noteDetails")]
    pub fn note_details(&self) -> Option<NoteDetails> {
        match &self.inner {
            NativeNoteFile::NoteDetails { details, .. } => Some(details.into()),
            _ => None,
        }
    }

    /// Returns the full note when the file includes it.
    pub fn note(&self) -> Option<Note> {
        match &self.inner {
            NativeNoteFile::NoteWithProof(note, _) => Some(note.into()),
            _ => None,
        }
    }

    /// Returns the inclusion proof if present.
    #[js_export(js_name = "inclusionProof")]
    pub fn inclusion_proof(&self) -> Option<NoteInclusionProof> {
        match &self.inner {
            NativeNoteFile::NoteWithProof(_, proof) => Some(proof.into()),
            _ => None,
        }
    }

    /// Returns the after-block hint when present.
    #[js_export(js_name = "afterBlockNum")]
    pub fn after_block_num(&self) -> Option<u32> {
        match &self.inner {
            NativeNoteFile::NoteDetails { after_block_num, .. } => Some(after_block_num.as_u32()),
            _ => None,
        }
    }

    /// Returns the note tag hint when present.
    #[js_export(js_name = "noteTag")]
    pub fn note_tag(&self) -> Option<NoteTag> {
        match &self.inner {
            NativeNoteFile::NoteDetails { tag, .. } => tag.map(Into::into),
            _ => None,
        }
    }

    /// Returns the note nullifier when present.
    ///
    /// Migration note (miden-client PR #2214): `NoteDetails::nullifier()`
    /// was removed (the nullifier moved onto `InputNoteRecord` and is
    /// optional there), so the `NoteDetails`-only variant returns `None`.
    pub fn nullifier(&self) -> Option<String> {
        match &self.inner {
            NativeNoteFile::NoteWithProof(note, _) => Some(note.nullifier().to_hex()),
            NativeNoteFile::NoteDetails { .. } | NativeNoteFile::NoteId(_) => None,
        }
    }

    /// Turn a notefile into its byte representation.
    #[js_export(js_name = serialize)]
    pub fn serialize(&self) -> Vec<u8> {
        let mut buffer = vec![];
        self.inner.write_into(&mut buffer);
        buffer
    }

    /// Given a valid byte representation of a `NoteFile`,
    /// return it as a struct.
    #[js_export(js_name = deserialize)]
    pub fn deserialize(bytes: &[u8]) -> Result<Self, JsErr> {
        let deserialized = NativeNoteFile::read_from_bytes(bytes)
            .map_err(|err| js_error_with_context(err, "notefile deserialization failed"))?;
        Ok(Self { inner: deserialized })
    }

    /// Creates a `NoteFile` from an input note, preserving proof when available.
    #[js_export(js_name = fromInputNote)]
    pub fn from_input_note(note: &InputNote) -> Self {
        if let Some(inclusion_proof) = note.proof() {
            Self {
                inner: NativeNoteFile::NoteWithProof(note.note().into(), inclusion_proof.into()),
            }
        } else {
            let assets = note.note().assets();
            let recipient = note.note().recipient();
            let details = NativeNoteDetails::new(assets.into(), recipient.into());
            Self { inner: details.into() }
        }
    }

    /// Creates a `NoteFile` from an output note, choosing details when present.
    #[js_export(js_name = fromOutputNote)]
    pub fn from_output_note(note: &OutputNote) -> Self {
        let native_note = note.note();
        match native_note.recipient() {
            Some(recipient) => {
                let assets = native_note.assets();
                let details = NativeNoteDetails::new(assets.clone(), recipient.clone());
                Self { inner: details.into() }
            },
            None => Self { inner: native_note.id().into() },
        }
    }

    /// Creates a `NoteFile` from note details.
    #[js_export(js_name = fromNoteDetails)]
    pub fn from_note_details(note_details: &NoteDetails) -> Self {
        note_details.into()
    }

    /// Creates a `NoteFile` from a note ID.
    #[js_export(js_name = fromNoteId)]
    pub fn from_note_id(note_details: &NoteId) -> Self {
        note_details.into()
    }
}

// CONVERSIONS
// ================================================================================================

impl From<NativeNoteFile> for NoteFile {
    fn from(note_file: NativeNoteFile) -> Self {
        NoteFile { inner: note_file }
    }
}

impl From<NoteFile> for NativeNoteFile {
    fn from(note_file: NoteFile) -> Self {
        note_file.inner
    }
}

impl From<&NoteDetails> for NoteFile {
    fn from(details: &NoteDetails) -> Self {
        let note_details: NativeNoteDetails = details.into();
        Self { inner: note_details.into() }
    }
}

impl From<&NoteId> for NoteFile {
    fn from(note_id: &NoteId) -> Self {
        let note_id: NativeNoteId = note_id.into();
        NoteFile { inner: note_id.into() }
    }
}

impl_napi_from_value!(NoteFile);