1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use js_export_macro::js_export;
use miden_client::note::{
NoteAttachment as NativeNoteAttachment,
NoteAttachmentScheme as NativeNoteAttachmentScheme,
};
use miden_client::{Felt as NativeFelt, Word as NativeWord};
use super::word::Word;
use crate::platform::{JsErr, from_str_err, js_u64_to_u64};
// NOTE ATTACHMENT SCHEME
// ================================================================================================
/// Describes the type of a note attachment.
///
/// Value `0` is reserved to signal that the scheme is none or absent. Whenever the kind of
/// attachment is not standardized or interoperability is unimportant, this none value can be used.
#[derive(Clone, Copy)]
#[js_export]
pub struct NoteAttachmentScheme(NativeNoteAttachmentScheme);
#[js_export]
impl NoteAttachmentScheme {
/// Creates a new `NoteAttachmentScheme` from a u32 value.
///
/// Errors if `scheme` is out of range (the 0.15 surface narrowed the
/// underlying type from u32 → u16, so values outside `0..=u16::MAX`
/// are rejected).
#[js_export(constructor)]
pub fn new(scheme: u32) -> Result<NoteAttachmentScheme, JsErr> {
let scheme_u16: u16 = scheme
.try_into()
.map_err(|_| from_str_err("attachment scheme value exceeds u16 range"))?;
NativeNoteAttachmentScheme::new(scheme_u16)
.map(NoteAttachmentScheme)
.map_err(|err| from_str_err(&format!("invalid attachment scheme: {err}")))
}
/// Returns the `NoteAttachmentScheme` that signals the absence of an attachment scheme.
pub fn none() -> NoteAttachmentScheme {
NoteAttachmentScheme(NativeNoteAttachmentScheme::none())
}
/// Returns true if the attachment scheme is the reserved value that signals an absent scheme.
#[js_export(js_name = "isNone")]
pub fn is_none(&self) -> bool {
self.0.is_none()
}
}
impl From<NativeNoteAttachmentScheme> for NoteAttachmentScheme {
fn from(native: NativeNoteAttachmentScheme) -> Self {
NoteAttachmentScheme(native)
}
}
impl From<&NoteAttachmentScheme> for NativeNoteAttachmentScheme {
fn from(scheme: &NoteAttachmentScheme) -> Self {
scheme.0
}
}
// NOTE ATTACHMENT
// ================================================================================================
/// An attachment to a note.
///
/// 0.15 protocol surface: an attachment is a `(scheme, content)` pair where
/// `content` is a flat `Vec<Word>` of 1..=256 words. The previous
/// `NoteAttachmentKind { Word, Array }` dispatch and the per-variant
/// `asWord` / `asArray` / `newWord` / `newArray` getters/constructors no
/// longer exist — content is always word-vector-shaped now. Use
/// `fromWord(scheme, word)` for the common single-word case or
/// `fromWords(scheme, words)` for multi-word content.
#[derive(Clone)]
#[js_export]
pub struct NoteAttachment(NativeNoteAttachment);
#[js_export]
impl NoteAttachment {
/// Creates a note attachment from an optional list of packed values.
///
/// Mirrors the encoding the JS-side `createNoteAttachment` helper performs
/// and preserves the `new NoteAttachment()` / `new NoteAttachment([...])`
/// ergonomics the package's own JS wrappers (`js/standalone.js`,
/// `js/resources/transactions.js`) rely on. The 0.15 surface has no truly
/// empty attachment (content is 1..=256 words), so the empty case is
/// represented as a single zero [`Word`] with the `none` scheme:
/// - no args / empty list → single zero-`Word`, `none` scheme.
/// - any number of values → padded to a whole number of `Word`s, `none` scheme.
///
/// # Errors
/// Returns an error if a value is not a canonical field element, or if the
/// packed word count exceeds `NoteAttachment::MAX_NUM_WORDS`.
#[js_export(constructor)]
pub fn new(values: Option<Vec<JsU64>>) -> Result<NoteAttachment, JsErr> {
let scheme = NativeNoteAttachmentScheme::none();
let values: Vec<u64> = values.unwrap_or_default().into_iter().map(js_u64_to_u64).collect();
if values.is_empty() {
let zero = NativeFelt::new(0).expect("0 is a valid field element");
let zero_word = NativeWord::from([zero; 4]);
return Ok(NoteAttachment(NativeNoteAttachment::with_word(scheme, zero_word)));
}
// Pad up to a whole number of 4-element words.
let mut padded = values;
while !padded.len().is_multiple_of(4) {
padded.push(0);
}
let words: Vec<NativeWord> = padded
.chunks_exact(4)
.map(|chunk| {
let felts: [NativeFelt; 4] = chunk
.iter()
.map(|&v| NativeFelt::new(v))
.collect::<Result<Vec<_>, _>>()
.map_err(|err| from_str_err(&format!("invalid field element: {err}")))?
.try_into()
.expect("chunk is exactly 4 elements");
Ok::<NativeWord, JsErr>(NativeWord::from(felts))
})
.collect::<Result<Vec<_>, _>>()?;
NativeNoteAttachment::with_words(scheme, words)
.map(NoteAttachment)
.map_err(|e| from_str_err(&e.to_string()))
}
/// Creates a new note attachment from a single word.
#[js_export(js_name = "fromWord")]
pub fn from_word(scheme: &NoteAttachmentScheme, word: &Word) -> NoteAttachment {
let native_word: NativeWord = word.into();
NoteAttachment(NativeNoteAttachment::with_word(scheme.into(), native_word))
}
/// Creates a new note attachment from a vector of words.
///
/// # Errors
/// Returns an error if `words` is empty or exceeds `NoteAttachment::MAX_NUM_WORDS`.
#[js_export(js_name = "fromWords")]
pub fn from_words(
scheme: &NoteAttachmentScheme,
words: Vec<Word>,
) -> Result<NoteAttachment, JsErr> {
let native_words: Vec<NativeWord> = words.iter().map(NativeWord::from).collect();
NativeNoteAttachment::with_words(scheme.into(), native_words)
.map(NoteAttachment)
.map_err(|e| from_str_err(&e.to_string()))
}
/// Returns the attachment scheme.
#[js_export(js_name = "attachmentScheme")]
pub fn attachment_scheme(&self) -> NoteAttachmentScheme {
self.0.attachment_scheme().into()
}
/// Returns the number of words in this attachment.
#[js_export(js_name = "numWords")]
pub fn num_words(&self) -> u16 {
self.0.num_words()
}
/// Returns the attachment content as its constituent words.
///
/// The content is always word-vector-shaped on the 0.15 surface, so this is
/// the inverse of `fromWord` / `fromWords`: it yields the same `Word`s the
/// attachment was built from (in order), letting JS callers decode the
/// packed values back out.
#[js_export(js_name = "toWords")]
pub fn to_words(&self) -> Vec<Word> {
self.0.content().as_words().iter().map(Word::from).collect()
}
// NOTE: the previous `newWord` / `newArray` constructors, `asWord` /
// `asArray` getters, `attachmentKind` accessor, and the
// `newNetworkAccountTarget` helper were removed in the migration to
// miden-client PR #2214. The 0.15 protocol surface dropped the
// word-vs-array content dispatch (content is always `Vec<Word>`) and
// the `NetworkAccountTarget` type does not exist on this surface.
}
// CONVERSIONS
// ================================================================================================
impl From<NativeNoteAttachment> for NoteAttachment {
fn from(native_note_attachment: NativeNoteAttachment) -> Self {
NoteAttachment(native_note_attachment)
}
}
impl From<&NativeNoteAttachment> for NoteAttachment {
fn from(native_note_attachment: &NativeNoteAttachment) -> Self {
NoteAttachment(native_note_attachment.clone())
}
}
impl From<NoteAttachment> for NativeNoteAttachment {
fn from(note_attachment: NoteAttachment) -> Self {
note_attachment.0
}
}
impl From<&NoteAttachment> for NativeNoteAttachment {
fn from(note_attachment: &NoteAttachment) -> Self {
note_attachment.0.clone()
}
}