Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Simplify *Error to JsValue conversion
The goal is to avoid `.map_err(|e| JsValue::from_str(&format!("{}", e)))` in WASM layer. Ideally, I see implementing `From/Into` for errors and use `?` but orphan rules should not allow that.

See code example - https://github.com/ergoplatform/sigma-rust/blob/develop/bindings/ergo-lib-wasm/src/contract.rs#L11-L20


Discussion / hints:
To get around the orphan rules we can wrap `JsValue` and write a conversion function as follows:
```rust
struct JsValueWrap(JsValue);

impl From<SigmaParsingError> for JsValueWrap {
    fn from(e: SigmaParsingError) -> Self {
        JsValueWrap(JsValue::from_str(&format!("{}", e)))
    }
}

fn conv<S: Into<JsValueWrap>>(s: S) -> JsValue {
    s.into().0
}
```

The resulting call will be `.map_err(conv)`. What do you think?
@kettlebell `.map_err(conv)` looks good. I like it!
PR #393 addresses this issue. There are a number of instances where the `Debug` trait is used to render the `JsValue` (`JsValue::from_str(&format! {"{:?}"`). One such error `TryExtractFromError` also implements the `Display` trait.

In any case I didn't change them in case some other library depends on these particular renderings of the error.
> PR #393 addresses this issue. There are a number of instances where the `Debug` trait is used to render the `JsValue` (`JsValue::from_str(&format! {"{:?}"`). One such error `TryExtractFromError` also implements the `Display` trait.
> 
> In any case I didn't change them in case some other library depends on these particular renderings of the error.

I think it's safe to change them as well. I'm not aware of any library that relays on these renderings of the error.

Fix patch:
diff --git a/bindings/ergo-lib-wasm/Cargo.toml b/bindings/ergo-lib-wasm/Cargo.toml
index e33ad67be..a7e90770d 100644
--- a/bindings/ergo-lib-wasm/Cargo.toml
+++ b/bindings/ergo-lib-wasm/Cargo.toml
@@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"]
 default = ["console_error_panic_hook"]
 
 [dependencies]
+base16 = "0.2.1"
 serde = { version = "1.0", features = ["derive"]}
 ergo-lib = { version = "^0.13.0", path = "../../ergo-lib" }
 serde_json = "1.0"
diff --git a/bindings/ergo-lib-wasm/src/address.rs b/bindings/ergo-lib-wasm/src/address.rs
index 828892394..da628aef2 100644
--- a/bindings/ergo-lib-wasm/src/address.rs
+++ b/bindings/ergo-lib-wasm/src/address.rs
@@ -5,7 +5,7 @@ use ergo_lib::ergotree_ir::sigma_protocol::dlog_group::EcPoint;
 use ergo_lib::ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 use wasm_bindgen::prelude::*;
 
-use crate::{ergo_tree::ErgoTree, error_conversion::conv};
+use crate::{ergo_tree::ErgoTree, error_conversion::to_js};
 
 extern crate derive_more;
 use derive_more::{From, Into};
@@ -132,14 +132,14 @@ impl Address {
     pub fn recreate_from_ergo_tree(ergo_tree: &ErgoTree) -> Result<Address, JsValue> {
         ergo_lib::ergotree_ir::address::Address::recreate_from_ergo_tree(&ergo_tree.clone().into())
             .map(Address)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Create a P2PK address from serialized PK bytes(EcPoint/GroupElement)
     pub fn p2pk_from_pk_bytes(bytes: &[u8]) -> Result<Address, JsValue> {
         ergo_lib::ergotree_ir::address::Address::p2pk_from_pk_bytes(bytes)
             .map(Address)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Decode (base58) testnet address from string, checking that address is from the testnet
@@ -149,7 +149,7 @@ impl Address {
         )
         .parse_address_from_str(s)
         .map(Address)
-        .map_err(conv)
+        .map_err(to_js)
     }
 
     /// Decode (base58) mainnet address from string, checking that address is from the mainnet
@@ -159,7 +159,7 @@ impl Address {
         )
         .parse_address_from_str(s)
         .map(Address)
-        .map_err(conv)
+        .map_err(to_js)
     }
 
     /// Decode (base58) address from string without checking the network prefix
@@ -167,7 +167,7 @@ impl Address {
     pub fn from_base58(s: &str) -> Result<Address, JsValue> {
         ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_address_from_str(s)
             .map(Address)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Encode (base58) address
@@ -182,7 +182,7 @@ impl Address {
     pub fn from_bytes(data: Vec<u8>) -> Result<Address, JsValue> {
         ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_address_from_bytes(&data)
             .map(Address)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Encode address as serialized bytes (that includes the network prefix)
@@ -203,12 +203,12 @@ impl Address {
         EcPoint::sigma_parse_bytes(bytes)
             .map(|point| ergo_lib::ergotree_ir::address::Address::P2Pk(ProveDlog::new(point)))
             .map(Address)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Creates an ErgoTree script from the address
     pub fn to_ergo_tree(&self) -> Result<ErgoTree, JsValue> {
-        self.0.script().map(|script| script.into()).map_err(conv)
+        self.0.script().map(|script| script.into()).map_err(to_js)
     }
 }
 
@@ -232,7 +232,7 @@ impl NetworkAddress {
     pub fn from_base58(s: &str) -> Result<NetworkAddress, JsValue> {
         ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_network_address_from_str(s)
             .map(NetworkAddress)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Encode (base58) address
@@ -246,7 +246,7 @@ impl NetworkAddress {
             &data,
         )
         .map(NetworkAddress)
-        .map_err(conv)
+        .map_err(to_js)
     }
 
     /// Encode address as serialized bytes
diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs
index 42552ee42..7ead05cc3 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -1,5 +1,6 @@
 //! Ergo constant values
 
+use crate::error_conversion::to_js;
 use crate::utils::I64;
 use ergo_lib::chain::Base16Str;
 use ergo_lib::ergotree_ir::mir::constant::{TryExtractFrom, TryExtractInto};
@@ -30,7 +31,7 @@ impl Constant {
                 ))
             })?;
         ergo_lib::ergotree_ir::mir::constant::Constant::try_from(bytes)
-            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map_err(to_js)
             .map(Constant)
     }
 
@@ -46,7 +47,7 @@ impl Constant {
 
     /// Extract i32 value, returning error if wrong type
     pub fn to_i32(&self) -> Result<i32, JsValue> {
-        i32::try_extract_from(self.0.clone()).map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+        i32::try_extract_from(self.0.clone()).map_err(to_js)
     }
 
     /// Create from i64
@@ -57,7 +58,7 @@ impl Constant {
     /// Extract i64 value, returning error if wrong type
     pub fn to_i64(&self) -> Result<I64, JsValue> {
         i64::try_extract_from(self.0.clone())
-            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map_err(to_js)
             .map(I64::from)
     }
 
@@ -70,7 +71,7 @@ impl Constant {
     pub fn to_byte_array(&self) -> Result<Uint8Array, JsValue> {
         Vec::<u8>::try_extract_from(self.0.clone())
             .map(|v| Uint8Array::from(v.as_slice()))
-            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map_err(to_js)
     }
 
     /// Create `Coll[Long]` from string array
@@ -115,8 +116,7 @@ impl Constant {
 
     /// Parse raw [`EcPoint`] value from bytes and make [`ProveDlog`] constant
     pub fn from_ecpoint_bytes(bytes: &[u8]) -> Result<Constant, JsValue> {
-        let ecp = EcPoint::sigma_parse_bytes(bytes)
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
+        let ecp = EcPoint::sigma_parse_bytes(bytes).map_err(to_js)?;
         let c: ergo_lib::ergotree_ir::mir::constant::Constant = ProveDlog::new(ecp).into();
         Ok(c.into())
     }
@@ -134,7 +134,7 @@ impl Constant {
             .0
             .clone()
             .try_extract_into::<(Vec<u8>, Vec<u8>)>()
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
+            .map_err(to_js)?;
         Ok(vec![
             Uint8Array::from(bytes1.as_slice()),
             Uint8Array::from(bytes2.as_slice()),
@@ -154,7 +154,7 @@ impl Constant {
             .0
             .clone()
             .try_extract_into::<(i64, i64)>()
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))?;
+            .map_err(to_js)?;
         Ok(vec![
             JsValue::from_str(&l1.to_string()),
             JsValue::from_str(&l2.to_string()),
diff --git a/bindings/ergo-lib-wasm/src/block_header.rs b/bindings/ergo-lib-wasm/src/block_header.rs
index 019fddaa9..2f51e99ac 100644
--- a/bindings/ergo-lib-wasm/src/block_header.rs
+++ b/bindings/ergo-lib-wasm/src/block_header.rs
@@ -5,6 +5,8 @@ use wasm_bindgen::prelude::*;
 extern crate derive_more;
 use derive_more::{From, Into};
 
+use crate::error_conversion::to_js;
+
 /// Block header
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
@@ -14,9 +16,7 @@ pub struct BlockHeader(ergo_lib::chain::block_header::BlockHeader);
 impl BlockHeader {
     /// Parse from JSON (Node API)
     pub fn from_json(json: &str) -> Result<BlockHeader, JsValue> {
-        serde_json::from_str(json)
-            .map(Self)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        serde_json::from_str(json).map(Self).map_err(to_js)
     }
 }
 
diff --git a/bindings/ergo-lib-wasm/src/box_selector.rs b/bindings/ergo-lib-wasm/src/box_selector.rs
index 3c8032346..028a6ba22 100644
--- a/bindings/ergo-lib-wasm/src/box_selector.rs
+++ b/bindings/ergo-lib-wasm/src/box_selector.rs
@@ -7,6 +7,7 @@ use wasm_bindgen::prelude::*;
 use crate::box_coll::ErgoBoxes;
 use crate::ergo_box::BoxValue;
 use crate::ergo_box::ErgoBoxAssetsDataList;
+use crate::error_conversion::to_js;
 use crate::token::Tokens;
 
 extern crate derive_more;
@@ -71,7 +72,7 @@ impl SimpleBoxSelector {
                 target_balance.clone().into(),
                 target_tokens.as_slice(),
             )
-            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map_err(to_js)
             .map(BoxSelection)
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/contract.rs b/bindings/ergo-lib-wasm/src/contract.rs
index adee77aa6..744e6067c 100644
--- a/bindings/ergo-lib-wasm/src/contract.rs
+++ b/bindings/ergo-lib-wasm/src/contract.rs
@@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*;
 
 use crate::address::Address;
 use crate::ergo_tree::ErgoTree;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 
 /// Defines the contract(script) that will be guarding box contents
 #[wasm_bindgen]
@@ -17,7 +17,7 @@ impl Contract {
     /// create new contract that allow spending of the guarded box by a given recipient ([`Address`])
     pub fn pay_to_address(recipient: &Address) -> Result<Contract, JsValue> {
         chain::contract::Contract::pay_to_address(&recipient.clone().into())
-            .map_err(conv)
+            .map_err(to_js)
             .map(Contract)
     }
 
diff --git a/bindings/ergo-lib-wasm/src/ergo_box.rs b/bindings/ergo-lib-wasm/src/ergo_box.rs
index f0949fc25..ea12c6a76 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box.rs
@@ -24,7 +24,7 @@ use wasm_bindgen::prelude::*;
 
 use crate::ast::Constant;
 use crate::ergo_tree::ErgoTree;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 use crate::token::Tokens;
 use crate::utils::I64;
 use crate::{contract::Contract, transaction::TxId};
@@ -46,7 +46,7 @@ impl BoxId {
     pub fn from_str(box_id_str: String) -> Result<BoxId, JsValue> {
         chain::ergo_box::BoxId::try_from(box_id_str)
             .map(BoxId)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Base16 encoded string
@@ -126,7 +126,7 @@ impl ErgoBox {
             tx_id.clone().into(),
             index,
         )
-        .map_err(conv)?;
+        .map_err(to_js)?;
         Ok(ErgoBox(b))
     }
 
@@ -166,12 +166,12 @@ impl ErgoBox {
 
     /// JSON representation
     pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(conv)
+        JsValue::from_serde(&self.0.clone()).map_err(to_js)
     }
 
     /// JSON representation
     pub fn from_json(json: &str) -> Result<ErgoBox, JsValue> {
-        serde_json::from_str(json).map(Self).map_err(conv)
+        serde_json::from_str(json).map(Self).map_err(to_js)
     }
 }
 
@@ -210,7 +210,7 @@ impl BoxValue {
     /// Create from i64 with bounds check
     pub fn from_i64(v: &I64) -> Result<BoxValue, JsValue> {
         Ok(BoxValue(
-            chain::ergo_box::BoxValue::try_from(i64::from(v.clone()) as u64).map_err(conv)?,
+            chain::ergo_box::BoxValue::try_from(i64::from(v.clone()) as u64).map_err(to_js)?,
         ))
     }
 
diff --git a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
index 9927c2e41..b0c6938ac 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
@@ -4,7 +4,7 @@ use wasm_bindgen::prelude::*;
 
 use crate::ast::Constant;
 use crate::contract::Contract;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 use crate::token::Token;
 use crate::token::TokenAmount;
 use crate::token::TokenId;
@@ -56,7 +56,7 @@ impl ErgoBoxCandidateBuilder {
 
     /// Calculate serialized box size(in bytes)
     pub fn calc_box_size_bytes(&self) -> Result<usize, JsValue> {
-        self.0.calc_box_size_bytes().map_err(conv)
+        self.0.calc_box_size_bytes().map_err(to_js)
     }
 
     /// Calculate minimal box value for the current box serialized size(in bytes)
@@ -64,7 +64,7 @@ impl ErgoBoxCandidateBuilder {
         self.0
             .calc_min_box_value()
             .map(BoxValue::from)
-            .map_err(conv)
+            .map_err(to_js)
     }
 
     /// Set register with a given id (R4-R9) to the given value
@@ -112,6 +112,6 @@ impl ErgoBoxCandidateBuilder {
 
     /// Build the box candidate
     pub fn build(&self) -> Result<ErgoBoxCandidate, JsValue> {
-        self.0.clone().build().map_err(conv).map(ErgoBoxCandidate)
+        self.0.clone().build().map_err(to_js).map(ErgoBoxCandidate)
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/ergo_tree.rs b/bindings/ergo-lib-wasm/src/ergo_tree.rs
index ae4c8eaeb..e5e3194b1 100644
--- a/bindings/ergo-lib-wasm/src/ergo_tree.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_tree.rs
@@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*;
 extern crate derive_more;
 use derive_more::{From, Into};
 
-use crate::{ast::Constant, error_conversion::conv};
+use crate::{ast::Constant, error_conversion::to_js};
 
 /// The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
 #[wasm_bindgen]
@@ -20,8 +20,7 @@ pub struct ErgoTree(ergo_lib::ergotree_ir::ergo_tree::ErgoTree);
 impl ErgoTree {
     /// Decode from base16 encoded serialized ErgoTree
     pub fn from_base16_bytes(s: &str) -> Result<ErgoTree, JsValue> {
-        let bytes = Base16DecodedBytes::try_from(s.to_string())
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
+        let bytes = Base16DecodedBytes::try_from(s.to_string()).map_err(to_js)?;
         ErgoTree::from_bytes(bytes.0)
     }
 
@@ -29,24 +28,22 @@ impl ErgoTree {
     pub fn from_bytes(data: Vec<u8>) -> Result<ErgoTree, JsValue> {
         ergo_lib::ergotree_ir::ergo_tree::ErgoTree::sigma_parse_bytes(&data)
             .map(ErgoTree)
-            .map_err(conv)
+            .map_err(to_js)
     }
     /// Encode Ergo tree as serialized bytes
     pub fn to_bytes(&self) -> Result<Vec<u8>, JsValue> {
-        self.0.sigma_serialize_bytes().map_err(conv)
+        self.0.sigma_serialize_bytes().map_err(to_js)
     }
 
     /// Returns Base16-encoded serialized bytes
     pub fn to_base16_bytes(&self) -> Result<String, JsValue> {
-        self.0.to_base16_bytes().map_err(conv)
+        self.0.to_base16_bytes().map_err(to_js)
     }
 
     /// Returns constants number as stored in serialized ErgoTree or error if the parsing of
     /// constants is failed
     pub fn constants_len(&self) -> Result<usize, JsValue> {
-        self.0
-            .constants_len()
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))
+        self.0.constants_len().map_err(to_js)
     }
 
     /// Returns constant with given index (as stored in serialized ErgoTree)
@@ -56,7 +53,7 @@ impl ErgoTree {
         self.0
             .get_constant(index)
             .map(|opt| opt.map(|c| c.into()))
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))
+            .map_err(to_js)
     }
 
     /// Consumes the calling ErgoTree and returns new ErgoTree with a new constant value
@@ -66,14 +63,12 @@ impl ErgoTree {
         self.0
             .with_constant(index, constant.clone().into())
             .map(ErgoTree)
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))
+            .map_err(to_js)
     }
 
     /// Serialized proposition expression of SigmaProp type with
     /// ConstantPlaceholder nodes instead of Constant nodes
     pub fn template_bytes(&self) -> Result<Vec<u8>, JsValue> {
-        self.0
-            .template_bytes()
-            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))
+        self.0.template_bytes().map_err(to_js)
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index 00553d13d..9ed229854 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -1,5 +1,8 @@
 //! Trait implementations to simplify conversion of `ergo_lib` errors into `JsValue`s
 
+use std::num::ParseIntError;
+
+use base16::DecodeError;
 use ergo_lib::{
     chain::{
         ergo_box::{box_builder::ErgoBoxCandidateBuilderError, BoxValueError},
@@ -8,6 +11,7 @@ use ergo_lib::{
     },
     ergotree_ir::{
         address::{AddressEncoderError, AddressError},
+        ergo_tree::{ErgoTreeConstantError, ErgoTreeConstantsParsingError, ErgoTreeError},
         mir::constant::TryExtractFromError,
         serialization::{SigmaParsingError, SigmaSerializationError},
     },
@@ -22,7 +26,7 @@ use wasm_bindgen::JsValue;
 pub struct JsValueWrap(JsValue);
 
 /// Converts any error satisfying `Into<JsValueWrap>` into `JsValue`.
-pub fn conv<S: Into<JsValueWrap>>(s: S) -> JsValue {
+pub fn to_js<S: Into<JsValueWrap>>(s: S) -> JsValue {
     s.into().0
 }
 
@@ -49,3 +53,19 @@ from_error_to_wrap!(BoxValueError);
 from_error_to_wrap!(TokenAmountError);
 from_error_to_wrap!(TxBuilderError);
 from_error_to_wrap!(WalletError);
+from_error_to_wrap!(DecodeError);
+
+macro_rules! from_error_to_wrap_via_debug {
+    ($t:ident) => {
+        impl std::convert::From<$t> for JsValueWrap {
+            fn from(e: $t) -> Self {
+                JsValueWrap(JsValue::from_str(&format!("{:?}", e)))
+            }
+        }
+    };
+}
+
+from_error_to_wrap_via_debug!(ErgoTreeError);
+from_error_to_wrap_via_debug!(ErgoTreeConstantError);
+from_error_to_wrap_via_debug!(ErgoTreeConstantsParsingError);
+from_error_to_wrap_via_debug!(ParseIntError);
diff --git a/bindings/ergo-lib-wasm/src/prover_result.rs b/bindings/ergo-lib-wasm/src/prover_result.rs
index ec8041abb..8c64931e4 100644
--- a/bindings/ergo-lib-wasm/src/prover_result.rs
+++ b/bindings/ergo-lib-wasm/src/prover_result.rs
@@ -2,7 +2,7 @@
 
 use wasm_bindgen::prelude::*;
 
-use crate::{context_extension::ContextExtension, error_conversion::conv};
+use crate::{context_extension::ContextExtension, error_conversion::to_js};
 extern crate derive_more;
 use derive_more::{From, Into};
 
@@ -25,6 +25,6 @@ impl ProverResult {
 
     /// JSON representation
     pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(conv)
+        JsValue::from_serde(&self.0.clone()).map_err(to_js)
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/token.rs b/bindings/ergo-lib-wasm/src/token.rs
index 9139b13ea..43ead1334 100644
--- a/bindings/ergo-lib-wasm/src/token.rs
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -8,7 +8,7 @@ use ergo_lib::chain::Digest32;
 use wasm_bindgen::prelude::*;
 
 use crate::ergo_box::BoxId;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 use crate::utils::I64;
 
 /// Token id (32 byte digest)
@@ -28,8 +28,8 @@ impl TokenId {
     #[allow(clippy::should_implement_trait)]
     pub fn from_str(str: &str) -> Result<TokenId, JsValue> {
         Base16DecodedBytes::try_from(str.to_string())
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
-            .and_then(|bytes| Digest32::try_from(bytes).map_err(conv))
+            .map_err(to_js)
+            .and_then(|bytes| Digest32::try_from(bytes).map_err(to_js))
             .map(|dig| dig.into())
             .map(TokenId)
     }
@@ -56,7 +56,7 @@ impl TokenAmount {
     /// Create from i64 with bounds check
     pub fn from_i64(v: &I64) -> Result<TokenAmount, JsValue> {
         Ok(Self(
-            chain::token::TokenAmount::try_from(i64::from(v.clone()) as u64).map_err(conv)?,
+            chain::token::TokenAmount::try_from(i64::from(v.clone()) as u64).map_err(to_js)?,
         ))
     }
 
@@ -100,7 +100,7 @@ impl Token {
 
     /// JSON representation
     pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(|e| JsValue::from_str(&format!("{}", e)))
+        JsValue::from_serde(&self.0.clone()).map_err(to_js)
     }
 }
 
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index 46098ec87..c26468e6c 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -3,7 +3,7 @@
 use crate::box_coll::ErgoBoxCandidates;
 use crate::box_coll::ErgoBoxes;
 use crate::data_input::DataInputs;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 use crate::input::{Inputs, UnsignedInputs};
 use ergo_lib::chain;
 use std::convert::TryFrom;
@@ -34,8 +34,7 @@ impl TxId {
     /// convert a hex string into a TxId
     #[allow(clippy::should_implement_trait)]
     pub fn from_str(s: &str) -> Result<TxId, JsValue> {
-        let bytes = ergo_lib::chain::Base16DecodedBytes::try_from(s.to_string())
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))?;
+        let bytes = ergo_lib::chain::Base16DecodedBytes::try_from(s.to_string()).map_err(to_js)?;
 
         bytes
             .try_into()
@@ -72,12 +71,12 @@ impl Transaction {
 
     /// JSON representation
     pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(conv)
+        JsValue::from_serde(&self.0.clone()).map_err(to_js)
     }
 
     /// JSON representation
     pub fn from_json(json: &str) -> Result<Transaction, JsValue> {
-        serde_json::from_str(json).map(Self).map_err(conv)
+        serde_json::from_str(json).map(Self).map_err(to_js)
     }
 
     /// Inputs for transaction
@@ -146,12 +145,12 @@ impl UnsignedTransaction {
 
     /// JSON representation
     pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(conv)
+        JsValue::from_serde(&self.0.clone()).map_err(to_js)
     }
 
     /// JSON representation
     pub fn from_json(json: &str) -> Result<UnsignedTransaction, JsValue> {
-        serde_json::from_str(json).map(Self).map_err(conv)
+        serde_json::from_str(json).map(Self).map_err(to_js)
     }
 }
 
diff --git a/bindings/ergo-lib-wasm/src/tx_builder.rs b/bindings/ergo-lib-wasm/src/tx_builder.rs
index 9a6e973e6..3e726d601 100644
--- a/bindings/ergo-lib-wasm/src/tx_builder.rs
+++ b/bindings/ergo-lib-wasm/src/tx_builder.rs
@@ -4,7 +4,7 @@ use wasm_bindgen::prelude::*;
 
 use crate::box_selector::BoxSelection;
 use crate::data_input::DataInputs;
-use crate::error_conversion::conv;
+use crate::error_conversion::to_js;
 use crate::{
     address::Address, box_coll::ErgoBoxCandidates, ergo_box::BoxValue,
     transaction::UnsignedTransaction,
@@ -58,7 +58,7 @@ impl TxBuilder {
         self.0
             .clone()
             .build()
-            .map_err(conv)
+            .map_err(to_js)
             .map(UnsignedTransaction::from)
     }
 
diff --git a/bindings/ergo-lib-wasm/src/utils.rs b/bindings/ergo-lib-wasm/src/utils.rs
index ea82f76b2..ecec15714 100644
--- a/bindings/ergo-lib-wasm/src/utils.rs
+++ b/bindings/ergo-lib-wasm/src/utils.rs
@@ -2,6 +2,8 @@
 
 use wasm_bindgen::prelude::*;
 
+use crate::error_conversion::to_js;
+
 /// helper methods to get the fee address for various networks
 #[wasm_bindgen]
 pub struct MinerAddress {}
@@ -28,10 +30,7 @@ impl I64 {
     /// Create from a standard rust string representation
     #[allow(clippy::should_implement_trait)]
     pub fn from_str(string: &str) -> Result<I64, JsValue> {
-        string
-            .parse::<i64>()
-            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
-            .map(I64)
+        string.parse::<i64>().map_err(to_js).map(I64)
     }
 
     /// String representation of the value for use from environments that don't support i64
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index 0efef8311..7eeb421b0 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -3,7 +3,7 @@ use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
 use crate::{
-    box_coll::ErgoBoxes, ergo_state_ctx::ErgoStateContext, error_conversion::conv,
+    box_coll::ErgoBoxes, ergo_state_ctx::ErgoStateContext, error_conversion::to_js,
     secret_key::SecretKeys, transaction::Transaction, transaction::UnsignedTransaction,
 };
 
@@ -46,7 +46,7 @@ impl Wallet {
         };
         self.0
             .sign_transaction(tx_context, &_state_context.clone().into())
-            .map_err(conv)
+            .map_err(to_js)
             .map(Transaction::from)
     }
 }


Test patch: