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

Issue:
Parse box value from JSON as either a number or a string
As described in https://github.com/rooooooooob/eips/blob/eip-0012/eip-0012.md#value

- parse box value and token amount as either string or number;
- serialize them as string or number (option);
- update eip-12;

Number is needed for node API. 


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/Cargo.toml b/bindings/ergo-lib-wasm/Cargo.toml
index c15726d94..2f8604e63 100644
--- a/bindings/ergo-lib-wasm/Cargo.toml
+++ b/bindings/ergo-lib-wasm/Cargo.toml
@@ -16,7 +16,7 @@ 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" }
+ergo-lib = { version = "^0.13.0", path = "../../ergo-lib"}
 serde_json = "1.0"
 js-sys = "0.3"
 
@@ -30,21 +30,27 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
 
 derive_more = "0.99"
 
+[dependencies.serde_with]
+version = "1.9.1"
+features = [ "json" ]
+
 [dependencies.wasm-bindgen]
 version = "0.2.74"
 features = ["serde-serialize"]
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.22"
-
-# [dev-dependencies.proptest]
-# # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
-# version = "0.10"
-# # The default feature set includes things like process forking which are not
-# # supported in Web Assembly.
-# default-features = false
-# # Enable using the `std` crate.
-# features = ["std"]
+ergotree-ir = { version = "^0.13.0", path = "../../ergotree-ir", features = ["arbitrary"] }
+ergo-lib = { version = "^0.13.0", path = "../../ergo-lib", features = ["arbitrary"] }
+
+[dev-dependencies.proptest]
+# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
+version = "1.0.0"
+# The default feature set includes things like process forking which are not
+# supported in Web Assembly.
+default-features = false
+# Enable using the `std` crate.
+features = ["std"]
 
 [package.metadata.wasm-pack.profile.release]
 # wasm-opt = ["-O", "--enable-mutable-globals"]
diff --git a/bindings/ergo-lib-wasm/src/ergo_box.rs b/bindings/ergo-lib-wasm/src/ergo_box.rs
index 7feeab00b..94b0ff5dc 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box.rs
@@ -27,6 +27,7 @@ use wasm_bindgen::prelude::*;
 use crate::ast::Constant;
 use crate::ergo_tree::ErgoTree;
 use crate::error_conversion::to_js;
+use crate::json::ErgoBoxJsonEip12;
 use crate::token::Tokens;
 use crate::utils::I64;
 use crate::{contract::Contract, transaction::TxId};
@@ -171,12 +172,21 @@ impl ErgoBox {
             .map(Constant::from)
     }
 
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(to_js)
+    /// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
+    pub fn to_json(&self) -> Result<String, JsValue> {
+        serde_json::to_string_pretty(&self.0.clone())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+
+    /// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
+    /// (similar to [`Self::to_json`], but as JS object with box value and token amounts encoding as strings)
+    pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
+        let box_dapp: ErgoBoxJsonEip12 = self.0.clone().into();
+        JsValue::from_serde(&box_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
-    /// JSON representation
+    /// parse from JSON
+    /// supports Ergo Node/Explorer API and box values and token amount encoded as strings
     pub fn from_json(json: &str) -> Result<ErgoBox, JsValue> {
         serde_json::from_str(json).map(Self).map_err(to_js)
     }
diff --git a/bindings/ergo-lib-wasm/src/json.rs b/bindings/ergo-lib-wasm/src/json.rs
new file mode 100644
index 000000000..2b49cb6a0
--- /dev/null
+++ b/bindings/ergo-lib-wasm/src/json.rs
@@ -0,0 +1,223 @@
+//! JSON serialization according to EIP-12 (using strings for BoxValue and TokenAmount)
+
+use std::convert::TryInto;
+
+use derive_more::FromStr;
+use ergo_lib::chain::transaction::unsigned::UnsignedTransaction;
+use ergo_lib::chain::transaction::DataInput;
+use ergo_lib::chain::transaction::Input;
+use ergo_lib::chain::transaction::Transaction;
+use ergo_lib::chain::transaction::TxId;
+use ergo_lib::chain::transaction::UnsignedInput;
+use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValue;
+use ergo_lib::ergotree_ir::chain::ergo_box::BoxId;
+use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox;
+use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergo_lib::ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
+use ergo_lib::ergotree_ir::chain::token::Token;
+use ergo_lib::ergotree_ir::chain::token::TokenId;
+use ergo_lib::ergotree_ir::ergo_tree::ErgoTree;
+use serde::Serialize;
+
+#[derive(Serialize, PartialEq, Debug, Clone)]
+pub(crate) struct TransactionJsonEip12 {
+    #[cfg_attr(feature = "json", serde(rename = "id"))]
+    pub tx_id: TxId,
+    /// inputs, that will be spent by this transaction.
+    #[cfg_attr(feature = "json", serde(rename = "inputs"))]
+    pub inputs: Vec<Input>,
+    /// inputs, that are not going to be spent by transaction, but will be reachable from inputs
+    /// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
+    /// included in transaction cost and they do not contain spending proofs.
+    #[cfg_attr(feature = "json", serde(rename = "dataInputs"))]
+    pub data_inputs: Vec<DataInput>,
+    #[cfg_attr(feature = "json", serde(rename = "outputs"))]
+    pub outputs: Vec<ErgoBoxJsonEip12>,
+}
+
+impl From<Transaction> for TransactionJsonEip12 {
+    fn from(t: Transaction) -> Self {
+        TransactionJsonEip12 {
+            tx_id: t.id(),
+            inputs: t.inputs.try_into().unwrap(),
+            data_inputs: t
+                .data_inputs
+                .map(|di| di.try_into().unwrap())
+                .unwrap_or_else(Vec::new),
+            outputs: t.outputs.into_iter().map(|b| b.into()).collect(),
+        }
+    }
+}
+
+#[derive(Serialize, PartialEq, Debug, Clone)]
+pub(crate) struct UnsignedTransactionJsonEip12 {
+    /// unsigned inputs, that will be spent by this transaction.
+    #[cfg_attr(feature = "json", serde(rename = "inputs"))]
+    pub inputs: Vec<UnsignedInput>,
+    /// inputs, that are not going to be spent by transaction, but will be reachable from inputs
+    /// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
+    /// included in transaction cost and they do not contain spending proofs.
+    #[cfg_attr(feature = "json", serde(rename = "dataInputs"))]
+    pub data_inputs: Vec<DataInput>,
+    /// box candidates to be created by this transaction
+    #[cfg_attr(feature = "json", serde(rename = "outputs"))]
+    pub outputs: Vec<ErgoBoxCandidateJsonEip12>,
+}
+
+impl From<UnsignedTransaction> for UnsignedTransactionJsonEip12 {
+    fn from(t: UnsignedTransaction) -> Self {
+        UnsignedTransactionJsonEip12 {
+            inputs: t.inputs.try_into().unwrap(),
+            data_inputs: t
+                .data_inputs
+                .map(|di| di.try_into().unwrap())
+                .unwrap_or_else(Vec::new),
+            outputs: t.output_candidates.into_iter().map(|b| b.into()).collect(),
+        }
+    }
+}
+
+#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
+pub(crate) struct ErgoBoxJsonEip12 {
+    #[serde(rename = "boxId", alias = "id")]
+    pub box_id: Option<BoxId>,
+    /// amount of money associated with the box
+    #[serde(rename = "value")]
+    pub value: BoxValueJsonEip12,
+    /// guarding script, which should be evaluated to true in order to open this box
+    #[serde(
+        rename = "ergoTree",
+        with = "ergo_lib::ergotree_ir::chain::json::ergo_tree"
+    )]
+    pub ergo_tree: ErgoTree,
+    /// secondary tokens the box contains
+    #[serde(rename = "assets")]
+    pub tokens: Vec<TokenJsonEip12>,
+    ///  additional registers the box can carry over
+    #[serde(rename = "additionalRegisters")]
+    pub additional_registers: NonMandatoryRegisters,
+    /// height when a transaction containing the box was created.
+    /// This height is declared by user and should not exceed height of the block,
+    /// containing the transaction with this box.
+    #[serde(rename = "creationHeight")]
+    pub creation_height: u32,
+    /// id of transaction which created the box
+    #[serde(rename = "transactionId", alias = "txId")]
+    pub transaction_id: TxId,
+    /// number of box (from 0 to total number of boxes the transaction with transactionId created - 1)
+    #[serde(rename = "index")]
+    pub index: u16,
+}
+
+impl From<ErgoBox> for ErgoBoxJsonEip12 {
+    fn from(b: ErgoBox) -> Self {
+        ErgoBoxJsonEip12 {
+            box_id: b.box_id().into(),
+            value: b.value.into(),
+            ergo_tree: b.ergo_tree,
+            tokens: b.tokens.into_iter().map(|t| t.into()).collect(),
+            additional_registers: b.additional_registers,
+            creation_height: b.creation_height,
+            transaction_id: b.transaction_id,
+            index: b.index,
+        }
+    }
+}
+
+/// Contains the same fields as `ErgoBox`, except if transaction id and index,
+/// that will be calculated after full transaction formation.
+/// Use [`box_builder::ErgoBoxCandidateBuilder`] to create an instance.
+#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
+pub(crate) struct ErgoBoxCandidateJsonEip12 {
+    /// amount of money associated with the box
+    #[serde(rename = "value")]
+    pub value: BoxValueJsonEip12,
+    /// guarding script, which should be evaluated to true in order to open this box
+    #[serde(
+        rename = "ergoTree",
+        with = "ergo_lib::ergotree_ir::chain::json::ergo_tree"
+    )]
+    pub ergo_tree: ErgoTree,
+    #[serde(rename = "assets")]
+    pub tokens: Vec<TokenJsonEip12>,
+    ///  additional registers the box can carry over
+    #[serde(rename = "additionalRegisters")]
+    pub additional_registers: NonMandatoryRegisters,
+    /// height when a transaction containing the box was created.
+    /// This height is declared by user and should not exceed height of the block,
+    /// containing the transaction with this box.
+    #[serde(rename = "creationHeight")]
+    pub creation_height: u32,
+}
+
+impl From<ErgoBoxCandidate> for ErgoBoxCandidateJsonEip12 {
+    fn from(b: ErgoBoxCandidate) -> Self {
+        ErgoBoxCandidateJsonEip12 {
+            value: b.value.into(),
+            ergo_tree: b.ergo_tree,
+            tokens: b.tokens.into_iter().map(|t| t.into()).collect(),
+            additional_registers: b.additional_registers,
+            creation_height: b.creation_height,
+        }
+    }
+}
+
+#[serde_with::serde_as]
+#[derive(
+    serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, FromStr,
+)]
+/// Box value in nanoERGs with bound checks
+pub(crate) struct BoxValueJsonEip12(#[serde_as(as = "serde_with::DisplayFromStr")] u64);
+
+impl From<BoxValue> for BoxValueJsonEip12 {
+    fn from(bv: BoxValue) -> Self {
+        BoxValueJsonEip12(*bv.as_u64())
+    }
+}
+
+/// Token represented with token id paired with it's amount
+#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
+pub struct TokenJsonEip12 {
+    /// token id
+    #[serde(rename = "tokenId")]
+    pub token_id: TokenId,
+    /// token amount
+    #[serde(rename = "amount")]
+    pub amount: TokenAmountJsonEip12,
+}
+
+impl From<Token> for TokenJsonEip12 {
+    fn from(t: Token) -> Self {
+        TokenJsonEip12 {
+            token_id: t.token_id,
+            amount: TokenAmountJsonEip12(*t.amount.as_u64()),
+        }
+    }
+}
+
+/// Token amount with bound checks
+#[serde_with::serde_as]
+#[derive(Serialize, PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
+pub struct TokenAmountJsonEip12(
+    // Encodes as string always
+    #[serde_as(as = "serde_with::DisplayFromStr")] u64,
+);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ergo_box_roundtrip_to_json(b in any::<ErgoBox>()) {
+            let wasm_box: crate::ergo_box::ErgoBox = b.into();
+            let j = wasm_box.to_json().unwrap();
+            // eprintln!("{}", j);
+            let wasm_box_parsed = crate::ergo_box::ErgoBox::from_json(&j).unwrap();
+            prop_assert_eq![wasm_box, wasm_box_parsed];
+        }
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/lib.rs b/bindings/ergo-lib-wasm/src/lib.rs
index 86490134c..01660a0e0 100644
--- a/bindings/ergo-lib-wasm/src/lib.rs
+++ b/bindings/ergo-lib-wasm/src/lib.rs
@@ -36,3 +36,4 @@ pub mod utils;
 pub mod wallet;
 
 mod error_conversion;
+pub(crate) mod json;
diff --git a/bindings/ergo-lib-wasm/src/prover_result.rs b/bindings/ergo-lib-wasm/src/prover_result.rs
index 8c64931e4..7d3331855 100644
--- a/bindings/ergo-lib-wasm/src/prover_result.rs
+++ b/bindings/ergo-lib-wasm/src/prover_result.rs
@@ -23,8 +23,8 @@ impl ProverResult {
         self.0.extension.clone().into()
     }
 
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(to_js)
+    /// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
+    pub fn to_json(&self) -> Result<String, JsValue> {
+        serde_json::to_string_pretty(&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 4db56356a..68d4d60d8 100644
--- a/bindings/ergo-lib-wasm/src/token.rs
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;
 
 use crate::ergo_box::BoxId;
 use crate::error_conversion::to_js;
+use crate::json::TokenJsonEip12;
 use crate::utils::I64;
 
 /// Token id (32 byte digest)
@@ -109,9 +110,17 @@ impl Token {
         TokenAmount(self.0.amount)
     }
 
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(to_js)
+    /// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
+    pub fn to_json(&self) -> Result<String, JsValue> {
+        serde_json::to_string_pretty(&self.0.clone())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+
+    /// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
+    /// (similar to [`Self::to_json`], but as JS object with token amount encoding as string)
+    pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
+        let t_dapp: TokenJsonEip12 = self.0.clone().into();
+        JsValue::from_serde(&t_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 }
 
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index a0c37ad0b..e1cfaae52 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -5,6 +5,8 @@ use crate::box_coll::ErgoBoxes;
 use crate::data_input::DataInputs;
 use crate::error_conversion::to_js;
 use crate::input::{Inputs, UnsignedInputs};
+use crate::json::TransactionJsonEip12;
+use crate::json::UnsignedTransactionJsonEip12;
 use ergo_lib::chain;
 use ergo_lib::chain::transaction::distinct_token_ids;
 use ergo_lib::ergotree_ir::chain::base16_bytes::Base16DecodedBytes;
@@ -91,12 +93,21 @@ impl Transaction {
         self.0.id().into()
     }
 
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(to_js)
+    /// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
+    pub fn to_json(&self) -> Result<String, JsValue> {
+        serde_json::to_string_pretty(&self.0.clone())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
-    /// JSON representation
+    /// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
+    /// (similar to [`Self::to_json`], but as JS object with box value and token amount encoding as strings)
+    pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
+        let tx_dapp: TransactionJsonEip12 = self.0.clone().into();
+        JsValue::from_serde(&tx_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+
+    /// parse from JSON
+    /// supports Ergo Node/Explorer API and box values and token amount encoded as strings
     pub fn from_json(json: &str) -> Result<Transaction, JsValue> {
         serde_json::from_str(json).map(Self).map_err(to_js)
     }
@@ -165,12 +176,21 @@ impl UnsignedTransaction {
         self.0.output_candidates.as_vec().clone().into()
     }
 
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0.clone()).map_err(to_js)
+    /// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
+    pub fn to_json(&self) -> Result<String, JsValue> {
+        serde_json::to_string_pretty(&self.0.clone())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+
+    /// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
+    /// (similar to [`Self::to_json`], but as JS object with box value and token amount encoding as strings)
+    pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
+        let tx_dapp: UnsignedTransactionJsonEip12 = self.0.clone().into();
+        JsValue::from_serde(&tx_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
-    /// JSON representation
+    /// parse from JSON
+    /// supports Ergo Node/Explorer API and box values and token amount encoded as strings
     pub fn from_json(json: &str) -> Result<UnsignedTransaction, JsValue> {
         serde_json::from_str(json).map(Self).map_err(to_js)
     }
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 763db5aa9..be02cf254 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -16,6 +16,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - add `to_bytes()` for `BoxValue` and `TokenAmount` in Wasm [#387](https://github.com/ergoplatform/sigma-rust/pull/387);
 - `Constant::sigma_serialize_bytes()`, `ContextExtension::sigma_serialize_bytes()` in Wasm [#387](https://github.com/ergoplatform/sigma-rust/pull/387);
 - `TokenId::as_bytes()` and `BoxId::as_bytes()` in Wasm [#387](https://github.com/ergoplatform/sigma-rust/pull/387);
+- add (Coll[Byte], Coll[Byte]) and (Long, Long) support for Constant conversion in JS [#386](https://github.com/ergoplatform/sigma-rust/pull/386);
+- Serialization for `SigmaBoolean` types [#341](https://github.com/ergoplatform/sigma-rust/pull/351);
+- Diffie-Hellman tuples support in sigma protocol [#315](https://github.com/ergoplatform/sigma-rust/pull/315);
+- `Coll.zip` [#350](https://github.com/ergoplatform/sigma-rust/pull/350);
+- `BigInt256` 256-bit BigInt [#325](https://github.com/ergoplatform/sigma-rust/pull/325);
+- Logical XOR [#349](https://github.com/ergoplatform/sigma-rust/pull/349);
+- `groupGenerator` global variable [#340](https://github.com/ergoplatform/sigma-rust/pull/340);
+- `GroupElement.getEncoded` method for GroupElement [#335](https://github.com/ergoplatform/sigma-rust/pull/335);
+- `Negate` IR node for GroupElement [#334](https://github.com/ergoplatform/sigma-rust/pull/334);
+- `Exponentiate` IR node for GroupElement [#321](https://github.com/ergoplatform/sigma-rust/pull/321);
+- WASM `to_js_eip12()` (along `to_json()`) that encodes JSON according to [EIP-12](https://github.com/ergoplatform/eips/pull/23) (similar to JSON by `to_json()`, but box value and token amount encodes as strings to avoid accuracy loss in JS) [#346](https://github.com/ergoplatform/sigma-rust/pull/346);
 - `Coll.slice` [#309](https://github.com/ergoplatform/sigma-rust/pull/309);
 - Byte-wise XOR for byte arrays [#310](https://github.com/ergoplatform/sigma-rust/pull/310);
 - `Constant::from_i64_str_array` and `to_i64_str_array` for `Coll[Long]` encoding [#311](https://github.com/ergoplatform/sigma-rust/pull/311);
@@ -27,19 +38,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - `Constant::to/from_ergo_box`to Wasm API [#397](https://github.com/ergoplatform/sigma-rust/pull/397);
 
 ### Changed(BREAKING!):
-- `ErgoTree::to_bytes()` renamed to  `ErgoTree::sigma_serialize_bytes()` in Wasm [#387](https://github.com/ergoplatform/sigma-rust/pull/387);
-- `ErgoBox` and other types that it uses moved to `ergotree_ir` crate and re-exported in `ergo_lib::ergotree_ir` [#397](https://github.com/ergoplatform/sigma-rust/pull/397);
-
-### Changed
-- `SigmaSerializable:sigma_serialize` errors are extended beyond `io::Error` [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
+- **WASM `to_json()` returns string (instead of `JsValue`) to avoid silent accuracy loss on JS object -> text conversion on JS side [#346](https://github.com/ergoplatform/sigma-rust/pull/346);**
 - `SigmaSerializable:sigma_serialize_bytes` made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - `ErgoBox::new`, `from_box_candidate` made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - `ErgoTree::new`, `template_bytes`, `to_base16_bytes` made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - `Transaction::new`  made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - WASM `ErgoBox::new`, `byte_to_sign` made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - WASM `ErgoTree::to_bytes`, `to_base16_bytes` made failible (returns `Result`) [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
+- `ErgoTree::to_bytes()` renamed to  `ErgoTree::sigma_serialize_bytes()` in Wasm [#387](https://github.com/ergoplatform/sigma-rust/pull/387);
+- `ErgoBox` and other types that it uses moved to `ergotree_ir` crate and re-exported in `ergo_lib::ergotree_ir` [#397](https://github.com/ergoplatform/sigma-rust/pull/397);
+
+### Changed
+- Switched to `ThreadRng` CSPRNG for source of randomness in sigma protocol [#315](https://github.com/ergoplatform/sigma-rust/pull/315);
+- `SigmaSerializable:sigma_serialize` errors are extended beyond `io::Error` [#328](https://github.com/ergoplatform/sigma-rust/pull/328);
 - `ergotree-ir::mir::constant::constant_placeholder` module is public;
 - `ErgoTree::set_constant` is removed in favor of `ErgoTree::with_constant` with an added check for matching constant type[#323](https://github.com/ergoplatform/sigma-rust/pull/323);
+- Eliminate and ban panics (unwrap, panic!, todo!, etc) in `sigma-ser`, `ergotree-ir` and `ergotree-interpreter` crates [#328](https://github.com/ergoplatform/sigma-rust/pull/328), [#344](https://github.com/ergoplatform/sigma-rust/pull/344);
 
 ## [0.13.3] - 2021-06-11
 
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index e7124d9f3..b926c4546 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -28,25 +28,34 @@ derive_more = "0.99"
 # bounded-vec = { git = "https://github.com/ergoplatform/bounded-vec", rev="f684d78" }
 bounded-vec = { version = "^0.4.0" }
 num-bigint = "0.4.0"
+proptest-derive = {version = "0.3.0", optional = true }
+
+[dependencies.proptest]
+# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
+version = "1.0.0"
+# The default feature set includes things like process forking which are not
+# supported in Web Assembly.
+default-features = false
+# Enable using the `std` crate.
+features = ["std"]
+optional = true
+
+[dependencies.serde_with]
+version = "1.9.1"
+features = [ "json" ]
+optional = true
 
 [features]
 default = ["json", "compiler"]
-json = ["serde", "serde_json"]
+json = ["serde", "serde_json", "serde_with"]
 compiler = ["ergoscript-compiler"]
+arbitrary = ["proptest", "proptest-derive"]
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.10"
-proptest-derive = "0.3.0"
 rand = "0.8.4"
 ergotree-ir = { version = "^0.13.0", path = "../ergotree-ir", features = ["arbitrary"] }
 ergotree-interpreter = { version = "^0.13.0", path = "../ergotree-interpreter", features = ["arbitrary"] }
 sigma-test-util = { version = "0.2.0", path = "../sigma-test-util" }
+pretty_assertions = "0.7.2"
 
-[dev-dependencies.proptest]
-# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
-version = "1.0.0"
-# The default feature set includes things like process forking which are not
-# supported in Web Assembly.
-default-features = false
-# Enable using the `std` crate.
-features = ["std"]
diff --git a/ergo-lib/src/chain.rs b/ergo-lib/src/chain.rs
index c707fe884..ac5dded18 100644
--- a/ergo-lib/src/chain.rs
+++ b/ergo-lib/src/chain.rs
@@ -1,7 +1,7 @@
 //! Ergo chain types
 
 #[cfg(feature = "json")]
-mod json;
+pub mod json;
 
 pub mod contract;
 pub mod ergo_box;
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index 2916541fc..ac938e755 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -6,6 +6,7 @@ pub(crate) mod transaction;
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use serde::{Deserialize, Serialize};
 
+/// Serde remote type
 #[cfg_attr(
     feature = "json",
     derive(Serialize, Deserialize),
diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml
index 934c16978..6cf73fa9a 100644
--- a/ergotree-interpreter/Cargo.toml
+++ b/ergotree-interpreter/Cargo.toml
@@ -25,13 +25,22 @@ lazy_static = "1.4"
 thiserror = "1"
 derive_more = "0.99"
 num-traits = "0.2.14"
-proptest = {version = "1.0.0", optional = true }
 proptest-derive = {version = "0.3.0", optional = true }
 base16 = "0.2.1"
 bytes = "1.1"
 num-bigint = "0.4.0"
 scorex_crypto_avltree = "0.1.0"
 
+[dependencies.proptest]
+# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
+version = "1.0.0"
+# The default feature set includes things like process forking which are not
+# supported in Web Assembly.
+default-features = false
+# Enable using the `std` crate.
+features = ["std"]
+optional = true
+
 [features]
 default = []
 arbitrary = ["proptest", "proptest-derive"]
diff --git a/ergotree-interpreter/src/eval/coll_slice.rs b/ergotree-interpreter/src/eval/coll_slice.rs
index 41549cf4e..0192adc93 100644
--- a/ergotree-interpreter/src/eval/coll_slice.rs
+++ b/ergotree-interpreter/src/eval/coll_slice.rs
@@ -87,7 +87,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), vec![]);
+        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), Vec::<i64>::new());
     }
 
     #[test]
diff --git a/ergotree-interpreter/src/eval/scoll.rs b/ergotree-interpreter/src/eval/scoll.rs
index ca8366612..34183ab3e 100644
--- a/ergotree-interpreter/src/eval/scoll.rs
+++ b/ergotree-interpreter/src/eval/scoll.rs
@@ -561,7 +561,7 @@ mod tests {
         .unwrap()
         .into();
         let res = eval_out_wo_ctx::<Vec<i32>>(&expr);
-        assert_eq!(res, vec![]);
+        assert_eq!(res, Vec::<i32>::new());
     }
 
     #[test]
diff --git a/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs b/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
index 1280f7783..08084dad9 100644
--- a/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
+++ b/ergotree-interpreter/src/sigma_protocol/crypto_utils.rs
@@ -1,4 +1,4 @@
-use rand::RngCore;
+use elliptic_curve::rand_core::RngCore;
 
 /// Generate cryptographically secure random bytes
 pub fn secure_random_bytes(how_many: usize) -> Vec<u8> {
diff --git a/ergotree-interpreter/src/sigma_protocol/private_input.rs b/ergotree-interpreter/src/sigma_protocol/private_input.rs
index 8df62987b..78513cc3d 100644
--- a/ergotree-interpreter/src/sigma_protocol/private_input.rs
+++ b/ergotree-interpreter/src/sigma_protocol/private_input.rs
@@ -1,11 +1,11 @@
 //! Private input types for the prover's secrets
 use std::convert::TryInto;
 
+use elliptic_curve::group::ff::PrimeField;
 use ergotree_ir::sigma_protocol::dlog_group;
 use ergotree_ir::sigma_protocol::sigma_boolean::ProveDhTuple;
 use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 
-use elliptic_curve::group::ff::PrimeField;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 use k256::Scalar;
 
diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml
index 53224f45f..008c000d2 100644
--- a/ergotree-ir/Cargo.toml
+++ b/ergotree-ir/Cargo.toml
@@ -19,11 +19,11 @@ sigma-util = { version = "^0.2.0", path = "../sigma-util" }
 k256 = { version = "0.9.6", features = ["zeroize", "arithmetic", "ecdsa"] }
 elliptic-curve = {version = "0.10.6", features = ["zeroize", "ff"]}
 thiserror = "1"
-rand = "0.8.4"
+# used in elliptic-curve(in ergo-lib), compiled here with WASM support
+getrandom = {version = "0.2.3", features = ["js"]}
 lazy_static = "1.4"
 derive_more = "0.99"
 impl-trait-for-tuples = "0.2.0"
-proptest = {version = "1.0.0", optional = true }
 proptest-derive = {version = "0.3.0", optional = true }
 bs58 = "0.4.0"
 base16 = "0.2.1"
@@ -39,11 +39,27 @@ indexmap = "1.3.2"
 serde = { version = "1.0", features = ["derive"], optional = true }
 serde_json = { version = "1.0", optional = true }
 
+[dependencies.proptest]
+# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
+version = "1.0.0"
+# The default feature set includes things like process forking which are not
+# supported in Web Assembly.
+default-features = false
+# Enable using the `std` crate.
+features = ["std"]
+optional = true
+
+[dependencies.serde_with]
+version = "1.9.1"
+features = [ "json" ]
+optional = true
+
 [features]
 default = []
 arbitrary = ["proptest", "proptest-derive"]
-json = ["serde", "serde_json"]
+json = ["serde", "serde_json", "serde_with"]
 
 [dev-dependencies]
-proptest = "1.0.0"
 sigma-test-util = { version = "0.2.0", path = "../sigma-test-util" }
+rand = "0.8.3"
+pretty_assertions = "0.7.2"
diff --git a/ergotree-ir/src/chain/digest32.rs b/ergotree-ir/src/chain/digest32.rs
index 0a02a2706..fcf286534 100644
--- a/ergotree-ir/src/chain/digest32.rs
+++ b/ergotree-ir/src/chain/digest32.rs
@@ -131,7 +131,7 @@ pub enum Digest32Error {
 /// Arbitrary
 #[allow(clippy::unwrap_used)]
 #[cfg(feature = "arbitrary")]
-pub mod arbitrary {
+pub(crate) mod arbitrary {
 
     use super::Digest;
     use proptest::prelude::{Arbitrary, BoxedStrategy};
@@ -149,3 +149,6 @@ pub mod arbitrary {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {}
diff --git a/ergotree-ir/src/chain/ergo_box/box_value.rs b/ergotree-ir/src/chain/ergo_box/box_value.rs
index 1765243af..c175b8e28 100644
--- a/ergotree-ir/src/chain/ergo_box/box_value.rs
+++ b/ergotree-ir/src/chain/ergo_box/box_value.rs
@@ -7,14 +7,27 @@ use crate::serialization::SigmaSerializeResult;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
+use derive_more::FromStr;
 use std::convert::TryFrom;
 use thiserror::Error;
 
+#[cfg(not(feature = "json"))]
+#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, FromStr)]
 /// Box value in nanoERGs with bound checks
-#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
-#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 pub struct BoxValue(pub(crate) u64);
 
+#[cfg(feature = "json")]
+#[serde_with::serde_as]
+#[derive(
+    serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, FromStr,
+)]
+/// Box value in nanoERGs with bound checks
+pub struct BoxValue(
+    // Tries to decode as u64 first, then fallback to string. Encodes as u64 always
+    // see details - https://docs.rs/serde_with/1.9.4/serde_with/struct.PickFirst.html
+    #[serde_as(as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")] pub(crate) u64,
+);
+
 impl BoxValue {
     /// Minimal box value per byte of the serialized box that was set on on launch
     pub const MIN_VALUE_PER_BOX_BYTE: u32 = 360;
diff --git a/ergotree-ir/src/chain/ergo_box/register.rs b/ergotree-ir/src/chain/ergo_box/register.rs
index fa98dbbf9..d0cd717d3 100644
--- a/ergotree-ir/src/chain/ergo_box/register.rs
+++ b/ergotree-ir/src/chain/ergo_box/register.rs
@@ -348,7 +348,7 @@ impl TryFrom<i8> for MandatoryRegisterId {
 
 #[allow(clippy::unwrap_used)]
 #[cfg(feature = "arbitrary")]
-pub mod arbitrary {
+pub(crate) mod arbitrary {
     use super::*;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
diff --git a/ergotree-ir/src/chain/json.rs b/ergotree-ir/src/chain/json.rs
index e7db0df90..723f3d479 100644
--- a/ergotree-ir/src/chain/json.rs
+++ b/ergotree-ir/src/chain/json.rs
@@ -3,7 +3,7 @@
 use serde::Serializer;
 
 pub(crate) mod ergo_box;
-pub(crate) mod ergo_tree;
+pub mod ergo_tree;
 
 /// Serialize bytes ([u8]) as base16 encoded string
 pub fn serialize_bytes<S, T>(bytes: T, serializer: S) -> Result<S::Ok, S::Error>
diff --git a/ergotree-ir/src/chain/json/ergo_box.rs b/ergotree-ir/src/chain/json/ergo_box.rs
index 8f60c1a92..4bc3e7cbd 100644
--- a/ergotree-ir/src/chain/json/ergo_box.rs
+++ b/ergotree-ir/src/chain/json/ergo_box.rs
@@ -22,6 +22,8 @@ use derive_more::From;
 use serde::Deserialize;
 use thiserror::Error;
 
+mod box_value;
+
 #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
 pub struct ErgoBoxFromJson {
     #[serde(rename = "boxId", alias = "id")]
@@ -164,6 +166,8 @@ mod tests {
     use crate::chain::ergo_box::ErgoBox;
     use crate::chain::ergo_box::NonMandatoryRegisterId;
     use crate::chain::ergo_box::NonMandatoryRegisters;
+    use crate::chain::token::Token;
+    use pretty_assertions::assert_eq;
     use proptest::prelude::*;
 
     proptest! {
@@ -330,4 +334,35 @@ mod tests {
         let b: ErgoBox = serde_json::from_str(box_json).unwrap();
         assert_eq!(b.value, 2875858910u64.try_into().unwrap());
     }
+
+    #[test]
+    fn parse_token_amount_as_num() {
+        let token_json = r#"
+        {
+            "tokenId": "2d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c",
+            "amount": 99999999998
+        }"#;
+        let t: Token = serde_json::from_str(token_json).unwrap();
+        assert_eq!(t.amount, 99999999998u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn parse_token_amount_as_str() {
+        let token_json = r#"               
+        {
+            "tokenId": "2d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c",
+            "amount": "99999999998"
+        }"#;
+        let t: Token = serde_json::from_str(token_json).unwrap();
+        assert_eq!(t.amount, 99999999998u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn encode_token_amount_as_num() {
+        let token_json = "{\n  \"tokenId\": \"2d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c\",\n  \"amount\": 99999999998\n}";
+        let t: Token = serde_json::from_str(token_json).unwrap();
+        assert_eq!(t.amount, 99999999998u64.try_into().unwrap());
+        let to_json = serde_json::to_string_pretty(&t).unwrap();
+        assert_eq!(to_json, token_json);
+    }
 }
diff --git a/ergotree-ir/src/chain/json/ergo_box/box_value.rs b/ergotree-ir/src/chain/json/ergo_box/box_value.rs
new file mode 100644
index 000000000..82130aef3
--- /dev/null
+++ b/ergotree-ir/src/chain/json/ergo_box/box_value.rs
@@ -0,0 +1,29 @@
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+mod tests {
+    use crate::chain::ergo_box::box_value::BoxValue;
+    use std::convert::TryInto;
+
+    #[test]
+    fn parse_value_as_str() {
+        let json = "\"67500000000\"";
+        let v: BoxValue = serde_json::from_str(json).unwrap();
+        assert_eq!(v, 67500000000u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn parse_value_as_num() {
+        let json = "67500000000";
+        let v: BoxValue = serde_json::from_str(json).unwrap();
+        assert_eq!(v, 67500000000u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn encode_value_as_num() {
+        let json = "67500000000";
+        let v: BoxValue = serde_json::from_str(json).unwrap();
+        assert_eq!(v, 67500000000u64.try_into().unwrap());
+        let to_json = serde_json::to_string_pretty(&v).unwrap();
+        assert_eq!(to_json, json);
+    }
+}
diff --git a/ergotree-ir/src/chain/json/ergo_tree.rs b/ergotree-ir/src/chain/json/ergo_tree.rs
index 49d4b64f9..d9a28a5b1 100644
--- a/ergotree-ir/src/chain/json/ergo_tree.rs
+++ b/ergotree-ir/src/chain/json/ergo_tree.rs
@@ -1,21 +1,25 @@
+//! ErgoTree JSON encoding
+
 use crate::ergo_tree::ErgoTree;
 use crate::serialization::SigmaSerializable;
 use serde::{Deserialize, Deserializer, Serializer};
 
 use super::serialize_bytes;
 
+/// Serializer (used in Wasm bindings)
 pub fn serialize<S>(ergo_tree: &ErgoTree, serializer: S) -> Result<S::Ok, S::Error>
 where
     S: Serializer,
 {
     use serde::ser::Error;
+
     let bytes = ergo_tree
         .sigma_serialize_bytes()
         .map_err(|err| Error::custom(err.to_string()))?;
     serialize_bytes(&bytes[..], serializer)
 }
 
-pub fn deserialize<'de, D>(deserializer: D) -> Result<ErgoTree, D::Error>
+pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<ErgoTree, D::Error>
 where
     D: Deserializer<'de>,
 {
diff --git a/ergotree-ir/src/chain/token.rs b/ergotree-ir/src/chain/token.rs
index 0ce8c1996..d558e0648 100644
--- a/ergotree-ir/src/chain/token.rs
+++ b/ergotree-ir/src/chain/token.rs
@@ -63,10 +63,22 @@ impl SigmaSerializable for TokenId {
 }
 
 /// Token amount with bound checks
+#[cfg(not(feature = "json"))]
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
-#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 pub struct TokenAmount(u64);
 
+/// Token amount with bound checks
+#[cfg(feature = "json")]
+#[serde_with::serde_as]
+#[derive(
+    serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord,
+)]
+pub struct TokenAmount(
+    // Tries to decode as u64 first, then fallback to string. Encodes as u64 always
+    // see details - https://docs.rs/serde_with/1.9.4/serde_with/struct.PickFirst.html
+    #[serde_as(as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")] u64,
+);
+
 impl TokenAmount {
     /// minimal allowed value
     pub const MIN_RAW: u64 = 1;
@@ -147,9 +159,9 @@ impl From<Token> for (Vec<i8>, i64) {
 }
 
 /// Token represented with token id paired with it's amount
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[derive(PartialEq, Eq, Debug, Clone)]
 #[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
-#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 pub struct Token {
     /// token id
     #[cfg_attr(feature = "json", serde(rename = "tokenId"))]
diff --git a/ergotree-ir/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs
index 4f014e897..6a0b21f7c 100644
--- a/ergotree-ir/src/sigma_protocol/dlog_group.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs
@@ -21,14 +21,15 @@ use crate::serialization::SigmaSerializeResult;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
-use elliptic_curve::group::{ff::PrimeField, prime::PrimeCurveAffine};
+use elliptic_curve::group::ff::PrimeField;
+use elliptic_curve::group::prime::PrimeCurveAffine;
+use elliptic_curve::rand_core::RngCore;
 use k256::elliptic_curve::sec1::ToEncodedPoint;
 use k256::{ProjectivePoint, PublicKey, Scalar};
 use num_bigint::BigUint;
 use num_bigint::Sign;
 use num_bigint::ToBigUint;
 use num_traits::ToPrimitive;
-use rand::RngCore;
 use std::convert::TryFrom;
 use std::ops::{Add, Mul, Neg};
 
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index 2edc6a694..fa5d8646f 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -361,6 +361,7 @@ mod arbitrary {
     }
 }
 
+#[allow(clippy::panic)]
 #[cfg(test)]
 #[allow(clippy::panic)]
 mod tests {
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
index 4fbfbfe1c..597931063 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs
@@ -83,6 +83,7 @@ mod arbitrary {
 
 #[allow(clippy::panic)]
 #[allow(clippy::unwrap_used)]
+#[allow(clippy::panic)]
 #[cfg(test)]
 mod tests {
     use super::*;


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_box.js b/bindings/ergo-lib-wasm/tests/test_box.js
new file mode 100644
index 000000000..84aa7051b
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/test_box.js
@@ -0,0 +1,45 @@
+import { expect, assert } from 'chai';
+
+import {
+  ErgoBoxes, I64, ErgoBox, Tokens, BoxValue
+} from '../pkg/ergo_lib_wasm';
+
+it('ErgoBox.to_js_eip12() test', async () => {
+  const box = ErgoBoxes.from_boxes_json([{
+    "id": "3e762407d99b006d53b6583adcca08ef690b42fb0b2ed7abf63179eb6b9033b2",
+    "txId": "93d344aa527e18e5a221db060ea1a868f46b61e4537e6e5f69ecc40334c15e38",
+    "value": 2875858910,
+    "index": 0,
+    "creationHeight": 352126,
+    "ergoTree": "101f0400040004020402040004000402050005000580dac4090580dac409050005c00c05c80104000e20b662db51cf2dc39f110a021c2a31c74f0a1a18ffffbf73e8a051a7b8c0f09ebc0580dac40904040404050005feffffffffffffffff01050005e807050005e807050005a0060101050005c00c05a006d81ed601b2db6501fe730000d602b2a5730100d603c17202d604db6308a7d605b27204730200d6068c720502d607db63087202d608b27207730300d6098c720802d60a9472067209d60bb27204730400d60c8c720b02d60db27207730500d60e8c720d02d60f94720c720ed610e4c6a70505d611e4c672020505d612e4c6a70405d613e4c672020405d614b2a5730600d615e4c672140405d61695720a73077215d61795720a72157308d61899c1a77309d619e4c672140505d61a997203730ad61be4c672010405d61ca172189c7212721bd61d9c7213721bd61e9593721d730b730c9d9c721a730d721dd1ededed938cb2db63087201730e0001730fedededed9272037310edec720a720fefed720a720fed939a720672109a72097211939a720c72129a720e7213eded939a721272167213939a721072177211939a72187219721aeded938c720d018c720b01938c7208018c720501938cb27207731100018cb272047312000193721995720f9ca1721b95937212731373149d721c72127216d801d61f997218721c9c9593721f7315731695937210731773189d721f7210721795720f95917216731992721e731a731b95917217731c90721e731d92721e731e",
+    "address": "9aFbqNsmDwSxCdcLDKmSxVTL58ms2A39Rpn2zodVzkBN5MzB8zvW5PFX551W1A5vUdFJ3yxwvwgYTTS4JrPQcb5qxBbRDJkGNikuqHRXhnbniK4ajumEj7ot2o7DbcNFaM674fWufQzSGS1KtgMw95ZojyqhswUNbKpYDV1PhKw62bEMdJL9vAvzea4KwKXGUTdYYkcPdQKFWXfrdo2nTS3ucFNxqyTRB3VtZk7AWE3eeNHFcXZ1kLkfrX1ZBjpQ7qrBemHk4KZgS8fzmm6hPSZThiVVtBfQ2CZhJQdAZjRwGrw5TDcZ4BBDAZxg9h13vZ7tQSPsdAtjMFQT1DxbqAruKxX38ZwaQ3UfWmbBpbJEThAQaS4gsCBBSjswrv8BvupxaHZ4oQmA2LZiz4nYaPr8MJtR4fbM9LErwV4yDVMb873bRE5TBF59NipUyHAir7ysajPjbGc8aRLqsMVjntFSCFYx7822RBrj7RRX11CpiGK6vdfKHe3k14EH6YaNXvGSq8DrfNHEK4SgreknTqCgjL6i3EMZKPCW8Lao3Q5tbJFnFjEyntpUDf5zfGgFURxzobeEY4USqFaxyppHkgLjQuFQtDWbYVu3ztQL6hdWHjZXMK4VVvEDeLd1woebD1CyqS5kJHpGa78wQZ4iKygw4ijYrodZpqqEwTXdqwEB6xaLfkxZCBPrYPST3xz67GGTBUFy6zkXP5vwVVM5gWQJFdWCZniAAzBpzHeVq1yzaBp5GTJgr9bfrrAmuX8ra1m125yfeT9sTWroVu",
+    "assets": [
+      {
+        "tokenId": "2d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c",
+        "index": 0,
+        "amount": 99999999998
+      },
+      {
+        "tokenId": "bcd5db3a2872f279ef89edaa51a9344a6095ea1f03396874b695b5ba95ff602e",
+        "index": 1,
+        "amount": 99995619990
+      },
+      {
+        "tokenId": "9f90c012e03bf99397e363fb1571b7999941e0862a217307e3467ee80cf53af7",
+        "index": 2,
+        "amount": 1
+      }
+    ],
+    "additionalRegisters": {
+      "R4": "0504",
+      "R5": "05d4d59604"
+    },
+    "spentTransactionId": null,
+    "mainChain": true
+  }]).get(0);
+  const j_obj = box.to_js_eip12();
+  assert(j_obj instanceof Object);
+  assert(j_obj.value == "2875858910", "should be a string of '2875858910'");
+  assert(j_obj.assets[0].amount == "99999999998", "should be a string of '99999999998'");
+});
+
diff --git a/bindings/ergo-lib-wasm/tests/test_transaction.js b/bindings/ergo-lib-wasm/tests/test_transaction.js
index 35b8d8b1f..4600da3dd 100644
--- a/bindings/ergo-lib-wasm/tests/test_transaction.js
+++ b/bindings/ergo-lib-wasm/tests/test_transaction.js
@@ -83,6 +83,8 @@ it('sign transaction', async () => {
   const box_selection = box_selector.select(unspent_boxes, target_balance, new Tokens());
   const tx_builder = TxBuilder.new(box_selection, tx_outputs, 0, fee, change_address, min_change_value);
   const tx = tx_builder.build();
+  // smoke test for to_js_eip12
+  assert(tx.to_js_eip12() != null);
   const tx_data_inputs = ErgoBoxes.from_boxes_json([]);
   const pre_header = PreHeader.from_block_header(block_headers.get(0));
   const ctx = new ErgoStateContext(pre_header);
@@ -91,6 +93,8 @@ it('sign transaction', async () => {
   const wallet = Wallet.from_secrets(sks);
   const signed_tx = wallet.sign_transaction(ctx, tx, unspent_boxes, tx_data_inputs);
   assert(signed_tx != null);
+  // smoke test for to_js_eip12
+  assert(signed_tx.to_js_eip12() != null);
 });
 
 it('TxBuilder mint token test', async () => {