1use alloc::format;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use std::collections::BTreeMap;
5
6use iris_crypto::PrivateKey as CryptoPrivateKey;
7use iris_grpc_proto::pb::common::v1 as pb_v1;
8use iris_grpc_proto::pb::common::v2 as pb;
9use iris_nockchain_types::{
10 builder::{MissingUnlocks, TxBuilder},
11 note::{Name, Note},
12 tx::RawTx,
13 v1::{Lock, LockRoot, NockchainTx, RawTxV1, SeedV1 as Seed, SpendCondition},
14 Nicks, SpendBuilder, TxEngineSettings,
15};
16use iris_ztd::{cue, Digest, U256};
17use serde::{Deserialize, Serialize};
18use wasm_bindgen::prelude::*;
19
20#[wasm_bindgen(js_name = digestToProtobuf)]
25pub fn digest_to_protobuf(d: Digest) -> pb_v1::Hash {
26 d.into()
27}
28
29#[wasm_bindgen(js_name = digestFromProtobuf)]
30pub fn digest_from_protobuf(value: pb_v1::Hash) -> Result<Digest, JsValue> {
31 value
32 .try_into()
33 .map_err(|e| JsValue::from_str(&format!("{}", e)))
34}
35
36#[wasm_bindgen(js_name = txEngineSettingsV1Default)]
38pub fn tx_engine_settings_v1_default() -> TxEngineSettings {
39 TxEngineSettings::v1_default()
40}
41
42#[wasm_bindgen(js_name = txEngineSettingsV1BythosDefault)]
44pub fn tx_engine_settings_v1_bythos_default() -> TxEngineSettings {
45 TxEngineSettings::v1_bythos_default()
46}
47
48#[wasm_bindgen(js_name = spendConditionFromProtobuf)]
51pub fn spend_condition_from_protobuf(value: pb::SpendCondition) -> Result<SpendCondition, JsValue> {
52 value
53 .try_into()
54 .map_err(|e| JsValue::from_str(&format!("{}", e)))
55}
56
57#[wasm_bindgen(js_name = spendConditionToProtobuf)]
60pub fn spend_condition_to_protobuf(value: SpendCondition) -> pb::SpendCondition {
61 value.into()
62}
63
64#[wasm_bindgen(js_name = noteToProtobuf)]
65pub fn note_to_protobuf(note: Note) -> pb::Note {
66 note.into()
67}
68
69#[wasm_bindgen(js_name = noteFromProtobuf)]
70pub fn note_from_protobuf(value: pb::Note) -> Result<Note, JsValue> {
71 value
72 .try_into()
73 .map_err(|e| JsValue::from_str(&format!("{}", e)))
74}
75
76#[wasm_bindgen(js_name = rawTxToProtobuf)]
82pub fn raw_tx_to_protobuf(tx: RawTxV1) -> pb::RawTransaction {
83 tx.into()
84}
85
86#[wasm_bindgen(js_name = rawTxFromProtobuf)]
87pub fn raw_tx_from_protobuf(tx: pb::RawTransaction) -> Result<RawTx, JsValue> {
88 tx.try_into()
89 .map_err(|e| JsValue::from_str(&format!("{}", e)))
90}
91
92#[wasm_bindgen]
93pub fn locky(sp: iris_nockchain_types::v1::SpendCondition) -> iris_nockchain_types::v1::Lock {
94 iris_nockchain_types::v1::Lock::Single(sp)
95}
96
97#[derive(Serialize, Deserialize, tsify::Tsify)]
98#[tsify(into_wasm_abi, from_wasm_abi)]
99pub struct TxNotes {
100 pub notes: Vec<Note>,
101 pub refund_locks: Vec<Option<LockRoot>>,
102}
103
104#[derive(Serialize, Deserialize, tsify::Tsify)]
105#[tsify(into_wasm_abi, from_wasm_abi)]
106#[serde(untagged)]
107#[allow(clippy::large_enum_variant)]
108pub enum TxLock {
109 None,
110 Some { lock: Lock, lock_sp_index: usize },
111}
112
113impl TxLock {
114 fn into_tuple(self) -> Option<(Lock, usize)> {
115 match self {
116 TxLock::None => None,
117 TxLock::Some {
118 lock,
119 lock_sp_index,
120 } => Some((lock, lock_sp_index)),
121 }
122 }
123}
124
125enum PrivateKeyBackend {
130 Bytes(BytesPrivateKeyBackend),
131}
132
133struct BytesPrivateKeyBackend {
134 signing_key: CryptoPrivateKey,
135 public_key_bytes: [u8; 97],
136}
137
138#[wasm_bindgen(js_name = PrivateKey)]
139pub struct WasmPrivateKey {
140 backend: PrivateKeyBackend,
141}
142
143#[wasm_bindgen(js_class = PrivateKey)]
144impl WasmPrivateKey {
145 #[wasm_bindgen(constructor)]
167 pub fn new(signing_key_bytes: &[u8]) -> Result<Self, JsValue> {
168 Self::from_bytes(signing_key_bytes)
169 }
170
171 #[wasm_bindgen(js_name = fromBytes)]
173 pub fn from_bytes(signing_key_bytes: &[u8]) -> Result<Self, JsValue> {
174 if signing_key_bytes.len() != 32 {
175 return Err(JsValue::from_str("Private key must be 32 bytes"));
176 }
177
178 let signing_key = CryptoPrivateKey(U256::from_be_slice(signing_key_bytes));
179 let public_key_bytes = signing_key.public_key().to_be_bytes();
180
181 Ok(Self {
182 backend: PrivateKeyBackend::Bytes(BytesPrivateKeyBackend {
183 signing_key,
184 public_key_bytes,
185 }),
186 })
187 }
188
189 #[wasm_bindgen(getter, js_name = publicKey)]
191 pub fn public_key(&self) -> Vec<u8> {
192 match &self.backend {
193 PrivateKeyBackend::Bytes(bytes_backend) => bytes_backend.public_key_bytes.to_vec(),
194 }
195 }
196
197 #[wasm_bindgen(getter, js_name = derivationPath)]
201 pub fn derivation_path(&self) -> Option<String> {
202 match &self.backend {
203 PrivateKeyBackend::Bytes(_) => None,
204 }
205 }
206
207 #[wasm_bindgen(js_name = backendKind)]
209 pub fn backend_kind(&self) -> String {
210 match &self.backend {
211 PrivateKeyBackend::Bytes(_) => "bytes".to_string(),
212 }
213 }
214}
215
216impl WasmPrivateKey {
217 fn signing_key(&self) -> &CryptoPrivateKey {
218 match &self.backend {
219 PrivateKeyBackend::Bytes(bytes_backend) => &bytes_backend.signing_key,
220 }
221 }
222}
223
224#[wasm_bindgen(js_name = TxBuilder)]
225pub struct WasmTxBuilder {
226 builder: TxBuilder,
227}
228
229#[wasm_bindgen(js_class = TxBuilder)]
230impl WasmTxBuilder {
231 #[wasm_bindgen(constructor)]
233 pub fn new(settings: TxEngineSettings) -> Self {
234 Self {
235 builder: TxBuilder::new(settings),
236 }
237 }
238
239 #[wasm_bindgen(js_name = fromTx)]
241 pub fn from_tx(
242 tx: RawTx,
243 notes: Vec<Note>,
244 refund_lock: Option<LockRoot>,
245 settings: TxEngineSettings,
246 ) -> Result<Self, JsValue> {
247 let internal_notes: BTreeMap<Name, (Note, Option<LockRoot>)> = notes
248 .into_iter()
249 .map(|n| (n.name(), (n, refund_lock.clone())))
250 .collect();
251
252 let builder =
253 TxBuilder::from_tx(tx, internal_notes, settings).map_err(|e| e.to_string())?;
254
255 Ok(Self { builder })
256 }
257
258 #[allow(clippy::too_many_arguments)]
259 #[wasm_bindgen(js_name = simpleSpend)]
260 pub fn simple_spend(
261 &mut self,
262 notes: Vec<Note>,
263 locks: Vec<TxLock>,
264 recipient: Digest,
265 gift: Nicks,
266 fee_override: Option<Nicks>,
267 refund_pkh: Digest,
268 include_lock_data: bool,
269 ) -> Result<(), JsValue> {
270 if notes.len() != locks.len() {
271 return Err(JsValue::from_str(
272 "notes and locks must have the same length",
273 ));
274 }
275
276 let internal_notes: Vec<(Note, Option<(Lock, usize)>)> = notes
277 .into_iter()
278 .zip(locks)
279 .map(|(n, lck)| (n, lck.into_tuple()))
280 .collect();
281
282 self.builder
283 .simple_spend_base(
284 internal_notes,
285 recipient,
286 gift,
287 refund_pkh,
288 include_lock_data,
289 )
290 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
291
292 if let Some(fee) = fee_override {
293 self.builder
294 .set_fee_and_balance_refund(fee, false, include_lock_data)
295 } else {
296 self.builder.recalc_and_set_fee(include_lock_data)
297 }
298 .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
299
300 Ok(())
301 }
302
303 pub fn spend(&mut self, spend: WasmSpendBuilder) -> Option<WasmSpendBuilder> {
305 self.builder.spend(spend.into()).map(|v| v.into())
306 }
307
308 #[wasm_bindgen(js_name = setFeeAndBalanceRefund)]
309 pub fn set_fee_and_balance_refund(
310 &mut self,
311 fee: Nicks,
312 adjust_fee: bool,
313 include_lock_data: bool,
314 ) -> Result<(), JsValue> {
315 self.builder
316 .set_fee_and_balance_refund(fee, adjust_fee, include_lock_data)
317 .map_err(|e| e.to_string())?;
318 Ok(())
319 }
320
321 #[wasm_bindgen(js_name = recalcAndSetFee)]
322 pub fn recalc_and_set_fee(&mut self, include_lock_data: bool) -> Result<(), JsValue> {
323 self.builder
324 .recalc_and_set_fee(include_lock_data)
325 .map_err(|e| e.to_string())?;
326 Ok(())
327 }
328
329 #[wasm_bindgen(js_name = addPreimage)]
330 pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<Digest>, JsValue> {
331 let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
332 Ok(self.builder.add_preimage(preimage))
333 }
334
335 #[wasm_bindgen]
336 pub async fn sign(&mut self, signing_key: &WasmPrivateKey) -> Result<(), JsValue> {
337 self.builder.sign(signing_key.signing_key());
338
339 Ok(())
340 }
341
342 #[wasm_bindgen]
343 pub fn validate(&mut self) -> Result<(), JsValue> {
344 self.builder
345 .validate()
346 .map_err(|v| JsValue::from_str(&v.to_string()))?;
347
348 Ok(())
349 }
350
351 #[wasm_bindgen(js_name = curFee)]
352 pub fn cur_fee(&self) -> Nicks {
353 self.builder.cur_fee()
354 }
355
356 #[wasm_bindgen(js_name = calcFee)]
357 pub fn calc_fee(&self) -> Nicks {
358 self.builder.calc_fee()
359 }
360
361 #[wasm_bindgen(js_name = allNotes)]
362 pub fn all_notes(&self) -> Result<Vec<Note>, JsValue> {
363 let mut ret = Vec::new();
364 for note in self.builder.all_notes().into_values() {
365 ret.push(note);
366 }
367 Ok(ret)
368 }
369
370 #[wasm_bindgen]
371 pub fn build(&self) -> Result<NockchainTx, JsValue> {
372 Ok(self.builder.build())
373 }
374
375 #[wasm_bindgen(js_name = allSpends)]
376 pub fn all_spends(&self) -> Vec<WasmSpendBuilder> {
377 self.builder
378 .all_spends()
379 .values()
380 .map(WasmSpendBuilder::from_internal)
381 .collect()
382 }
383}
384
385#[wasm_bindgen(js_name = SpendBuilder)]
390pub struct WasmSpendBuilder {
391 builder: SpendBuilder,
392}
393
394#[wasm_bindgen(js_class = SpendBuilder)]
395impl WasmSpendBuilder {
396 #[wasm_bindgen(constructor)]
398 pub fn new(
399 note: Note,
400 lock: Option<Lock>,
401 lock_sp_index: Option<usize>,
402 refund_lock: Option<LockRoot>,
403 ) -> Result<Self, JsValue> {
404 Ok(Self {
405 builder: SpendBuilder::new(note, lock.zip(lock_sp_index), refund_lock)
406 .map_err(|e| JsValue::from_str(&e.to_string()))?,
407 })
408 }
409
410 pub fn fee(&mut self, fee: Nicks) {
411 self.builder.fee(fee);
412 }
413
414 #[wasm_bindgen(js_name = computeRefund)]
415 pub fn compute_refund(&mut self, include_lock_data: bool) {
416 self.builder.compute_refund(include_lock_data);
417 }
418
419 #[wasm_bindgen(js_name = curRefund)]
420 pub fn cur_refund(&self) -> Option<Seed> {
421 self.builder.cur_refund().cloned()
422 }
423
424 #[wasm_bindgen(js_name = isBalanced)]
425 pub fn is_balanced(&self) -> bool {
426 self.builder.is_balanced()
427 }
428
429 pub fn seed(&mut self, seed: Seed) -> Result<(), JsValue> {
430 self.builder.seed(seed);
431 Ok(())
432 }
433
434 #[wasm_bindgen(js_name = invalidateSigs)]
435 pub fn invalidate_sigs(&mut self) {
436 self.builder.invalidate_sigs();
437 }
438
439 #[wasm_bindgen(js_name = missingUnlocks)]
440 pub fn missing_unlocks(&self) -> Result<Vec<MissingUnlocks>, JsValue> {
441 Ok(self.builder.missing_unlocks())
443 }
444
445 #[wasm_bindgen(js_name = addPreimage)]
446 pub fn add_preimage(&mut self, preimage_jam: &[u8]) -> Result<Option<Digest>, JsValue> {
447 let preimage = cue(preimage_jam).ok_or("Unable to cue preimage jam")?;
448 Ok(self.builder.add_preimage(preimage))
449 }
450
451 pub async fn sign(&mut self, signing_key: &WasmPrivateKey) -> Result<bool, JsValue> {
452 Ok(self.builder.sign(signing_key.signing_key()))
453 }
454
455 fn from_internal(internal: &SpendBuilder) -> Self {
456 Self {
457 builder: internal.clone(),
458 }
459 }
460}
461
462impl From<SpendBuilder> for WasmSpendBuilder {
463 fn from(builder: SpendBuilder) -> Self {
464 Self { builder }
465 }
466}
467
468impl From<WasmSpendBuilder> for SpendBuilder {
469 fn from(value: WasmSpendBuilder) -> Self {
470 value.builder
471 }
472}