1use std::collections::{BTreeMap, BTreeSet};
2
3use alloc::format;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use ibig::UBig;
7use iris_crypto::PrivateKey;
8use iris_grpc_proto::pb::common::v1 as pb_v1;
9use iris_grpc_proto::pb::common::v2 as pb;
10use iris_nockchain_types::{
11 builder::TxBuilder,
12 note::{Name, Note, NoteData, NoteDataEntry, Pkh, TimelockRange, Version},
13 tx::{LockPrimitive, RawTx, Seed, SpendCondition},
14 Nicks,
15};
16use iris_nockchain_types::{Hax, LockTim, MissingUnlocks, Source, SpendBuilder, Spends};
17use iris_ztd::{cue, jam, Digest, Hashable as HashableTrait, NounDecode};
18use serde::{Deserialize, Serialize};
19use wasm_bindgen::prelude::*;
20
21#[wasm_bindgen(js_name = Digest)]
26#[derive(Clone, Serialize, Deserialize)]
27#[serde(transparent)]
28pub struct WasmDigest {
29 #[wasm_bindgen(skip)]
30 pub value: String,
31}
32
33#[wasm_bindgen(js_class = Digest)]
34impl WasmDigest {
35 #[wasm_bindgen(constructor)]
36 pub fn new(value: String) -> Self {
37 Self { value }
38 }
39
40 #[wasm_bindgen(getter)]
41 pub fn value(&self) -> String {
42 self.value.clone()
43 }
44
45 fn to_internal(&self) -> Result<Digest, &'static str> {
46 self.value.as_str().try_into()
47 }
48
49 fn from_internal(digest: &Digest) -> Self {
50 Self {
51 value: digest.to_string(),
52 }
53 }
54
55 #[wasm_bindgen(js_name = toProtobuf)]
56 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
57 let digest = self.to_internal().map_err(JsValue::from_str)?;
58 let pb = pb_v1::Hash::from(digest);
59 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
60 }
61
62 #[wasm_bindgen(js_name = fromProtobuf)]
63 pub fn from_protobuf(value: JsValue) -> Result<WasmDigest, JsValue> {
64 let pb: pb_v1::Hash = serde_wasm_bindgen::from_value(value)?;
65 let digest: Digest = pb
66 .try_into()
67 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
68 Ok(WasmDigest::from_internal(&digest))
69 }
70}
71
72#[wasm_bindgen(js_name = Version)]
73#[derive(Clone, Serialize, Deserialize)]
74pub struct WasmVersion {
75 version: u32,
76}
77
78#[wasm_bindgen(js_class = Version)]
79impl WasmVersion {
80 #[wasm_bindgen(constructor)]
81 pub fn new(version: u32) -> Self {
82 Self { version }
83 }
84
85 #[wasm_bindgen(js_name = V0)]
86 pub fn v0() -> Self {
87 Self { version: 0 }
88 }
89
90 #[wasm_bindgen(js_name = V1)]
91 pub fn v1() -> Self {
92 Self { version: 1 }
93 }
94
95 #[wasm_bindgen(js_name = V2)]
96 pub fn v2() -> Self {
97 Self { version: 2 }
98 }
99
100 fn to_internal(&self) -> Version {
101 self.version.into()
102 }
103
104 fn from_internal(version: &Version) -> Self {
105 Self {
106 version: version.clone().into(),
107 }
108 }
109}
110
111#[wasm_bindgen(js_name = Name)]
112#[derive(Clone, Serialize, Deserialize)]
113pub struct WasmName {
114 #[wasm_bindgen(skip)]
115 pub first: Digest,
116 #[wasm_bindgen(skip)]
117 pub last: Digest,
118}
119
120#[wasm_bindgen(js_class = Name)]
121impl WasmName {
122 #[wasm_bindgen(constructor)]
123 pub fn new(first: String, last: String) -> Result<Self, JsValue> {
124 let first = Digest::try_from(&*first)?;
125 let last = Digest::try_from(&*last)?;
126 Ok(Self { first, last })
127 }
128
129 #[wasm_bindgen(getter)]
130 pub fn first(&self) -> String {
131 self.first.to_string()
132 }
133
134 #[wasm_bindgen(getter)]
135 pub fn last(&self) -> String {
136 self.last.to_string()
137 }
138
139 fn to_internal(&self) -> Name {
140 Name::new(self.first, self.last)
141 }
142
143 #[allow(dead_code)]
144 fn from_internal(name: &Name) -> Self {
145 Self {
148 first: name.first,
149 last: name.last,
150 }
151 }
152
153 #[wasm_bindgen(js_name = toProtobuf)]
154 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
155 let name = self.to_internal();
156 let pb = pb_v1::Name::from(name);
157 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
158 }
159
160 #[wasm_bindgen(js_name = fromProtobuf)]
161 pub fn from_protobuf(value: JsValue) -> Result<WasmName, JsValue> {
162 let pb: pb_v1::Name = serde_wasm_bindgen::from_value(value)?;
163 let name: Name = pb
164 .try_into()
165 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
166 Ok(WasmName::from_internal(&name))
167 }
168}
169
170#[wasm_bindgen(js_name = TimelockRange)]
171#[derive(Clone, Serialize, Deserialize)]
172pub struct WasmTimelockRange {
173 #[wasm_bindgen(skip)]
174 pub min: Option<u64>,
175 #[wasm_bindgen(skip)]
176 pub max: Option<u64>,
177}
178
179#[wasm_bindgen(js_class = TimelockRange)]
180impl WasmTimelockRange {
181 #[wasm_bindgen(constructor)]
182 pub fn new(min: Option<u64>, max: Option<u64>) -> Self {
183 Self { min, max }
184 }
185
186 #[wasm_bindgen(getter)]
187 pub fn min(&self) -> Option<u64> {
188 self.min
189 }
190
191 #[wasm_bindgen(getter)]
192 pub fn max(&self) -> Option<u64> {
193 self.max
194 }
195
196 fn to_internal(&self) -> TimelockRange {
197 TimelockRange::new(self.min, self.max)
198 }
199
200 fn from_internal(internal: TimelockRange) -> WasmTimelockRange {
201 WasmTimelockRange {
202 min: internal.min,
203 max: internal.max,
204 }
205 }
206}
207
208#[wasm_bindgen(js_name = Source)]
209#[derive(Clone, Serialize, Deserialize)]
210pub struct WasmSource {
211 #[wasm_bindgen(skip)]
212 pub hash: WasmDigest,
213 #[wasm_bindgen(skip)]
214 pub is_coinbase: bool,
215}
216
217#[wasm_bindgen(js_class = Source)]
218impl WasmSource {
219 #[wasm_bindgen(getter, js_name = hash)]
220 pub fn hash(&self) -> WasmDigest {
221 self.hash.clone()
222 }
223
224 #[wasm_bindgen(getter, js_name = isCoinbase)]
225 pub fn is_coinbase(&self) -> bool {
226 self.is_coinbase
227 }
228
229 fn to_internal(&self) -> Result<Source, String> {
230 Ok(Source {
231 hash: self.hash.to_internal()?,
232 is_coinbase: self.is_coinbase,
233 })
234 }
235
236 fn from_internal(internal: &Source) -> Self {
237 Self {
238 hash: WasmDigest::from_internal(&internal.hash),
239 is_coinbase: internal.is_coinbase,
240 }
241 }
242}
243
244#[wasm_bindgen(js_name = NoteDataEntry)]
249#[derive(Clone, Serialize, Deserialize)]
250pub struct WasmNoteDataEntry {
251 #[wasm_bindgen(skip)]
252 pub key: String,
253 #[wasm_bindgen(skip)]
254 pub blob: Vec<u8>,
255}
256
257#[wasm_bindgen(js_class = NoteDataEntry)]
258impl WasmNoteDataEntry {
259 #[wasm_bindgen(constructor)]
260 pub fn new(key: String, blob: Vec<u8>) -> Self {
261 Self { key, blob }
262 }
263
264 #[wasm_bindgen(getter)]
265 pub fn key(&self) -> String {
266 self.key.clone()
267 }
268
269 #[wasm_bindgen(getter)]
270 pub fn blob(&self) -> Vec<u8> {
271 self.blob.clone()
272 }
273
274 fn to_internal(&self) -> Result<NoteDataEntry, String> {
275 let val = cue(&self.blob).ok_or_else(|| "Failed to deserialize noun".to_string())?;
276 Ok(NoteDataEntry {
277 key: self.key.clone(),
278 val,
279 })
280 }
281
282 fn from_internal(entry: &NoteDataEntry) -> Self {
283 Self {
284 key: entry.key.clone(),
285 blob: jam(entry.val.clone()),
286 }
287 }
288
289 #[wasm_bindgen(js_name = toProtobuf)]
290 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
291 let entry = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
292 let pb = pb::NoteDataEntry::from(entry);
293 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
294 }
295
296 #[wasm_bindgen(js_name = fromProtobuf)]
297 pub fn from_protobuf(value: JsValue) -> Result<WasmNoteDataEntry, JsValue> {
298 let pb: pb::NoteDataEntry = serde_wasm_bindgen::from_value(value)?;
299 let entry: NoteDataEntry = pb
300 .try_into()
301 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
302 Ok(WasmNoteDataEntry::from_internal(&entry))
303 }
304}
305
306#[wasm_bindgen(js_name = NoteData)]
307#[derive(Clone, Serialize, Deserialize)]
308pub struct WasmNoteData {
309 #[wasm_bindgen(skip)]
310 pub entries: Vec<WasmNoteDataEntry>,
311}
312
313#[wasm_bindgen(js_class = NoteData)]
314impl WasmNoteData {
315 #[wasm_bindgen(constructor)]
316 pub fn new(entries: Vec<WasmNoteDataEntry>) -> Self {
317 Self { entries }
318 }
319
320 #[wasm_bindgen]
321 pub fn empty() -> Self {
322 Self {
323 entries: Vec::new(),
324 }
325 }
326
327 #[wasm_bindgen(js_name = fromPkh)]
328 pub fn from_pkh(pkh: WasmPkh) -> Result<Self, JsValue> {
329 let note_data = NoteData::from_pkh(pkh.to_internal()?);
330 Ok(Self::from_internal(¬e_data))
331 }
332
333 #[wasm_bindgen(getter)]
334 pub fn entries(&self) -> Vec<WasmNoteDataEntry> {
335 self.entries.clone()
336 }
337
338 fn to_internal(&self) -> Result<NoteData, String> {
339 let entries: Result<Vec<NoteDataEntry>, String> =
340 self.entries.iter().map(|e| e.to_internal()).collect();
341 Ok(NoteData(entries?))
342 }
343
344 fn from_internal(note_data: &NoteData) -> Self {
345 Self {
346 entries: note_data
347 .0
348 .iter()
349 .map(WasmNoteDataEntry::from_internal)
350 .collect(),
351 }
352 }
353
354 #[wasm_bindgen(js_name = toProtobuf)]
355 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
356 let data = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
357 let pb = pb::NoteData::from(data);
358 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
359 }
360
361 #[wasm_bindgen(js_name = fromProtobuf)]
362 pub fn from_protobuf(value: JsValue) -> Result<WasmNoteData, JsValue> {
363 let pb: pb::NoteData = serde_wasm_bindgen::from_value(value)?;
364 let data: NoteData = pb
365 .try_into()
366 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
367 Ok(WasmNoteData::from_internal(&data))
368 }
369}
370
371#[wasm_bindgen(js_name = Note)]
372#[derive(Clone, Serialize, Deserialize)]
373pub struct WasmNote {
374 #[wasm_bindgen(skip)]
375 pub version: WasmVersion,
376 #[wasm_bindgen(skip)]
377 pub origin_page: u64,
378 #[wasm_bindgen(skip)]
379 pub name: WasmName,
380 #[wasm_bindgen(skip)]
381 pub note_data: WasmNoteData,
382 #[wasm_bindgen(skip)]
383 pub assets: Nicks,
384}
385
386#[wasm_bindgen(js_class = Note)]
387impl WasmNote {
388 #[wasm_bindgen(constructor)]
389 pub fn new(
390 version: WasmVersion,
391 origin_page: u64,
392 name: WasmName,
393 note_data: WasmNoteData,
394 assets: Nicks,
395 ) -> Self {
396 Self {
397 version,
398 origin_page,
399 name,
400 note_data,
401 assets,
402 }
403 }
404
405 #[wasm_bindgen(getter)]
406 pub fn version(&self) -> WasmVersion {
407 self.version.clone()
408 }
409
410 #[wasm_bindgen(getter, js_name = originPage)]
411 pub fn origin_page(&self) -> u64 {
412 self.origin_page
413 }
414
415 #[wasm_bindgen(getter)]
416 pub fn name(&self) -> WasmName {
417 self.name.clone()
418 }
419
420 #[wasm_bindgen(getter, js_name = noteData)]
421 pub fn note_data(&self) -> WasmNoteData {
422 self.note_data.clone()
423 }
424
425 #[wasm_bindgen(getter)]
426 pub fn assets(&self) -> Nicks {
427 self.assets
428 }
429
430 #[wasm_bindgen]
431 pub fn hash(&self) -> Result<WasmDigest, JsValue> {
432 let note = self
433 .to_internal()
434 .map_err(|e| JsValue::from_str(&e.to_string()))?;
435 Ok(WasmDigest::from_internal(¬e.hash()))
436 }
437
438 #[wasm_bindgen(js_name = fromProtobuf)]
441 pub fn from_protobuf(pb_note: JsValue) -> Result<WasmNote, JsValue> {
442 let pb: pb::Note = serde_wasm_bindgen::from_value(pb_note)?;
443 let note: Note = pb
444 .try_into()
445 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
446 Ok(WasmNote::from_internal(note))
447 }
448
449 #[wasm_bindgen(js_name = toProtobuf)]
450 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
451 let note = self
452 .to_internal()
453 .map_err(|e| JsValue::from_str(&e.to_string()))?;
454 let pb = pb::Note::from(note);
455 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
456 }
457
458 fn to_internal(&self) -> Result<Note, String> {
459 Ok(Note::new(
460 self.version.to_internal(),
461 self.origin_page,
462 self.name.to_internal(),
463 self.note_data.to_internal()?,
464 self.assets,
465 ))
466 }
467
468 fn from_internal(internal: Note) -> Self {
469 Self {
470 version: WasmVersion::from_internal(&internal.version),
471 origin_page: internal.origin_page,
472 name: WasmName::from_internal(&internal.name),
473 note_data: WasmNoteData::from_internal(&internal.note_data),
474 assets: internal.assets,
475 }
476 }
477}
478
479#[wasm_bindgen(js_name = Pkh)]
484#[derive(Clone, Serialize, Deserialize)]
485pub struct WasmPkh {
486 #[wasm_bindgen(skip)]
487 pub m: u64,
488 #[wasm_bindgen(skip)]
489 pub hashes: Vec<String>,
490}
491
492#[wasm_bindgen(js_class = Pkh)]
493impl WasmPkh {
494 #[wasm_bindgen(constructor)]
495 pub fn new(m: u64, hashes: Vec<String>) -> Self {
496 Self { m, hashes }
497 }
498
499 #[wasm_bindgen]
500 pub fn single(hash: String) -> Self {
501 Self {
502 m: 1,
503 hashes: alloc::vec![hash],
504 }
505 }
506
507 #[wasm_bindgen(getter)]
508 pub fn m(&self) -> u64 {
509 self.m
510 }
511
512 #[wasm_bindgen(getter)]
513 pub fn hashes(&self) -> Vec<String> {
514 self.hashes.clone()
515 }
516
517 fn to_internal(&self) -> Result<Pkh, String> {
518 let hashes: Result<Vec<Digest>, _> =
519 self.hashes.iter().map(|s| s.as_str().try_into()).collect();
520 Ok(Pkh::new(self.m, hashes?))
521 }
522
523 fn from_internal(internal: Pkh) -> Self {
524 Self::new(
525 internal.m,
526 internal.hashes.into_iter().map(|v| v.to_string()).collect(),
527 )
528 }
529
530 #[wasm_bindgen(js_name = toProtobuf)]
531 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
532 let pkh = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
533 let pb = pb::PkhLock::from(pkh);
534 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
535 }
536
537 #[wasm_bindgen(js_name = fromProtobuf)]
538 pub fn from_protobuf(value: JsValue) -> Result<WasmPkh, JsValue> {
539 let pb: pb::PkhLock = serde_wasm_bindgen::from_value(value)?;
540 let pkh: Pkh = pb
541 .try_into()
542 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
543 Ok(WasmPkh::from_internal(pkh))
544 }
545}
546
547#[wasm_bindgen(js_name = LockTim)]
548#[derive(Clone, Serialize, Deserialize)]
549pub struct WasmLockTim {
550 #[wasm_bindgen(skip)]
551 pub rel: WasmTimelockRange,
552 #[wasm_bindgen(skip)]
553 pub abs: WasmTimelockRange,
554}
555
556#[wasm_bindgen(js_class = LockTim)]
557impl WasmLockTim {
558 #[wasm_bindgen(constructor)]
559 pub fn new(rel: WasmTimelockRange, abs: WasmTimelockRange) -> Self {
560 Self { rel, abs }
561 }
562
563 #[wasm_bindgen]
564 pub fn coinbase() -> Self {
565 let tim = LockTim::coinbase();
566 Self {
567 rel: WasmTimelockRange {
568 min: tim.rel.min,
569 max: tim.rel.max,
570 },
571 abs: WasmTimelockRange {
572 min: tim.abs.min,
573 max: tim.abs.max,
574 },
575 }
576 }
577
578 #[wasm_bindgen(getter)]
579 pub fn rel(&self) -> WasmTimelockRange {
580 self.rel.clone()
581 }
582
583 #[wasm_bindgen(getter)]
584 pub fn abs(&self) -> WasmTimelockRange {
585 self.abs.clone()
586 }
587
588 fn to_internal(&self) -> LockTim {
589 LockTim {
590 rel: self.rel.to_internal(),
591 abs: self.abs.to_internal(),
592 }
593 }
594
595 fn from_internal(internal: LockTim) -> WasmLockTim {
596 WasmLockTim {
597 rel: WasmTimelockRange::from_internal(internal.rel),
598 abs: WasmTimelockRange::from_internal(internal.abs),
599 }
600 }
601
602 #[wasm_bindgen(js_name = toProtobuf)]
603 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
604 let tim = self.to_internal();
605 let pb = pb::LockTim::from(tim);
606 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
607 }
608
609 #[wasm_bindgen(js_name = fromProtobuf)]
610 pub fn from_protobuf(value: JsValue) -> Result<WasmLockTim, JsValue> {
611 let pb: pb::LockTim = serde_wasm_bindgen::from_value(value)?;
612 let tim: LockTim = pb
613 .try_into()
614 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
615 Ok(WasmLockTim::from_internal(tim))
616 }
617}
618
619#[wasm_bindgen(js_name = Hax)]
620#[derive(Clone, Serialize, Deserialize)]
621pub struct WasmHax {
622 #[wasm_bindgen(skip)]
623 pub digests: Vec<WasmDigest>,
624}
625
626#[wasm_bindgen(js_class = Hax)]
627impl WasmHax {
628 #[wasm_bindgen(constructor)]
629 pub fn new(digests: Vec<WasmDigest>) -> Self {
630 Self { digests }
631 }
632
633 #[wasm_bindgen(getter)]
634 pub fn digests(&self) -> Vec<WasmDigest> {
635 self.digests.clone()
636 }
637
638 fn to_internal(&self) -> Result<Hax, String> {
639 Ok(Hax(self
640 .digests
641 .iter()
642 .map(WasmDigest::to_internal)
643 .collect::<Result<Vec<_>, _>>()?))
644 }
645
646 fn from_internal(internal: Hax) -> Self {
647 Self::new(internal.0.iter().map(WasmDigest::from_internal).collect())
648 }
649}
650
651#[wasm_bindgen(js_name = LockPrimitive)]
652#[derive(Clone, Serialize, Deserialize)]
653pub struct WasmLockPrimitive {
654 variant: String,
655 #[wasm_bindgen(skip)]
656 pub pkh_data: Option<WasmPkh>,
657 #[wasm_bindgen(skip)]
658 pub tim_data: Option<WasmLockTim>,
659 #[wasm_bindgen(skip)]
660 pub hax_data: Option<WasmHax>,
661}
662
663#[wasm_bindgen(js_class = LockPrimitive)]
664impl WasmLockPrimitive {
665 #[wasm_bindgen(js_name = newPkh)]
666 pub fn new_pkh(pkh: WasmPkh) -> WasmLockPrimitive {
667 Self {
668 variant: "pkh".to_string(),
669 pkh_data: Some(pkh),
670 tim_data: None,
671 hax_data: None,
672 }
673 }
674
675 #[wasm_bindgen(js_name = newTim)]
676 pub fn new_tim(tim: WasmLockTim) -> WasmLockPrimitive {
677 Self {
678 variant: "tim".to_string(),
679 pkh_data: None,
680 tim_data: Some(tim),
681 hax_data: None,
682 }
683 }
684
685 #[wasm_bindgen(js_name = newHax)]
686 pub fn new_hax(hax: WasmHax) -> Self {
687 Self {
688 variant: "hax".to_string(),
689 pkh_data: None,
690 tim_data: None,
691 hax_data: Some(hax),
692 }
693 }
694
695 #[wasm_bindgen(js_name = newBrn)]
696 pub fn new_brn() -> Self {
697 Self {
698 variant: "brn".to_string(),
699 pkh_data: None,
700 tim_data: None,
701 hax_data: None,
702 }
703 }
704
705 fn to_internal(&self) -> Result<LockPrimitive, String> {
706 match self.variant.as_str() {
707 "pkh" => {
708 if let Some(ref pkh) = self.pkh_data {
709 Ok(LockPrimitive::Pkh(pkh.to_internal()?))
710 } else {
711 Err("Missing pkh data".to_string())
712 }
713 }
714 "tim" => {
715 if let Some(ref tim) = self.tim_data {
716 Ok(LockPrimitive::Tim(tim.to_internal()))
717 } else {
718 Err("Missing tim data".to_string())
719 }
720 }
721 "hax" => {
722 if let Some(ref hax) = self.hax_data {
723 Ok(LockPrimitive::Hax(hax.to_internal()?))
724 } else {
725 Err("Missing hax data".to_string())
726 }
727 }
728 "brn" => Ok(LockPrimitive::Brn),
729 _ => Err("Invalid lock primitive variant".to_string()),
730 }
731 }
732
733 fn from_internal(internal: LockPrimitive) -> Self {
734 match internal {
735 LockPrimitive::Pkh(p) => Self::new_pkh(WasmPkh::from_internal(p)),
736 LockPrimitive::Tim(t) => Self::new_tim(WasmLockTim::from_internal(t)),
737 LockPrimitive::Hax(h) => Self::new_hax(WasmHax::from_internal(h)),
738 LockPrimitive::Brn => Self::new_brn(),
739 }
740 }
741
742 #[wasm_bindgen(js_name = toProtobuf)]
743 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
744 let prim = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
745 let pb = pb::LockPrimitive::from(prim);
746 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
747 }
748
749 #[wasm_bindgen(js_name = fromProtobuf)]
750 pub fn from_protobuf(value: JsValue) -> Result<WasmLockPrimitive, JsValue> {
751 let pb: pb::LockPrimitive = serde_wasm_bindgen::from_value(value)?;
752 let prim: LockPrimitive = pb
753 .try_into()
754 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
755 Ok(WasmLockPrimitive::from_internal(prim))
756 }
757}
758
759#[wasm_bindgen(js_name = SpendCondition)]
760#[derive(Clone, Serialize, Deserialize)]
761pub struct WasmSpendCondition {
762 #[wasm_bindgen(skip)]
763 pub primitives: Vec<WasmLockPrimitive>,
764}
765
766#[wasm_bindgen(js_class = SpendCondition)]
767impl WasmSpendCondition {
768 #[wasm_bindgen(constructor)]
769 pub fn new(primitives: Vec<WasmLockPrimitive>) -> Self {
770 Self { primitives }
771 }
772
773 #[wasm_bindgen(js_name = newPkh)]
774 pub fn new_pkh(pkh: WasmPkh) -> WasmSpendCondition {
775 let primitive = WasmLockPrimitive::new_pkh(pkh);
776 Self {
777 primitives: alloc::vec![primitive],
778 }
779 }
780
781 #[wasm_bindgen]
782 pub fn hash(&self) -> Result<WasmDigest, JsValue> {
783 let condition = self
784 .to_internal()
785 .map_err(|e| JsValue::from_str(&e.to_string()))?;
786 Ok(WasmDigest::from_internal(&condition.hash()))
787 }
788
789 #[wasm_bindgen(js_name = firstName)]
790 pub fn first_name(&self) -> Result<WasmDigest, JsValue> {
791 let condition = self
792 .to_internal()
793 .map_err(|e| JsValue::from_str(&e.to_string()))?;
794 Ok(WasmDigest::from_internal(&condition.first_name()))
795 }
796
797 fn to_internal(&self) -> Result<SpendCondition, String> {
798 let mut primitives = Vec::new();
799 for prim in &self.primitives {
800 primitives.push(prim.to_internal()?);
801 }
802 Ok(SpendCondition(primitives))
803 }
804
805 fn from_internal(internal: SpendCondition) -> Self {
806 Self::new(
807 internal
808 .0
809 .into_iter()
810 .map(WasmLockPrimitive::from_internal)
811 .collect(),
812 )
813 }
814
815 #[wasm_bindgen(js_name = toProtobuf)]
816 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
817 let cond = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
818 let pb = pb::SpendCondition::from(cond);
819 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
820 }
821
822 #[wasm_bindgen(js_name = fromProtobuf)]
823 pub fn from_protobuf(value: JsValue) -> Result<WasmSpendCondition, JsValue> {
824 let pb: pb::SpendCondition = serde_wasm_bindgen::from_value(value)?;
825 let cond: SpendCondition = pb
826 .try_into()
827 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
828 Ok(WasmSpendCondition::from_internal(cond))
829 }
830}
831
832#[wasm_bindgen(js_name = Seed)]
833pub struct WasmSeed {
834 #[wasm_bindgen(skip)]
835 pub output_source: Option<WasmSource>,
836 #[wasm_bindgen(skip)]
837 pub lock_root: WasmDigest,
838 #[wasm_bindgen(skip)]
839 pub gift: Nicks,
840 #[wasm_bindgen(skip)]
841 pub note_data: WasmNoteData,
842 #[wasm_bindgen(skip)]
843 pub parent_hash: WasmDigest,
844}
845
846#[wasm_bindgen(js_class = Seed)]
847impl WasmSeed {
848 #[wasm_bindgen(constructor)]
849 pub fn new(
850 output_source: Option<WasmSource>,
851 lock_root: WasmDigest,
852 gift: Nicks,
853 note_data: WasmNoteData,
854 parent_hash: WasmDigest,
855 ) -> Self {
856 Self {
857 output_source,
858 lock_root,
859 gift,
860 note_data,
861 parent_hash,
862 }
863 }
864
865 #[wasm_bindgen(js_name = newSinglePkh)]
866 pub fn new_single_pkh(
867 pkh: WasmDigest,
868 gift: Nicks,
869 parent_hash: WasmDigest,
870 include_lock_data: bool,
871 ) -> Result<Self, JsValue> {
872 let seed = Seed::new_single_pkh(
873 pkh.to_internal()?,
874 gift,
875 parent_hash.to_internal()?,
876 include_lock_data,
877 );
878 Ok(seed.into())
879 }
880
881 #[wasm_bindgen(getter, js_name = outputSource)]
882 pub fn output_source(&self) -> Option<WasmSource> {
883 self.output_source.clone()
884 }
885
886 #[wasm_bindgen(setter, js_name = outputSource)]
887 pub fn set_output_source(&mut self, output_source: Option<WasmSource>) {
888 self.output_source = output_source;
889 }
890
891 #[wasm_bindgen(getter, js_name = lockRoot)]
892 pub fn lock_root(&self) -> WasmDigest {
893 self.lock_root.clone()
894 }
895
896 #[wasm_bindgen(setter, js_name = lockRoot)]
897 pub fn set_lock_root(&mut self, lock_root: WasmDigest) {
898 self.lock_root = lock_root;
899 }
900
901 #[wasm_bindgen(getter)]
902 pub fn gift(&self) -> Nicks {
903 self.gift
904 }
905
906 #[wasm_bindgen(setter)]
907 pub fn set_gift(&mut self, gift: Nicks) {
908 self.gift = gift;
909 }
910
911 #[wasm_bindgen(getter, js_name = noteData)]
912 pub fn note_data(&self) -> WasmNoteData {
913 self.note_data.clone()
914 }
915
916 #[wasm_bindgen(setter, js_name = noteData)]
917 pub fn set_note_data(&mut self, note_data: WasmNoteData) {
918 self.note_data = note_data;
919 }
920
921 #[wasm_bindgen(getter, js_name = parentHash)]
922 pub fn parent_hash(&self) -> WasmDigest {
923 self.parent_hash.clone()
924 }
925
926 #[wasm_bindgen(setter, js_name = parentHash)]
927 pub fn set_parent_hash(&mut self, parent_hash: WasmDigest) {
928 self.parent_hash = parent_hash;
929 }
930
931 fn to_internal(&self) -> Result<Seed, String> {
932 Ok(Seed {
933 output_source: self
934 .output_source
935 .as_ref()
936 .map(WasmSource::to_internal)
937 .transpose()?,
938 lock_root: self.lock_root.to_internal()?,
939 gift: self.gift,
940 note_data: self.note_data.to_internal()?,
941 parent_hash: self.parent_hash.to_internal()?,
942 })
943 }
944}
945
946impl From<Seed> for WasmSeed {
947 fn from(value: Seed) -> Self {
948 Self {
949 output_source: value.output_source.as_ref().map(WasmSource::from_internal),
950 lock_root: WasmDigest::from_internal(&value.lock_root),
951 gift: value.gift,
952 note_data: WasmNoteData::from_internal(&value.note_data),
953 parent_hash: WasmDigest::from_internal(&value.parent_hash),
954 }
955 }
956}
957
958#[wasm_bindgen]
959impl WasmSeed {
960 #[wasm_bindgen(js_name = toProtobuf)]
961 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
962 let seed = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
963 let pb = pb::Seed::from(seed);
964 serde_wasm_bindgen::to_value(&pb).map_err(|e| e.into())
965 }
966
967 fn from_internal(seed: Seed) -> Self {
968 seed.into()
969 }
970
971 #[wasm_bindgen(js_name = fromProtobuf)]
972 pub fn from_protobuf(value: JsValue) -> Result<WasmSeed, JsValue> {
973 let pb: pb::Seed = serde_wasm_bindgen::from_value(value)?;
974 let seed: Seed = pb
975 .try_into()
976 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
977 Ok(WasmSeed::from_internal(seed))
978 }
979}
980
981#[wasm_bindgen(js_name = TxBuilder)]
986pub struct WasmTxBuilder {
987 builder: TxBuilder,
988}
989
990#[wasm_bindgen(js_class = TxBuilder)]
991impl WasmTxBuilder {
992 #[wasm_bindgen(constructor)]
994 pub fn new(fee_per_word: Nicks) -> Self {
995 Self {
996 builder: TxBuilder::new(fee_per_word),
997 }
998 }
999
1000 #[wasm_bindgen(js_name = fromTx)]
1005 pub fn from_tx(
1006 tx: WasmRawTx,
1007 notes: Vec<WasmNote>,
1008 spend_conditions: Vec<WasmSpendCondition>,
1009 ) -> Result<Self, JsValue> {
1010 if notes.len() != spend_conditions.len() {
1011 return Err(JsValue::from_str(
1012 "notes and spend_conditions must have the same length",
1013 ));
1014 }
1015
1016 let internal_notes: Result<BTreeMap<Name, (Note, SpendCondition)>, String> = notes
1017 .iter()
1018 .zip(spend_conditions.iter())
1019 .map(|(n, sc)| Ok((n.to_internal()?, sc.to_internal()?)))
1020 .map(|v| v.map(|(a, b)| (a.name.clone(), (a, b))))
1021 .collect();
1022 let internal_notes = internal_notes.map_err(|e| JsValue::from_str(&e.to_string()))?;
1023
1024 let builder = TxBuilder::from_tx(tx.internal, internal_notes).map_err(|e| e.to_string())?;
1025
1026 Ok(Self { builder })
1027 }
1028
1029 #[allow(clippy::too_many_arguments)]
1063 #[wasm_bindgen(js_name = simpleSpend)]
1064 pub fn simple_spend(
1065 &mut self,
1066 notes: Vec<WasmNote>,
1067 spend_conditions: Vec<WasmSpendCondition>,
1068 recipient: WasmDigest,
1069 gift: Nicks,
1070 fee_override: Option<Nicks>,
1071 refund_pkh: WasmDigest,
1072 include_lock_data: bool,
1073 ) -> Result<(), JsValue> {
1074 if notes.len() != spend_conditions.len() {
1075 return Err(JsValue::from_str(
1076 "notes and spend_conditions must have the same length",
1077 ));
1078 }
1079
1080 let internal_notes: Result<Vec<(Note, SpendCondition)>, String> = notes
1081 .iter()
1082 .zip(spend_conditions.iter())
1083 .map(|(n, sc)| Ok((n.to_internal()?, sc.to_internal()?)))
1084 .collect();
1085 let internal_notes = internal_notes.map_err(|e| JsValue::from_str(&e.to_string()))?;
1086
1087 self.builder
1088 .simple_spend_base(
1089 internal_notes,
1090 recipient.to_internal()?,
1091 gift,
1092 refund_pkh.to_internal()?,
1093 include_lock_data,
1094 )
1095 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1096
1097 if let Some(fee) = fee_override {
1098 self.builder
1099 .set_fee_and_balance_refund(fee, false, include_lock_data)
1100 } else {
1101 self.builder.recalc_and_set_fee(include_lock_data)
1102 }
1103 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1104
1105 Ok(())
1106 }
1107
1108 pub fn spend(&mut self, spend: WasmSpendBuilder) -> Option<WasmSpendBuilder> {
1110 self.builder.spend(spend.into()).map(|v| v.into())
1111 }
1112
1113 #[wasm_bindgen(js_name = setFeeAndBalanceRefund)]
1121 pub fn set_fee_and_balance_refund(
1122 &mut self,
1123 fee: Nicks,
1124 adjust_fee: bool,
1125 include_lock_data: bool,
1126 ) -> Result<(), JsValue> {
1127 self.builder
1128 .set_fee_and_balance_refund(fee, adjust_fee, include_lock_data)
1129 .map_err(|e| e.to_string())?;
1130 Ok(())
1131 }
1132
1133 #[wasm_bindgen(js_name = recalcAndSetFee)]
1135 pub fn recalc_and_set_fee(&mut self, include_lock_data: bool) -> Result<(), JsValue> {
1136 self.builder
1137 .recalc_and_set_fee(include_lock_data)
1138 .map_err(|e| e.to_string())?;
1139 Ok(())
1140 }
1141
1142 #[wasm_bindgen(js_name = addPreimage)]
1144 pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<WasmDigest>, JsValue> {
1145 let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
1146 Ok(self
1147 .builder
1148 .add_preimage(preimage)
1149 .map(|v| WasmDigest::from_internal(&v)))
1150 }
1151
1152 #[wasm_bindgen]
1156 pub fn sign(&mut self, signing_key_bytes: &[u8]) -> Result<(), JsValue> {
1157 if signing_key_bytes.len() != 32 {
1158 return Err(JsValue::from_str("Private key must be 32 bytes"));
1159 }
1160 let signing_key = PrivateKey(UBig::from_be_bytes(signing_key_bytes));
1161
1162 self.builder.sign(&signing_key);
1163
1164 Ok(())
1165 }
1166
1167 #[wasm_bindgen]
1169 pub fn validate(&mut self) -> Result<(), JsValue> {
1170 self.builder
1171 .validate()
1172 .map_err(|v| JsValue::from_str(&v.to_string()))?;
1173
1174 Ok(())
1175 }
1176
1177 #[wasm_bindgen(js_name = curFee)]
1179 pub fn cur_fee(&self) -> Nicks {
1180 self.builder.cur_fee()
1181 }
1182
1183 #[wasm_bindgen(js_name = calcFee)]
1192 pub fn calc_fee(&self) -> Nicks {
1193 self.builder.calc_fee()
1194 }
1195
1196 #[wasm_bindgen(js_name = allNotes)]
1197 pub fn all_notes(&self) -> WasmTxNotes {
1198 let mut ret = WasmTxNotes {
1199 notes: vec![],
1200 spend_conditions: vec![],
1201 };
1202 self.builder
1203 .all_notes()
1204 .into_values()
1205 .for_each(|(note, spend_condition)| {
1206 ret.notes.push(WasmNote::from_internal(note));
1207 ret.spend_conditions
1208 .push(WasmSpendCondition::from_internal(spend_condition));
1209 });
1210 ret
1211 }
1212
1213 #[wasm_bindgen]
1214 pub fn build(&self) -> Result<WasmRawTx, JsValue> {
1215 let tx = self.builder.build();
1216 Ok(WasmRawTx::from_internal(&tx))
1217 }
1218}
1219
1220#[wasm_bindgen(js_name = TxNotes)]
1221pub struct WasmTxNotes {
1222 #[wasm_bindgen(skip)]
1223 pub notes: Vec<WasmNote>,
1224 #[wasm_bindgen(skip)]
1225 pub spend_conditions: Vec<WasmSpendCondition>,
1226}
1227
1228#[wasm_bindgen(js_class = TxNotes)]
1229impl WasmTxNotes {
1230 #[wasm_bindgen(getter)]
1231 pub fn notes(&self) -> Vec<WasmNote> {
1232 self.notes.clone()
1233 }
1234
1235 #[wasm_bindgen(getter, js_name = spendConditions)]
1236 pub fn spend_conditions(&self) -> Vec<WasmSpendCondition> {
1237 self.spend_conditions.clone()
1238 }
1239}
1240
1241#[wasm_bindgen(js_name = SpendBuilder)]
1246pub struct WasmSpendBuilder {
1247 builder: SpendBuilder,
1248}
1249
1250#[wasm_bindgen(js_class = SpendBuilder)]
1251impl WasmSpendBuilder {
1252 #[wasm_bindgen(constructor)]
1254 pub fn new(
1255 note: WasmNote,
1256 spend_condition: WasmSpendCondition,
1257 refund_lock: Option<WasmSpendCondition>,
1258 ) -> Result<Self, JsValue> {
1259 Ok(Self {
1260 builder: SpendBuilder::new(
1261 note.to_internal()?,
1262 spend_condition.to_internal()?,
1263 refund_lock.map(|v| v.to_internal()).transpose()?,
1264 ),
1265 })
1266 }
1267
1268 pub fn fee(&mut self, fee: Nicks) {
1270 self.builder.fee(fee);
1271 }
1272
1273 #[wasm_bindgen(js_name = computeRefund)]
1275 pub fn compute_refund(&mut self, include_lock_data: bool) {
1276 self.builder.compute_refund(include_lock_data);
1277 }
1278
1279 #[wasm_bindgen(js_name = curRefund)]
1281 pub fn cur_refund(&self) -> Option<WasmSeed> {
1282 self.builder.cur_refund().map(|v| WasmSeed::from(v.clone()))
1283 }
1284
1285 #[wasm_bindgen(js_name = isBalanced)]
1289 pub fn is_balanced(&self) -> bool {
1290 self.builder.is_balanced()
1291 }
1292
1293 pub fn seed(&mut self, seed: WasmSeed) -> Result<(), JsValue> {
1300 self.builder.seed(seed.to_internal()?);
1301 Ok(())
1302 }
1303
1304 #[wasm_bindgen(js_name = invalidateSigs)]
1310 pub fn invalidate_sigs(&mut self) {
1311 self.builder.invalidate_sigs();
1312 }
1313
1314 #[wasm_bindgen(js_name = missingUnlocks)]
1323 pub fn missing_unlocks(&self) -> Result<Vec<JsValue>, JsValue> {
1324 self.builder
1325 .missing_unlocks()
1326 .into_iter()
1327 .map(|v| serde_wasm_bindgen::to_value(&WasmMissingUnlocks::from_internal(&v)))
1328 .collect::<Result<Vec<_>, _>>()
1329 .map_err(|e| e.into())
1330 }
1331
1332 #[wasm_bindgen(js_name = addPreimage)]
1334 pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<WasmDigest>, JsValue> {
1335 let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
1336 Ok(self
1337 .builder
1338 .add_preimage(preimage)
1339 .map(|v| WasmDigest::from_internal(&v)))
1340 }
1341
1342 pub fn sign(&mut self, signing_key_bytes: &[u8]) -> Result<bool, JsValue> {
1344 if signing_key_bytes.len() != 32 {
1345 return Err(JsValue::from_str("Private key must be 32 bytes"));
1346 }
1347 let signing_key = PrivateKey(UBig::from_be_bytes(signing_key_bytes));
1348 Ok(self.builder.sign(&signing_key))
1349 }
1350}
1351
1352impl From<SpendBuilder> for WasmSpendBuilder {
1353 fn from(builder: SpendBuilder) -> Self {
1354 Self { builder }
1355 }
1356}
1357
1358impl From<WasmSpendBuilder> for SpendBuilder {
1359 fn from(value: WasmSpendBuilder) -> Self {
1360 value.builder
1361 }
1362}
1363
1364#[derive(Serialize, Deserialize)]
1365pub enum WasmMissingUnlocks {
1366 Pkh {
1367 num_sigs: u64,
1368 sig_of: BTreeSet<String>,
1369 },
1370 Hax {
1371 preimages_for: BTreeSet<String>,
1372 },
1373 Brn,
1374}
1375
1376impl WasmMissingUnlocks {
1377 fn from_internal(internal: &MissingUnlocks) -> Self {
1378 match internal {
1379 MissingUnlocks::Pkh { num_sigs, sig_of } => Self::Pkh {
1380 num_sigs: *num_sigs,
1381 sig_of: sig_of
1382 .iter()
1383 .map(|v| WasmDigest::from_internal(v).value)
1384 .collect(),
1385 },
1386 MissingUnlocks::Hax { preimages_for } => Self::Hax {
1387 preimages_for: preimages_for
1388 .iter()
1389 .map(|v| WasmDigest::from_internal(v).value)
1390 .collect(),
1391 },
1392 MissingUnlocks::Brn => Self::Brn,
1393 }
1394 }
1395}
1396
1397#[wasm_bindgen(js_name = RawTx)]
1402pub struct WasmRawTx {
1403 #[wasm_bindgen(skip)]
1405 pub(crate) internal: RawTx,
1406}
1407
1408#[wasm_bindgen(js_class = RawTx)]
1409impl WasmRawTx {
1410 #[wasm_bindgen(getter)]
1411 pub fn version(&self) -> WasmVersion {
1412 WasmVersion::from_internal(&self.internal.version)
1413 }
1414
1415 #[wasm_bindgen(getter)]
1416 pub fn id(&self) -> WasmDigest {
1417 WasmDigest::from_internal(&self.internal.id)
1418 }
1419
1420 #[wasm_bindgen(getter)]
1421 pub fn name(&self) -> String {
1422 self.internal.id.to_string()
1423 }
1424
1425 fn from_internal(tx: &RawTx) -> Self {
1426 Self {
1427 internal: tx.clone(),
1428 }
1429 }
1430
1431 #[wasm_bindgen(js_name = toProtobuf)]
1433 pub fn to_protobuf(&self) -> Result<JsValue, JsValue> {
1434 let pb_tx = pb::RawTransaction::from(self.internal.clone());
1435 serde_wasm_bindgen::to_value(&pb_tx)
1436 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
1437 }
1438
1439 #[wasm_bindgen(js_name = fromProtobuf)]
1440 pub fn from_protobuf(value: JsValue) -> Result<WasmRawTx, JsValue> {
1441 let pb: pb::RawTransaction = serde_wasm_bindgen::from_value(value)?;
1442 let tx: RawTx = pb
1443 .try_into()
1444 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
1445 Ok(WasmRawTx::from_internal(&tx))
1446 }
1447
1448 #[wasm_bindgen(js_name = toJam)]
1450 pub fn to_jam(&self) -> js_sys::Uint8Array {
1451 let n = self.internal.to_nockchain_tx();
1452 js_sys::Uint8Array::from(&jam(n)[..])
1453 }
1454
1455 #[wasm_bindgen(js_name = fromJam)]
1456 pub fn from_jam(jam: &[u8]) -> Result<Self, JsValue> {
1457 let n = cue(jam).ok_or("Unable to decode jam")?;
1458 let (txid, spends): (String, Spends) =
1459 NounDecode::from_noun(&n).ok_or("Unable to decode noun")?;
1460 let tx = RawTx {
1461 version: Version::V1,
1462 id: Digest::try_from(&*txid)?,
1463 spends,
1464 };
1465 Ok(Self::from_internal(&tx))
1466 }
1467
1468 #[wasm_bindgen]
1470 pub fn outputs(&self) -> Vec<WasmNote> {
1471 self.internal
1472 .outputs()
1473 .into_iter()
1474 .map(WasmNote::from_internal)
1475 .collect()
1476 }
1477}