1use crate::box_coll::ErgoBoxCandidates;
4use crate::box_coll::ErgoBoxes;
5use crate::context_extension::ContextExtension;
6use crate::data_input::DataInputs;
7use crate::ergo_box::BoxId;
8use crate::ergo_box::ErgoBox;
9use crate::error_conversion::to_js;
10use crate::input::{Inputs, UnsignedInputs};
11use crate::json::TransactionJsonEip12;
12use crate::json::UnsignedTransactionJsonEip12;
13use ergo_lib::chain;
14use ergo_lib::chain::transaction::{distinct_token_ids, TxIoVec};
15use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
16use ergo_lib::wallet::signing::make_context;
17use gloo_utils::format::JsValueSerdeExt;
18use js_sys::Uint8Array;
19use std::convert::{TryFrom, TryInto};
20use wasm_bindgen::prelude::*;
21
22extern crate derive_more;
23
24use crate::ergo_state_ctx::ErgoStateContext;
25use crate::transaction::reduced::Propositions;
26use derive_more::{From, Into};
27use ergo_lib::ergo_chain_types::{Base16DecodedBytes, Base16EncodedBytes, Digest32};
28
29pub mod reduced;
30
31#[wasm_bindgen]
33pub struct CommitmentHint(
34 ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::CommitmentHint,
35);
36
37#[wasm_bindgen]
39pub struct HintsBag(
40 pub(crate) ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag,
41);
42
43#[wasm_bindgen]
44impl HintsBag {
45 pub fn empty() -> HintsBag {
47 HintsBag(ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag::empty())
48 }
49
50 pub fn add_commitment(&mut self, hint: CommitmentHint) {
52 self.0.add_hint(
53 ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::Hint::CommitmentHint(
54 hint.0,
55 ),
56 );
57 }
58
59 pub fn len(&self) -> usize {
61 self.0.hints.len()
62 }
63
64 pub fn get(&self, index: usize) -> Result<CommitmentHint, JsValue> {
66 let commitment = self.0.commitments()[index].clone();
67 Ok(CommitmentHint(commitment))
68 }
69}
70
71impl From<ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag> for HintsBag {
72 fn from(t: ergo_lib::ergotree_interpreter::sigma_protocol::prover::hint::HintsBag) -> Self {
73 HintsBag(t)
74 }
75}
76
77#[wasm_bindgen]
79pub struct TransactionHintsBag(pub(crate) ergo_lib::wallet::multi_sig::TransactionHintsBag);
80
81#[wasm_bindgen]
82impl TransactionHintsBag {
83 pub fn empty() -> TransactionHintsBag {
85 TransactionHintsBag(ergo_lib::wallet::multi_sig::TransactionHintsBag::empty())
86 }
87
88 pub fn add_hints_for_input(&mut self, index: usize, hints_bag: &HintsBag) {
90 self.0.add_hints_for_input(index, hints_bag.0.clone());
91 }
92
93 pub fn all_hints_for_input(&self, index: usize) -> HintsBag {
95 HintsBag::from(self.0.all_hints_for_input(index))
96 }
97
98 pub fn to_json(&self) -> Result<JsValue, JsValue> {
100 <JsValue as JsValueSerdeExt>::from_serde(&self.0).map_err(to_js)
101 }
102
103 pub fn from_json(json: &str) -> Result<TransactionHintsBag, JsValue> {
105 serde_json::from_str(json).map(Self).map_err(to_js)
106 }
107}
108
109impl From<ergo_lib::wallet::multi_sig::TransactionHintsBag> for TransactionHintsBag {
110 fn from(t: ergo_lib::wallet::multi_sig::TransactionHintsBag) -> Self {
111 TransactionHintsBag(t)
112 }
113}
114
115#[wasm_bindgen]
117pub fn extract_hints(
118 signed_transaction: Transaction,
119 state_context: &ErgoStateContext,
120 boxes_to_spend: &ErgoBoxes,
121 data_boxes: &ErgoBoxes,
122 real_propositions: Propositions,
123 simulated_propositions: Propositions,
124) -> Result<TransactionHintsBag, JsValue> {
125 let boxes_to_spend = boxes_to_spend.clone().into();
126
127 let data_boxes = data_boxes.clone().into();
128 let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
129 signed_transaction.0,
130 boxes_to_spend,
131 data_boxes,
132 )
133 .map_err(to_js)?;
134 Ok(TransactionHintsBag::from(
135 ergo_lib::wallet::multi_sig::extract_hints(
136 &tx_context,
137 &state_context.0.clone(),
138 real_propositions.0,
139 simulated_propositions.0,
140 )
141 .map_err(to_js)?,
142 ))
143}
144
145#[wasm_bindgen]
147#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
148pub struct TxId(pub(crate) chain::transaction::TxId);
149
150#[wasm_bindgen]
151impl TxId {
152 pub fn zero() -> TxId {
154 chain::transaction::TxId::zero().into()
155 }
156
157 pub fn to_str(&self) -> String {
159 let base16_bytes = Base16EncodedBytes::new(self.0 .0 .0.as_ref());
160 base16_bytes.into()
161 }
162
163 #[allow(clippy::should_implement_trait)]
165 pub fn from_str(s: &str) -> Result<TxId, JsValue> {
166 let bytes = Base16DecodedBytes::try_from(s.to_string()).map_err(to_js)?;
167
168 bytes
169 .try_into()
170 .map(|digest| chain::transaction::TxId(digest).into())
171 .map_err(|_e| {
172 JsValue::from_str(&format!(
173 "Expected a Vec of length {} but it was {}",
174 Digest32::SIZE,
175 s.len()
176 ))
177 })
178 }
179}
180
181#[wasm_bindgen]
192pub struct Transaction(chain::transaction::Transaction);
193
194#[wasm_bindgen]
195impl Transaction {
196 #[wasm_bindgen(constructor)]
198 pub fn new(
199 inputs: &Inputs,
200 data_inputs: &DataInputs,
201 outputs: &ErgoBoxCandidates,
202 ) -> Result<Transaction, JsValue> {
203 let inputs: Vec<chain::transaction::Input> = inputs.into();
204 let data_inputs: Vec<chain::transaction::DataInput> = data_inputs.into();
205 let outputs: Vec<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBoxCandidate> =
206 outputs.clone().into();
207 Ok(Transaction(
208 chain::transaction::Transaction::new(
209 TxIoVec::try_from(inputs).map_err(to_js)?,
210 TxIoVec::try_from(data_inputs).map_err(to_js)?.into(),
211 TxIoVec::try_from(outputs).map_err(to_js)?,
212 )
213 .map_err(to_js)?,
214 ))
215 }
216
217 pub fn from_unsigned_tx(
220 unsigned_tx: UnsignedTransaction,
221 proofs: Vec<Uint8Array>,
222 ) -> Result<Transaction, JsValue> {
223 chain::transaction::Transaction::from_unsigned_tx(
224 unsigned_tx.0,
225 proofs
226 .into_iter()
227 .map(|bytes| bytes.to_vec().into())
228 .collect(),
229 )
230 .map_err(|e| JsValue::from_str(&format!("{}", e)))
231 .map(Into::into)
232 }
233
234 pub fn id(&self) -> TxId {
236 self.0.id().into()
237 }
238
239 pub fn to_json(&self) -> Result<String, JsValue> {
241 serde_json::to_string_pretty(&self.0.clone())
242 .map_err(|e| JsValue::from_str(&format!("{}", e)))
243 }
244
245 pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
248 let tx_dapp: TransactionJsonEip12 = self.0.clone().into();
249 <JsValue as JsValueSerdeExt>::from_serde(&tx_dapp)
250 .map_err(|e| JsValue::from_str(&format!("{}", e)))
251 }
252
253 pub fn from_json(json: &str) -> Result<Transaction, JsValue> {
256 serde_json::from_str(json).map(Self).map_err(to_js)
257 }
258
259 pub fn inputs(&self) -> Inputs {
261 self.0.inputs.as_vec().clone().into()
262 }
263
264 pub fn data_inputs(&self) -> DataInputs {
266 self.0
267 .data_inputs
268 .clone()
269 .map(|di| di.as_vec().clone())
270 .unwrap_or_default()
271 .into()
272 }
273
274 pub fn output_candidates(&self) -> ErgoBoxCandidates {
276 self.0.output_candidates.as_vec().clone().into()
277 }
278
279 pub fn outputs(&self) -> ErgoBoxes {
281 self.0.outputs.clone().to_vec().into()
282 }
283
284 pub fn sigma_serialize_bytes(&self) -> Result<Vec<u8>, JsValue> {
286 self.0.sigma_serialize_bytes().map_err(to_js)
287 }
288
289 pub fn sigma_parse_bytes(data: Vec<u8>) -> Result<Transaction, JsValue> {
291 ergo_lib::chain::transaction::Transaction::sigma_parse_bytes(&data)
292 .map(Transaction)
293 .map_err(to_js)
294 }
295
296 pub fn verify_p2pk_input(&self, input_box: ErgoBox) -> Result<bool, JsValue> {
299 self.0.verify_p2pk_input(input_box.into()).map_err(to_js)
300 }
301}
302
303impl From<chain::transaction::Transaction> for Transaction {
304 fn from(t: chain::transaction::Transaction) -> Self {
305 Transaction(t)
306 }
307}
308
309#[wasm_bindgen]
311#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
312pub struct UnsignedTransaction(pub(crate) chain::transaction::unsigned::UnsignedTransaction);
313
314#[wasm_bindgen]
315impl UnsignedTransaction {
316 #[wasm_bindgen(constructor)]
318 pub fn new(
319 inputs: &UnsignedInputs,
320 data_inputs: &DataInputs,
321 output_candidates: &ErgoBoxCandidates,
322 ) -> Result<UnsignedTransaction, JsValue> {
323 let opt_data_input = if data_inputs.len() > 0 {
324 Some(TxIoVec::from_vec(data_inputs.into()).map_err(to_js)?)
325 } else {
326 None
327 };
328
329 Ok(chain::transaction::unsigned::UnsignedTransaction::new(
330 TxIoVec::from_vec(inputs.into()).map_err(to_js)?,
331 opt_data_input,
332 TxIoVec::from_vec(output_candidates.into()).map_err(to_js)?,
333 )
334 .map_err(to_js)?
335 .into())
336 }
337
338 pub fn with_input_context_ext(
342 mut self,
343 input_id: &BoxId,
344 ext: &ContextExtension,
345 ) -> Result<UnsignedTransaction, JsValue> {
346 let input = self
347 .0
348 .inputs
349 .iter_mut()
350 .find(|input| input.box_id == input_id.clone().into())
351 .ok_or_else(|| JsValue::from_str("Box input id not found"))?;
352 input.extension = ext.clone().into();
353
354 Ok(self.clone())
355 }
356
357 pub fn id(&self) -> TxId {
359 self.0.id().into()
360 }
361
362 pub fn inputs(&self) -> UnsignedInputs {
364 self.0.inputs.as_vec().clone().into()
365 }
366
367 pub fn data_inputs(&self) -> DataInputs {
369 self.0
370 .clone()
371 .data_inputs
372 .map(|di| di.as_vec().clone())
373 .unwrap_or_default()
374 .into()
375 }
376
377 pub fn output_candidates(&self) -> ErgoBoxCandidates {
379 self.0.output_candidates.as_vec().clone().into()
380 }
381
382 pub fn to_json(&self) -> Result<String, JsValue> {
384 serde_json::to_string_pretty(&self.0.clone())
385 .map_err(|e| JsValue::from_str(&format!("{}", e)))
386 }
387
388 pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
391 let tx_dapp: UnsignedTransactionJsonEip12 = self.0.clone().into();
392 <JsValue as JsValueSerdeExt>::from_serde(&tx_dapp)
393 .map_err(|e| JsValue::from_str(&format!("{}", e)))
394 }
395
396 pub fn from_json(json: &str) -> Result<UnsignedTransaction, JsValue> {
399 serde_json::from_str(json).map(Self).map_err(to_js)
400 }
401
402 pub fn distinct_token_ids(&self) -> Vec<Uint8Array> {
404 distinct_token_ids(self.0.output_candidates.clone())
405 .iter()
406 .map(|id| Uint8Array::from(id.as_ref()))
407 .collect()
408 }
409}
410
411#[wasm_bindgen]
413pub fn verify_tx_input_proof(
414 input_idx: usize,
415 state_context: &ErgoStateContext,
416 tx: &Transaction,
417 boxes_to_spend: &ErgoBoxes,
418 data_boxes: &ErgoBoxes,
419) -> Result<bool, JsValue> {
420 let boxes_to_spend = boxes_to_spend.clone().into();
421 let data_boxes = data_boxes.clone().into();
422 let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
423 tx.0.clone(),
424 boxes_to_spend,
425 data_boxes,
426 )
427 .map_err(to_js)?;
428 let state_context_inner = state_context.clone().into();
429 let mut context = make_context(&state_context_inner, &tx_context, input_idx).map_err(to_js)?;
430 Ok(ergo_lib::chain::transaction::verify_tx_input_proof(
431 &tx_context,
432 &mut context,
433 &state_context_inner,
434 input_idx,
435 &tx_context.spending_tx.bytes_to_sign().map_err(to_js)?,
436 )
437 .map_err(to_js)?
438 .result)
439}
440
441#[wasm_bindgen]
443pub fn validate_tx(
444 tx: &Transaction,
445 state_context: &ErgoStateContext,
446 boxes_to_spend: &ErgoBoxes,
447 data_boxes: &ErgoBoxes,
448) -> Result<(), JsValue> {
449 let boxes_to_spend = boxes_to_spend.clone().into();
450 let data_boxes = data_boxes.clone().into();
451 let tx_context = ergo_lib::wallet::signing::TransactionContext::new(
452 tx.0.clone(),
453 boxes_to_spend,
454 data_boxes,
455 )
456 .map_err(to_js)?;
457 tx_context.validate(&state_context.0).map_err(to_js)
458}