rose_wasm/
tx.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use alloc::format;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use ibig::UBig;
7use rose_crypto::PrivateKey;
8use rose_grpc_proto::pb::common::v1 as pb_v1;
9use rose_grpc_proto::pb::common::v2 as pb;
10use rose_nockchain_types::{
11    builder::TxBuilder,
12    note::{Name, Note, NoteData, NoteDataEntry, Pkh, TimelockRange, Version},
13    tx::{LockPrimitive, LockRoot, NockchainTx, RawTx, Seed, SpendCondition},
14    Nicks,
15};
16use rose_nockchain_types::{Hax, LockTim, MissingUnlocks, Source, SpendBuilder};
17use rose_ztd::{cue, jam, Digest, Hashable as HashableTrait, NounDecode, NounEncode};
18use serde::{Deserialize, Serialize};
19use wasm_bindgen::prelude::*;
20
21use crate::memo::memo_from_js;
22
23// ============================================================================
24// Wasm Types - Core Types
25// ============================================================================
26
27#[wasm_bindgen(js_name = Digest)]
28#[derive(Clone, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct WasmDigest {
31    #[wasm_bindgen(skip)]
32    pub value: String,
33}
34
35#[wasm_bindgen(js_class = Digest)]
36impl WasmDigest {
37    #[wasm_bindgen(constructor)]
38    pub fn new(value: String) -> Self {
39        Self { value }
40    }
41
42    #[wasm_bindgen(getter)]
43    pub fn value(&self) -> String {
44        self.value.clone()
45    }
46
47    fn to_internal(&self) -> Result<Digest, &'static str> {
48        self.value.as_str().try_into()
49    }
50
51    fn from_internal(digest: &Digest) -> Self {
52        Self {
53            value: digest.to_string(),
54        }
55    }
56
57    #[wasm_bindgen(js_name = toProtobuf)]
58    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
59        let digest = self.to_internal().map_err(JsValue::from_str)?;
60        let pb = pb_v1::Hash::from(digest);
61        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
62    }
63
64    #[wasm_bindgen(js_name = fromProtobuf)]
65    pub fn from_protobuf(value: JsValue) -> Result<WasmDigest, JsValue> {
66        let pb: pb_v1::Hash = serde_wasm_bindgen::from_value(value)?;
67        let digest: Digest = pb
68            .try_into()
69            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
70        Ok(WasmDigest::from_internal(&digest))
71    }
72}
73
74#[wasm_bindgen(js_name = Version)]
75#[derive(Clone, Serialize, Deserialize)]
76pub struct WasmVersion {
77    version: u32,
78}
79
80#[wasm_bindgen(js_class = Version)]
81impl WasmVersion {
82    #[wasm_bindgen(constructor)]
83    pub fn new(version: u32) -> Self {
84        Self { version }
85    }
86
87    #[wasm_bindgen(js_name = V0)]
88    pub fn v0() -> Self {
89        Self { version: 0 }
90    }
91
92    #[wasm_bindgen(js_name = V1)]
93    pub fn v1() -> Self {
94        Self { version: 1 }
95    }
96
97    #[wasm_bindgen(js_name = V2)]
98    pub fn v2() -> Self {
99        Self { version: 2 }
100    }
101
102    fn to_internal(&self) -> Version {
103        self.version.into()
104    }
105
106    fn from_internal(version: &Version) -> Self {
107        Self {
108            version: version.clone().into(),
109        }
110    }
111}
112
113#[wasm_bindgen(js_name = Name)]
114#[derive(Clone, Serialize, Deserialize)]
115pub struct WasmName {
116    #[wasm_bindgen(skip)]
117    pub first: Digest,
118    #[wasm_bindgen(skip)]
119    pub last: Digest,
120}
121
122#[wasm_bindgen(js_class = Name)]
123impl WasmName {
124    #[wasm_bindgen(constructor)]
125    pub fn new(first: String, last: String) -> Result<Self, JsValue> {
126        let first = Digest::try_from(&*first)?;
127        let last = Digest::try_from(&*last)?;
128        Ok(Self { first, last })
129    }
130
131    #[wasm_bindgen(getter)]
132    pub fn first(&self) -> String {
133        self.first.to_string()
134    }
135
136    #[wasm_bindgen(getter)]
137    pub fn last(&self) -> String {
138        self.last.to_string()
139    }
140
141    fn to_internal(&self) -> Name {
142        Name::new(self.first, self.last)
143    }
144
145    #[allow(dead_code)]
146    fn from_internal(name: &Name) -> Self {
147        // We need to access Name fields via hash since they are private
148        // For now, we'll only support construction, not reading back
149        Self {
150            first: name.first,
151            last: name.last,
152        }
153    }
154
155    #[wasm_bindgen(js_name = toProtobuf)]
156    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
157        let name = self.to_internal();
158        let pb = pb_v1::Name::from(name);
159        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
160    }
161
162    #[wasm_bindgen(js_name = fromProtobuf)]
163    pub fn from_protobuf(value: JsValue) -> Result<WasmName, JsValue> {
164        let pb: pb_v1::Name = serde_wasm_bindgen::from_value(value)?;
165        let name: Name = pb
166            .try_into()
167            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
168        Ok(WasmName::from_internal(&name))
169    }
170}
171
172#[wasm_bindgen(js_name = TimelockRange)]
173#[derive(Clone, Serialize, Deserialize)]
174pub struct WasmTimelockRange {
175    #[wasm_bindgen(skip)]
176    pub min: Option<u64>,
177    #[wasm_bindgen(skip)]
178    pub max: Option<u64>,
179}
180
181#[wasm_bindgen(js_class = TimelockRange)]
182impl WasmTimelockRange {
183    #[wasm_bindgen(constructor)]
184    pub fn new(min: Option<u64>, max: Option<u64>) -> Self {
185        Self { min, max }
186    }
187
188    #[wasm_bindgen(getter)]
189    pub fn min(&self) -> Option<u64> {
190        self.min
191    }
192
193    #[wasm_bindgen(getter)]
194    pub fn max(&self) -> Option<u64> {
195        self.max
196    }
197
198    fn to_internal(&self) -> TimelockRange {
199        TimelockRange::new(self.min, self.max)
200    }
201
202    fn from_internal(internal: TimelockRange) -> WasmTimelockRange {
203        WasmTimelockRange {
204            min: internal.min,
205            max: internal.max,
206        }
207    }
208}
209
210#[wasm_bindgen(js_name = Source)]
211#[derive(Clone, Serialize, Deserialize)]
212pub struct WasmSource {
213    #[wasm_bindgen(skip)]
214    pub hash: WasmDigest,
215    #[wasm_bindgen(skip)]
216    pub is_coinbase: bool,
217}
218
219#[wasm_bindgen(js_class = Source)]
220impl WasmSource {
221    #[wasm_bindgen(getter, js_name = hash)]
222    pub fn hash(&self) -> WasmDigest {
223        self.hash.clone()
224    }
225
226    #[wasm_bindgen(getter, js_name = isCoinbase)]
227    pub fn is_coinbase(&self) -> bool {
228        self.is_coinbase
229    }
230
231    fn to_internal(&self) -> Result<Source, String> {
232        Ok(Source {
233            hash: self.hash.to_internal()?,
234            is_coinbase: self.is_coinbase,
235        })
236    }
237
238    fn from_internal(internal: &Source) -> Self {
239        Self {
240            hash: WasmDigest::from_internal(&internal.hash),
241            is_coinbase: internal.is_coinbase,
242        }
243    }
244}
245
246// ============================================================================
247// Wasm Types - Note Types
248// ============================================================================
249
250#[wasm_bindgen(js_name = NoteDataEntry)]
251#[derive(Clone, Serialize, Deserialize)]
252pub struct WasmNoteDataEntry {
253    #[wasm_bindgen(skip)]
254    pub key: String,
255    #[wasm_bindgen(skip)]
256    pub blob: Vec<u8>,
257}
258
259#[wasm_bindgen(js_class = NoteDataEntry)]
260impl WasmNoteDataEntry {
261    #[wasm_bindgen(constructor)]
262    pub fn new(key: String, blob: Vec<u8>) -> Self {
263        Self { key, blob }
264    }
265
266    #[wasm_bindgen(getter)]
267    pub fn key(&self) -> String {
268        self.key.clone()
269    }
270
271    #[wasm_bindgen(getter)]
272    pub fn blob(&self) -> Vec<u8> {
273        self.blob.clone()
274    }
275
276    fn to_internal(&self) -> Result<NoteDataEntry, String> {
277        let val = cue(&self.blob).ok_or_else(|| "Failed to deserialize noun".to_string())?;
278        Ok(NoteDataEntry {
279            key: self.key.clone(),
280            val,
281        })
282    }
283
284    fn from_internal(entry: &NoteDataEntry) -> Self {
285        Self {
286            key: entry.key.clone(),
287            blob: jam(entry.val.clone()),
288        }
289    }
290
291    #[wasm_bindgen(js_name = toProtobuf)]
292    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
293        let entry = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
294        let pb = pb::NoteDataEntry::from(entry);
295        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
296    }
297
298    #[wasm_bindgen(js_name = fromProtobuf)]
299    pub fn from_protobuf(value: JsValue) -> Result<WasmNoteDataEntry, JsValue> {
300        let pb: pb::NoteDataEntry = serde_wasm_bindgen::from_value(value)?;
301        let entry: NoteDataEntry = pb
302            .try_into()
303            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
304        Ok(WasmNoteDataEntry::from_internal(&entry))
305    }
306}
307
308#[wasm_bindgen(js_name = NoteData)]
309#[derive(Clone, Serialize, Deserialize)]
310pub struct WasmNoteData {
311    #[wasm_bindgen(skip)]
312    pub entries: Vec<WasmNoteDataEntry>,
313}
314
315#[wasm_bindgen(js_class = NoteData)]
316impl WasmNoteData {
317    #[wasm_bindgen(constructor)]
318    pub fn new(entries: Vec<WasmNoteDataEntry>) -> Self {
319        Self { entries }
320    }
321
322    #[wasm_bindgen]
323    pub fn empty() -> Self {
324        Self {
325            entries: Vec::new(),
326        }
327    }
328
329    #[wasm_bindgen(js_name = fromPkh)]
330    pub fn from_pkh(pkh: WasmPkh) -> Result<Self, JsValue> {
331        let note_data = NoteData::from_pkh(pkh.to_internal()?);
332        Ok(Self::from_internal(&note_data))
333    }
334
335    #[wasm_bindgen(getter)]
336    pub fn entries(&self) -> Vec<WasmNoteDataEntry> {
337        self.entries.clone()
338    }
339
340    fn to_internal(&self) -> Result<NoteData, String> {
341        let entries: Result<Vec<NoteDataEntry>, String> =
342            self.entries.iter().map(|e| e.to_internal()).collect();
343        Ok(NoteData { entries: entries? })
344    }
345
346    fn from_internal(note_data: &NoteData) -> Self {
347        Self {
348            entries: note_data
349                .entries
350                .iter()
351                .map(WasmNoteDataEntry::from_internal)
352                .collect(),
353        }
354    }
355
356    #[wasm_bindgen(js_name = toProtobuf)]
357    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
358        let data = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
359        let pb = pb::NoteData::from(data);
360        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
361    }
362
363    #[wasm_bindgen(js_name = fromProtobuf)]
364    pub fn from_protobuf(value: JsValue) -> Result<WasmNoteData, JsValue> {
365        let pb: pb::NoteData = serde_wasm_bindgen::from_value(value)?;
366        let data: NoteData = pb
367            .try_into()
368            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
369        Ok(WasmNoteData::from_internal(&data))
370    }
371}
372
373#[wasm_bindgen(js_name = Note)]
374#[derive(Clone, Serialize, Deserialize)]
375pub struct WasmNote {
376    #[wasm_bindgen(skip)]
377    pub version: WasmVersion,
378    #[wasm_bindgen(skip)]
379    pub origin_page: u64,
380    #[wasm_bindgen(skip)]
381    pub name: WasmName,
382    #[wasm_bindgen(skip)]
383    pub note_data: WasmNoteData,
384    #[wasm_bindgen(skip)]
385    pub assets: Nicks,
386}
387
388#[wasm_bindgen(js_class = Note)]
389impl WasmNote {
390    #[wasm_bindgen(constructor)]
391    pub fn new(
392        version: WasmVersion,
393        origin_page: u64,
394        name: WasmName,
395        note_data: WasmNoteData,
396        assets: Nicks,
397    ) -> Self {
398        Self {
399            version,
400            origin_page,
401            name,
402            note_data,
403            assets,
404        }
405    }
406
407    #[wasm_bindgen(getter)]
408    pub fn version(&self) -> WasmVersion {
409        self.version.clone()
410    }
411
412    #[wasm_bindgen(getter, js_name = originPage)]
413    pub fn origin_page(&self) -> u64 {
414        self.origin_page
415    }
416
417    #[wasm_bindgen(getter)]
418    pub fn name(&self) -> WasmName {
419        self.name.clone()
420    }
421
422    #[wasm_bindgen(getter, js_name = noteData)]
423    pub fn note_data(&self) -> WasmNoteData {
424        self.note_data.clone()
425    }
426
427    #[wasm_bindgen(getter)]
428    pub fn assets(&self) -> Nicks {
429        self.assets
430    }
431
432    #[wasm_bindgen]
433    pub fn hash(&self) -> Result<WasmDigest, JsValue> {
434        let note = self
435            .to_internal()
436            .map_err(|e| JsValue::from_str(&e.to_string()))?;
437        Ok(WasmDigest::from_internal(&note.hash()))
438    }
439
440    /// Create a WasmNote from a protobuf Note object (from get_balance response)
441    /// Expects response.notes[i].note (handles version internally)
442    #[wasm_bindgen(js_name = fromProtobuf)]
443    pub fn from_protobuf(pb_note: JsValue) -> Result<WasmNote, JsValue> {
444        let pb: pb::Note = serde_wasm_bindgen::from_value(pb_note)?;
445        let note: Note = pb
446            .try_into()
447            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
448        Ok(WasmNote::from_internal(note))
449    }
450
451    #[wasm_bindgen(js_name = toProtobuf)]
452    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
453        let note = self
454            .to_internal()
455            .map_err(|e| JsValue::from_str(&e.to_string()))?;
456        let pb = pb::Note::from(note);
457        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
458    }
459
460    fn to_internal(&self) -> Result<Note, String> {
461        Ok(Note::new(
462            self.version.to_internal(),
463            self.origin_page,
464            self.name.to_internal(),
465            self.note_data.to_internal()?,
466            self.assets,
467        ))
468    }
469
470    fn from_internal(internal: Note) -> Self {
471        Self {
472            version: WasmVersion::from_internal(&internal.version),
473            origin_page: internal.origin_page,
474            name: WasmName::from_internal(&internal.name),
475            note_data: WasmNoteData::from_internal(&internal.note_data),
476            assets: internal.assets,
477        }
478    }
479}
480
481// ============================================================================
482// Wasm Types - Transaction Types
483// ============================================================================
484
485#[wasm_bindgen(js_name = Pkh)]
486#[derive(Clone, Serialize, Deserialize)]
487pub struct WasmPkh {
488    #[wasm_bindgen(skip)]
489    pub m: u64,
490    #[wasm_bindgen(skip)]
491    pub hashes: Vec<String>,
492}
493
494#[wasm_bindgen(js_class = Pkh)]
495impl WasmPkh {
496    #[wasm_bindgen(constructor)]
497    pub fn new(m: u64, hashes: Vec<String>) -> Self {
498        Self { m, hashes }
499    }
500
501    #[wasm_bindgen]
502    pub fn single(hash: String) -> Self {
503        Self {
504            m: 1,
505            hashes: alloc::vec![hash],
506        }
507    }
508
509    #[wasm_bindgen(getter)]
510    pub fn m(&self) -> u64 {
511        self.m
512    }
513
514    #[wasm_bindgen(getter)]
515    pub fn hashes(&self) -> Vec<String> {
516        self.hashes.clone()
517    }
518
519    fn to_internal(&self) -> Result<Pkh, String> {
520        let hashes: Result<Vec<Digest>, _> =
521            self.hashes.iter().map(|s| s.as_str().try_into()).collect();
522        Ok(Pkh::new(self.m, hashes?))
523    }
524
525    fn from_internal(internal: Pkh) -> Self {
526        Self::new(
527            internal.m,
528            internal.hashes.into_iter().map(|v| v.to_string()).collect(),
529        )
530    }
531
532    #[wasm_bindgen(js_name = toProtobuf)]
533    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
534        let pkh = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
535        let pb = pb::PkhLock::from(pkh);
536        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
537    }
538
539    #[wasm_bindgen(js_name = fromProtobuf)]
540    pub fn from_protobuf(value: JsValue) -> Result<WasmPkh, JsValue> {
541        let pb: pb::PkhLock = serde_wasm_bindgen::from_value(value)?;
542        let pkh: Pkh = pb
543            .try_into()
544            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
545        Ok(WasmPkh::from_internal(pkh))
546    }
547}
548
549#[wasm_bindgen(js_name = LockTim)]
550#[derive(Clone, Serialize, Deserialize)]
551pub struct WasmLockTim {
552    #[wasm_bindgen(skip)]
553    pub rel: WasmTimelockRange,
554    #[wasm_bindgen(skip)]
555    pub abs: WasmTimelockRange,
556}
557
558#[wasm_bindgen(js_class = LockTim)]
559impl WasmLockTim {
560    #[wasm_bindgen(constructor)]
561    pub fn new(rel: WasmTimelockRange, abs: WasmTimelockRange) -> Self {
562        Self { rel, abs }
563    }
564
565    #[wasm_bindgen]
566    pub fn coinbase() -> Self {
567        let tim = LockTim::coinbase();
568        Self {
569            rel: WasmTimelockRange {
570                min: tim.rel.min,
571                max: tim.rel.max,
572            },
573            abs: WasmTimelockRange {
574                min: tim.abs.min,
575                max: tim.abs.max,
576            },
577        }
578    }
579
580    #[wasm_bindgen(getter)]
581    pub fn rel(&self) -> WasmTimelockRange {
582        self.rel.clone()
583    }
584
585    #[wasm_bindgen(getter)]
586    pub fn abs(&self) -> WasmTimelockRange {
587        self.abs.clone()
588    }
589
590    fn to_internal(&self) -> LockTim {
591        LockTim {
592            rel: self.rel.to_internal(),
593            abs: self.abs.to_internal(),
594        }
595    }
596
597    fn from_internal(internal: LockTim) -> WasmLockTim {
598        WasmLockTim {
599            rel: WasmTimelockRange::from_internal(internal.rel),
600            abs: WasmTimelockRange::from_internal(internal.abs),
601        }
602    }
603
604    #[wasm_bindgen(js_name = toProtobuf)]
605    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
606        let tim = self.to_internal();
607        let pb = pb::LockTim::from(tim);
608        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
609    }
610
611    #[wasm_bindgen(js_name = fromProtobuf)]
612    pub fn from_protobuf(value: JsValue) -> Result<WasmLockTim, JsValue> {
613        let pb: pb::LockTim = serde_wasm_bindgen::from_value(value)?;
614        let tim: LockTim = pb
615            .try_into()
616            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
617        Ok(WasmLockTim::from_internal(tim))
618    }
619}
620
621#[wasm_bindgen(js_name = Hax)]
622#[derive(Clone, Serialize, Deserialize)]
623pub struct WasmHax {
624    #[wasm_bindgen(skip)]
625    pub digests: Vec<WasmDigest>,
626}
627
628#[wasm_bindgen(js_class = Hax)]
629impl WasmHax {
630    #[wasm_bindgen(constructor)]
631    pub fn new(digests: Vec<WasmDigest>) -> Self {
632        Self { digests }
633    }
634
635    #[wasm_bindgen(getter)]
636    pub fn digests(&self) -> Vec<WasmDigest> {
637        self.digests.clone()
638    }
639
640    fn to_internal(&self) -> Result<Hax, String> {
641        Ok(Hax(self
642            .digests
643            .iter()
644            .map(WasmDigest::to_internal)
645            .collect::<Result<Vec<_>, _>>()?))
646    }
647
648    fn from_internal(internal: Hax) -> Self {
649        Self::new(internal.0.iter().map(WasmDigest::from_internal).collect())
650    }
651}
652
653#[wasm_bindgen(js_name = LockPrimitive)]
654#[derive(Clone, Serialize, Deserialize)]
655pub struct WasmLockPrimitive {
656    variant: String,
657    #[wasm_bindgen(skip)]
658    pub pkh_data: Option<WasmPkh>,
659    #[wasm_bindgen(skip)]
660    pub tim_data: Option<WasmLockTim>,
661    #[wasm_bindgen(skip)]
662    pub hax_data: Option<WasmHax>,
663}
664
665#[wasm_bindgen(js_class = LockPrimitive)]
666impl WasmLockPrimitive {
667    #[wasm_bindgen(js_name = newPkh)]
668    pub fn new_pkh(pkh: WasmPkh) -> WasmLockPrimitive {
669        Self {
670            variant: "pkh".to_string(),
671            pkh_data: Some(pkh),
672            tim_data: None,
673            hax_data: None,
674        }
675    }
676
677    #[wasm_bindgen(js_name = newTim)]
678    pub fn new_tim(tim: WasmLockTim) -> WasmLockPrimitive {
679        Self {
680            variant: "tim".to_string(),
681            pkh_data: None,
682            tim_data: Some(tim),
683            hax_data: None,
684        }
685    }
686
687    #[wasm_bindgen(js_name = newHax)]
688    pub fn new_hax(hax: WasmHax) -> Self {
689        Self {
690            variant: "hax".to_string(),
691            pkh_data: None,
692            tim_data: None,
693            hax_data: Some(hax),
694        }
695    }
696
697    #[wasm_bindgen(js_name = newBrn)]
698    pub fn new_brn() -> Self {
699        Self {
700            variant: "brn".to_string(),
701            pkh_data: None,
702            tim_data: None,
703            hax_data: None,
704        }
705    }
706
707    fn to_internal(&self) -> Result<LockPrimitive, String> {
708        match self.variant.as_str() {
709            "pkh" => {
710                if let Some(ref pkh) = self.pkh_data {
711                    Ok(LockPrimitive::Pkh(pkh.to_internal()?))
712                } else {
713                    Err("Missing pkh data".to_string())
714                }
715            }
716            "tim" => {
717                if let Some(ref tim) = self.tim_data {
718                    Ok(LockPrimitive::Tim(tim.to_internal()))
719                } else {
720                    Err("Missing tim data".to_string())
721                }
722            }
723            "hax" => {
724                if let Some(ref hax) = self.hax_data {
725                    Ok(LockPrimitive::Hax(hax.to_internal()?))
726                } else {
727                    Err("Missing hax data".to_string())
728                }
729            }
730            "brn" => Ok(LockPrimitive::Brn),
731            _ => Err("Invalid lock primitive variant".to_string()),
732        }
733    }
734
735    fn from_internal(internal: LockPrimitive) -> Self {
736        match internal {
737            LockPrimitive::Pkh(p) => Self::new_pkh(WasmPkh::from_internal(p)),
738            LockPrimitive::Tim(t) => Self::new_tim(WasmLockTim::from_internal(t)),
739            LockPrimitive::Hax(h) => Self::new_hax(WasmHax::from_internal(h)),
740            LockPrimitive::Brn => Self::new_brn(),
741        }
742    }
743
744    #[wasm_bindgen(js_name = toProtobuf)]
745    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
746        let prim = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
747        let pb = pb::LockPrimitive::from(prim);
748        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
749    }
750
751    #[wasm_bindgen(js_name = fromProtobuf)]
752    pub fn from_protobuf(value: JsValue) -> Result<WasmLockPrimitive, JsValue> {
753        let pb: pb::LockPrimitive = serde_wasm_bindgen::from_value(value)?;
754        let prim: LockPrimitive = pb
755            .try_into()
756            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
757        Ok(WasmLockPrimitive::from_internal(prim))
758    }
759}
760
761#[wasm_bindgen(js_name = SpendCondition)]
762#[derive(Clone, Serialize, Deserialize)]
763pub struct WasmSpendCondition {
764    #[wasm_bindgen(skip)]
765    pub primitives: Vec<WasmLockPrimitive>,
766}
767
768#[wasm_bindgen(js_class = SpendCondition)]
769impl WasmSpendCondition {
770    #[wasm_bindgen(constructor)]
771    pub fn new(primitives: Vec<WasmLockPrimitive>) -> Self {
772        Self { primitives }
773    }
774
775    #[wasm_bindgen(js_name = newPkh)]
776    pub fn new_pkh(pkh: WasmPkh) -> WasmSpendCondition {
777        let primitive = WasmLockPrimitive::new_pkh(pkh);
778        Self {
779            primitives: alloc::vec![primitive],
780        }
781    }
782
783    #[wasm_bindgen]
784    pub fn hash(&self) -> Result<WasmDigest, JsValue> {
785        let condition = self
786            .to_internal()
787            .map_err(|e| JsValue::from_str(&e.to_string()))?;
788        Ok(WasmDigest::from_internal(&condition.hash()))
789    }
790
791    #[wasm_bindgen(js_name = firstName)]
792    pub fn first_name(&self) -> Result<WasmDigest, JsValue> {
793        let condition = self
794            .to_internal()
795            .map_err(|e| JsValue::from_str(&e.to_string()))?;
796        Ok(WasmDigest::from_internal(&condition.first_name()))
797    }
798
799    fn to_internal(&self) -> Result<SpendCondition, String> {
800        let mut primitives = Vec::new();
801        for prim in &self.primitives {
802            primitives.push(prim.to_internal()?);
803        }
804        Ok(SpendCondition(primitives))
805    }
806
807    fn from_internal(internal: SpendCondition) -> Self {
808        Self::new(
809            internal
810                .0
811                .into_iter()
812                .map(WasmLockPrimitive::from_internal)
813                .collect(),
814        )
815    }
816
817    #[wasm_bindgen(js_name = toProtobuf)]
818    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
819        let cond = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
820        let pb = pb::SpendCondition::from(cond);
821        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
822    }
823
824    #[wasm_bindgen(js_name = fromProtobuf)]
825    pub fn from_protobuf(value: JsValue) -> Result<WasmSpendCondition, JsValue> {
826        let pb: pb::SpendCondition = serde_wasm_bindgen::from_value(value)?;
827        let cond: SpendCondition = pb
828            .try_into()
829            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
830        Ok(WasmSpendCondition::from_internal(cond))
831    }
832}
833
834#[wasm_bindgen(js_name = LockRoot)]
835#[derive(Clone, Debug)]
836pub struct WasmLockRoot {
837    #[wasm_bindgen(skip)]
838    pub internal: LockRoot,
839}
840
841#[wasm_bindgen(js_class = LockRoot)]
842impl WasmLockRoot {
843    #[wasm_bindgen(js_name = fromHash)]
844    pub fn from_hash(hash: WasmDigest) -> Result<Self, JsValue> {
845        Ok(Self {
846            internal: LockRoot::Hash(hash.to_internal()?),
847        })
848    }
849
850    #[wasm_bindgen(js_name = fromSpendCondition)]
851    pub fn from_spend_condition(cond: WasmSpendCondition) -> Result<Self, JsValue> {
852        Ok(Self {
853            internal: LockRoot::Lock(cond.to_internal()?),
854        })
855    }
856
857    #[wasm_bindgen(getter, js_name = hash)]
858    pub fn hash(&self) -> WasmDigest {
859        WasmDigest::from_internal(&self.internal.hash())
860    }
861
862    #[wasm_bindgen(getter, js_name = lock)]
863    pub fn lock(&self) -> Option<WasmSpendCondition> {
864        match &self.internal {
865            LockRoot::Lock(cond) => Some(WasmSpendCondition::from_internal(cond.clone())),
866            _ => None,
867        }
868    }
869
870    fn to_internal(&self) -> LockRoot {
871        self.internal.clone()
872    }
873
874    fn from_internal(internal: LockRoot) -> Self {
875        Self { internal }
876    }
877}
878
879#[wasm_bindgen(js_name = Seed)]
880pub struct WasmSeed {
881    #[wasm_bindgen(skip)]
882    pub output_source: Option<WasmSource>,
883    #[wasm_bindgen(skip)]
884    pub lock_root: WasmLockRoot,
885    #[wasm_bindgen(skip)]
886    pub gift: Nicks,
887    #[wasm_bindgen(skip)]
888    pub note_data: WasmNoteData,
889    #[wasm_bindgen(skip)]
890    pub parent_hash: WasmDigest,
891}
892
893#[wasm_bindgen(js_class = Seed)]
894impl WasmSeed {
895    #[wasm_bindgen(constructor)]
896    pub fn new(
897        output_source: Option<WasmSource>,
898        lock_root: WasmLockRoot,
899        gift: Nicks,
900        note_data: WasmNoteData,
901        parent_hash: WasmDigest,
902    ) -> Self {
903        Self {
904            output_source,
905            lock_root,
906            gift,
907            note_data,
908            parent_hash,
909        }
910    }
911
912    #[wasm_bindgen(js_name = newSinglePkh)]
913    pub fn new_single_pkh(
914        pkh: WasmDigest,
915        gift: Nicks,
916        parent_hash: WasmDigest,
917        include_lock_data: bool,
918        memo: Option<JsValue>,
919    ) -> Result<Self, JsValue> {
920        let memo = memo_from_js(memo)?;
921        let seed = Seed::new_single_pkh(
922            pkh.to_internal()?,
923            gift,
924            parent_hash.to_internal()?,
925            include_lock_data,
926            memo,
927        );
928        Ok(seed.into())
929    }
930
931    #[wasm_bindgen(getter, js_name = outputSource)]
932    pub fn output_source(&self) -> Option<WasmSource> {
933        self.output_source.clone()
934    }
935
936    #[wasm_bindgen(setter, js_name = outputSource)]
937    pub fn set_output_source(&mut self, output_source: Option<WasmSource>) {
938        self.output_source = output_source;
939    }
940
941    #[wasm_bindgen(getter, js_name = lockRoot)]
942    pub fn lock_root(&self) -> WasmLockRoot {
943        self.lock_root.clone()
944    }
945
946    #[wasm_bindgen(setter, js_name = lockRoot)]
947    pub fn set_lock_root(&mut self, lock_root: WasmLockRoot) {
948        self.lock_root = lock_root;
949    }
950
951    #[wasm_bindgen(getter)]
952    pub fn gift(&self) -> Nicks {
953        self.gift
954    }
955
956    #[wasm_bindgen(setter)]
957    pub fn set_gift(&mut self, gift: Nicks) {
958        self.gift = gift;
959    }
960
961    #[wasm_bindgen(getter, js_name = noteData)]
962    pub fn note_data(&self) -> WasmNoteData {
963        self.note_data.clone()
964    }
965
966    #[wasm_bindgen(setter, js_name = noteData)]
967    pub fn set_note_data(&mut self, note_data: WasmNoteData) {
968        self.note_data = note_data;
969    }
970
971    #[wasm_bindgen(getter, js_name = parentHash)]
972    pub fn parent_hash(&self) -> WasmDigest {
973        self.parent_hash.clone()
974    }
975
976    #[wasm_bindgen(setter, js_name = parentHash)]
977    pub fn set_parent_hash(&mut self, parent_hash: WasmDigest) {
978        self.parent_hash = parent_hash;
979    }
980
981    fn to_internal(&self) -> Result<Seed, String> {
982        Ok(Seed {
983            output_source: self
984                .output_source
985                .as_ref()
986                .map(WasmSource::to_internal)
987                .transpose()?,
988            lock_root: self.lock_root.to_internal(),
989            gift: self.gift,
990            note_data: self.note_data.to_internal()?,
991            parent_hash: self.parent_hash.to_internal()?,
992        })
993    }
994}
995
996impl From<Seed> for WasmSeed {
997    fn from(value: Seed) -> Self {
998        Self {
999            output_source: value.output_source.as_ref().map(WasmSource::from_internal),
1000            lock_root: WasmLockRoot::from_internal(value.lock_root),
1001            gift: value.gift,
1002            note_data: WasmNoteData::from_internal(&value.note_data),
1003            parent_hash: WasmDigest::from_internal(&value.parent_hash),
1004        }
1005    }
1006}
1007
1008#[wasm_bindgen]
1009impl WasmSeed {
1010    #[wasm_bindgen(js_name = toProtobuf)]
1011    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
1012        let seed = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
1013        let pb = pb::Seed::from(seed);
1014        serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
1015    }
1016
1017    fn from_internal(seed: Seed) -> Self {
1018        seed.into()
1019    }
1020
1021    #[wasm_bindgen(js_name = fromProtobuf)]
1022    pub fn from_protobuf(value: JsValue) -> Result<WasmSeed, JsValue> {
1023        let pb: pb::Seed = serde_wasm_bindgen::from_value(value)?;
1024        let seed: Seed = pb
1025            .try_into()
1026            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1027        Ok(WasmSeed::from_internal(seed))
1028    }
1029}
1030
1031// ============================================================================
1032// Wasm Transaction Builder
1033// ============================================================================
1034
1035#[wasm_bindgen(js_name = TxBuilder)]
1036pub struct WasmTxBuilder {
1037    builder: TxBuilder,
1038}
1039
1040#[wasm_bindgen(js_class = TxBuilder)]
1041impl WasmTxBuilder {
1042    /// Create an empty transaction builder
1043    #[wasm_bindgen(constructor)]
1044    pub fn new(fee_per_word: Nicks) -> Self {
1045        Self {
1046            builder: TxBuilder::new(fee_per_word),
1047        }
1048    }
1049
1050    /// Reconstruct a builder from raw transaction and its input notes.
1051    ///
1052    /// To get the builder back, you must pass the notes and their corresponding spend conditions.
1053    /// If serializing the builder, call `WasmTxBuilder::all_notes`.
1054    #[wasm_bindgen(js_name = fromTx)]
1055    pub fn from_tx(
1056        tx: WasmRawTx,
1057        notes: Vec<WasmNote>,
1058        spend_conditions: Vec<WasmSpendCondition>,
1059    ) -> Result<Self, JsValue> {
1060        if notes.len() != spend_conditions.len() {
1061            return Err(JsValue::from_str(
1062                "notes and spend_conditions must have the same length",
1063            ));
1064        }
1065
1066        let internal_notes: Result<BTreeMap<Name, (Note, SpendCondition)>, String> = notes
1067            .iter()
1068            .zip(spend_conditions.iter())
1069            .map(|(n, sc)| Ok((n.to_internal()?, sc.to_internal()?)))
1070            .map(|v| v.map(|(a, b)| (a.name.clone(), (a, b))))
1071            .collect();
1072        let internal_notes = internal_notes.map_err(|e| JsValue::from_str(&e.to_string()))?;
1073
1074        let builder = TxBuilder::from_tx(tx.internal, internal_notes).map_err(|e| e.to_string())?;
1075
1076        Ok(Self { builder })
1077    }
1078
1079    /// Perform a simple-spend on this builder.
1080    ///
1081    /// It is HIGHLY recommended to not mix `simpleSpend` with other types of spends.
1082    ///
1083    /// This performs a fairly complex set of operations, in order to mimic behavior of nockchain
1084    /// CLI wallet's create-tx option. Note that we do not do 1-1 mapping of that functionality,
1085    /// most notably - if `recipient` is the same as `refund_pkh`, we will create 1 seed, while the
1086    /// CLI wallet will create 2.
1087    ///
1088    /// Another difference is that you should call `sign` and `validate` after calling this method.
1089    ///
1090    /// Internally, the transaction builder takes ALL of the `notes` provided, and stores them for
1091    /// fee adjustments. If there are multiple notes being used, our fee setup also differs from
1092    /// the CLI, because we first greedily spend the notes out, and then take fees from any
1093    /// remaining refunds.
1094    ///
1095    /// This function prioritizes using the least number of notes possible, because that lowers the
1096    /// fee used.
1097    ///
1098    /// You may choose to override the fee with `fee_override`, but do note that `validate` will
1099    /// fail, in case this fee is too small.
1100    ///
1101    /// `include_lock_data` can be used to include `%lock` key in note-data, with the
1102    /// `SpendCondition` used. However, note-data costs 1 << 15 nicks, which means, it can get
1103    /// expensive.
1104    #[allow(clippy::too_many_arguments)]
1105    #[wasm_bindgen(js_name = simpleSpend)]
1106    pub fn simple_spend(
1107        &mut self,
1108        notes: Vec<WasmNote>,
1109        spend_conditions: Vec<WasmSpendCondition>,
1110        recipient: WasmDigest,
1111        gift: Nicks,
1112        fee_override: Option<Nicks>,
1113        refund_pkh: WasmDigest,
1114        include_lock_data: bool,
1115        memo: Option<JsValue>,
1116    ) -> Result<(), JsValue> {
1117        if notes.len() != spend_conditions.len() {
1118            return Err(JsValue::from_str(
1119                "notes and spend_conditions must have the same length",
1120            ));
1121        }
1122
1123        let internal_notes: Result<Vec<(Note, SpendCondition)>, String> = notes
1124            .iter()
1125            .zip(spend_conditions.iter())
1126            .map(|(n, sc)| Ok((n.to_internal()?, sc.to_internal()?)))
1127            .collect();
1128        let internal_notes = internal_notes.map_err(|e| JsValue::from_str(&e.to_string()))?;
1129        let memo = memo_from_js(memo)?;
1130
1131        self.builder
1132            .simple_spend_base(
1133                internal_notes,
1134                recipient.to_internal()?,
1135                gift,
1136                refund_pkh.to_internal()?,
1137                include_lock_data,
1138                memo,
1139            )
1140            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1141
1142        if let Some(fee) = fee_override {
1143            self.builder
1144                .set_fee_and_balance_refund(fee, false, include_lock_data)
1145        } else {
1146            self.builder.recalc_and_set_fee(include_lock_data)
1147        }
1148        .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1149
1150        Ok(())
1151    }
1152
1153    /// Append a `SpendBuilder` to this transaction
1154    pub fn spend(&mut self, spend: WasmSpendBuilder) -> Option<WasmSpendBuilder> {
1155        self.builder.spend(spend.into()).map(|v| v.into())
1156    }
1157
1158    /// Distributes `fee` across builder's spends, and balances refunds out
1159    ///
1160    /// `adjust_fee` parameter allows the fee to be slightly tweaked, whenever notes are added or
1161    /// removed to/from the builder's fee note pool. This is because using more or less notes
1162    /// impacts the exact fee being required. If the caller estimates fee and sets it, adding more
1163    /// notes will change the exact fee needed, and setting this parameter to true will allow one
1164    /// to not have to call this function multiple times.
1165    #[wasm_bindgen(js_name = setFeeAndBalanceRefund)]
1166    pub fn set_fee_and_balance_refund(
1167        &mut self,
1168        fee: Nicks,
1169        adjust_fee: bool,
1170        include_lock_data: bool,
1171    ) -> Result<(), JsValue> {
1172        self.builder
1173            .set_fee_and_balance_refund(fee, adjust_fee, include_lock_data)
1174            .map_err(|e| e.to_string())?;
1175        Ok(())
1176    }
1177
1178    /// Recalculate fee and set it, balancing things out with refunds
1179    #[wasm_bindgen(js_name = recalcAndSetFee)]
1180    pub fn recalc_and_set_fee(&mut self, include_lock_data: bool) -> Result<(), JsValue> {
1181        self.builder
1182            .recalc_and_set_fee(include_lock_data)
1183            .map_err(|e| e.to_string())?;
1184        Ok(())
1185    }
1186
1187    /// Appends `preimage_jam` to all spend conditions that expect this preimage.
1188    #[wasm_bindgen(js_name = addPreimage)]
1189    pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<WasmDigest>, JsValue> {
1190        let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
1191        Ok(self
1192            .builder
1193            .add_preimage(preimage)
1194            .map(|v| WasmDigest::from_internal(&v)))
1195    }
1196
1197    /// Sign the transaction with a private key.
1198    ///
1199    /// This will sign all spends that are still missing signature from
1200    #[wasm_bindgen]
1201    pub fn sign(&mut self, signing_key_bytes: &[u8]) -> Result<(), JsValue> {
1202        if signing_key_bytes.len() != 32 {
1203            return Err(JsValue::from_str("Private key must be 32 bytes"));
1204        }
1205        let signing_key = PrivateKey(UBig::from_be_bytes(signing_key_bytes));
1206
1207        self.builder.sign(&signing_key);
1208
1209        Ok(())
1210    }
1211
1212    /// Validate the transaction.
1213    #[wasm_bindgen]
1214    pub fn validate(&mut self) -> Result<(), JsValue> {
1215        self.builder
1216            .validate()
1217            .map_err(|v| JsValue::from_str(&v.to_string()))?;
1218
1219        Ok(())
1220    }
1221
1222    /// Gets the current fee set on all spends.
1223    #[wasm_bindgen(js_name = curFee)]
1224    pub fn cur_fee(&self) -> Nicks {
1225        self.builder.cur_fee()
1226    }
1227
1228    /// Calculates the fee needed for the transaction.
1229    ///
1230    /// NOTE: if the transaction is unsigned, this function will estimate the fee needed, supposing
1231    /// all signatures are added. However, this heuristic is only accurate for one signature. In
1232    /// addition, this fee calculation does not estimate the size of missing preimages.
1233    ///
1234    /// So, first, add missing preimages, and only then calc the fee. If you're building a multisig
1235    /// transaction, this value might be incorrect.
1236    #[wasm_bindgen(js_name = calcFee)]
1237    pub fn calc_fee(&self) -> Nicks {
1238        self.builder.calc_fee()
1239    }
1240
1241    #[wasm_bindgen(js_name = allNotes)]
1242    pub fn all_notes(&self) -> WasmTxNotes {
1243        let mut ret = WasmTxNotes {
1244            notes: vec![],
1245            spend_conditions: vec![],
1246        };
1247        self.builder
1248            .all_notes()
1249            .into_values()
1250            .for_each(|(note, spend_condition)| {
1251                ret.notes.push(WasmNote::from_internal(note));
1252                ret.spend_conditions
1253                    .push(WasmSpendCondition::from_internal(spend_condition));
1254            });
1255        ret
1256    }
1257
1258    #[wasm_bindgen]
1259    pub fn build(&self) -> Result<WasmNockchainTx, JsValue> {
1260        let tx = self.builder.build();
1261        Ok(WasmNockchainTx::from_internal(&tx))
1262    }
1263
1264    #[wasm_bindgen(js_name = allSpends)]
1265    pub fn all_spends(&self) -> Vec<WasmSpendBuilder> {
1266        self.builder
1267            .all_spends()
1268            .values()
1269            .map(WasmSpendBuilder::from_internal)
1270            .collect()
1271    }
1272}
1273
1274#[wasm_bindgen(js_name = TxNotes)]
1275pub struct WasmTxNotes {
1276    #[wasm_bindgen(skip)]
1277    pub notes: Vec<WasmNote>,
1278    #[wasm_bindgen(skip)]
1279    pub spend_conditions: Vec<WasmSpendCondition>,
1280}
1281
1282#[wasm_bindgen(js_class = TxNotes)]
1283impl WasmTxNotes {
1284    #[wasm_bindgen(getter)]
1285    pub fn notes(&self) -> Vec<WasmNote> {
1286        self.notes.clone()
1287    }
1288
1289    #[wasm_bindgen(getter, js_name = spendConditions)]
1290    pub fn spend_conditions(&self) -> Vec<WasmSpendCondition> {
1291        self.spend_conditions.clone()
1292    }
1293}
1294
1295// ============================================================================
1296// Wasm Spend Builder
1297// ============================================================================
1298
1299#[wasm_bindgen(js_name = SpendBuilder)]
1300pub struct WasmSpendBuilder {
1301    builder: SpendBuilder,
1302}
1303
1304#[wasm_bindgen(js_class = SpendBuilder)]
1305impl WasmSpendBuilder {
1306    /// Create a new `SpendBuilder` with a given note and spend condition
1307    #[wasm_bindgen(constructor)]
1308    pub fn new(
1309        note: WasmNote,
1310        spend_condition: WasmSpendCondition,
1311        refund_lock: Option<WasmSpendCondition>,
1312    ) -> Result<Self, JsValue> {
1313        Ok(Self {
1314            builder: SpendBuilder::new(
1315                note.to_internal()?,
1316                spend_condition.to_internal()?,
1317                refund_lock.map(|v| v.to_internal()).transpose()?,
1318            ),
1319        })
1320    }
1321
1322    /// Set the fee of this spend
1323    pub fn fee(&mut self, fee: Nicks) {
1324        self.builder.fee(fee);
1325    }
1326
1327    /// Compute refund from any spare assets, given `refund_lock` was passed
1328    #[wasm_bindgen(js_name = computeRefund)]
1329    pub fn compute_refund(&mut self, include_lock_data: bool) {
1330        self.builder.compute_refund(include_lock_data);
1331    }
1332
1333    /// Get current refund
1334    #[wasm_bindgen(js_name = curRefund)]
1335    pub fn cur_refund(&self) -> Option<WasmSeed> {
1336        self.builder.cur_refund().map(|v| WasmSeed::from(v.clone()))
1337    }
1338
1339    /// Checks whether note.assets = seeds + fee
1340    ///
1341    /// This function needs to return true for `TxBuilder::validate` to pass
1342    #[wasm_bindgen(js_name = isBalanced)]
1343    pub fn is_balanced(&self) -> bool {
1344        self.builder.is_balanced()
1345    }
1346
1347    /// Add seed to this spend
1348    ///
1349    /// Seed is an output with a recipient (as defined by the spend condition).
1350    ///
1351    /// Nockchain transaction engine will take all seeds with matching lock from all spends in the
1352    /// transaction, and merge them into one output note.
1353    pub fn seed(&mut self, seed: WasmSeed) -> Result<(), JsValue> {
1354        self.builder.seed(seed.to_internal()?);
1355        Ok(())
1356    }
1357
1358    /// Manually invalidate signatures
1359    ///
1360    /// Each spend's fee+seeds are bound to one or more signatures. If they get changed, the
1361    /// signature becomes invalid. This builder automatically invalidates signatures upon relevant
1362    /// modifications, but this functionality is provided nonetheless.
1363    #[wasm_bindgen(js_name = invalidateSigs)]
1364    pub fn invalidate_sigs(&mut self) {
1365        self.builder.invalidate_sigs();
1366    }
1367
1368    /// Get the list of missing "unlocks"
1369    ///
1370    /// An unlock is a spend condition to be satisfied. For instance, for a `Pkh` spend condition,
1371    /// if the transaction is unsigned, this function will return a Pkh type missing unlock, with
1372    /// the list of valid PKH's and number of signatures needed. This will not return PKHs that are
1373    /// already attatched to the spend (relevant for multisigs). For `Hax` spend condition, this
1374    /// will return any missing preimages. This function will return a list of not-yet-validated
1375    /// spend conditions.
1376    #[wasm_bindgen(js_name = missingUnlocks)]
1377    pub fn missing_unlocks(&self) -> Result<Vec<JsValue>, JsValue> {
1378        self.builder
1379            .missing_unlocks()
1380            .into_iter()
1381            .map(|v| serde_wasm_bindgen::to_value(&WasmMissingUnlocks::from_internal(&v)))
1382            .collect::<Result<Vec<_>, _>>()
1383            .map_err(|e| e.into())
1384    }
1385
1386    /// Attatch a preimage to this spend
1387    #[wasm_bindgen(js_name = addPreimage)]
1388    pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<WasmDigest>, JsValue> {
1389        let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
1390        Ok(self
1391            .builder
1392            .add_preimage(preimage)
1393            .map(|v| WasmDigest::from_internal(&v)))
1394    }
1395
1396    /// Sign the transaction with a given private key
1397    pub fn sign(&mut self, signing_key_bytes: &[u8]) -> Result<bool, JsValue> {
1398        if signing_key_bytes.len() != 32 {
1399            return Err(JsValue::from_str("Private key must be 32 bytes"));
1400        }
1401        let signing_key = PrivateKey(UBig::from_be_bytes(signing_key_bytes));
1402        Ok(self.builder.sign(&signing_key))
1403    }
1404
1405    fn from_internal(internal: &SpendBuilder) -> Self {
1406        Self {
1407            builder: internal.clone(),
1408        }
1409    }
1410
1411    #[allow(unused)]
1412    fn to_internal(&self) -> SpendBuilder {
1413        self.builder.clone()
1414    }
1415}
1416
1417impl From<SpendBuilder> for WasmSpendBuilder {
1418    fn from(builder: SpendBuilder) -> Self {
1419        Self { builder }
1420    }
1421}
1422
1423impl From<WasmSpendBuilder> for SpendBuilder {
1424    fn from(value: WasmSpendBuilder) -> Self {
1425        value.builder
1426    }
1427}
1428
1429#[derive(Serialize, Deserialize)]
1430pub enum WasmMissingUnlocks {
1431    Pkh {
1432        num_sigs: u64,
1433        sig_of: BTreeSet<String>,
1434    },
1435    Hax {
1436        preimages_for: BTreeSet<String>,
1437    },
1438    Brn,
1439}
1440
1441impl WasmMissingUnlocks {
1442    fn from_internal(internal: &MissingUnlocks) -> Self {
1443        match internal {
1444            MissingUnlocks::Pkh { num_sigs, sig_of } => Self::Pkh {
1445                num_sigs: *num_sigs,
1446                sig_of: sig_of
1447                    .iter()
1448                    .map(|v| WasmDigest::from_internal(v).value)
1449                    .collect(),
1450            },
1451            MissingUnlocks::Hax { preimages_for } => Self::Hax {
1452                preimages_for: preimages_for
1453                    .iter()
1454                    .map(|v| WasmDigest::from_internal(v).value)
1455                    .collect(),
1456            },
1457            MissingUnlocks::Brn => Self::Brn,
1458        }
1459    }
1460}
1461
1462// ============================================================================
1463// Wasm Raw Transaction
1464// ============================================================================
1465
1466#[wasm_bindgen(js_name = RawTx)]
1467pub struct WasmRawTx {
1468    // Store the full RawTx internally so we can convert to protobuf
1469    #[wasm_bindgen(skip)]
1470    pub(crate) internal: RawTx,
1471}
1472
1473#[wasm_bindgen(js_class = RawTx)]
1474impl WasmRawTx {
1475    #[wasm_bindgen(getter)]
1476    pub fn version(&self) -> WasmVersion {
1477        WasmVersion::from_internal(&self.internal.version)
1478    }
1479
1480    #[wasm_bindgen(getter)]
1481    pub fn id(&self) -> WasmDigest {
1482        WasmDigest::from_internal(&self.internal.id)
1483    }
1484
1485    #[wasm_bindgen(getter)]
1486    pub fn name(&self) -> String {
1487        self.internal.id.to_string()
1488    }
1489
1490    fn from_internal(tx: &RawTx) -> Self {
1491        Self {
1492            internal: tx.clone(),
1493        }
1494    }
1495
1496    /// Convert to protobuf RawTransaction for sending via gRPC
1497    #[wasm_bindgen(js_name = toProtobuf)]
1498    pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
1499        let pb_tx = pb::RawTransaction::from(self.internal.clone());
1500        serde_wasm_bindgen::to_value(&pb_tx)
1501            .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
1502    }
1503
1504    #[wasm_bindgen(js_name = fromProtobuf)]
1505    pub fn from_protobuf(value: JsValue) -> Result<WasmRawTx, JsValue> {
1506        let pb: pb::RawTransaction = serde_wasm_bindgen::from_value(value)?;
1507        let tx: RawTx = pb
1508            .try_into()
1509            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1510        //web_sys::console::log_1(&JsValue::from_str(&format!("{tx:?}")));
1511        Ok(WasmRawTx::from_internal(&tx))
1512    }
1513
1514    /// Convert to jammed transaction file for inspecting through CLI
1515    #[wasm_bindgen(js_name = toJam)]
1516    pub fn to_jam(&self) -> js_sys::Uint8Array {
1517        let n = self.internal.to_noun();
1518        js_sys::Uint8Array::from(&jam(n)[..])
1519    }
1520
1521    #[wasm_bindgen(js_name = fromJam)]
1522    pub fn from_jam(jam: &[u8]) -> Result<Self, JsValue> {
1523        let n = cue(jam).ok_or("Unable to decode jam")?;
1524        let tx: RawTx = NounDecode::from_noun(&n).ok_or("Unable to decode noun")?;
1525        Ok(Self::from_internal(&tx))
1526    }
1527
1528    /// Calculate output notes from the transaction spends.
1529    #[wasm_bindgen]
1530    pub fn outputs(&self) -> Vec<WasmNote> {
1531        self.internal
1532            .outputs()
1533            .into_iter()
1534            .map(WasmNote::from_internal)
1535            .collect()
1536    }
1537
1538    #[wasm_bindgen(js_name = toNockchainTx)]
1539    pub fn to_nockchain_tx(&self) -> WasmNockchainTx {
1540        WasmNockchainTx::from_internal(&self.internal.to_nockchain_tx())
1541    }
1542
1543    /// Sign all spends in this raw transaction with a private key.
1544    ///
1545    /// This is useful for legacy (v0) spends, where the spend kind carries a Signature rather than
1546    /// a Witness.
1547    #[wasm_bindgen(js_name = signAll)]
1548    pub fn sign_all(&mut self, signing_key_bytes: &[u8]) -> Result<(), JsValue> {
1549        if signing_key_bytes.len() != 32 {
1550            return Err(JsValue::from_str("Private key must be 32 bytes"));
1551        }
1552        let signing_key = PrivateKey(UBig::from_be_bytes(signing_key_bytes));
1553        self.internal.sign_all(&signing_key);
1554        Ok(())
1555    }
1556}
1557
1558#[wasm_bindgen(js_name = NockchainTx)]
1559pub struct WasmNockchainTx {
1560    #[wasm_bindgen(skip)]
1561    pub(crate) internal: NockchainTx,
1562}
1563
1564#[wasm_bindgen(js_class = NockchainTx)]
1565impl WasmNockchainTx {
1566    #[wasm_bindgen(getter)]
1567    pub fn version(&self) -> WasmVersion {
1568        WasmVersion::from_internal(&self.internal.version)
1569    }
1570
1571    #[wasm_bindgen(getter)]
1572    pub fn id(&self) -> WasmDigest {
1573        WasmDigest::from_internal(&self.internal.id)
1574    }
1575
1576    #[wasm_bindgen(getter)]
1577    pub fn name(&self) -> String {
1578        self.internal.id.to_string()
1579    }
1580
1581    fn from_internal(tx: &NockchainTx) -> Self {
1582        Self {
1583            internal: tx.clone(),
1584        }
1585    }
1586
1587    /// Convert to jammed transaction file for inspecting through CLI
1588    #[wasm_bindgen(js_name = toJam)]
1589    pub fn to_jam(&self) -> js_sys::Uint8Array {
1590        let n = self.internal.to_noun();
1591        js_sys::Uint8Array::from(&jam(n)[..])
1592    }
1593
1594    /// Convert from CLI-compatible jammed transaction file
1595    #[wasm_bindgen(js_name = fromJam)]
1596    pub fn from_jam(jam: &[u8]) -> Result<Self, JsValue> {
1597        let n = cue(jam).ok_or("Unable to decode jam")?;
1598        let tx: NockchainTx = NounDecode::from_noun(&n).ok_or("Unable to decode noun")?;
1599        Ok(Self::from_internal(&tx))
1600    }
1601
1602    #[wasm_bindgen]
1603    pub fn outputs(&self) -> Vec<WasmNote> {
1604        self.internal
1605            .outputs()
1606            .into_iter()
1607            .map(WasmNote::from_internal)
1608            .collect()
1609    }
1610
1611    #[wasm_bindgen(js_name = toRawTx)]
1612    pub fn to_raw_tx(&self) -> WasmRawTx {
1613        WasmRawTx::from_internal(&self.internal.to_raw_tx())
1614    }
1615}