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

Issue:
Add to/from ErgoBox conversion for Constant
#343 is a prerequisite for this issue.


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-c-core/src/lib.rs b/bindings/ergo-lib-c-core/src/lib.rs
index 4095b998c..3f94a55c6 100644
--- a/bindings/ergo-lib-c-core/src/lib.rs
+++ b/bindings/ergo-lib-c-core/src/lib.rs
@@ -13,9 +13,9 @@
 mod error;
 pub use error::*;
 
-use ergo_lib::ergotree_ir::address::{AddressEncoder, NetworkPrefix};
+use ergo_lib::ergotree_ir::chain::address::{AddressEncoder, NetworkPrefix};
 
-pub struct Address(ergo_lib::ergotree_ir::address::Address);
+pub struct Address(ergo_lib::ergotree_ir::chain::address::Address);
 pub type AddressPtr = *mut Address;
 
 pub unsafe fn address_from_testnet(
diff --git a/bindings/ergo-lib-c/src/lib.rs b/bindings/ergo-lib-c/src/lib.rs
index 0d4f831c7..71b9adf90 100644
--- a/bindings/ergo-lib-c/src/lib.rs
+++ b/bindings/ergo-lib-c/src/lib.rs
@@ -10,7 +10,7 @@
 // #![deny(missing_docs)]
 #![allow(clippy::missing_safety_doc)]
 
-use ergo_lib::chain;
+use ergo_lib::ergotree_ir::chain;
 
 use ergo_lib_c_core::{address_delete, address_from_testnet};
 pub use ergo_lib_c_core::{Address, Error};
@@ -22,7 +22,7 @@ use std::{
 pub type ErrorPtr = *mut Error;
 pub type AddressPtr = *mut Address;
 
-pub struct ErgoStateContext(chain::ergo_state_context::ErgoStateContext);
+pub struct ErgoStateContext(ergo_lib::chain::ergo_state_context::ErgoStateContext);
 pub type ErgoStateContextPtr = *mut ErgoStateContext;
 
 #[no_mangle]
@@ -143,7 +143,7 @@ pub extern "C" fn ergo_wallet_wallet_new_signed_tx(
     todo!()
 }
 
-pub struct Transaction(chain::transaction::Transaction);
+pub struct Transaction(ergo_lib::chain::transaction::Transaction);
 pub type TransactionPtr = *mut Transaction;
 
 #[no_mangle]
diff --git a/bindings/ergo-lib-wasm/src/address.rs b/bindings/ergo-lib-wasm/src/address.rs
index a96d58769..80dead316 100644
--- a/bindings/ergo-lib-wasm/src/address.rs
+++ b/bindings/ergo-lib-wasm/src/address.rs
@@ -21,9 +21,9 @@ pub enum NetworkPrefix {
     Testnet = 16,
 }
 
-impl From<NetworkPrefix> for ergo_lib::ergotree_ir::address::NetworkPrefix {
+impl From<NetworkPrefix> for ergo_lib::ergotree_ir::chain::address::NetworkPrefix {
     fn from(v: NetworkPrefix) -> Self {
-        use ergo_lib::ergotree_ir::address::NetworkPrefix::*;
+        use ergo_lib::ergotree_ir::chain::address::NetworkPrefix::*;
         match v {
             NetworkPrefix::Mainnet => Mainnet,
             NetworkPrefix::Testnet => Testnet,
@@ -31,12 +31,12 @@ impl From<NetworkPrefix> for ergo_lib::ergotree_ir::address::NetworkPrefix {
     }
 }
 
-impl From<ergo_lib::ergotree_ir::address::NetworkPrefix> for NetworkPrefix {
-    fn from(v: ergo_lib::ergotree_ir::address::NetworkPrefix) -> Self {
+impl From<ergo_lib::ergotree_ir::chain::address::NetworkPrefix> for NetworkPrefix {
+    fn from(v: ergo_lib::ergotree_ir::chain::address::NetworkPrefix) -> Self {
         use NetworkPrefix::*;
         match v {
-            ergo_lib::ergotree_ir::address::NetworkPrefix::Mainnet => Mainnet,
-            ergo_lib::ergotree_ir::address::NetworkPrefix::Testnet => Testnet,
+            ergo_lib::ergotree_ir::chain::address::NetworkPrefix::Mainnet => Mainnet,
+            ergo_lib::ergotree_ir::chain::address::NetworkPrefix::Testnet => Testnet,
         }
     }
 }
@@ -53,9 +53,9 @@ pub enum AddressTypePrefix {
     Pay2S = 3,
 }
 
-impl From<AddressTypePrefix> for ergo_lib::ergotree_ir::address::AddressTypePrefix {
+impl From<AddressTypePrefix> for ergo_lib::ergotree_ir::chain::address::AddressTypePrefix {
     fn from(v: AddressTypePrefix) -> Self {
-        use ergo_lib::ergotree_ir::address::AddressTypePrefix::*;
+        use ergo_lib::ergotree_ir::chain::address::AddressTypePrefix::*;
         match v {
             AddressTypePrefix::P2Pk => P2Pk,
             AddressTypePrefix::Pay2Sh => Pay2Sh,
@@ -64,13 +64,13 @@ impl From<AddressTypePrefix> for ergo_lib::ergotree_ir::address::AddressTypePref
     }
 }
 
-impl From<ergo_lib::ergotree_ir::address::AddressTypePrefix> for AddressTypePrefix {
-    fn from(v: ergo_lib::ergotree_ir::address::AddressTypePrefix) -> Self {
+impl From<ergo_lib::ergotree_ir::chain::address::AddressTypePrefix> for AddressTypePrefix {
+    fn from(v: ergo_lib::ergotree_ir::chain::address::AddressTypePrefix) -> Self {
         use AddressTypePrefix::*;
         match v {
-            ergo_lib::ergotree_ir::address::AddressTypePrefix::P2Pk => P2Pk,
-            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2Sh => Pay2Sh,
-            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2S => Pay2S,
+            ergo_lib::ergotree_ir::chain::address::AddressTypePrefix::P2Pk => P2Pk,
+            ergo_lib::ergotree_ir::chain::address::AddressTypePrefix::Pay2Sh => Pay2Sh,
+            ergo_lib::ergotree_ir::chain::address::AddressTypePrefix::Pay2S => Pay2S,
         }
     }
 }
@@ -120,7 +120,7 @@ impl From<ergo_lib::ergotree_ir::address::AddressTypePrefix> for AddressTypePref
  */
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct Address(ergo_lib::ergotree_ir::address::Address);
+pub struct Address(ergo_lib::ergotree_ir::chain::address::Address);
 
 #[wasm_bindgen]
 impl Address {
@@ -130,22 +130,24 @@ impl Address {
     /// Re-create the address from this ErgoTree.
     /// `tree` - ErgoTree that was created from an 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(to_js)
+        ergo_lib::ergotree_ir::chain::address::Address::recreate_from_ergo_tree(
+            &ergo_tree.clone().into(),
+        )
+        .map(Address)
+        .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)
+        ergo_lib::ergotree_ir::chain::address::Address::p2pk_from_pk_bytes(bytes)
             .map(Address)
             .map_err(to_js)
     }
 
     /// Decode (base58) testnet address from string, checking that address is from the testnet
     pub fn from_testnet_str(s: &str) -> Result<Address, JsValue> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::new(
-            ergo_lib::ergotree_ir::address::NetworkPrefix::Testnet,
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::new(
+            ergo_lib::ergotree_ir::chain::address::NetworkPrefix::Testnet,
         )
         .parse_address_from_str(s)
         .map(Address)
@@ -154,8 +156,8 @@ impl Address {
 
     /// Decode (base58) mainnet address from string, checking that address is from the mainnet
     pub fn from_mainnet_str(s: &str) -> Result<Address, JsValue> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::new(
-            ergo_lib::ergotree_ir::address::NetworkPrefix::Mainnet,
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::new(
+            ergo_lib::ergotree_ir::chain::address::NetworkPrefix::Mainnet,
         )
         .parse_address_from_str(s)
         .map(Address)
@@ -165,14 +167,14 @@ impl Address {
     /// Decode (base58) address from string without checking the network prefix
     #[allow(clippy::should_implement_trait)]
     pub fn from_base58(s: &str) -> Result<Address, JsValue> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_address_from_str(s)
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::unchecked_parse_address_from_str(s)
             .map(Address)
             .map_err(to_js)
     }
 
     /// Encode (base58) address
     pub fn to_base58(&self, network_prefix: NetworkPrefix) -> String {
-        ergo_lib::ergotree_ir::address::AddressEncoder::encode_address_as_string(
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::encode_address_as_string(
             network_prefix.into(),
             &self.0,
         )
@@ -180,14 +182,16 @@ impl Address {
 
     /// Decode from a serialized address (that includes the network prefix)
     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(to_js)
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::unchecked_parse_address_from_bytes(
+            &data,
+        )
+        .map(Address)
+        .map_err(to_js)
     }
 
     /// Encode address as serialized bytes (that includes the network prefix)
     pub fn to_bytes(&self, network_prefix: NetworkPrefix) -> Vec<u8> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::encode_address_as_bytes(
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::encode_address_as_bytes(
             network_prefix.into(),
             &self.0,
         )
@@ -201,7 +205,9 @@ impl Address {
     /// Create an address from a public key
     pub fn from_public_key(bytes: &[u8]) -> Result<Address, JsValue> {
         EcPoint::sigma_parse_bytes(bytes)
-            .map(|point| ergo_lib::ergotree_ir::address::Address::P2Pk(ProveDlog::new(point)))
+            .map(|point| {
+                ergo_lib::ergotree_ir::chain::address::Address::P2Pk(ProveDlog::new(point))
+            })
             .map(Address)
             .map_err(to_js)
     }
@@ -216,13 +222,13 @@ impl Address {
 /// These two combined together form a base58 encoding
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct NetworkAddress(ergo_lib::ergotree_ir::address::NetworkAddress);
+pub struct NetworkAddress(ergo_lib::ergotree_ir::chain::address::NetworkAddress);
 
 #[wasm_bindgen]
 impl NetworkAddress {
     /// create a new NetworkAddress(address + network prefix) for a given network type
     pub fn new(network: NetworkPrefix, address: &Address) -> NetworkAddress {
-        NetworkAddress(ergo_lib::ergotree_ir::address::NetworkAddress::new(
+        NetworkAddress(ergo_lib::ergotree_ir::chain::address::NetworkAddress::new(
             network.into(),
             &address.clone().into(),
         ))
@@ -230,7 +236,7 @@ impl NetworkAddress {
 
     /// Decode (base58) a NetworkAddress (address + network prefix) from string
     pub fn from_base58(s: &str) -> Result<NetworkAddress, JsValue> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_network_address_from_str(s)
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::unchecked_parse_network_address_from_str(s)
             .map(NetworkAddress)
             .map_err(to_js)
     }
@@ -242,7 +248,7 @@ impl NetworkAddress {
 
     /// Decode from a serialized address
     pub fn from_bytes(data: Vec<u8>) -> Result<NetworkAddress, JsValue> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_network_address_from_bytes(
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::unchecked_parse_network_address_from_bytes(
             &data,
         )
         .map(NetworkAddress)
@@ -251,7 +257,7 @@ impl NetworkAddress {
 
     /// Encode address as serialized bytes
     pub fn to_bytes(&self) -> Vec<u8> {
-        ergo_lib::ergotree_ir::address::AddressEncoder::encode_address_as_bytes(
+        ergo_lib::ergotree_ir::chain::address::AddressEncoder::encode_address_as_bytes(
             self.network().into(),
             &self.address().into(),
         )
diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs
index 2767ddc63..fd46c9ea5 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -1,8 +1,10 @@
 //! Ergo constant values
 
+use crate::ergo_box::ErgoBox;
 use crate::error_conversion::to_js;
 use crate::utils::I64;
-use ergo_lib::chain::Base16Str;
+use ergo_lib::ergotree_ir::base16_str::Base16Str;
+use ergo_lib::ergotree_ir::chain::base16_bytes::Base16DecodedBytes;
 use ergo_lib::ergotree_ir::mir::constant::{TryExtractFrom, TryExtractInto};
 use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
 use ergo_lib::ergotree_ir::sigma_protocol::dlog_group::EcPoint;
@@ -23,21 +25,21 @@ pub struct Constant(ergo_lib::ergotree_ir::mir::constant::Constant);
 impl Constant {
     /// Decode from Base16-encoded ErgoTree serialized value
     pub fn decode_from_base16(base16_bytes_str: String) -> Result<Constant, JsValue> {
-        let bytes = ergo_lib::chain::Base16DecodedBytes::try_from(base16_bytes_str.clone())
-            .map_err(|_| {
-                JsValue::from_str(&format!(
-                    "failed to decode base16 from: {}",
-                    base16_bytes_str.clone()
-                ))
-            })?;
+        let bytes = Base16DecodedBytes::try_from(base16_bytes_str.clone()).map_err(|_| {
+            JsValue::from_str(&format!(
+                "failed to decode base16 from: {}",
+                base16_bytes_str.clone()
+            ))
+        })?;
         ergo_lib::ergotree_ir::mir::constant::Constant::try_from(bytes)
             .map_err(to_js)
             .map(Constant)
     }
 
-    /// Encode as Base16-encoded ErgoTree serialized value
-    pub fn encode_to_base16(&self) -> String {
-        self.0.base16_str()
+    /// Encode as Base16-encoded ErgoTree serialized value or return an error if serialization
+    /// failed
+    pub fn encode_to_base16(&self) -> Result<String, JsValue> {
+        self.0.base16_str().map_err(to_js)
     }
 
     /// Returns serialized bytes or fails with error if Constant cannot be serialized
@@ -167,4 +169,20 @@ impl Constant {
             JsValue::from_str(&l2.to_string()),
         ])
     }
+
+    /// Create from ErgoBox value
+    pub fn from_ergo_box(v: &ErgoBox) -> Constant {
+        let b: ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox = v.clone().into();
+        let c: ergo_lib::ergotree_ir::mir::constant::Constant = b.into();
+        Constant(c)
+    }
+
+    /// Extract ErgoBox value, returning error if wrong type
+    pub fn to_ergo_box(&self) -> Result<ErgoBox, JsValue> {
+        self.0
+            .clone()
+            .try_extract_into::<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox>()
+            .map(Into::into)
+            .map_err(to_js)
+    }
 }
diff --git a/bindings/ergo-lib-wasm/src/block_header.rs b/bindings/ergo-lib-wasm/src/block_header.rs
index d8274221a..054dd8279 100644
--- a/bindings/ergo-lib-wasm/src/block_header.rs
+++ b/bindings/ergo-lib-wasm/src/block_header.rs
@@ -1,6 +1,5 @@
 //! Block header
 
-use ergo_lib::chain::HeaderJsonHelper;
 use ergo_lib::ergotree_ir::chain::header::Header;
 use wasm_bindgen::prelude::*;
 
@@ -18,10 +17,7 @@ pub struct BlockHeader(Header);
 impl BlockHeader {
     /// Parse from JSON (Node API)
     pub fn from_json(json: &str) -> Result<BlockHeader, JsValue> {
-        serde_json::from_str(json)
-            .map(|HeaderJsonHelper(header)| header)
-            .map(Self)
-            .map_err(to_js)
+        serde_json::from_str(json).map(Self).map_err(to_js)
     }
 }
 
@@ -42,10 +38,9 @@ impl BlockHeaders {
                     let jb_str = jb
                         .as_string()
                         .ok_or(JsValue::from_str("Expected BlockHeader JSON as string"))?;
-                    serde_json::from_str(jb_str.as_str()).map(|HeaderJsonHelper(header)| header)
+                    serde_json::from_str(jb_str.as_str())
                 } else {
-                    jb.into_serde::<HeaderJsonHelper>()
-                        .map(|HeaderJsonHelper(header)| header)
+                    jb.into_serde::<ergo_lib::ergotree_ir::chain::header::Header>()
                 }
                 .map_err(|e| {
                     JsValue::from_str(&format!(
diff --git a/bindings/ergo-lib-wasm/src/box_coll.rs b/bindings/ergo-lib-wasm/src/box_coll.rs
index b72fa61ee..81166f8cd 100644
--- a/bindings/ergo-lib-wasm/src/box_coll.rs
+++ b/bindings/ergo-lib-wasm/src/box_coll.rs
@@ -1,5 +1,5 @@
 //! Box collection types
-use ergo_lib::chain;
+use ergo_lib::ergotree_ir::chain;
 use wasm_bindgen::prelude::*;
 
 use crate::ergo_box::{ErgoBox, ErgoBoxCandidate};
diff --git a/bindings/ergo-lib-wasm/src/box_selector.rs b/bindings/ergo-lib-wasm/src/box_selector.rs
index 028a6ba22..2602e9f63 100644
--- a/bindings/ergo-lib-wasm/src/box_selector.rs
+++ b/bindings/ergo-lib-wasm/src/box_selector.rs
@@ -1,5 +1,4 @@
 //! Simple box selection algorithms
-use ergo_lib::chain;
 use ergo_lib::wallet;
 use ergo_lib::wallet::box_selector::BoxSelector;
 use wasm_bindgen::prelude::*;
@@ -16,19 +15,21 @@ use derive_more::{From, Into};
 /// Selected boxes with change boxes (by [`BoxSelector`])
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct BoxSelection(wallet::box_selector::BoxSelection<ergo_lib::chain::ergo_box::ErgoBox>);
+pub struct BoxSelection(
+    wallet::box_selector::BoxSelection<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox>,
+);
 
 #[wasm_bindgen]
 impl BoxSelection {
     /// Create a selection to easily inject custom selection algorithms
     #[wasm_bindgen(constructor)]
     pub fn new(boxes: &ErgoBoxes, change: &ErgoBoxAssetsDataList) -> Self {
-        BoxSelection(
-            wallet::box_selector::BoxSelection::<ergo_lib::chain::ergo_box::ErgoBox> {
-                boxes: boxes.clone().into(),
-                change_boxes: change.clone().into(),
-            },
-        )
+        BoxSelection(wallet::box_selector::BoxSelection::<
+            ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox,
+        > {
+            boxes: boxes.clone().into(),
+            change_boxes: change.clone().into(),
+        })
     }
 
     /// Selected boxes to spend as transaction inputs
@@ -65,7 +66,8 @@ impl SimpleBoxSelector {
         target_balance: &BoxValue,
         target_tokens: &Tokens,
     ) -> Result<BoxSelection, JsValue> {
-        let target_tokens: Vec<chain::token::Token> = target_tokens.clone().into();
+        let target_tokens: Vec<ergo_lib::ergotree_ir::chain::token::Token> =
+            target_tokens.clone().into();
         self.0
             .select(
                 inputs.clone().into(),
diff --git a/bindings/ergo-lib-wasm/src/ergo_box.rs b/bindings/ergo-lib-wasm/src/ergo_box.rs
index 0aaa7b492..7feeab00b 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box.rs
@@ -18,8 +18,8 @@
 
 use std::convert::TryFrom;
 
-use chain::ergo_box::NonMandatoryRegisters;
-use ergo_lib::chain;
+use ergo_lib::ergotree_ir::chain;
+use ergo_lib::ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
 use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
 use js_sys::Uint8Array;
 use wasm_bindgen::prelude::*;
@@ -123,7 +123,7 @@ impl ErgoBox {
         index: u16,
         tokens: &Tokens,
     ) -> Result<ErgoBox, JsValue> {
-        let chain_contract: chain::contract::Contract = contract.clone().into();
+        let chain_contract: ergo_lib::chain::contract::Contract = contract.clone().into();
         let b = chain::ergo_box::ErgoBox::new(
             value.0,
             chain_contract.ergo_tree(),
@@ -206,7 +206,7 @@ impl From<chain::ergo_box::ErgoBox> for ErgoBox {
 /// Box value in nanoERGs with bound checks
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct BoxValue(pub(crate) chain::ergo_box::BoxValue);
+pub struct BoxValue(pub(crate) chain::ergo_box::box_value::BoxValue);
 
 #[wasm_bindgen]
 impl BoxValue {
@@ -214,19 +214,20 @@ impl BoxValue {
     /// Allows box size upto 2777 bytes with current min box value per byte of 360 nanoERGs
     #[allow(non_snake_case)]
     pub fn SAFE_USER_MIN() -> BoxValue {
-        BoxValue(chain::ergo_box::BoxValue::SAFE_USER_MIN)
+        BoxValue(chain::ergo_box::box_value::BoxValue::SAFE_USER_MIN)
     }
 
     /// Number of units inside one ERGO (i.e. one ERG using nano ERG representation)
     #[allow(non_snake_case)]
     pub fn UNITS_PER_ERGO() -> I64 {
-        (chain::ergo_box::BoxValue::UNITS_PER_ERGO as i64).into()
+        (chain::ergo_box::box_value::BoxValue::UNITS_PER_ERGO as i64).into()
     }
 
     /// 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(to_js)?,
+            chain::ergo_box::box_value::BoxValue::try_from(i64::from(v.clone()) as u64)
+                .map_err(to_js)?,
         ))
     }
 
@@ -241,14 +242,14 @@ impl BoxValue {
     }
 }
 
-impl From<BoxValue> for chain::ergo_box::BoxValue {
+impl From<BoxValue> for chain::ergo_box::box_value::BoxValue {
     fn from(v: BoxValue) -> Self {
         v.0
     }
 }
 
-impl From<chain::ergo_box::BoxValue> for BoxValue {
-    fn from(v: chain::ergo_box::BoxValue) -> Self {
+impl From<chain::ergo_box::box_value::BoxValue> for BoxValue {
+    fn from(v: chain::ergo_box::box_value::BoxValue) -> Self {
         BoxValue(v)
     }
 }
@@ -256,14 +257,14 @@ impl From<chain::ergo_box::BoxValue> for BoxValue {
 /// Pair of <value, tokens> for an box
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct ErgoBoxAssetsData(chain::ergo_box::ErgoBoxAssetsData);
+pub struct ErgoBoxAssetsData(ergo_lib::wallet::box_selector::ErgoBoxAssetsData);
 
 #[wasm_bindgen]
 impl ErgoBoxAssetsData {
     /// Create empty SimpleBoxSelector
     #[wasm_bindgen(constructor)]
     pub fn new(value: &BoxValue, tokens: &Tokens) -> Self {
-        ErgoBoxAssetsData(chain::ergo_box::ErgoBoxAssetsData {
+        ErgoBoxAssetsData(ergo_lib::wallet::box_selector::ErgoBoxAssetsData {
             value: value.clone().into(),
             tokens: tokens.clone().into(),
         })
@@ -309,13 +310,13 @@ impl ErgoBoxAssetsDataList {
     }
 }
 
-impl From<ErgoBoxAssetsDataList> for Vec<chain::ergo_box::ErgoBoxAssetsData> {
+impl From<ErgoBoxAssetsDataList> for Vec<ergo_lib::wallet::box_selector::ErgoBoxAssetsData> {
     fn from(v: ErgoBoxAssetsDataList) -> Self {
         v.0.iter().map(|i| i.0.clone()).collect()
     }
 }
-impl From<Vec<chain::ergo_box::ErgoBoxAssetsData>> for ErgoBoxAssetsDataList {
-    fn from(v: Vec<chain::ergo_box::ErgoBoxAssetsData>) -> Self {
+impl From<Vec<ergo_lib::wallet::box_selector::ErgoBoxAssetsData>> for ErgoBoxAssetsDataList {
+    fn from(v: Vec<ergo_lib::wallet::box_selector::ErgoBoxAssetsData>) -> Self {
         let mut assets = ErgoBoxAssetsDataList::new();
         for asset in &v {
             assets.add(&ErgoBoxAssetsData(asset.clone()))
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 b0c6938ac..a08f76068 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
@@ -104,10 +104,11 @@ impl ErgoBoxCandidateBuilder {
 
     /// Add given token id and token amount
     pub fn add_token(&mut self, token_id: &TokenId, amount: &TokenAmount) {
-        self.0.add_token(chain::token::Token {
-            token_id: token_id.clone().into(),
-            amount: amount.clone().into(),
-        });
+        self.0
+            .add_token(ergo_lib::ergotree_ir::chain::token::Token {
+                token_id: token_id.clone().into(),
+                amount: amount.clone().into(),
+            });
     }
 
     /// Build the box candidate
diff --git a/bindings/ergo-lib-wasm/src/ergo_tree.rs b/bindings/ergo-lib-wasm/src/ergo_tree.rs
index dce51cf75..166427599 100644
--- a/bindings/ergo-lib-wasm/src/ergo_tree.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_tree.rs
@@ -2,7 +2,7 @@
 
 use std::convert::TryFrom;
 
-use ergo_lib::chain::Base16DecodedBytes;
+use ergo_lib::ergotree_ir::chain::base16_bytes::Base16DecodedBytes;
 use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
 use wasm_bindgen::prelude::*;
 
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index 9ed229854..f814c4c74 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -3,14 +3,14 @@
 use std::num::ParseIntError;
 
 use base16::DecodeError;
+use ergo_lib::ergotree_ir::chain::address::AddressEncoderError;
+use ergo_lib::ergotree_ir::chain::address::AddressError;
+use ergo_lib::ergotree_ir::chain::digest32::Digest32Error;
+use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValueError;
+use ergo_lib::ergotree_ir::chain::token::TokenAmountError;
 use ergo_lib::{
-    chain::{
-        ergo_box::{box_builder::ErgoBoxCandidateBuilderError, BoxValueError},
-        token::TokenAmountError,
-        Digest32Error,
-    },
+    chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
-        address::{AddressEncoderError, AddressError},
         ergo_tree::{ErgoTreeConstantError, ErgoTreeConstantsParsingError, ErgoTreeError},
         mir::constant::TryExtractFromError,
         serialization::{SigmaParsingError, SigmaSerializationError},
diff --git a/bindings/ergo-lib-wasm/src/token.rs b/bindings/ergo-lib-wasm/src/token.rs
index 823bc93e0..4db56356a 100644
--- a/bindings/ergo-lib-wasm/src/token.rs
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -2,9 +2,9 @@
 
 use std::convert::TryFrom;
 
-use ergo_lib::chain;
-use ergo_lib::chain::Base16DecodedBytes;
-use ergo_lib::ergotree_ir::chain::digest::Digest32;
+use ergo_lib::ergotree_ir::chain;
+use ergo_lib::ergotree_ir::chain::base16_bytes::Base16DecodedBytes;
+use ergo_lib::ergotree_ir::chain::digest32::Digest32;
 use js_sys::Uint8Array;
 use wasm_bindgen::prelude::*;
 
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index 364dbec3e..a0c37ad0b 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -7,7 +7,9 @@ use crate::error_conversion::to_js;
 use crate::input::{Inputs, UnsignedInputs};
 use ergo_lib::chain;
 use ergo_lib::chain::transaction::distinct_token_ids;
-use ergo_lib::ergotree_ir::chain::digest::Digest32;
+use ergo_lib::ergotree_ir::chain::base16_bytes::Base16DecodedBytes;
+use ergo_lib::ergotree_ir::chain::base16_bytes::Base16EncodedBytes;
+use ergo_lib::ergotree_ir::chain::digest32::Digest32;
 use js_sys::Uint8Array;
 use std::convert::TryFrom;
 use std::convert::TryInto;
@@ -30,14 +32,14 @@ impl TxId {
 
     /// get the tx id as bytes
     pub fn to_str(&self) -> String {
-        let base16_bytes = ergo_lib::chain::Base16EncodedBytes::new(self.0 .0 .0.as_ref());
+        let base16_bytes = Base16EncodedBytes::new(self.0 .0 .0.as_ref());
         base16_bytes.into()
     }
 
     /// 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(to_js)?;
+        let bytes = Base16DecodedBytes::try_from(s.to_string()).map_err(to_js)?;
 
         bytes
             .try_into()
diff --git a/bindings/ergo-lib-wasm/src/tx_builder.rs b/bindings/ergo-lib-wasm/src/tx_builder.rs
index 3e726d601..34057f16a 100644
--- a/bindings/ergo-lib-wasm/src/tx_builder.rs
+++ b/bindings/ergo-lib-wasm/src/tx_builder.rs
@@ -1,5 +1,5 @@
 //! Unsigned transaction builder
-use ergo_lib::{chain, wallet};
+use ergo_lib::wallet;
 use wasm_bindgen::prelude::*;
 
 use crate::box_selector::BoxSelection;
@@ -12,14 +12,16 @@ use crate::{
 
 /// Unsigned transaction builder
 #[wasm_bindgen]
-pub struct TxBuilder(wallet::tx_builder::TxBuilder<chain::ergo_box::ErgoBox>);
+pub struct TxBuilder(
+    wallet::tx_builder::TxBuilder<ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox>,
+);
 
 #[wasm_bindgen]
 impl TxBuilder {
     /// Suggested transaction fee (semi-default value used across wallets and dApps as of Oct 2020)
     #[allow(non_snake_case)]
     pub fn SUGGESTED_TX_FEE() -> BoxValue {
-        BoxValue(wallet::tx_builder::SUGGESTED_TX_FEE)
+        BoxValue(wallet::tx_builder::SUGGESTED_TX_FEE())
     }
 
     /// Creates new TxBuilder
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index 7eeb421b0..b3cac4092 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -1,5 +1,5 @@
 //! Wallet-like features
-use ergo_lib::chain;
+use ergo_lib::ergotree_ir::chain;
 use wasm_bindgen::prelude::*;
 
 use crate::{
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index de24fa2bc..763db5aa9 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -24,10 +24,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - `SubstConst` IR node and serialization [#318] (https://github.com/ergoplatform/sigma-rust/pull/318);
 - Better Debug print for EC point [#319](https://github.com/ergoplatform/sigma-rust/pull/319);
 - `Constant::from_ecpoint_bytes` to Wasm API [#324](https://github.com/ergoplatform/sigma-rust/pull/324);
-
+- `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);
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index a2192b57a..e7124d9f3 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -16,13 +16,13 @@ crate-type = ["cdylib", "rlib"]
 [dependencies]
 sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
 sigma-util = { version = "^0.2.0", path = "../sigma-util" }
-ergotree-ir = { version = "^0.13.0", path = "../ergotree-ir" }
+ergotree-ir = { version = "^0.13.0", path = "../ergotree-ir", features = ["json"] }
 ergotree-interpreter = { version = "^0.13.0", path = "../ergotree-interpreter" }
 ergoscript-compiler = { version = "^0.9.0", path = "../ergoscript-compiler" , optional = true}
 indexmap = "1.3.2"
 base16 = "0.2.1"
 serde = { version = "1.0", features = ["derive"], optional = true }
-serde_json = "1.0"
+serde_json = { version = "1.0", optional = true }
 thiserror = "1"
 derive_more = "0.99"
 # bounded-vec = { git = "https://github.com/ergoplatform/bounded-vec", rev="f684d78" }
@@ -31,7 +31,7 @@ num-bigint = "0.4.0"
 
 [features]
 default = ["json", "compiler"]
-json = ["serde"]
+json = ["serde", "serde_json"]
 compiler = ["ergoscript-compiler"]
 
 [dev-dependencies]
diff --git a/ergo-lib/src/chain.rs b/ergo-lib/src/chain.rs
index d0b9d1cb0..c707fe884 100644
--- a/ergo-lib/src/chain.rs
+++ b/ergo-lib/src/chain.rs
@@ -1,19 +1,9 @@
 //! Ergo chain types
 
-pub use block_header::HeaderJsonHelper;
-
-mod block_header;
 #[cfg(feature = "json")]
 mod json;
 
-mod base16_bytes;
-mod digest32;
-
-pub use base16_bytes::*;
-pub use digest32::*;
-
 pub mod contract;
 pub mod ergo_box;
 pub mod ergo_state_context;
-pub mod token;
 pub mod transaction;
diff --git a/ergo-lib/src/chain/block_header.rs b/ergo-lib/src/chain/block_header.rs
deleted file mode 100644
index a1df1af13..000000000
--- a/ergo-lib/src/chain/block_header.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-//! Block header
-use num_bigint::BigInt;
-
-use ergotree_ir::chain::{
-    block_id::BlockId,
-    digest::{ADDigest, Digest32},
-    header::Header,
-    votes::Votes,
-};
-use ergotree_ir::sigma_protocol::dlog_group;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
-
-/// New-type wrapper for deserializing the remote `Header` type.
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[derive(Debug, Clone)]
-pub struct HeaderJsonHelper(
-    #[cfg_attr(feature = "json", serde(with = "BlockHeaderRef"))] pub Header,
-);
-
-/// Block header reference to `Header` type in ergotree-ir.
-///
-/// We do not implement serde traits in ergotree-ir, but they are requested
-/// by crate users. So we split definition into the main one in ergotree-ir and
-/// the one which implements `Serialize` and `Deserialize` (which is here).
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(feature = "json", serde(remote = "Header"))]
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub(in crate::chain) struct BlockHeaderRef {
-    /// Block version, to be increased on every soft and hardfork
-    version: u8,
-    /// Bytes representation of ModifierId of this Header
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    id: BlockId,
-    /// Id of a parent block
-    #[cfg_attr(
-        feature = "json",
-        serde(rename = "parentId", with = "block_id::BlockIdRef")
-    )]
-    parent_id: BlockId,
-    /// Hash of ADProofs for transactions in a block
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    ad_proofs_root: Digest32,
-    /// AvlTree of a state after block application
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    state_root: ADDigest,
-    /// Root hash (for a Merkle tree) of transactions in a block.
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    transaction_root: Digest32,
-    /// Timestamp of a block in ms from UNIX epoch
-    timestamp: u64,
-    /// Current difficulty in a compressed view.
-    #[cfg_attr(feature = "json", serde(rename = "nBits"))]
-    n_bits: u64,
-    /// Block height
-    height: u32,
-    /// Root hash of extension section
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    extension_root: Digest32,
-    /// Public key of miner. Part of Autolykos solution.
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    miner_pk: Box<dlog_group::EcPoint>,
-    /// One-time public key. Prevents revealing of miners secret.
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    pow_onetime_pk: Box<dlog_group::EcPoint>,
-    /// nonce
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    nonce: Vec<u8>,
-    /// Distance between pseudo-random number, corresponding to nonce `nonce` and a secret,
-    /// corresponding to `miner_pk`. The lower `pow_distance` is, the harder it was to find this solution.
-    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
-    pow_distance: BigInt,
-    /// Votes
-    #[cfg_attr(feature = "json", serde(with = "votes::VotesRef"))]
-    votes: Votes,
-}
-
-mod votes {
-    use std::convert::{TryFrom, TryInto};
-
-    use ergotree_ir::chain::votes::{Votes, VotesError};
-    #[cfg(feature = "json")]
-    use serde::{Deserialize, Serialize};
-
-    use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
-
-    /// Reference for remote Votes type. Remote Votes wasn't used, because in ergo-lib
-    /// this type is mostly needed for json serialization and deserialization. Such traits
-    /// of Votes aren't needed in ergotree-ir.
-    #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-    #[cfg_attr(
-        feature = "json",
-        serde(
-            into = "Base16EncodedBytes",
-            try_from = "crate::chain::json::block_header::VotesEncodingVariants"
-        )
-    )]
-    #[derive(PartialEq, Eq, Debug, Clone)]
-    #[cfg_attr(feature = "json", serde(remote = "Votes"))]
-    pub(super) struct VotesRef([u8; 3]);
-
-    impl TryFrom<Base16DecodedBytes> for Votes {
-        type Error = VotesError;
-
-        fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
-            bytes.0.try_into()
-        }
-    }
-
-    impl From<Votes> for Base16EncodedBytes {
-        fn from(v: Votes) -> Self {
-            Base16EncodedBytes::new(v.0.as_ref())
-        }
-    }
-}
-
-mod block_id {
-    use ergotree_ir::chain::digest::Digest32;
-    #[cfg(feature = "json")]
-    use reexport_for_json::*;
-
-    /// Reference for BlockId type. Remote BlockId wasn't used, because in ergo-lib
-    /// this type is mostly needed for json serialization and deserialization. Such traits
-    /// of BlockId aren't needed in ergotree-ir.
-    #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-    #[cfg_attr(feature = "json", serde(remote = "BlockId"))]
-    #[derive(PartialEq, Eq, Debug, Clone)]
-    pub(super) struct BlockIdRef(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
-
-    #[cfg(feature = "json")]
-    mod reexport_for_json {
-        pub(super) use crate::chain::DigestRef;
-        pub(super) use ergotree_ir::chain::block_id::BlockId;
-        pub(super) use serde::{Deserialize, Serialize};
-    }
-}
diff --git a/ergo-lib/src/chain/contract.rs b/ergo-lib/src/chain/contract.rs
index 98030283d..8379c2bfb 100644
--- a/ergo-lib/src/chain/contract.rs
+++ b/ergo-lib/src/chain/contract.rs
@@ -1,6 +1,6 @@
 //! Ergo contract
 
-use ergotree_ir::address::Address;
+use ergotree_ir::chain::address::Address;
 use ergotree_ir::ergo_tree::ErgoTree;
 use ergotree_ir::serialization::SigmaParsingError;
 
diff --git a/ergo-lib/src/chain/digest32.rs b/ergo-lib/src/chain/digest32.rs
deleted file mode 100644
index 8589a3f1f..000000000
--- a/ergo-lib/src/chain/digest32.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
-use ergotree_ir::chain::digest::Digest;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
-use std::convert::TryFrom;
-use std::convert::TryInto;
-use thiserror::Error;
-
-/// Reference to remote Digest type. Remote Digest wasn't used, because in ergo-lib
-/// this type is mostly needed for json serialization and deserialization. Such traits
-/// of Digest aren't needed in ergotree-ir.
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(
-    feature = "json",
-    serde(
-        into = "Base16EncodedBytes",
-        try_from = "Base16DecodedBytes",
-        remote = "Digest"
-    )
-)]
-#[derive(PartialEq, Eq, Hash, Clone)]
-pub(crate) struct DigestRef<const N: usize>(pub(crate) Box<[u8; N]>);
-
-impl<const N: usize> TryFrom<Base16DecodedBytes> for Digest<N> {
-    type Error = Digest32Error;
-    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
-        let arr: [u8; N] = bytes.0.as_slice().try_into()?;
-        Ok(Digest(Box::new(arr)))
-    }
-}
-
-impl<const N: usize> From<Digest<N>> for Base16EncodedBytes {
-    fn from(v: Digest<N>) -> Self {
-        Base16EncodedBytes::new(v.0.as_ref())
-    }
-}
-
-impl<const N: usize> TryFrom<String> for DigestRef<N> {
-    type Error = Digest32Error;
-
-    fn try_from(value: String) -> Result<Self, Self::Error> {
-        let bytes = Base16DecodedBytes::try_from(value)?;
-        Digest::try_from(bytes).map(|dig| DigestRef(dig.0))
-    }
-}
-
-impl<const N: usize> From<DigestRef<N>> for String {
-    fn from(v: DigestRef<N>) -> Self {
-        let bytes = Base16EncodedBytes::from(Into::<Digest<N>>::into(v));
-        bytes.into()
-    }
-}
-
-impl<const N: usize> From<DigestRef<N>> for Digest<N> {
-    fn from(value: DigestRef<N>) -> Self {
-        Digest(value.0)
-    }
-}
-
-impl<const N: usize> From<Digest<N>> for DigestRef<N> {
-    fn from(value: Digest<N>) -> Self {
-        DigestRef(value.0)
-    }
-}
-
-/// Invalid byte array size
-#[derive(Error, Debug)]
-pub enum Digest32Error {
-    /// error decoding from Base16
-    #[error("error decoding from Base16: {0}")]
-    Base16DecodingError(#[from] base16::DecodeError),
-    /// Invalid byte array size
-    #[error("Invalid byte array size ({0})")]
-    InvalidSize(#[from] std::array::TryFromSliceError),
-}
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index f3ead65a9..9a62e3832 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -1,660 +1,3 @@
-//! Ergo box
+//! Ergo box related types
 
 pub mod box_builder;
-mod box_id;
-mod box_value;
-mod register;
-
-pub use box_id::*;
-pub use box_value::*;
-use ergotree_ir::chain::digest::{blake2b256_hash, Digest32};
-use ergotree_ir::ergo_tree::ErgoTree;
-use ergotree_ir::ir_ergo_box::IrBoxId;
-use ergotree_ir::ir_ergo_box::IrErgoBox;
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
-use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
-use ergotree_ir::serialization::SigmaParsingError;
-use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::serialization::SigmaSerializationError;
-use ergotree_ir::serialization::SigmaSerializeResult;
-use ergotree_ir::util::AsVecI8;
-pub use register::*;
-
-#[cfg(feature = "json")]
-use super::json;
-use super::token::TokenAmount;
-use super::{
-    token::{Token, TokenId},
-    transaction::TxId,
-};
-
-use indexmap::IndexSet;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
-use std::convert::TryFrom;
-#[cfg(feature = "json")]
-use thiserror::Error;
-
-use std::convert::TryInto;
-
-/// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
-/// In Bitcoin, such an object is associated with some monetary value (arbitrary,
-/// but with predefined precision, so we use integer arithmetic to work with the value),
-/// and also a guarding script (aka proposition) to protect the box from unauthorized opening.
-///
-/// In other way, a box is a state element locked by some proposition (ErgoTree).
-///
-/// In Ergo, box is just a collection of registers, some with mandatory types and semantics,
-/// others could be used by applications in any way.
-/// We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
-/// Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
-/// Register R3 contains height specified by user (protocol checks if it was <= current height when
-/// transaction was accepted) and also transaction identifier and box index in the transaction outputs.
-/// Registers R4-R9 are free for arbitrary usage.
-///
-/// A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
-/// can not be linked to the same box.
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(feature = "json", serde(try_from = "json::ergo_box::ErgoBoxFromJson"))]
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoBox {
-    #[cfg_attr(feature = "json", serde(rename = "boxId", alias = "id"))]
-    box_id: BoxId,
-    /// amount of money associated with the box
-    #[cfg_attr(feature = "json", serde(rename = "value"))]
-    pub value: BoxValue,
-    /// guarding script, which should be evaluated to true in order to open this box
-    #[cfg_attr(feature = "json", serde(rename = "ergoTree", with = "json::ergo_tree"))]
-    pub ergo_tree: ErgoTree,
-    /// secondary tokens the box contains
-    #[cfg_attr(feature = "json", serde(rename = "assets"))]
-    pub tokens: Vec<Token>,
-    ///  additional registers the box can carry over
-    #[cfg_attr(feature = "json", 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.
-    #[cfg_attr(feature = "json", serde(rename = "creationHeight"))]
-    pub creation_height: u32,
-    /// id of transaction which created the box
-    #[cfg_attr(feature = "json", 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)
-    #[cfg_attr(feature = "json", serde(rename = "index"))]
-    pub index: u16,
-}
-
-impl ErgoBox {
-    /// Maximum number of tokens in the box
-    pub const MAX_TOKENS_COUNT: usize = u8::MAX as usize;
-
-    /// Crate new box
-    pub fn new(
-        value: BoxValue,
-        ergo_tree: ErgoTree,
-        tokens: Vec<Token>,
-        additional_registers: NonMandatoryRegisters,
-        creation_height: u32,
-        transaction_id: TxId,
-        index: u16,
-    ) -> Result<ErgoBox, SigmaSerializationError> {
-        let box_with_zero_id = ErgoBox {
-            box_id: BoxId::zero(),
-            value,
-            ergo_tree,
-            tokens,
-            additional_registers,
-            creation_height,
-            transaction_id,
-            index,
-        };
-        let box_id = box_with_zero_id.calc_box_id()?;
-        Ok(ErgoBox {
-            box_id,
-            ..box_with_zero_id
-        })
-    }
-
-    /// Box id (Blake2b256 hash of serialized box)
-    pub fn box_id(&self) -> BoxId {
-        self.box_id.clone()
-    }
-
-    /// Create ErgoBox from ErgoBoxCandidate by adding transaction id
-    /// and index of the box in the transaction
-    pub fn from_box_candidate(
-        box_candidate: &ErgoBoxCandidate,
-        transaction_id: TxId,
-        index: u16,
-    ) -> Result<ErgoBox, SigmaSerializationError> {
-        let box_with_zero_id = ErgoBox {
-            box_id: BoxId::zero(),
-            value: box_candidate.value,
-            ergo_tree: box_candidate.ergo_tree.clone(),
-            tokens: box_candidate.tokens.clone(),
-            additional_registers: box_candidate.additional_registers.clone(),
-            creation_height: box_candidate.creation_height,
-            transaction_id,
-            index,
-        };
-        let box_id = box_with_zero_id.calc_box_id()?;
-        Ok(ErgoBox {
-            box_id,
-            ..box_with_zero_id
-        })
-    }
-
-    fn calc_box_id(&self) -> Result<BoxId, SigmaSerializationError> {
-        let bytes = self.sigma_serialize_bytes()?;
-        Ok(blake2b256_hash(&bytes).into())
-    }
-
-    /// Get register value
-    pub fn get_register(&self, id: RegisterId) -> Option<Constant> {
-        match id {
-            RegisterId::MandatoryRegisterId(id) => match id {
-                MandatoryRegisterId::R0 => Some(self.value.into()),
-                MandatoryRegisterId::R1 => Some(self.script_bytes().unwrap().into()),
-                MandatoryRegisterId::R2 => Some(self.tokens_raw().into()),
-                MandatoryRegisterId::R3 => Some(self.creation_info().into()),
-            },
-            RegisterId::NonMandatoryRegisterId(id) => self.additional_registers.get(id).cloned(),
-        }
-    }
-}
-
-impl IrErgoBox for ErgoBox {
-    fn id(&self) -> IrBoxId {
-        self.box_id.clone().into()
-    }
-
-    fn value(&self) -> i64 {
-        self.value.as_i64()
-    }
-
-    fn tokens_raw(&self) -> Vec<(Vec<i8>, i64)> {
-        self.tokens
-            .clone()
-            .into_iter()
-            .map(|t| (t.token_id.into(), t.amount.into()))
-            .collect()
-    }
-
-    fn additional_registers(&self) -> &[Constant] {
-        self.additional_registers.get_ordered_values()
-    }
-
-    fn get_register(&self, id: i8) -> Option<Constant> {
-        RegisterId::try_from(id)
-            .ok()
-            .and_then(|reg_id| self.get_register(reg_id))
-    }
-
-    fn creation_height(&self) -> i32 {
-        self.creation_height as i32
-    }
-
-    fn script_bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        Ok(self.ergo_tree.sigma_serialize_bytes()?.as_vec_i8())
-    }
-
-    /// Tuple of height when block got included into the blockchain and transaction identifier with
-    /// box index in the transaction outputs serialized to the byte array.
-    fn creation_info(&self) -> (i32, Vec<i8>) {
-        let mut bytes = Vec::with_capacity(Digest32::SIZE + 2);
-        bytes.extend_from_slice(self.transaction_id.0 .0.as_ref());
-        bytes.extend_from_slice(&self.index.to_be_bytes());
-        (self.creation_height as i32, bytes.as_vec_i8())
-    }
-
-    fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        Ok(self.sigma_serialize_bytes()?.as_vec_i8())
-    }
-
-    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        let candidate: ErgoBoxCandidate = self.clone().into();
-        Ok(candidate.sigma_serialize_bytes()?.as_vec_i8())
-    }
-}
-
-/// Assets that ErgoBox holds
-pub trait ErgoBoxAssets {
-    /// Box value
-    fn value(&self) -> BoxValue;
-    /// Tokens (ids and amounts)
-    fn tokens(&self) -> Vec<Token>;
-}
-
-/// Returns the total value of the given boxes
-pub fn sum_value<T: ErgoBoxAssets>(bs: &[T]) -> u64 {
-    bs.iter().map(|b| *b.value().as_u64()).sum()
-}
-
-/// Returns the total token amounts (all tokens combined)
-pub fn sum_tokens(ts: &[Token]) -> HashMap<TokenId, TokenAmount> {
-    let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
-    ts.iter().for_each(|t| {
-        res.entry(t.token_id.clone())
-            .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
-            .or_insert(t.amount);
-    });
-    res
-}
-
-/// Returns the total token amounts (all tokens combined) of the given boxes
-pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(bs: &[T]) -> HashMap<TokenId, TokenAmount> {
-    let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
-    bs.iter().for_each(|b| {
-        b.tokens().iter().for_each(|t| {
-            res.entry(t.token_id.clone())
-                .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
-                .or_insert(t.amount);
-        });
-    });
-    res
-}
-
-/// Simple struct to hold ErgoBoxAssets values
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoBoxAssetsData {
-    /// Box value
-    pub value: BoxValue,
-    /// Tokens
-    pub tokens: Vec<Token>,
-}
-
-impl ErgoBoxAssets for ErgoBoxAssetsData {
-    fn value(&self) -> BoxValue {
-        self.value
-    }
-
-    fn tokens(&self) -> Vec<Token> {
-        self.tokens.clone()
-    }
-}
-
-impl ErgoBoxAssets for ErgoBoxCandidate {
-    fn value(&self) -> BoxValue {
-        self.value
-    }
-
-    fn tokens(&self) -> Vec<Token> {
-        self.tokens.clone()
-    }
-}
-
-impl ErgoBoxAssets for ErgoBox {
-    fn value(&self) -> BoxValue {
-        self.value
-    }
-
-    fn tokens(&self) -> Vec<Token> {
-        self.tokens.clone()
-    }
-}
-
-/// id of the ergo box
-pub trait ErgoBoxId {
-    /// Id of the ergo box
-    fn box_id(&self) -> BoxId;
-}
-
-impl ErgoBoxId for ErgoBox {
-    fn box_id(&self) -> BoxId {
-        self.box_id.clone()
-    }
-}
-
-/// Errors on parsing ErgoBox from JSON
-#[cfg(feature = "json")]
-#[derive(Error, PartialEq, Eq, Debug, Clone)]
-pub enum ErgoBoxFromJsonError {
-    /// Box id parsed from JSON differs from calculated from box serialized bytes
-    #[error("Box id parsed from JSON differs from calculated from box serialized bytes")]
-    InvalidBoxId,
-    /// Box serialization failed (id calculation)
-    #[error("Box serialization failed (id calculation): {0}")]
-    SerializationError(#[from] SigmaSerializationError),
-}
-
-#[cfg(feature = "json")]
-impl TryFrom<json::ergo_box::ErgoBoxFromJson> for ErgoBox {
-    type Error = ErgoBoxFromJsonError;
-    fn try_from(box_json: json::ergo_box::ErgoBoxFromJson) -> Result<Self, Self::Error> {
-        let box_with_zero_id = ErgoBox {
-            box_id: BoxId::zero(),
-            value: box_json.value,
-            ergo_tree: box_json.ergo_tree,
-            tokens: box_json.tokens,
-            additional_registers: box_json.additional_registers,
-            creation_height: box_json.creation_height,
-            transaction_id: box_json.transaction_id,
-            index: box_json.index,
-        };
-        let box_id = box_with_zero_id.calc_box_id()?;
-        let ergo_box = ErgoBox {
-            box_id,
-            ..box_with_zero_id
-        };
-        match box_json.box_id {
-            Some(box_id) => {
-                if ergo_box.box_id() == box_id {
-                    Ok(ergo_box)
-                } else {
-                    Err(ErgoBoxFromJsonError::InvalidBoxId)
-                }
-            }
-            None => Ok(ergo_box),
-        }
-    }
-}
-
-impl SigmaSerializable for ErgoBox {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        let ergo_tree_bytes = self.ergo_tree.sigma_serialize_bytes()?;
-        serialize_box_with_indexed_digests(
-            &self.value,
-            ergo_tree_bytes,
-            &self.tokens,
-            &self.additional_registers,
-            self.creation_height,
-            None,
-            w,
-        )?;
-        self.transaction_id.sigma_serialize(w)?;
-        w.put_u16(self.index)?;
-        Ok(())
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        let box_candidate = ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)?;
-        let tx_id = TxId::sigma_parse(r)?;
-        let index = r.get_u16()?;
-        Ok(ErgoBox::from_box_candidate(&box_candidate, tx_id, 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.
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct ErgoBoxCandidate {
-    /// amount of money associated with the box
-    #[cfg_attr(feature = "json", serde(rename = "value"))]
-    pub value: BoxValue,
-    /// guarding script, which should be evaluated to true in order to open this box
-    #[cfg_attr(feature = "json", serde(rename = "ergoTree", with = "json::ergo_tree"))]
-    pub ergo_tree: ErgoTree,
-    /// secondary tokens the box contains
-    #[cfg_attr(feature = "json", serde(rename = "assets"))]
-    pub tokens: Vec<Token>,
-    ///  additional registers the box can carry over
-    #[cfg_attr(feature = "json", 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.
-    #[cfg_attr(feature = "json", serde(rename = "creationHeight"))]
-    pub creation_height: u32,
-}
-
-impl ErgoBoxCandidate {
-    /// Box serialization with token ids optionally saved in transaction
-    /// (in this case only token index is saved)
-    pub fn serialize_body_with_indexed_digests<W: SigmaByteWrite>(
-        &self,
-        token_ids_in_tx: Option<&IndexSet<TokenId>>,
-        w: &mut W,
-    ) -> SigmaSerializeResult {
-        serialize_box_with_indexed_digests(
-            &self.value,
-            self.ergo_tree.sigma_serialize_bytes()?,
-            &self.tokens,
-            &self.additional_registers,
-            self.creation_height,
-            token_ids_in_tx,
-            w,
-        )
-    }
-
-    /// Box deserialization with token ids optionally parsed in transaction
-    pub fn parse_body_with_indexed_digests<R: SigmaByteRead>(
-        digests_in_tx: Option<&IndexSet<TokenId>>,
-        r: &mut R,
-    ) -> Result<ErgoBoxCandidate, SigmaParsingError> {
-        parse_box_with_indexed_digests(digests_in_tx, r)
-    }
-}
-
-impl SigmaSerializable for ErgoBoxCandidate {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        self.serialize_body_with_indexed_digests(None, w)
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)
-    }
-}
-
-impl From<ErgoBox> for ErgoBoxCandidate {
-    fn from(b: ErgoBox) -> Self {
-        ErgoBoxCandidate {
-            value: b.value,
-            ergo_tree: b.ergo_tree,
-            tokens: b.tokens,
-            additional_registers: b.additional_registers,
-            creation_height: b.creation_height,
-        }
-    }
-}
-
-/// ErgoBox and ErgoBoxCandidate serialization
-
-/// Box serialization with token ids optionally saved in transaction
-/// (in this case only token index is saved)
-pub fn serialize_box_with_indexed_digests<W: SigmaByteWrite>(
-    box_value: &BoxValue,
-    ergo_tree_bytes: Vec<u8>,
-    tokens: &[Token],
-    additional_registers: &NonMandatoryRegisters,
-    creation_height: u32,
-    token_ids_in_tx: Option<&IndexSet<TokenId>>,
-    w: &mut W,
-) -> SigmaSerializeResult {
-    // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L95-L95
-    box_value.sigma_serialize(w)?;
-    w.write_all(&ergo_tree_bytes[..])?;
-    w.put_u32(creation_height)?;
-    w.put_u8(u8::try_from(tokens.len()).unwrap())?;
-
-    tokens.iter().try_for_each(|t| {
-        match token_ids_in_tx {
-            Some(token_ids) => Ok(w.put_u32(
-                u32::try_from(
-                    token_ids
-                        .get_full(&t.token_id)
-                        // this is not a true runtime error it just means that
-                        // calling site messed up the token ids
-                        .expect("failed to find token id in tx's digest index")
-                        .0,
-                )
-                .unwrap(),
-            )?),
-            None => t.token_id.sigma_serialize(w),
-        }
-        .and_then(|()| Ok(w.put_u64(t.amount.into())?))
-    })?;
-    additional_registers.sigma_serialize(w)
-}
-
-/// Box deserialization with token ids optionally parsed in transaction
-pub fn parse_box_with_indexed_digests<R: SigmaByteRead>(
-    digests_in_tx: Option<&IndexSet<TokenId>>,
-    r: &mut R,
-) -> Result<ErgoBoxCandidate, SigmaParsingError> {
-    // reference implementation -https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L144-L144
-
-    let value = BoxValue::sigma_parse(r)?;
-    let ergo_tree = ErgoTree::sigma_parse(r)?;
-    let creation_height = r.get_u32()?;
-    let tokens_count = r.get_u8()?;
-    let mut tokens = Vec::with_capacity(tokens_count as usize);
-    for _ in 0..tokens_count {
-        let token_id = match digests_in_tx {
-            None => TokenId::sigma_parse(r)?,
-            Some(digests) => {
-                let digest_index = r.get_u32()?;
-                match digests.get_index(digest_index as usize) {
-                    Some(i) => Ok((*i).clone()),
-                    None => Err(SigmaParsingError::Misc(
-                        "failed to find token id in tx digests".to_string(),
-                    )),
-                }?
-            }
-        };
-        let amount = r.get_u64()?;
-        tokens.push(Token {
-            token_id,
-            amount: amount.try_into()?,
-        })
-    }
-
-    let additional_registers = NonMandatoryRegisters::sigma_parse(r)?;
-
-    Ok(ErgoBoxCandidate {
-        value,
-        ergo_tree,
-        tokens,
-        additional_registers,
-        creation_height,
-    })
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::box_value::tests::ArbBoxValueRange;
-    use super::*;
-    use ergotree_ir::serialization::sigma_serialize_roundtrip;
-    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
-    use sigma_test_util::force_any_val;
-
-    impl Arbitrary for ErgoBoxCandidate {
-        type Parameters = ArbBoxValueRange;
-
-        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-            (
-                any_with::<BoxValue>(args),
-                any::<ErgoTree>(),
-                vec(any::<Token>(), 0..3),
-                any::<u32>(),
-                any::<NonMandatoryRegisters>(),
-            )
-                .prop_map(
-                    |(value, ergo_tree, tokens, creation_height, additional_registers)| Self {
-                        value,
-                        ergo_tree,
-                        tokens,
-                        additional_registers,
-                        creation_height,
-                    },
-                )
-                .boxed()
-        }
-        type Strategy = BoxedStrategy<Self>;
-    }
-
-    impl Arbitrary for ErgoBox {
-        type Parameters = ArbBoxValueRange;
-
-        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-            (
-                any_with::<ErgoBoxCandidate>(args),
-                any::<TxId>(),
-                any::<u16>(),
-            )
-                .prop_map(|(box_candidate, tx_id, index)| {
-                    Self::from_box_candidate(&box_candidate, tx_id, index).unwrap()
-                })
-                .boxed()
-        }
-        type Strategy = BoxedStrategy<Self>;
-    }
-
-    impl Arbitrary for ErgoBoxAssetsData {
-        type Parameters = ArbBoxValueRange;
-
-        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
-            (any_with::<BoxValue>(args), vec(any::<Token>(), 0..3))
-                .prop_map(|(value, tokens)| Self { value, tokens })
-                .boxed()
-        }
-
-        type Strategy = BoxedStrategy<Self>;
-    }
-
-    #[test]
-    fn test_sum_tokens_repeating_token_id() {
-        let token = force_any_val::<Token>();
-        let b = ErgoBoxAssetsData {
-            value: BoxValue::SAFE_USER_MIN,
-            tokens: vec![token.clone(), token.clone()],
-        };
-        assert_eq!(
-            u64::from(
-                *sum_tokens_from_boxes(vec![b.clone(), b].as_slice())
-                    .get(&token.token_id)
-                    .unwrap()
-            ),
-            u64::from(token.amount) * 4
-        );
-    }
-
-    #[test]
-    fn get_register_mandatory() {
-        let b = force_any_val::<ErgoBox>();
-        assert_eq!(b.get_register(RegisterId::R0).unwrap(), b.value.into());
-        assert_eq!(
-            b.get_register(RegisterId::R1).unwrap(),
-            b.script_bytes().unwrap().into()
-        );
-        assert_eq!(
-            b.get_register(RegisterId::R2).unwrap(),
-            b.tokens_raw().into()
-        );
-        assert_eq!(
-            b.get_register(RegisterId::R3).unwrap(),
-            b.creation_info().into()
-        );
-    }
-
-    #[test]
-    fn creation_info() {
-        let b = force_any_val::<ErgoBox>();
-        assert_eq!(b.creation_info().0, b.creation_height as i32);
-        let mut expected_bytes = Vec::new();
-        expected_bytes.extend_from_slice(b.transaction_id.0 .0.as_ref());
-        expected_bytes.extend_from_slice(&b.index.to_be_bytes());
-        assert_eq!(b.creation_info().1, expected_bytes.to_vec().as_vec_i8());
-    }
-
-    proptest! {
-
-        #[test]
-        fn sum_tokens_eq(b in any::<ErgoBoxAssetsData>()) {
-            prop_assert_eq!(sum_tokens(b.tokens().as_slice()), sum_tokens_from_boxes(vec![b].as_slice()))
-        }
-
-        #[test]
-        fn ergo_box_candidate_ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
-            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
-        }
-
-        #[test]
-        fn ergo_box_ser_roundtrip(v in any::<ErgoBox>()) {
-            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
-        }
-    }
-}
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index c245321fd..36ef60147 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -3,11 +3,12 @@
 use std::collections::HashMap;
 use std::convert::{TryFrom, TryInto};
 
-use crate::chain::token::Token;
-
-use super::box_value::BoxValue;
-use super::register::{NonMandatoryRegisterId, NonMandatoryRegisters, NonMandatoryRegistersError};
-use super::ErgoBoxCandidate;
+use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::ergo_box::NonMandatoryRegisterId;
+use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
+use ergotree_ir::chain::ergo_box::NonMandatoryRegistersError;
+use ergotree_ir::chain::token::Token;
 use ergotree_ir::ergo_tree::ErgoTree;
 use ergotree_ir::mir::constant::Constant;
 use ergotree_ir::serialization::{SigmaSerializable, SigmaSerializationError};
@@ -244,12 +245,11 @@ impl ErgoBoxCandidateBuilder {
 #[cfg(test)]
 mod tests {
 
+    use ergotree_ir::base16_str::Base16Str;
+    use ergotree_ir::chain::token::TokenId;
     use sigma_test_util::force_any_val;
     use NonMandatoryRegisterId::*;
 
-    use crate::chain::token::TokenId;
-    use crate::chain::Base16Str;
-
     use super::*;
 
     #[test]
@@ -352,7 +352,8 @@ mod tests {
                 .additional_registers
                 .get(NonMandatoryRegisterId::R4)
                 .unwrap()
-                .base16_str(),
+                .base16_str()
+                .unwrap(),
             "0e03555344",
             "invalid encoding of token name in R4"
         );
@@ -361,7 +362,8 @@ mod tests {
                 .additional_registers
                 .get(NonMandatoryRegisterId::R5)
                 .unwrap()
-                .base16_str(),
+                .base16_str()
+                .unwrap(),
             "0e184e6f7468696e67206261636b65642055534420746f6b656e",
             "invalid encoding of token description in R5"
         );
@@ -370,7 +372,8 @@ mod tests {
                 .additional_registers
                 .get(NonMandatoryRegisterId::R6)
                 .unwrap()
-                .base16_str(),
+                .base16_str()
+                .unwrap(),
             "0e0132",
             "invalid encoding of token number of decimals in R6"
         );
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index 702e84b86..2916541fc 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -1,23 +1,11 @@
 //! JSON serialization
 
-pub(crate) mod block_header;
 pub(crate) mod context_extension;
-pub(crate) mod ergo_box;
-pub(crate) mod ergo_tree;
 pub(crate) mod transaction;
 
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
-use serde::Serializer;
 use serde::{Deserialize, Serialize};
 
-pub fn serialize_bytes<S, T>(bytes: T, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-    T: AsRef<[u8]>,
-{
-    serializer.serialize_str(&base16::encode_lower(bytes.as_ref()))
-}
-
 #[cfg_attr(
     feature = "json",
     derive(Serialize, Deserialize),
diff --git a/ergo-lib/src/chain/json/block_header.rs b/ergo-lib/src/chain/json/block_header.rs
deleted file mode 100644
index caed1f54c..000000000
--- a/ergo-lib/src/chain/json/block_header.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use serde::Deserialize;
-use std::convert::TryFrom;
-use std::convert::TryInto;
-
-use ergotree_ir::chain::votes::{Votes, VotesError};
-
-use crate::chain::Base16DecodedBytes;
-
-#[derive(Deserialize, Debug, Clone)]
-#[serde(untagged)]
-pub enum VotesEncodingVariants {
-    AsStr(Base16DecodedBytes),
-    AsByteArray(Vec<u8>), // explorer v1
-}
-
-impl TryFrom<VotesEncodingVariants> for Votes {
-    type Error = VotesError;
-
-    fn try_from(value: VotesEncodingVariants) -> Result<Self, Self::Error> {
-        match value {
-            VotesEncodingVariants::AsStr(bytes) => bytes.try_into(),
-            VotesEncodingVariants::AsByteArray(bytes) => bytes.try_into(),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use ergotree_ir::chain::header::Header;
-
-    use crate::chain::block_header::BlockHeaderRef;
-
-    // Tries to convert json string to `Header`.
-    fn header_try_from_json(json: &str) -> Result<Header, serde_json::Error> {
-        let mut de = serde_json::Deserializer::from_str(json);
-        // actually, returns `Header`: https://serde.rs/remote-derive.html
-        BlockHeaderRef::deserialize(&mut de)
-    }
-
-    #[test]
-    fn parse_block_header() {
-        let json = r#"{
-            "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
-            "difficulty": "626412390187008",
-            "votes": "040000",
-            "timestamp": 1618929697400,
-            "size": 221,
-            "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
-            "height": 471746,
-            "nBits": 117586360,
-            "version": 2,
-            "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
-            "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
-            "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
-            "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
-            "powSolutions": {
-              "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
-              "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
-              "n": "5939ecfee6b0d7f4",
-              "d": 0
-            },
-            "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
-            "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
-            "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
-        }"#;
-        let header: Header = header_try_from_json(json).unwrap();
-        assert_eq!(header.height, 471746);
-    }
-
-    #[test]
-    fn parse_block_header_explorer_v1() {
-        // see https://api.ergoplatform.com/api/v1/blocks/de68a9cd727510d01eae3146f862261661f3bebdfd3c45c19d431b2ae81fb4b6
-        let json = r#"{
-            "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
-            "difficulty": "626412390187008",
-            "votes": [4,0,0],
-            "timestamp": 1618929697400,
-            "size": 221,
-            "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
-            "height": 471746,
-            "nBits": 117586360,
-            "version": 2,
-            "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
-            "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
-            "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
-            "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
-            "powSolutions": {
-              "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
-              "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
-              "n": "5939ecfee6b0d7f4",
-              "d": 0
-            },
-            "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
-            "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
-            "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
-        }"#;
-        let header: Header = header_try_from_json(json).unwrap();
-        assert_eq!(header.height, 471746);
-    }
-}
diff --git a/ergo-lib/src/chain/json/transaction.rs b/ergo-lib/src/chain/json/transaction.rs
index f270937cc..44d5160fa 100644
--- a/ergo-lib/src/chain/json/transaction.rs
+++ b/ergo-lib/src/chain/json/transaction.rs
@@ -1,8 +1,7 @@
 use crate::chain::transaction::{DataInput, Input, UnsignedInput};
-use crate::chain::{
-    ergo_box::{ErgoBox, ErgoBoxCandidate},
-    transaction::TxId,
-};
+use ergotree_ir::chain::ergo_box::ErgoBox;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::tx_id::TxId;
 use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 3f2fa8890..21fd09bed 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -5,11 +5,15 @@ pub mod input;
 pub mod unsigned;
 
 use bounded_vec::BoundedVec;
+use ergotree_ir::chain::digest32::blake2b256_hash;
+use ergotree_ir::chain::ergo_box::ErgoBox;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::token::TokenId;
+pub use ergotree_ir::chain::tx_id::TxId;
 use thiserror::Error;
 
 pub use data_input::*;
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
-use ergotree_ir::chain::digest::{blake2b256_hash, Digest32};
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SigmaParsingError;
@@ -20,14 +24,9 @@ pub use input::*;
 
 use self::unsigned::UnsignedTransaction;
 
-#[cfg(feature = "json")]
-use super::digest32::DigestRef;
 #[cfg(feature = "json")]
 use super::json;
-use super::{ergo_box::ErgoBox, ergo_box::ErgoBoxCandidate, token::TokenId};
 use indexmap::IndexSet;
-#[cfg(test)]
-use proptest_derive::Arbitrary;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
@@ -35,42 +34,6 @@ use std::convert::TryFrom;
 use std::convert::TryInto;
 use std::iter::FromIterator;
 
-/// Transaction id (ModifierId in sigmastate)
-#[derive(PartialEq, Eq, Hash, Debug, Clone)]
-#[cfg_attr(test, derive(Arbitrary))]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct TxId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] pub Digest32);
-
-impl TxId {
-    /// All zeros
-    pub fn zero() -> TxId {
-        TxId(Digest32::zero())
-    }
-}
-
-impl SigmaSerializable for TxId {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        self.0.sigma_serialize(w)?;
-        Ok(())
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        Ok(Self(Digest32::sigma_parse(r)?))
-    }
-}
-
-#[cfg(feature = "json")]
-impl From<TxId> for String {
-    fn from(v: TxId) -> Self {
-        String::from(Into::<DigestRef<32>>::into(v.0))
-    }
-}
-
-impl AsRef<[u8]> for TxId {
-    fn as_ref(&self) -> &[u8] {
-        self.0.as_ref()
-    }
-}
-
 /// BouncedVec type for Tx inputs and output_candidates
 pub type TxIoVec<T> = BoundedVec<T, 1, { u16::MAX as usize }>;
 
diff --git a/ergo-lib/src/chain/transaction/data_input.rs b/ergo-lib/src/chain/transaction/data_input.rs
index ec2a92a76..33a1d9a6c 100644
--- a/ergo-lib/src/chain/transaction/data_input.rs
+++ b/ergo-lib/src/chain/transaction/data_input.rs
@@ -1,21 +1,17 @@
 //! DataInput type
 
-use crate::chain::ergo_box::BoxId;
+use ergotree_ir::chain::ergo_box::BoxId;
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SigmaParsingError;
 use ergotree_ir::serialization::SigmaSerializable;
 use ergotree_ir::serialization::SigmaSerializeResult;
-#[cfg(test)]
-use proptest::prelude::*;
-#[cfg(test)]
-use proptest_derive::Arbitrary;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
 /// Inputs, that are used to enrich script context, but won't be spent by the transaction
 #[derive(PartialEq, Eq, Debug, Clone)]
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 pub struct DataInput {
     /// id of the box to add into context (should be in UTXO)
@@ -45,6 +41,7 @@ mod tests {
     use ergotree_ir::serialization::sigma_serialize_roundtrip;
 
     use super::*;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/chain/transaction/input.rs b/ergo-lib/src/chain/transaction/input.rs
index 4cd89871d..922bf43a7 100644
--- a/ergo-lib/src/chain/transaction/input.rs
+++ b/ergo-lib/src/chain/transaction/input.rs
@@ -2,9 +2,9 @@
 
 pub mod prover_result;
 
-use crate::chain::ergo_box::{BoxId, ErgoBoxId};
 use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
+use ergotree_ir::chain::ergo_box::BoxId;
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SigmaParsingError;
@@ -13,6 +13,7 @@ use ergotree_ir::serialization::SigmaSerializeResult;
 
 #[cfg(feature = "json")]
 use crate::chain::json::context_extension::ContextExtensionSerde;
+use crate::wallet::box_selector::ErgoBoxId;
 #[cfg(feature = "json")]
 use serde::ser::SerializeStruct;
 #[cfg(feature = "json")]
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index 9bcdacf6c..5bb211dfa 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -1,17 +1,18 @@
 //! Unsigned (without proofs) transaction
 
-use crate::chain::token::TokenId;
-
 use super::distinct_token_ids;
 use super::input::{Input, UnsignedInput};
 #[cfg(feature = "json")]
 use super::json;
 use super::prover_result::ProverResult;
 use super::DataInput;
+use super::Transaction;
 use super::TxIoVec;
-use super::{super::ergo_box::ErgoBoxCandidate, Transaction, TxId};
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
-use ergotree_ir::chain::digest::blake2b256_hash;
+use ergotree_ir::chain::digest32::blake2b256_hash;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::token::TokenId;
+use ergotree_ir::chain::tx_id::TxId;
 use ergotree_ir::serialization::SigmaSerializationError;
 use indexmap::IndexSet;
 #[cfg(feature = "json")]
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index 710847c62..f776eb7bd 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -11,7 +11,7 @@
 #![deny(missing_docs)]
 // Clippy exclusions
 #![allow(clippy::unit_arg)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 pub mod chain;
 pub mod constants;
diff --git a/ergo-lib/src/wallet/box_selector.rs b/ergo-lib/src/wallet/box_selector.rs
index 9a3fa86ae..0d7df6f85 100644
--- a/ergo-lib/src/wallet/box_selector.rs
+++ b/ergo-lib/src/wallet/box_selector.rs
@@ -1,14 +1,18 @@
 //! Box selection for transaction inputs
 
 mod simple;
+use std::collections::HashMap;
+
+use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+use ergotree_ir::chain::ergo_box::box_value::BoxValueError;
+use ergotree_ir::chain::ergo_box::BoxId;
+use ergotree_ir::chain::ergo_box::ErgoBox;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::token::Token;
+use ergotree_ir::chain::token::TokenAmount;
+use ergotree_ir::chain::token::TokenId;
 pub use simple::*;
 
-use crate::chain::ergo_box::BoxValueError;
-use crate::chain::ergo_box::ErgoBoxAssetsData;
-use crate::chain::{
-    ergo_box::{BoxValue, ErgoBoxAssets},
-    token::Token,
-};
 use thiserror::Error;
 
 /// Selected boxes (by [`BoxSelector`])
@@ -55,3 +59,147 @@ impl From<BoxValueError> for BoxSelectorError {
         BoxSelectorError::BoxValueError(e)
     }
 }
+
+/// Assets that ErgoBox holds
+pub trait ErgoBoxAssets {
+    /// Box value
+    fn value(&self) -> BoxValue;
+    /// Tokens (ids and amounts)
+    fn tokens(&self) -> Vec<Token>;
+}
+
+/// Simple struct to hold ErgoBoxAssets values
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBoxAssetsData {
+    /// Box value
+    pub value: BoxValue,
+    /// Tokens
+    pub tokens: Vec<Token>,
+}
+
+impl ErgoBoxAssets for ErgoBoxAssetsData {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<Token> {
+        self.tokens.clone()
+    }
+}
+
+impl ErgoBoxAssets for ErgoBoxCandidate {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<Token> {
+        self.tokens.clone()
+    }
+}
+
+impl ErgoBoxAssets for ErgoBox {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<Token> {
+        self.tokens.clone()
+    }
+}
+
+/// id of the ergo box
+pub trait ErgoBoxId {
+    /// Id of the ergo box
+    fn box_id(&self) -> BoxId;
+}
+
+impl ErgoBoxId for ErgoBox {
+    fn box_id(&self) -> BoxId {
+        self.box_id()
+    }
+}
+
+/// Returns the total value of the given boxes
+pub fn sum_value<T: ErgoBoxAssets>(bs: &[T]) -> u64 {
+    bs.iter().map(|b| *b.value().as_u64()).sum()
+}
+
+/// Returns the total token amounts (all tokens combined)
+pub fn sum_tokens(ts: &[Token]) -> HashMap<TokenId, TokenAmount> {
+    let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
+    ts.iter().for_each(|t| {
+        res.entry(t.token_id.clone())
+            .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
+            .or_insert(t.amount);
+    });
+    res
+}
+
+/// Returns the total token amounts (all tokens combined) of the given boxes
+pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(bs: &[T]) -> HashMap<TokenId, TokenAmount> {
+    let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
+    bs.iter().for_each(|b| {
+        b.tokens().iter().for_each(|t| {
+            res.entry(t.token_id.clone())
+                .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
+                .or_insert(t.amount);
+        });
+    });
+    res
+}
+
+#[cfg(test)]
+mod tests {
+
+    use ergotree_ir::chain::ergo_box::box_value::arbitrary::ArbBoxValueRange;
+    use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+    use ergotree_ir::chain::token::Token;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+    use sigma_test_util::force_any_val;
+
+    use crate::wallet::box_selector::sum_tokens;
+    use crate::wallet::box_selector::sum_tokens_from_boxes;
+
+    use super::ErgoBoxAssetsData;
+
+    impl Arbitrary for ErgoBoxAssetsData {
+        type Parameters = ArbBoxValueRange;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (any_with::<BoxValue>(args), vec(any::<Token>(), 0..3))
+                .prop_map(|(value, tokens)| Self { value, tokens })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary() -> Self::Strategy {
+            Self::arbitrary_with(Default::default())
+        }
+    }
+
+    #[test]
+    fn test_sum_tokens_repeating_token_id() {
+        let token = force_any_val::<Token>();
+        let b = ErgoBoxAssetsData {
+            value: BoxValue::SAFE_USER_MIN,
+            tokens: vec![token.clone(), token.clone()],
+        };
+        assert_eq!(
+            u64::from(
+                *sum_tokens_from_boxes(vec![b.clone(), b].as_slice())
+                    .get(&token.token_id)
+                    .unwrap()
+            ),
+            u64::from(token.amount) * 4
+        );
+    }
+
+    proptest! {
+
+        #[test]
+        fn sum_tokens_eq(b in any::<ErgoBoxAssetsData>()) {
+            prop_assert_eq!(sum_tokens(b.tokens.as_slice()), sum_tokens_from_boxes(vec![b].as_slice()))
+        }
+    }
+}
diff --git a/ergo-lib/src/wallet/box_selector/simple.rs b/ergo-lib/src/wallet/box_selector/simple.rs
index 1e2c7758c..611977102 100644
--- a/ergo-lib/src/wallet/box_selector/simple.rs
+++ b/ergo-lib/src/wallet/box_selector/simple.rs
@@ -4,16 +4,17 @@ use std::cmp::min;
 use std::collections::HashMap;
 use std::convert::TryInto;
 
-use crate::chain::ergo_box::sum_tokens;
-use crate::chain::ergo_box::sum_tokens_from_boxes;
-use crate::chain::ergo_box::BoxValue;
-use crate::chain::ergo_box::ErgoBoxAssets;
-use crate::chain::ergo_box::ErgoBoxAssetsData;
-use crate::chain::token::Token;
-use crate::chain::token::TokenAmount;
-use crate::chain::token::TokenId;
+use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+use ergotree_ir::chain::token::Token;
+use ergotree_ir::chain::token::TokenAmount;
+use ergotree_ir::chain::token::TokenId;
+
+use crate::wallet::box_selector::sum_tokens;
+use crate::wallet::box_selector::sum_tokens_from_boxes;
+use crate::wallet::box_selector::ErgoBoxAssetsData;
 
 use super::BoxSelectorError;
+use super::ErgoBoxAssets;
 use super::{BoxSelection, BoxSelector};
 
 /// Simple box selector, collects inputs(sorted by targeted assets) until target balance is reached
@@ -165,12 +166,12 @@ impl Default for SimpleBoxSelector {
 mod tests {
     use std::convert::TryFrom;
 
-    use crate::chain::ergo_box::checked_sum;
-    use crate::chain::ergo_box::sum_value;
-    use crate::chain::ergo_box::ErgoBox;
-    use crate::chain::ergo_box::ErgoBoxAssetsData;
+    use ergotree_ir::chain::ergo_box::box_value::checked_sum;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use proptest::{collection::vec, prelude::*};
 
+    use crate::wallet::box_selector::sum_value;
+
     use super::*;
 
     #[test]
@@ -310,7 +311,7 @@ mod tests {
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
-                    .all(|b| b.tokens.iter().any(|t| t.token_id == *target_token_id)),
+                    .all(|b| b.tokens().iter().any(|t| t.token_id == *target_token_id)),
                 "only boxes that have target token should be selected, got: {0:?}", selection.boxes
             );
         }
@@ -340,7 +341,7 @@ mod tests {
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
-                    .all(|b| b.tokens.iter().any(|t| t.token_id == *target_token_id)),
+                    .all(|b| b.tokens().iter().any(|t| t.token_id == *target_token_id)),
                 "only boxes that have target token should be selected, got: {0:?}", selection.boxes
             );
         }
@@ -385,7 +386,7 @@ mod tests {
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
-                    .all(|b| b.tokens.iter().any(|t| t.token_id == *target_token1_id || t.token_id == *target_token2_id)),
+                    .all(|b| b.tokens().iter().any(|t| t.token_id == *target_token1_id || t.token_id == *target_token2_id)),
                 "only boxes that have target tokens should be selected, got: {0:?}", selection.boxes
             );
         }
diff --git a/ergo-lib/src/wallet/secret_key.rs b/ergo-lib/src/wallet/secret_key.rs
index f24887125..08bb1e77f 100644
--- a/ergo-lib/src/wallet/secret_key.rs
+++ b/ergo-lib/src/wallet/secret_key.rs
@@ -1,7 +1,7 @@
 //! Secret types
 use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
 use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
-use ergotree_ir::address::Address;
+use ergotree_ir::chain::address::Address;
 
 /// Types of secrets
 #[derive(PartialEq, Debug, Clone)]
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 5a25f840f..fa99ff8f3 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -1,14 +1,12 @@
 //! Transaction signing
 
 use ergotree_interpreter::sigma_protocol::prover::hint::HintsBag;
+use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::serialization::SigmaSerializationError;
-use std::collections::HashMap;
 use std::rc::Rc;
 
-use crate::chain::ergo_box::BoxId;
 use crate::chain::transaction::Input;
 use crate::chain::{
-    ergo_box::ErgoBox,
     ergo_state_context::ErgoStateContext,
     transaction::{unsigned::UnsignedTransaction, Transaction},
 };
@@ -17,10 +15,6 @@ use ergotree_interpreter::eval::context::Context;
 use ergotree_interpreter::eval::env::Env;
 use ergotree_interpreter::sigma_protocol::prover::ProverError;
 use ergotree_interpreter::sigma_protocol::prover::{ContextExtension, Prover};
-use ergotree_ir::ir_ergo_box::IrBoxId;
-use ergotree_ir::ir_ergo_box::IrErgoBox;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
 use thiserror::Error;
 
 /// Errors on transaction signing
@@ -51,43 +45,6 @@ pub struct TransactionContext {
     pub data_boxes: Vec<ErgoBox>,
 }
 
-/// Holding all ErgoBox needed for interpreter [`ergotree_interpreter::eval::context::Context`]
-#[derive(Debug)]
-pub struct ErgoBoxArena(HashMap<BoxId, ErgoBox>);
-
-impl ErgoBoxArena {
-    /// Create new arena and store given boxes
-    pub fn new(
-        self_box: ErgoBox,
-        outputs: Vec<ErgoBox>,
-        data_inputs: Vec<ErgoBox>,
-        inputs: Vec<ErgoBox>,
-    ) -> Self {
-        let mut m = HashMap::new();
-        m.insert(self_box.box_id(), self_box);
-        outputs.into_iter().for_each(|b| {
-            m.insert(b.box_id(), b);
-        });
-        inputs.into_iter().for_each(|b| {
-            m.insert(b.box_id(), b);
-        });
-        data_inputs.into_iter().for_each(|b| {
-            m.insert(b.box_id(), b);
-        });
-        ErgoBoxArena(m)
-    }
-}
-
-impl IrErgoBoxArena for ErgoBoxArena {
-    fn get(&self, id: &IrBoxId) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError> {
-        self.0
-            .get(&id.into())
-            .cloned()
-            .ok_or_else(|| IrErgoBoxArenaError(format!("ErgoBox with id {0:?} not found", id)))
-            .map(|b| Rc::new(b) as Rc<dyn IrErgoBox>)
-    }
-}
-
 /// `self_index` - index of the SELF box in the tx_ctx.boxes_to_spend
 pub fn make_context(
     state_ctx: &ErgoStateContext,
@@ -108,22 +65,16 @@ pub fn make_context(
         .map(|(idx, b)| ErgoBox::from_box_candidate(b, tx_ctx.spending_tx.id(), idx as u16))
         .collect::<Result<Vec<ErgoBox>, SigmaSerializationError>>()?;
     let data_inputs: Vec<ErgoBox> = tx_ctx.data_boxes.clone();
-    let self_box_ir = self_box.box_id().into();
-    let outputs_ir = outputs.iter().map(|b| b.box_id().into()).collect();
+    let self_box_ir = Rc::new(self_box);
+    let outputs_ir = outputs.into_iter().map(Rc::new).collect();
     let inputs_ir = tx_ctx
         .boxes_to_spend
-        .iter()
-        .map(|b| b.box_id().into())
+        .clone()
+        .into_iter()
+        .map(Rc::new)
         .collect();
-    let data_inputs_ir = data_inputs.iter().map(|b| b.box_id().into()).collect();
-    let box_arena = Rc::new(ErgoBoxArena::new(
-        self_box,
-        outputs,
-        data_inputs,
-        tx_ctx.boxes_to_spend.clone(),
-    ));
+    let data_inputs_ir = data_inputs.into_iter().map(Rc::new).collect();
     Ok(Context {
-        box_arena,
         height,
         self_box: self_box_ir,
         outputs: outputs_ir,
@@ -180,8 +131,11 @@ mod tests {
     use ergotree_interpreter::sigma_protocol::verifier::TestVerifier;
     use ergotree_interpreter::sigma_protocol::verifier::Verifier;
     use ergotree_interpreter::sigma_protocol::verifier::VerifierError;
-    use ergotree_ir::address::AddressEncoder;
-    use ergotree_ir::address::NetworkPrefix;
+    use ergotree_ir::chain::address::AddressEncoder;
+    use ergotree_ir::chain::address::NetworkPrefix;
+    use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+    use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
+    use ergotree_ir::chain::tx_id::TxId;
     use proptest::collection::vec;
     use proptest::prelude::*;
     use rand::prelude::SliceRandom;
@@ -189,8 +143,7 @@ mod tests {
     use sigma_test_util::force_any_val;
 
     use crate::chain::{
-        ergo_box::{box_builder::ErgoBoxCandidateBuilder, BoxValue, NonMandatoryRegisters},
-        transaction::{TxId, UnsignedInput},
+        ergo_box::box_builder::ErgoBoxCandidateBuilder, transaction::UnsignedInput,
     };
     use ergotree_ir::ergo_tree::ErgoTree;
     use ergotree_ir::mir::expr::Expr;
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index 23b765246..9cf9138eb 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -1,31 +1,35 @@
 //! Builder for an UnsignedTransaction
 
+use ergotree_ir::chain::ergo_box::box_value::BoxValueError;
 use std::collections::HashSet;
 use std::convert::TryInto;
 
 use bounded_vec::BoundedVecOutOfBounds;
 use ergotree_interpreter::sigma_protocol;
 use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
-use ergotree_ir::address::{Address, AddressEncoder, NetworkPrefix};
+use ergotree_ir::chain::address::Address;
+use ergotree_ir::chain::address::AddressEncoder;
+use ergotree_ir::chain::address::NetworkPrefix;
+use ergotree_ir::chain::ergo_box::box_value::BoxValue;
+use ergotree_ir::chain::ergo_box::BoxId;
+use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
+use ergotree_ir::chain::token::Token;
+use ergotree_ir::chain::token::TokenId;
 use ergotree_ir::serialization::{SigmaParsingError, SigmaSerializable, SigmaSerializationError};
 use thiserror::Error;
 
 use crate::chain::contract::Contract;
 use crate::chain::ergo_box::box_builder::{ErgoBoxCandidateBuilder, ErgoBoxCandidateBuilderError};
-use crate::chain::ergo_box::{sum_tokens_from_boxes, sum_value, BoxId, BoxValue, BoxValueError};
-use crate::chain::token::{Token, TokenId};
+use crate::chain::transaction::unsigned::UnsignedTransaction;
 use crate::chain::transaction::{DataInput, Input, Transaction, UnsignedInput};
-use crate::chain::{
-    ergo_box::ErgoBoxAssets, ergo_box::ErgoBoxCandidate, ergo_box::ErgoBoxId,
-    transaction::unsigned::UnsignedTransaction,
-};
 use crate::constants::MINERS_FEE_MAINNET_ADDRESS;
 
+use super::box_selector::sum_tokens_from_boxes;
+use super::box_selector::sum_value;
+use super::box_selector::ErgoBoxAssets;
+use super::box_selector::ErgoBoxId;
 use super::box_selector::{BoxSelection, BoxSelectorError};
 
-/// Suggested transaction fee (1100000 nanoERGs, semi-default value used across wallets and dApps as of Oct 2020)
-pub const SUGGESTED_TX_FEE: BoxValue = BoxValue(1100000u64);
-
 /// Unsigned transaction builder
 #[derive(Clone)]
 pub struct TxBuilder<S: ErgoBoxAssets> {
@@ -237,6 +241,12 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
     }
 }
 
+/// Suggested transaction fee (1100000 nanoERGs, semi-default value used across wallets and dApps as of Oct 2020)
+#[allow(non_snake_case)]
+pub fn SUGGESTED_TX_FEE() -> BoxValue {
+    BoxValue::new(1100000u64).unwrap()
+}
+
 /// Create a box with miner's contract and a given value
 pub fn new_miner_fee_box(
     fee_amount: BoxValue,
@@ -287,16 +297,17 @@ mod tests {
 
     use std::convert::TryInto;
 
+    use ergotree_ir::chain::ergo_box::box_value::checked_sum;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
+    use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
+    use ergotree_ir::chain::token::arbitrary::ArbTokenIdParam;
+    use ergotree_ir::chain::token::TokenAmount;
+    use ergotree_ir::chain::tx_id::TxId;
     use ergotree_ir::ergo_tree::ErgoTree;
     use proptest::{collection::vec, prelude::*};
     use sigma_test_util::force_any_val;
     use sigma_test_util::force_any_val_with;
 
-    use crate::chain::{
-        ergo_box::{checked_sum, ErgoBox, NonMandatoryRegisters},
-        token::{tests::ArbTokenIdParam, Token, TokenAmount, TokenId},
-        transaction::TxId,
-    };
     use crate::wallet::box_selector::{BoxSelector, SimpleBoxSelector};
 
     use super::*;
@@ -534,7 +545,7 @@ mod tests {
             0,
         )
         .unwrap();
-        let tx_fee = super::SUGGESTED_TX_FEE;
+        let tx_fee = super::SUGGESTED_TX_FEE();
         let out_box_value = input.value.checked_sub(&tx_fee).unwrap();
         let box_builder =
             ErgoBoxCandidateBuilder::new(out_box_value, force_any_val::<ErgoTree>(), 0);
diff --git a/ergoscript-compiler/src/lib.rs b/ergoscript-compiler/src/lib.rs
index f09d1ac08..aa799758c 100644
--- a/ergoscript-compiler/src/lib.rs
+++ b/ergoscript-compiler/src/lib.rs
@@ -11,7 +11,7 @@
 #![deny(missing_docs)]
 // Clippy exclusions
 #![allow(clippy::unit_arg)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 pub(crate) mod ast;
 pub(crate) mod binder;
diff --git a/ergotree-interpreter/src/contracts.rs b/ergotree-interpreter/src/contracts.rs
index 1205c0b05..58681b5c8 100644
--- a/ergotree-interpreter/src/contracts.rs
+++ b/ergotree-interpreter/src/contracts.rs
@@ -5,8 +5,8 @@ mod tests {
     use std::rc::Rc;
 
     use crate::eval::tests::eval_out_wo_ctx;
-    use ergotree_ir::address::AddressEncoder;
-    use ergotree_ir::address::NetworkPrefix;
+    use ergotree_ir::chain::address::AddressEncoder;
+    use ergotree_ir::chain::address::NetworkPrefix;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
 
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index be6ada082..f4bc6639f 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -2,7 +2,6 @@
 use std::rc::Rc;
 
 use ergotree_ir::ergo_tree::ErgoTreeError;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
 use ergotree_ir::mir::constant::TryExtractFromError;
 use ergotree_ir::mir::expr::Expr;
 use ergotree_ir::mir::value::Value;
@@ -121,9 +120,6 @@ pub enum EvalError {
     /// Arithmetic exception error
     #[error("Arithmetic exception: {0}")]
     ArithmeticException(String),
-    /// Cannot find ErgoBox in Context
-    #[error("Cannot find ErgoBox in Context: {0:?}")]
-    ErgoBoxNotFound(#[from] IrErgoBoxArenaError),
     /// Misc error
     #[error("error: {0}")]
     Misc(String),
diff --git a/ergotree-interpreter/src/eval/coll_by_index.rs b/ergotree-interpreter/src/eval/coll_by_index.rs
index ee1c2de84..df40bf938 100644
--- a/ergotree-interpreter/src/eval/coll_by_index.rs
+++ b/ergotree-interpreter/src/eval/coll_by_index.rs
@@ -43,7 +43,7 @@ impl Evaluable for ByIndex {
 #[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
-    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
     use sigma_test_util::force_any_val;
@@ -61,8 +61,8 @@ mod tests {
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<IrBoxId>(&expr, ctx.clone()),
-            ctx.outputs.get(0).unwrap().clone()
+            eval_out::<Rc<ErgoBox>>(&expr, ctx.clone()).box_id(),
+            ctx.outputs.get(0).unwrap().box_id()
         );
     }
 
diff --git a/ergotree-interpreter/src/eval/coll_filter.rs b/ergotree-interpreter/src/eval/coll_filter.rs
index b4a4c70a9..0a24155e2 100644
--- a/ergotree-interpreter/src/eval/coll_filter.rs
+++ b/ergotree-interpreter/src/eval/coll_filter.rs
@@ -79,7 +79,7 @@ mod tests {
 
     use super::*;
 
-    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::mir::bin_op::BinOp;
     use ergotree_ir::mir::bin_op::RelationOp;
     use ergotree_ir::mir::expr::Expr;
@@ -130,10 +130,10 @@ mod tests {
             .into();
             let ctx = Rc::new(ctx);
             assert_eq!(
-                eval_out::<Vec<IrBoxId>>(&expr, ctx.clone()),
+                eval_out::<Vec<Rc<ErgoBox>>>(&expr, ctx.clone()),
                 ctx.data_inputs.clone()
                     .into_iter()
-                    .filter(| b| 1 <= b.get_box(&ctx.box_arena).unwrap().value()).collect::<Vec<IrBoxId>>()
+                    .filter(| b| 1 <= b.value.as_i64()).collect::<Vec<Rc<ErgoBox>>>()
             );
         }
     }
diff --git a/ergotree-interpreter/src/eval/coll_fold.rs b/ergotree-interpreter/src/eval/coll_fold.rs
index ea45907b4..4bd030ee7 100644
--- a/ergotree-interpreter/src/eval/coll_fold.rs
+++ b/ergotree-interpreter/src/eval/coll_fold.rs
@@ -128,7 +128,7 @@ mod tests {
                 eval_out::<i64>(&expr, ctx.clone()),
                 ctx.data_inputs
                     .iter()
-                    .fold(0i64, |acc, b| acc + b.get_box(&ctx.box_arena).unwrap().value())
+                    .fold(0i64, |acc, b| acc + b.value.as_i64())
             );
         }
 
diff --git a/ergotree-interpreter/src/eval/coll_map.rs b/ergotree-interpreter/src/eval/coll_map.rs
index bdb913552..3b77586e6 100644
--- a/ergotree-interpreter/src/eval/coll_map.rs
+++ b/ergotree-interpreter/src/eval/coll_map.rs
@@ -129,7 +129,7 @@ mod tests {
                 eval_out::<Vec<i64>>(&expr, ctx.clone()),
                 ctx.data_inputs
                     .iter()
-                    .map(| b| b.get_box(&ctx.box_arena).unwrap().value() + 1).collect::<Vec<i64>>()
+                    .map(| b| b.value.as_i64() + 1).collect::<Vec<i64>>()
             );
         }
 
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index e145ec406..e7821d0a3 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -1,28 +1,23 @@
-pub(crate) mod ir_ergo_box_dummy;
-
 use std::rc::Rc;
 
 use crate::sigma_protocol::prover::ContextExtension;
+use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::chain::header::Header;
-use ergotree_ir::ir_ergo_box::IrBoxId;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
 use ergotree_ir::mir::header::PreHeader;
 
 /// Interpreter's context (blockchain state)
 #[derive(Debug)]
 pub struct Context {
-    /// Arena with all boxes (from self, inputs, outputs, data_inputs)
-    pub box_arena: Rc<dyn IrErgoBoxArena>,
     /// Current height
     pub height: u32,
     /// Box that contains the script we're evaluating (from spending transaction inputs)
-    pub self_box: IrBoxId,
+    pub self_box: Rc<ErgoBox>,
     /// Spending transaction outputs
-    pub outputs: Vec<IrBoxId>,
+    pub outputs: Vec<Rc<ErgoBox>>,
     /// Spending transaction data inputs
-    pub data_inputs: Vec<IrBoxId>,
+    pub data_inputs: Vec<Rc<ErgoBox>>,
     /// Spending transaction inputs
-    pub inputs: Vec<IrBoxId>,
+    pub inputs: Vec<Rc<ErgoBox>>,
     /// Pre header of current block
     pub pre_header: PreHeader,
     /// Fixed number of last block headers in descending order (first header is the newest one)
@@ -43,11 +38,8 @@ impl Context {
 
 #[cfg(feature = "arbitrary")]
 mod arbitrary {
-    use std::collections::HashMap;
 
-    use super::ir_ergo_box_dummy::*;
     use super::*;
-    use ergotree_ir::ir_ergo_box::IrErgoBox;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
@@ -57,10 +49,10 @@ mod arbitrary {
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             (
                 0..i32::MAX as u32,
-                any::<IrErgoBoxDummy>(),
-                vec(any::<IrErgoBoxDummy>(), 1..3),
-                vec(any::<IrErgoBoxDummy>(), 1..3),
-                vec(any::<IrErgoBoxDummy>(), 0..3),
+                any::<ErgoBox>(),
+                vec(any::<ErgoBox>(), 1..3),
+                vec(any::<ErgoBox>(), 1..3),
+                vec(any::<ErgoBox>(), 0..3),
                 any::<PreHeader>(),
                 any::<ContextExtension>(),
                 any::<[Header; 10]>(),
@@ -76,29 +68,12 @@ mod arbitrary {
                         extension,
                         headers,
                     )| {
-                        let self_box_id = self_box.id();
-                        let outputs_ids = outputs.iter().map(|b| b.id()).collect();
-                        let inputs_ids = inputs.iter().map(|b| b.id()).collect();
-                        let data_inputs_ids = data_inputs.iter().map(|b| b.id()).collect();
-                        let mut m = HashMap::new();
-                        m.insert(self_box_id.clone(), self_box);
-                        outputs.into_iter().for_each(|b| {
-                            m.insert(b.id(), b);
-                        });
-                        inputs.into_iter().for_each(|b| {
-                            m.insert(b.id(), b);
-                        });
-                        data_inputs.into_iter().for_each(|b| {
-                            m.insert(b.id(), b);
-                        });
-                        let box_arena = IrErgoBoxDummyArena(m);
                         Self {
-                            box_arena: Rc::new(box_arena) as Rc<dyn IrErgoBoxArena>,
                             height,
-                            self_box: self_box_id,
-                            outputs: outputs_ids,
-                            data_inputs: data_inputs_ids,
-                            inputs: inputs_ids,
+                            self_box: Rc::new(self_box),
+                            outputs: outputs.into_iter().map(Rc::new).collect(),
+                            data_inputs: data_inputs.into_iter().map(Rc::new).collect(),
+                            inputs: inputs.into_iter().map(Rc::new).collect(),
                             pre_header,
                             extension,
                             headers,
diff --git a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
deleted file mode 100644
index 4704a9f8c..000000000
--- a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-use std::collections::HashMap;
-use std::rc::Rc;
-
-use ergotree_ir::ir_ergo_box::IrBoxId;
-use ergotree_ir::ir_ergo_box::IrErgoBox;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
-use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::SigmaSerializationError;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub(crate) struct IrErgoBoxDummyArena(pub HashMap<IrBoxId, IrErgoBoxDummy>);
-
-impl IrErgoBoxArena for IrErgoBoxDummyArena {
-    fn get(&self, id: &IrBoxId) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError> {
-        self.0
-            .get(id)
-            .cloned()
-            .ok_or_else(|| IrErgoBoxArenaError(format!("IrErgoBox with id {0:?} not found", id)))
-            .map(|b| Rc::new(b) as Rc<dyn IrErgoBox>)
-    }
-}
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub(crate) struct IrErgoBoxDummy {
-    pub(crate) id: IrBoxId,
-    pub(crate) value: i64,
-    pub(crate) tokens: Vec<(Vec<i8>, i64)>,
-    pub(crate) additional_registers: Vec<Constant>,
-    pub(crate) creation_height: i32,
-    pub(crate) script_bytes: Vec<i8>,
-    pub(crate) creation_info: (i32, Vec<i8>),
-    pub(crate) sigma_serialize_bytes: Vec<i8>,
-}
-
-impl IrErgoBox for IrErgoBoxDummy {
-    fn id(&self) -> IrBoxId {
-        self.id.clone()
-    }
-
-    fn value(&self) -> i64 {
-        self.value
-    }
-
-    fn tokens_raw(&self) -> Vec<(Vec<i8>, i64)> {
-        self.tokens.clone()
-    }
-
-    fn additional_registers(&self) -> &[Constant] {
-        self.additional_registers.as_slice()
-    }
-
-    fn get_register(&self, id: i8) -> Option<Constant> {
-        match id {
-            0 => Some(self.value.into()),
-            1 => Some(self.script_bytes.clone().into()),
-            2 => Some(self.tokens.clone().into()),
-            3 => Some(self.creation_info.clone().into()),
-            _ => self.additional_registers.get(id as usize - 4).cloned(),
-        }
-    }
-
-    fn creation_height(&self) -> i32 {
-        self.creation_height
-    }
-
-    fn script_bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        Ok(self.script_bytes.clone())
-    }
-
-    fn creation_info(&self) -> (i32, Vec<i8>) {
-        self.creation_info.clone()
-    }
-
-    fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        Ok(self.sigma_serialize_bytes.clone())
-    }
-
-    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
-        self.bytes()
-    }
-}
-
-#[cfg(feature = "arbitrary")]
-/// Arbitrary impl
-pub(crate) mod arbitrary {
-    use num_traits::abs;
-    use sigma_util::DIGEST32_SIZE;
-
-    use super::*;
-    use proptest::collection::vec;
-    use proptest::prelude::*;
-
-    impl Arbitrary for IrErgoBoxDummy {
-        type Parameters = ();
-
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (
-                any::<[i8; DIGEST32_SIZE]>(),
-                100000i64..999999999,
-                vec(any::<([i8; DIGEST32_SIZE], u64)>(), 0..3),
-                1i32..1000,
-                vec(any::<Constant>(), 0..5),
-                vec(any::<i8>(), 100..1000),
-                vec(any::<i8>(), 100..1000),
-                vec(any::<i8>(), DIGEST32_SIZE + 2..=DIGEST32_SIZE + 2),
-            )
-                .prop_map(
-                    |(
-                        id,
-                        value,
-                        tokens,
-                        creation_height,
-                        additional_registers,
-                        script_bytes,
-                        sigma_serialize_bytes,
-                        tx_id_box_index,
-                    )| {
-                        Self {
-                            id: IrBoxId(id),
-                            value,
-                            tokens: tokens
-                                .into_iter()
-                                .map(|(id, amount)| (id.to_vec(), abs(amount as i64)))
-                                .collect(),
-                            additional_registers,
-                            creation_height,
-                            script_bytes,
-                            sigma_serialize_bytes,
-                            creation_info: (creation_height, tx_id_box_index),
-                        }
-                    },
-                )
-                .boxed()
-        }
-        type Strategy = BoxedStrategy<Self>;
-    }
-}
-
-#[allow(clippy::unwrap_used)]
-#[cfg(test)]
-mod tests {
-    use sigma_test_util::force_any_val;
-
-    use super::*;
-
-    #[test]
-    fn get_register() {
-        let b = force_any_val::<IrErgoBoxDummy>();
-        assert_eq!(b.get_register(0).unwrap(), b.value().into());
-        assert_eq!(b.get_register(1).unwrap(), b.script_bytes().unwrap().into());
-        assert_eq!(b.get_register(2).unwrap(), b.tokens_raw().into());
-        assert_eq!(b.get_register(3).unwrap(), b.creation_info().into());
-    }
-}
diff --git a/ergotree-interpreter/src/eval/deserialize_register.rs b/ergotree-interpreter/src/eval/deserialize_register.rs
index 6050b730b..6ea2018f5 100644
--- a/ergotree-interpreter/src/eval/deserialize_register.rs
+++ b/ergotree-interpreter/src/eval/deserialize_register.rs
@@ -1,3 +1,5 @@
+use std::convert::TryInto;
+
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::deserialize_register::DeserializeRegister;
 use ergotree_ir::mir::expr::Expr;
@@ -14,10 +16,13 @@ impl Evaluable for DeserializeRegister {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         match ctx
             .ctx
-            .box_arena
-            .get(&ctx.ctx.self_box)?
-            .get_register(self.reg as i8)
-        {
+            .self_box
+            .get_register(self.reg.try_into().map_err(|e| {
+                EvalError::RegisterIdOutOfBounds(format!(
+                    "register index is out of bounds: {:?} ",
+                    e
+                ))
+            })?) {
             Some(c) => {
                 if c.tpe != SType::SColl(SType::SByte.into()) {
                     Err(EvalError::UnexpectedExpr(format!(
@@ -58,6 +63,8 @@ mod tests {
 
     use std::rc::Rc;
 
+    use ergotree_ir::chain::ergo_box::ErgoBox;
+    use ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
     use ergotree_ir::mir::bin_op::BinOp;
     use ergotree_ir::mir::bin_op::RelationOp;
     use ergotree_ir::mir::constant::Constant;
@@ -67,22 +74,16 @@ mod tests {
     use ergotree_ir::types::stype::SType;
     use sigma_test_util::force_any_val;
 
-    use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummy;
-    use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummyArena;
     use crate::eval::context::Context;
     use crate::eval::tests::try_eval_out;
 
     use super::*;
 
-    fn make_ctx_with_self_box(self_box: IrErgoBoxDummy) -> Context {
+    fn make_ctx_with_self_box(self_box: ErgoBox) -> Context {
         let ctx = force_any_val::<Context>();
-        let mut m = std::collections::HashMap::new();
-        let self_box_id = self_box.id.clone();
-        m.insert(self_box_id.clone(), self_box);
         Context {
             height: 0u32,
-            self_box: self_box_id,
-            box_arena: Rc::new(IrErgoBoxDummyArena(m)),
+            self_box: Rc::new(self_box),
             ..ctx
         }
     }
@@ -97,10 +98,8 @@ mod tests {
         }
         .into();
         let reg_value: Constant = inner_expr.sigma_serialize_bytes().unwrap().into();
-        let b = IrErgoBoxDummy {
-            additional_registers: vec![reg_value],
-            ..force_any_val::<IrErgoBoxDummy>()
-        };
+        let b = force_any_val::<ErgoBox>()
+            .with_additional_registers(vec![reg_value].try_into().unwrap());
         // expected SBoolean
         let expr: Expr = DeserializeRegister {
             reg: 4,
@@ -114,10 +113,8 @@ mod tests {
 
     #[test]
     fn eval_reg_is_empty() {
-        let b = IrErgoBoxDummy {
-            additional_registers: vec![],
-            ..force_any_val::<IrErgoBoxDummy>()
-        };
+        let b =
+            force_any_val::<ErgoBox>().with_additional_registers(NonMandatoryRegisters::empty());
         // no default provided
         let expr: Expr = DeserializeRegister {
             reg: 5,
@@ -153,10 +150,8 @@ mod tests {
     fn eval_reg_wrong_type() {
         // SInt, expected SColl(SByte)
         let reg_value: Constant = 1i32.into();
-        let b = IrErgoBoxDummy {
-            additional_registers: vec![reg_value],
-            ..force_any_val::<IrErgoBoxDummy>()
-        };
+        let b = force_any_val::<ErgoBox>()
+            .with_additional_registers(vec![reg_value].try_into().unwrap());
         let expr: Expr = DeserializeRegister {
             reg: 4,
             tpe: SType::SBoolean,
@@ -172,10 +167,8 @@ mod tests {
         // SInt
         let inner_expr: Expr = 1i32.into();
         let reg_value: Constant = inner_expr.sigma_serialize_bytes().unwrap().into();
-        let b = IrErgoBoxDummy {
-            additional_registers: vec![reg_value],
-            ..force_any_val::<IrErgoBoxDummy>()
-        };
+        let b = force_any_val::<ErgoBox>()
+            .with_additional_registers(vec![reg_value].try_into().unwrap());
         // expected SBoolean
         let expr: Expr = DeserializeRegister {
             reg: 4,
diff --git a/ergotree-interpreter/src/eval/extract_amount.rs b/ergotree-interpreter/src/eval/extract_amount.rs
index 4f860a8b2..196ad030e 100644
--- a/ergotree-interpreter/src/eval/extract_amount.rs
+++ b/ergotree-interpreter/src/eval/extract_amount.rs
@@ -10,7 +10,7 @@ impl Evaluable for ExtractAmount {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(Value::Long(ctx.ctx.box_arena.get(&b)?.value())),
+            Value::CBox(b) => Ok(Value::Long(b.value.as_i64())),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractAmount input to be Value::CBox, got {0:?}",
                 input_v
@@ -39,7 +39,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&e, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
-        );
+            ctx.self_box.value.as_i64()
+        )
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_bytes.rs b/ergotree-interpreter/src/eval/extract_bytes.rs
index 0dc83fff0..5f41fc65c 100644
--- a/ergotree-interpreter/src/eval/extract_bytes.rs
+++ b/ergotree-interpreter/src/eval/extract_bytes.rs
@@ -1,5 +1,6 @@
 use ergotree_ir::mir::extract_bytes::ExtractBytes;
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::serialization::SigmaSerializable;
 
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
@@ -10,7 +11,7 @@ impl Evaluable for ExtractBytes {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.bytes()?.into()),
+            Value::CBox(b) => Ok(b.sigma_serialize_bytes()?.into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractBytes input to be Value::CBox, got {0:?}",
                 input_v
@@ -27,6 +28,7 @@ mod tests {
     use crate::eval::tests::eval_out;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::util::AsVecI8;
     use sigma_test_util::force_any_val;
     use std::rc::Rc;
 
@@ -39,11 +41,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<i8>>(&e, ctx.clone()),
-            ctx.self_box
-                .get_box(&ctx.box_arena)
-                .unwrap()
-                .bytes()
-                .unwrap()
+            ctx.self_box.sigma_serialize_bytes().unwrap().as_vec_i8()
         );
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs
index 914bbfa80..bd30d2723 100644
--- a/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs
+++ b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs
@@ -10,7 +10,7 @@ impl Evaluable for ExtractBytesWithNoRef {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.bytes_without_ref()?.into()),
+            Value::CBox(b) => Ok(b.bytes_without_ref()?.into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractBytesWithNoRef input to be Value::CBox, got {0:?}",
                 input_v
@@ -39,11 +39,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<i8>>(&e, ctx.clone()),
-            ctx.self_box
-                .get_box(&ctx.box_arena)
-                .unwrap()
-                .bytes_without_ref()
-                .unwrap()
+            ctx.self_box.bytes_without_ref().unwrap()
         );
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_creation_info.rs b/ergotree-interpreter/src/eval/extract_creation_info.rs
index 13fd62a87..bda52b36c 100644
--- a/ergotree-interpreter/src/eval/extract_creation_info.rs
+++ b/ergotree-interpreter/src/eval/extract_creation_info.rs
@@ -10,7 +10,7 @@ impl Evaluable for ExtractCreationInfo {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.creation_info().into()),
+            Value::CBox(b) => Ok(b.creation_info().into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractCreationInfo input to be Value::CBox, got {0:?}",
                 input_v
@@ -40,12 +40,6 @@ mod tests {
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
         let v = eval_out::<(i32, Vec<i8>)>(&expr, ctx.clone());
-        assert_eq!(
-            v,
-            ctx.self_box
-                .get_box(&ctx.box_arena)
-                .unwrap()
-                .creation_info()
-        );
+        assert_eq!(v, ctx.self_box.creation_info());
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_id.rs b/ergotree-interpreter/src/eval/extract_id.rs
index 6bef6e57f..83571158a 100644
--- a/ergotree-interpreter/src/eval/extract_id.rs
+++ b/ergotree-interpreter/src/eval/extract_id.rs
@@ -10,7 +10,10 @@ impl Evaluable for ExtractId {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.id().to_bytes().into()),
+            Value::CBox(b) => {
+                let bytes: Vec<i8> = b.box_id().into();
+                Ok(bytes.into())
+            }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractId input to be Value::CBox, got {0:?}",
                 input_v
@@ -37,13 +40,7 @@ mod tests {
         }
         .into();
         let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<Vec<i8>>(&e, ctx.clone()),
-            ctx.self_box
-                .get_box(&ctx.box_arena)
-                .unwrap()
-                .id()
-                .to_bytes()
-        );
+        let bytes: Vec<i8> = ctx.self_box.box_id().into();
+        assert_eq!(eval_out::<Vec<i8>>(&e, ctx), bytes);
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_reg_as.rs b/ergotree-interpreter/src/eval/extract_reg_as.rs
index fa0cff558..50a53253d 100644
--- a/ergotree-interpreter/src/eval/extract_reg_as.rs
+++ b/ergotree-interpreter/src/eval/extract_reg_as.rs
@@ -1,4 +1,7 @@
-use ergotree_ir::ir_ergo_box::IrBoxId;
+use std::convert::TryInto;
+use std::rc::Rc;
+
+use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
 use ergotree_ir::mir::value::Value;
@@ -10,12 +13,18 @@ use crate::eval::Evaluable;
 
 impl Evaluable for ExtractRegisterAs {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let ir_box_id = self.input.eval(env, ctx)?.try_extract_into::<IrBoxId>()?;
+        let ir_box = self
+            .input
+            .eval(env, ctx)?
+            .try_extract_into::<Rc<ErgoBox>>()?;
         Ok(Value::Opt(Box::new(
-            ctx.ctx
-                .box_arena
-                .get(&ir_box_id)?
-                .get_register(self.register_id)
+            ir_box
+                .get_register(self.register_id.try_into().map_err(|e| {
+                    EvalError::RegisterIdOutOfBounds(format!(
+                        "register index is out of bounds: {:?} ",
+                        e
+                    ))
+                })?)
                 .map(|c| Value::from(c.v)),
         )))
     }
@@ -47,6 +56,6 @@ mod tests {
         let option_get_expr: Expr = OptionGet::try_build(get_reg_expr).unwrap().into();
         let ctx = Rc::new(force_any_val::<Context>());
         let v = eval_out::<i64>(&option_get_expr, ctx.clone());
-        assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
+        assert_eq!(v, ctx.self_box.value.as_i64());
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_script_bytes.rs b/ergotree-interpreter/src/eval/extract_script_bytes.rs
index 66ccc3d58..745936e4f 100644
--- a/ergotree-interpreter/src/eval/extract_script_bytes.rs
+++ b/ergotree-interpreter/src/eval/extract_script_bytes.rs
@@ -10,7 +10,7 @@ impl Evaluable for ExtractScriptBytes {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.script_bytes()?.into()),
+            Value::CBox(b) => Ok(b.script_bytes()?.into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractScriptBytes input to be Value::CBox, got {0:?}",
                 input_v
@@ -39,11 +39,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<i8>>(&e, ctx.clone()),
-            ctx.self_box
-                .get_box(&ctx.box_arena)
-                .unwrap()
-                .script_bytes()
-                .unwrap()
+            ctx.self_box.script_bytes().unwrap()
         );
     }
 }
diff --git a/ergotree-interpreter/src/eval/global_vars.rs b/ergotree-interpreter/src/eval/global_vars.rs
index d2b73f41e..c9663ab40 100644
--- a/ergotree-interpreter/src/eval/global_vars.rs
+++ b/ergotree-interpreter/src/eval/global_vars.rs
@@ -32,7 +32,7 @@ mod tests {
     use crate::eval::tests::eval_out;
     use ergoscript_compiler::compiler::compile_expr;
     use ergoscript_compiler::script_env::ScriptEnv;
-    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::sigma_protocol::dlog_group::EcPoint;
     use sigma_test_util::force_any_val;
 
@@ -49,8 +49,8 @@ mod tests {
     fn eval_self_box() {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<IrBoxId>(&GlobalVars::SelfBox.into(), ctx.clone()),
-            ctx.self_box
+            eval_out::<Rc<ErgoBox>>(&GlobalVars::SelfBox.into(), ctx.clone()).as_ref(),
+            ctx.self_box.as_ref()
         );
     }
 
@@ -58,7 +58,7 @@ mod tests {
     fn eval_outputs() {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<Vec<IrBoxId>>(&GlobalVars::Outputs.into(), ctx.clone()),
+            eval_out::<Vec<Rc<ErgoBox>>>(&GlobalVars::Outputs.into(), ctx.clone()),
             ctx.outputs
         );
     }
@@ -67,7 +67,7 @@ mod tests {
     fn eval_inputs() {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<Vec<IrBoxId>>(&GlobalVars::Inputs.into(), ctx.clone()),
+            eval_out::<Vec<Rc<ErgoBox>>>(&GlobalVars::Inputs.into(), ctx.clone()),
             ctx.inputs
         );
     }
diff --git a/ergotree-interpreter/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs
index 67693c314..5791db03f 100644
--- a/ergotree-interpreter/src/eval/method_call.rs
+++ b/ergotree-interpreter/src/eval/method_call.rs
@@ -48,7 +48,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&option_get_expr, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
+            ctx.self_box.value.as_i64()
         );
     }
 }
diff --git a/ergotree-interpreter/src/eval/option_get.rs b/ergotree-interpreter/src/eval/option_get.rs
index d835255a1..d844887bf 100644
--- a/ergotree-interpreter/src/eval/option_get.rs
+++ b/ergotree-interpreter/src/eval/option_get.rs
@@ -47,6 +47,6 @@ mod tests {
         let option_get_expr: Expr = OptionGet::try_build(get_reg_expr).unwrap().into();
         let ctx = Rc::new(force_any_val::<Context>());
         let v = eval_out::<i64>(&option_get_expr, ctx.clone());
-        assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
+        assert_eq!(v, ctx.self_box.value.as_i64());
     }
 }
diff --git a/ergotree-interpreter/src/eval/option_get_or_else.rs b/ergotree-interpreter/src/eval/option_get_or_else.rs
index 204a438f1..c29844eff 100644
--- a/ergotree-interpreter/src/eval/option_get_or_else.rs
+++ b/ergotree-interpreter/src/eval/option_get_or_else.rs
@@ -29,6 +29,7 @@ mod tests {
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
+    use ergotree_ir::mir::get_var::GetVar;
     use ergotree_ir::mir::global_vars::GlobalVars;
     use ergotree_ir::types::stype::SType;
     use sigma_test_util::force_any_val;
@@ -49,20 +50,18 @@ mod tests {
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
         let v = eval_out::<i64>(&option_get_expr, ctx.clone());
-        assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
+        assert_eq!(v, ctx.self_box.value.as_i64());
     }
 
     #[test]
     fn eval_empty() {
-        let get_reg_expr: Expr = ExtractRegisterAs::new(
-            GlobalVars::SelfBox.into(),
-            9,
-            SType::SOption(SType::SLong.into()),
-        )
-        .unwrap()
+        let get_var_expr: Expr = GetVar {
+            var_id: 99,
+            var_tpe: SType::SLong,
+        }
         .into();
         let default_expr: Constant = 1i64.into();
-        let option_get_expr: Expr = OptionGetOrElse::new(get_reg_expr, default_expr.into())
+        let option_get_expr: Expr = OptionGetOrElse::new(get_var_expr, default_expr.into())
             .unwrap()
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
diff --git a/ergotree-interpreter/src/eval/property_call.rs b/ergotree-interpreter/src/eval/property_call.rs
index 815303f1f..ce83762bf 100644
--- a/ergotree-interpreter/src/eval/property_call.rs
+++ b/ergotree-interpreter/src/eval/property_call.rs
@@ -20,7 +20,7 @@ mod tests {
     use super::*;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
-    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::types::scontext;
     use sigma_test_util::force_any_val;
@@ -32,6 +32,9 @@ mod tests {
             .unwrap()
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(eval_out::<Vec<IrBoxId>>(&pc, ctx.clone()), ctx.data_inputs);
+        assert_eq!(
+            eval_out::<Vec<Rc<ErgoBox>>>(&pc, ctx.clone()),
+            ctx.data_inputs
+        );
     }
 }
diff --git a/ergotree-interpreter/src/eval/savltree.rs b/ergotree-interpreter/src/eval/savltree.rs
index 7a3185e6f..88660a373 100644
--- a/ergotree-interpreter/src/eval/savltree.rs
+++ b/ergotree-interpreter/src/eval/savltree.rs
@@ -1,5 +1,5 @@
 use bytes::Bytes;
-use ergotree_ir::mir::avl_tree_data::ADDigest;
+use ergotree_ir::chain::digest32::ADDigest;
 use ergotree_ir::mir::avl_tree_data::AvlTreeData;
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::value::Value;
diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs
index ddfbc1fba..3fd96f46b 100644
--- a/ergotree-interpreter/src/eval/sbox.rs
+++ b/ergotree-interpreter/src/eval/sbox.rs
@@ -1,39 +1,42 @@
+use std::convert::TryInto;
+use std::rc::Rc;
+
 use crate::eval::EvalError;
 
-use ergotree_ir::ir_ergo_box::IrBoxId;
+use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::value::Value;
 
 use super::EvalFn;
 
-pub(crate) static VALUE_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static VALUE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     Ok(Value::Long(
-        obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.ctx.box_arena)?
-            .value(),
+        obj.try_extract_into::<Rc<ErgoBox>>()?.value.as_i64(),
     ))
 };
 
-pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, ctx, obj, args| {
+pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
     Ok(Value::Opt(Box::new(
-        obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.ctx.box_arena)?
+        obj.try_extract_into::<Rc<ErgoBox>>()?
             .get_register(
                 args.get(0)
                     .cloned()
                     .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
-                    .try_extract_into::<i8>()?,
+                    .try_extract_into::<i8>()?
+                    .try_into()
+                    .map_err(|e| {
+                        EvalError::RegisterIdOutOfBounds(format!(
+                            "register index is out of bounds: {:?} ",
+                            e
+                        ))
+                    })?,
             )
             .map(|c| Value::from(c.v)),
     )))
 };
 
-pub(crate) static TOKENS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
-    let res: Value = obj
-        .try_extract_into::<IrBoxId>()?
-        .get_box(&ctx.ctx.box_arena)?
-        .tokens_raw()
-        .into();
+pub(crate) static TOKENS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let res: Value = obj.try_extract_into::<Rc<ErgoBox>>()?.tokens_raw().into();
     Ok(res)
 };
 
@@ -59,7 +62,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&expr, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
+            ctx.self_box.value.as_i64()
         );
     }
 
@@ -71,7 +74,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<(Vec<i8>, i64)>>(&expr, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().tokens_raw()
+            ctx.self_box.tokens_raw()
         );
     }
 }
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
index 01c69f8ba..85e0d6007 100644
--- a/ergotree-interpreter/src/eval/scontext.rs
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -35,8 +35,8 @@ pub(crate) static SELF_BOX_INDEX_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
         .ctx
         .inputs
         .clone()
-        .into_iter()
-        .position(|it| it == ctx.ctx.self_box)
+        .iter()
+        .position(|it| it == &ctx.ctx.self_box)
         .ok_or_else(|| EvalError::NotFound("Context.selfBoxIndex: box not found".to_string()))?;
     Ok(Value::Int(box_index as i32))
 };
@@ -49,18 +49,24 @@ pub(crate) static HEADERS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
         )));
     }
     Ok(Value::Coll(CollKind::WrappedColl {
-        items: ctx.ctx.headers.clone().map(Value::Header).to_vec(),
+        items: ctx
+            .ctx
+            .headers
+            .clone()
+            .map(Box::new)
+            .map(Value::Header)
+            .to_vec(),
         elem_tpe: SType::SHeader,
     }))
 };
 
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
-#[allow(clippy::unwrap_used)]
+#[allow(clippy::unwrap_used, clippy::expect_used)]
 mod tests {
-    use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummy;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::chain::header::Header;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::property_call::PropertyCall;
@@ -70,11 +76,11 @@ mod tests {
 
     fn make_ctx_inputs_includes_self_box() -> Context {
         let ctx = force_any_val::<Context>();
-        let self_box = force_any_val::<IrErgoBoxDummy>();
-        let inputs = vec![force_any_val::<IrErgoBoxDummy>().id, self_box.id.clone()];
+        let self_box = force_any_val::<ErgoBox>();
+        let inputs = vec![force_any_val::<ErgoBox>().into(), self_box.clone().into()];
         Context {
             height: 0u32,
-            self_box: self_box.id,
+            self_box: self_box.into(),
             inputs,
             ..ctx
         }
diff --git a/ergotree-interpreter/src/lib.rs b/ergotree-interpreter/src/lib.rs
index 89367b241..b09b6aa18 100644
--- a/ergotree-interpreter/src/lib.rs
+++ b/ergotree-interpreter/src/lib.rs
@@ -11,7 +11,7 @@
 #![deny(missing_docs)]
 // Clippy exclusions
 #![allow(clippy::unit_arg)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 #![deny(clippy::unwrap_used)]
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml
index f665753e7..53224f45f 100644
--- a/ergotree-ir/Cargo.toml
+++ b/ergotree-ir/Cargo.toml
@@ -35,10 +35,14 @@ num-traits = "0.2.14"
 num-derive = "0.3.3"
 strum = "0.21"
 strum_macros = "0.21"
+indexmap = "1.3.2"
+serde = { version = "1.0", features = ["derive"], optional = true }
+serde_json = { version = "1.0", optional = true }
 
 [features]
 default = []
 arbitrary = ["proptest", "proptest-derive"]
+json = ["serde", "serde_json"]
 
 [dev-dependencies]
 proptest = "1.0.0"
diff --git a/ergotree-ir/src/base16_str.rs b/ergotree-ir/src/base16_str.rs
new file mode 100644
index 000000000..6be0cf155
--- /dev/null
+++ b/ergotree-ir/src/base16_str.rs
@@ -0,0 +1,9 @@
+//! Trait for base16-encoded serialized bytes
+
+use crate::serialization::SigmaSerializationError;
+
+/// Encodes serialized bytes as Base16
+pub trait Base16Str {
+    /// Returns serialized bytes encoded as Base16
+    fn base16_str(&self) -> Result<String, SigmaSerializationError>;
+}
diff --git a/ergotree-ir/src/chain.rs b/ergotree-ir/src/chain.rs
index f9351657d..5ec12c8cf 100644
--- a/ergotree-ir/src/chain.rs
+++ b/ergotree-ir/src/chain.rs
@@ -1,6 +1,13 @@
-//! Predefined types encapsulating chain data
+//! On-chain types
 
+pub mod address;
+pub mod base16_bytes;
 pub mod block_id;
-pub mod digest;
+pub mod digest32;
+pub mod ergo_box;
 pub mod header;
+#[cfg(feature = "json")]
+pub mod json;
+pub mod token;
+pub mod tx_id;
 pub mod votes;
diff --git a/ergotree-ir/src/address.rs b/ergotree-ir/src/chain/address.rs
similarity index 100%
rename from ergotree-ir/src/address.rs
rename to ergotree-ir/src/chain/address.rs
diff --git a/ergo-lib/src/chain/base16_bytes.rs b/ergotree-ir/src/chain/base16_bytes.rs
similarity index 71%
rename from ergo-lib/src/chain/base16_bytes.rs
rename to ergotree-ir/src/chain/base16_bytes.rs
index e7bbd7500..ca19c14a6 100644
--- a/ergo-lib/src/chain/base16_bytes.rs
+++ b/ergotree-ir/src/chain/base16_bytes.rs
@@ -1,11 +1,14 @@
 //! Transitioning type for Base16 encoded bytes in JSON serialization
 
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::SigmaParsingError;
-use ergotree_ir::serialization::SigmaSerializable;
+use crate::chain::digest32::Digest;
+use crate::chain::digest32::Digest32Error;
+use crate::mir::constant::Constant;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
+use std::convert::TryInto;
 extern crate derive_more;
 use derive_more::{From, Into};
 
@@ -54,12 +57,6 @@ impl From<Base16DecodedBytes> for Vec<u8> {
     }
 }
 
-impl From<Constant> for Base16EncodedBytes {
-    fn from(v: Constant) -> Base16EncodedBytes {
-        Base16EncodedBytes::new(&v.sigma_serialize_bytes().unwrap())
-    }
-}
-
 impl TryFrom<Base16DecodedBytes> for Constant {
     type Error = SigmaParsingError;
 
@@ -68,20 +65,16 @@ impl TryFrom<Base16DecodedBytes> for Constant {
     }
 }
 
-/// Encodes serialized bytes as Base16
-pub trait Base16Str {
-    /// Returns serialized bytes encoded as Base16
-    fn base16_str(&self) -> String;
-}
-
-impl Base16Str for &Constant {
-    fn base16_str(&self) -> String {
-        base16::encode_lower(&self.sigma_serialize_bytes().unwrap())
+impl<const N: usize> From<Digest<N>> for Base16EncodedBytes {
+    fn from(v: Digest<N>) -> Self {
+        Base16EncodedBytes::new(v.0.as_ref())
     }
 }
 
-impl Base16Str for Constant {
-    fn base16_str(&self) -> String {
-        base16::encode_lower(&self.sigma_serialize_bytes().unwrap())
+impl<const N: usize> TryFrom<Base16DecodedBytes> for Digest<N> {
+    type Error = Digest32Error;
+    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
+        let arr: [u8; N] = bytes.0.as_slice().try_into()?;
+        Ok(Digest(Box::new(arr)))
     }
 }
diff --git a/ergotree-ir/src/chain/block_id.rs b/ergotree-ir/src/chain/block_id.rs
index 2f01bb193..ae8229c0d 100644
--- a/ergotree-ir/src/chain/block_id.rs
+++ b/ergotree-ir/src/chain/block_id.rs
@@ -1,7 +1,8 @@
 //! Main "remote" type for [BlockId]()
 
-use super::digest::Digest32;
+use super::digest32::Digest32;
 
 /// Block id
-#[derive(PartialEq, Eq, Debug, Clone, Default)]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BlockId(pub Digest32);
diff --git a/ergotree-ir/src/chain/digest.rs b/ergotree-ir/src/chain/digest32.rs
similarity index 53%
rename from ergotree-ir/src/chain/digest.rs
rename to ergotree-ir/src/chain/digest32.rs
index d95ccd140..d0e8205c7 100644
--- a/ergotree-ir/src/chain/digest.rs
+++ b/ergotree-ir/src/chain/digest32.rs
@@ -1,18 +1,25 @@
-//! Main "remote" type for [Digest]()
-
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
-    SigmaSerializable, SigmaSerializeResult,
-};
+//! Digest types for various sizes
+
+use crate::chain::base16_bytes::Base16DecodedBytes;
+use crate::chain::base16_bytes::Base16EncodedBytes;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializeResult;
 use crate::util::AsVecI8;
-
-/// Blake2b256 hash (256 bit)
-pub fn blake2b256_hash(bytes: &[u8]) -> Digest32 {
-    Digest(sigma_util::hash::blake2b256_hash(bytes))
-}
+use std::convert::TryFrom;
+use std::convert::TryInto;
+use std::fmt::Formatter;
+use thiserror::Error;
 
 /// N-bytes array in a box. `Digest32` is most type synonym.
-#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+    feature = "json",
+    serde(into = "Base16EncodedBytes", try_from = "Base16DecodedBytes")
+)]
+#[derive(PartialEq, Eq, Hash, Clone)]
 pub struct Digest<const N: usize>(pub Box<[u8; N]>);
 
 /// 32 byte array used as ID of some value: block, transaction, etc.
@@ -23,7 +30,7 @@ pub type Digest32 = Digest<32>;
 pub type ADDigest = Digest<33>;
 
 impl<const N: usize> Digest<N> {
-    /// Digest size bytes
+    /// Digest size 32 bytes
     pub const SIZE: usize = N;
 
     /// All zeros
@@ -32,21 +39,26 @@ impl<const N: usize> Digest<N> {
     }
 }
 
-impl<const N: usize> Default for Digest<N> {
-    fn default() -> Self {
-        Digest::zero()
+impl<const N: usize> std::fmt::Debug for Digest<N> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        base16::encode_lower(&(*self.0)).fmt(f)
     }
 }
 
+/// Blake2b256 hash (256 bit)
+pub fn blake2b256_hash(bytes: &[u8]) -> Digest32 {
+    Digest(sigma_util::hash::blake2b256_hash(bytes))
+}
+
 impl<const N: usize> From<[u8; N]> for Digest<N> {
     fn from(bytes: [u8; N]) -> Self {
         Digest(Box::new(bytes))
     }
 }
 
-impl<const N: usize> From<Digest<N>> for [u8; N] {
+impl<const N: usize> From<Digest<N>> for Vec<i8> {
     fn from(v: Digest<N>) -> Self {
-        *v.0
+        v.0.to_vec().as_vec_i8()
     }
 }
 
@@ -56,15 +68,25 @@ impl<const N: usize> From<Digest<N>> for Vec<u8> {
     }
 }
 
-impl<const N: usize> From<Digest<N>> for Vec<i8> {
+impl<const N: usize> From<Digest<N>> for [u8; N] {
     fn from(v: Digest<N>) -> Self {
-        v.0.to_vec().as_vec_i8()
+        *v.0
     }
 }
 
-impl AsRef<[u8]> for Digest32 {
-    fn as_ref(&self) -> &[u8] {
-        &self.0[..]
+impl<const N: usize> From<Digest<N>> for String {
+    fn from(v: Digest<N>) -> Self {
+        base16::encode_lower(&v.0.as_ref())
+    }
+}
+
+impl<const N: usize> TryFrom<String> for Digest<N> {
+    type Error = Digest32Error;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        let bytes = base16::decode(&value)?;
+        let arr: [u8; N] = bytes.as_slice().try_into()?;
+        Ok(Digest(Box::new(arr)))
     }
 }
 
@@ -73,7 +95,6 @@ impl<const N: usize> SigmaSerializable for Digest<N> {
         w.write_all(self.0.as_ref())?;
         Ok(())
     }
-
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
         let mut bytes = [0; N];
         r.read_exact(&mut bytes)?;
@@ -81,30 +102,41 @@ impl<const N: usize> SigmaSerializable for Digest<N> {
     }
 }
 
-#[cfg(feature = "arbitrary")]
-mod arbitrary {
-    #![allow(clippy::expect_used)]
+impl AsRef<[u8]> for Digest32 {
+    fn as_ref(&self) -> &[u8] {
+        &self.0[..]
+    }
+}
 
-    use std::convert::TryInto;
+/// Invalid byte array size
+#[derive(Error, Debug)]
+pub enum Digest32Error {
+    /// error decoding from Base16
+    #[error("error decoding from Base16: {0}")]
+    Base16DecodingError(#[from] base16::DecodeError),
+    /// Invalid byte array size
+    #[error("Invalid byte array size ({0})")]
+    InvalidSize(#[from] std::array::TryFromSliceError),
+}
 
-    use proptest::prelude::{Arbitrary, BoxedStrategy};
-    use proptest::{collection::vec, prelude::*};
+/// Arbitrary
+#[allow(clippy::unwrap_used)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
 
     use super::Digest;
+    use proptest::prelude::{Arbitrary, BoxedStrategy};
+    use proptest::{collection::vec, prelude::*};
+    use std::convert::TryInto;
 
     impl<const N: usize> Arbitrary for Digest<N> {
         type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
         fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
             vec(any::<u8>(), Self::SIZE)
-                .prop_map(|v| {
-                    Digest(Box::new(
-                        v.try_into()
-                            .expect("internal error: vec has length != Digest::SIZE"),
-                    ))
-                })
+                .prop_map(|v| Digest(Box::new(v.try_into().unwrap())))
                 .boxed()
         }
-
-        type Strategy = BoxedStrategy<Self>;
     }
 }
diff --git a/ergotree-ir/src/chain/ergo_box.rs b/ergotree-ir/src/chain/ergo_box.rs
new file mode 100644
index 000000000..f5357a77e
--- /dev/null
+++ b/ergotree-ir/src/chain/ergo_box.rs
@@ -0,0 +1,522 @@
+//! Ergo box
+
+mod box_id;
+pub mod box_value;
+mod register;
+
+use crate::ergo_tree::ErgoTree;
+use crate::mir::constant::Constant;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializationError;
+use crate::serialization::SigmaSerializeResult;
+use crate::util::AsVecI8;
+
+pub use box_id::*;
+pub use register::*;
+
+use indexmap::IndexSet;
+use sigma_util::hash::blake2b256_hash;
+use std::convert::TryFrom;
+
+use std::convert::TryInto;
+
+use self::box_value::BoxValue;
+
+use super::digest32::Digest32;
+use super::token::Token;
+use super::token::TokenId;
+use super::tx_id::TxId;
+
+/// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
+/// In Bitcoin, such an object is associated with some monetary value (arbitrary,
+/// but with predefined precision, so we use integer arithmetic to work with the value),
+/// and also a guarding script (aka proposition) to protect the box from unauthorized opening.
+///
+/// In other way, a box is a state element locked by some proposition (ErgoTree).
+///
+/// In Ergo, box is just a collection of registers, some with mandatory types and semantics,
+/// others could be used by applications in any way.
+/// We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
+/// Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
+/// Register R3 contains height specified by user (protocol checks if it was <= current height when
+/// transaction was accepted) and also transaction identifier and box index in the transaction outputs.
+/// Registers R4-R9 are free for arbitrary usage.
+///
+/// A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
+/// can not be linked to the same box.
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(
+    feature = "json",
+    serde(try_from = "crate::chain::json::ergo_box::ErgoBoxFromJson")
+)]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBox {
+    #[cfg_attr(feature = "json", serde(rename = "boxId", alias = "id"))]
+    box_id: BoxId,
+    /// amount of money associated with the box
+    #[cfg_attr(feature = "json", serde(rename = "value"))]
+    pub value: BoxValue,
+    /// guarding script, which should be evaluated to true in order to open this box
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "ergoTree", with = "crate::chain::json::ergo_tree")
+    )]
+    pub ergo_tree: ErgoTree,
+    /// secondary tokens the box contains
+    #[cfg_attr(feature = "json", serde(rename = "assets"))]
+    pub tokens: Vec<Token>,
+    ///  additional registers the box can carry over
+    #[cfg_attr(feature = "json", 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.
+    #[cfg_attr(feature = "json", serde(rename = "creationHeight"))]
+    pub creation_height: u32,
+    /// id of transaction which created the box
+    #[cfg_attr(feature = "json", 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)
+    #[cfg_attr(feature = "json", serde(rename = "index"))]
+    pub index: u16,
+}
+
+impl ErgoBox {
+    /// Maximum number of tokens in the box
+    pub const MAX_TOKENS_COUNT: usize = u8::MAX as usize;
+
+    /// Crate new box
+    pub fn new(
+        value: BoxValue,
+        ergo_tree: ErgoTree,
+        tokens: Vec<Token>,
+        additional_registers: NonMandatoryRegisters,
+        creation_height: u32,
+        transaction_id: TxId,
+        index: u16,
+    ) -> Result<ErgoBox, SigmaSerializationError> {
+        let box_with_zero_id = ErgoBox {
+            box_id: BoxId::zero(),
+            value,
+            ergo_tree,
+            tokens,
+            additional_registers,
+            creation_height,
+            transaction_id,
+            index,
+        };
+        let box_id = box_with_zero_id.calc_box_id()?;
+        Ok(ErgoBox {
+            box_id,
+            ..box_with_zero_id
+        })
+    }
+
+    /// Box id (Blake2b256 hash of serialized box)
+    pub fn box_id(&self) -> BoxId {
+        self.box_id.clone()
+    }
+
+    /// Create ErgoBox from ErgoBoxCandidate by adding transaction id
+    /// and index of the box in the transaction
+    pub fn from_box_candidate(
+        box_candidate: &ErgoBoxCandidate,
+        transaction_id: TxId,
+        index: u16,
+    ) -> Result<ErgoBox, SigmaSerializationError> {
+        let box_with_zero_id = ErgoBox {
+            box_id: BoxId::zero(),
+            value: box_candidate.value,
+            ergo_tree: box_candidate.ergo_tree.clone(),
+            tokens: box_candidate.tokens.clone(),
+            additional_registers: box_candidate.additional_registers.clone(),
+            creation_height: box_candidate.creation_height,
+            transaction_id,
+            index,
+        };
+        let box_id = box_with_zero_id.calc_box_id()?;
+        Ok(ErgoBox {
+            box_id,
+            ..box_with_zero_id
+        })
+    }
+
+    pub(crate) fn calc_box_id(&self) -> Result<BoxId, SigmaSerializationError> {
+        let bytes = self.sigma_serialize_bytes()?;
+        let hash = blake2b256_hash(&bytes);
+        Ok(Digest32::from(*hash).into())
+    }
+
+    /// Get register value
+    pub fn get_register(&self, id: RegisterId) -> Option<Constant> {
+        match id {
+            RegisterId::MandatoryRegisterId(id) => match id {
+                MandatoryRegisterId::R0 => Some(self.value.into()),
+                // chance of box script is not serializable are tiny comparing to returning Result
+                #[allow(clippy::unwrap_used)]
+                MandatoryRegisterId::R1 => Some(self.script_bytes().unwrap().into()),
+                MandatoryRegisterId::R2 => Some(self.tokens_raw().into()),
+                MandatoryRegisterId::R3 => Some(self.creation_info().into()),
+            },
+            RegisterId::NonMandatoryRegisterId(id) => self.additional_registers.get(id).cloned(),
+        }
+    }
+
+    /// Returns tokens as tuple of byte array and amount as primitive types
+    pub fn tokens_raw(&self) -> Vec<(Vec<i8>, i64)> {
+        self.tokens.clone().into_iter().map(Into::into).collect()
+    }
+
+    /// Returns serialized ergo_tree guarding this box
+    pub fn script_bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
+        Ok(self.ergo_tree.sigma_serialize_bytes()?.as_vec_i8())
+    }
+
+    /// Tuple of height when block got included into the blockchain and transaction identifier with
+    /// box index in the transaction outputs serialized to the byte array.
+    pub fn creation_info(&self) -> (i32, Vec<i8>) {
+        let mut bytes = Vec::with_capacity(Digest32::SIZE + 2);
+        bytes.extend_from_slice(self.transaction_id.0 .0.as_ref());
+        bytes.extend_from_slice(&self.index.to_be_bytes());
+        (self.creation_height as i32, bytes.as_vec_i8())
+    }
+
+    /// Returns serialized ErgoBox without tx_id and index
+    pub fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
+        let candidate: ErgoBoxCandidate = self.clone().into();
+        Ok(candidate.sigma_serialize_bytes()?.as_vec_i8())
+    }
+}
+
+impl SigmaSerializable for ErgoBox {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        let ergo_tree_bytes = self.ergo_tree.sigma_serialize_bytes()?;
+        serialize_box_with_indexed_digests(
+            &self.value,
+            ergo_tree_bytes,
+            &self.tokens,
+            &self.additional_registers,
+            self.creation_height,
+            None,
+            w,
+        )?;
+        self.transaction_id.sigma_serialize(w)?;
+        w.put_u16(self.index)?;
+        Ok(())
+    }
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let box_candidate = ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)?;
+        let tx_id = TxId::sigma_parse(r)?;
+        let index = r.get_u16()?;
+        Ok(ErgoBox::from_box_candidate(&box_candidate, tx_id, index)?)
+    }
+}
+
+#[cfg(feature = "json")]
+impl TryFrom<super::json::ergo_box::ErgoBoxFromJson> for ErgoBox {
+    type Error = super::json::ergo_box::ErgoBoxFromJsonError;
+    fn try_from(box_json: super::json::ergo_box::ErgoBoxFromJson) -> Result<Self, Self::Error> {
+        let box_with_zero_id = ErgoBox {
+            box_id: BoxId::zero(),
+            value: box_json.value,
+            ergo_tree: box_json.ergo_tree,
+            tokens: box_json.tokens,
+            additional_registers: box_json.additional_registers,
+            creation_height: box_json.creation_height,
+            transaction_id: box_json.transaction_id,
+            index: box_json.index,
+        };
+        let box_id = box_with_zero_id.calc_box_id()?;
+        let ergo_box = ErgoBox {
+            box_id,
+            ..box_with_zero_id
+        };
+        match box_json.box_id {
+            Some(box_id) => {
+                if ergo_box.box_id() == box_id {
+                    Ok(ergo_box)
+                } else {
+                    Err(super::json::ergo_box::ErgoBoxFromJsonError::InvalidBoxId)
+                }
+            }
+            None => Ok(ergo_box),
+        }
+    }
+}
+
+/// Contains the same fields as `ErgoBox`, except if transaction id and index,
+/// that will be calculated after full transaction formation.
+/// Use `ErgoBoxCandidateBuilder` from ergo-lib crate to create an instance.
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct ErgoBoxCandidate {
+    /// amount of money associated with the box
+    #[cfg_attr(feature = "json", serde(rename = "value"))]
+    pub value: BoxValue,
+    /// guarding script, which should be evaluated to true in order to open this box
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "ergoTree", with = "crate::chain::json::ergo_tree")
+    )]
+    pub ergo_tree: ErgoTree,
+    /// secondary tokens the box contains
+    #[cfg_attr(feature = "json", serde(rename = "assets"))]
+    pub tokens: Vec<Token>,
+    ///  additional registers the box can carry over
+    #[cfg_attr(feature = "json", 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.
+    #[cfg_attr(feature = "json", serde(rename = "creationHeight"))]
+    pub creation_height: u32,
+}
+
+impl ErgoBoxCandidate {
+    /// Box serialization with token ids optionally saved in transaction
+    /// (in this case only token index is saved)
+    pub fn serialize_body_with_indexed_digests<W: SigmaByteWrite>(
+        &self,
+        token_ids_in_tx: Option<&IndexSet<TokenId>>,
+        w: &mut W,
+    ) -> SigmaSerializeResult {
+        serialize_box_with_indexed_digests(
+            &self.value,
+            self.ergo_tree.sigma_serialize_bytes()?,
+            &self.tokens,
+            &self.additional_registers,
+            self.creation_height,
+            token_ids_in_tx,
+            w,
+        )
+    }
+
+    /// Box deserialization with token ids optionally parsed in transaction
+    pub fn parse_body_with_indexed_digests<R: SigmaByteRead>(
+        digests_in_tx: Option<&IndexSet<TokenId>>,
+        r: &mut R,
+    ) -> Result<ErgoBoxCandidate, SigmaParsingError> {
+        parse_box_with_indexed_digests(digests_in_tx, r)
+    }
+}
+
+impl SigmaSerializable for ErgoBoxCandidate {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        self.serialize_body_with_indexed_digests(None, w)
+    }
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)
+    }
+}
+
+impl From<ErgoBox> for ErgoBoxCandidate {
+    fn from(b: ErgoBox) -> Self {
+        ErgoBoxCandidate {
+            value: b.value,
+            ergo_tree: b.ergo_tree,
+            tokens: b.tokens,
+            additional_registers: b.additional_registers,
+            creation_height: b.creation_height,
+        }
+    }
+}
+
+/// ErgoBox and ErgoBoxCandidate serialization
+
+/// Box serialization with token ids optionally saved in transaction
+/// (in this case only token index is saved)
+pub fn serialize_box_with_indexed_digests<W: SigmaByteWrite>(
+    box_value: &BoxValue,
+    ergo_tree_bytes: Vec<u8>,
+    tokens: &[Token],
+    additional_registers: &NonMandatoryRegisters,
+    creation_height: u32,
+    token_ids_in_tx: Option<&IndexSet<TokenId>>,
+    w: &mut W,
+) -> SigmaSerializeResult {
+    // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L95-L95
+    box_value.sigma_serialize(w)?;
+    w.write_all(&ergo_tree_bytes[..])?;
+    w.put_u32(creation_height)?;
+    // until https://github.com/ergoplatform/sigma-rust/issues/416 is done
+    #[allow(clippy::unwrap_used)]
+    w.put_u8(u8::try_from(tokens.len()).unwrap())?;
+
+    tokens.iter().try_for_each(|t| {
+        match token_ids_in_tx {
+            Some(token_ids) => Ok(w.put_u32(
+                #[allow(clippy::unwrap_used)]
+                u32::try_from(
+                    #[allow(clippy::expect_used)]
+                    token_ids
+                        .get_full(&t.token_id)
+                        // this is not a true runtime error it just means that
+                        // calling site messed up the token ids
+                        .expect("failed to find token id in tx's digest index")
+                        .0,
+                )
+                .unwrap(),
+            )?),
+            None => t.token_id.sigma_serialize(w),
+        }
+        .and_then(|()| Ok(w.put_u64(t.amount.into())?))
+    })?;
+    additional_registers.sigma_serialize(w)
+}
+
+/// Box deserialization with token ids optionally parsed in transaction
+pub fn parse_box_with_indexed_digests<R: SigmaByteRead>(
+    digests_in_tx: Option<&IndexSet<TokenId>>,
+    r: &mut R,
+) -> Result<ErgoBoxCandidate, SigmaParsingError> {
+    // reference implementation -https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L144-L144
+
+    let value = BoxValue::sigma_parse(r)?;
+    let ergo_tree = ErgoTree::sigma_parse(r)?;
+    let creation_height = r.get_u32()?;
+    let tokens_count = r.get_u8()?;
+    let mut tokens = Vec::with_capacity(tokens_count as usize);
+    for _ in 0..tokens_count {
+        let token_id = match digests_in_tx {
+            None => TokenId::sigma_parse(r)?,
+            Some(digests) => {
+                let digest_index = r.get_u32()?;
+                match digests.get_index(digest_index as usize) {
+                    Some(i) => Ok((*i).clone()),
+                    None => Err(SigmaParsingError::Misc(
+                        "failed to find token id in tx digests".to_string(),
+                    )),
+                }?
+            }
+        };
+        let amount = r.get_u64()?;
+        tokens.push(Token {
+            token_id,
+            amount: amount.try_into()?,
+        })
+    }
+
+    let additional_registers = NonMandatoryRegisters::sigma_parse(r)?;
+
+    Ok(ErgoBoxCandidate {
+        value,
+        ergo_tree,
+        tokens,
+        additional_registers,
+        creation_height,
+    })
+}
+
+/// Arbitrary
+#[allow(clippy::unwrap_used)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use super::box_value::arbitrary::ArbBoxValueRange;
+    use super::*;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    impl Arbitrary for ErgoBoxCandidate {
+        type Parameters = ArbBoxValueRange;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (
+                any_with::<BoxValue>(args),
+                any::<ErgoTree>(),
+                vec(any::<Token>(), 0..3),
+                any::<u32>(),
+                any::<NonMandatoryRegisters>(),
+            )
+                .prop_map(
+                    |(value, ergo_tree, tokens, creation_height, additional_registers)| Self {
+                        value,
+                        ergo_tree,
+                        tokens,
+                        additional_registers,
+                        creation_height,
+                    },
+                )
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    impl Arbitrary for ErgoBox {
+        type Parameters = ArbBoxValueRange;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (
+                any_with::<ErgoBoxCandidate>(args),
+                any::<TxId>(),
+                any::<u16>(),
+            )
+                .prop_map(|(box_candidate, tx_id, index)| {
+                    Self::from_box_candidate(&box_candidate, tx_id, index).unwrap()
+                })
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    impl ErgoBox {
+        /// Returns copy of the current ErgoBox with given additional registers set
+        pub fn with_additional_registers(self, registers: NonMandatoryRegisters) -> ErgoBox {
+            ErgoBox {
+                additional_registers: registers,
+                ..self
+            }
+        }
+    }
+}
+
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+    use sigma_test_util::force_any_val;
+
+    #[test]
+    fn get_register_mandatory() {
+        let b = force_any_val::<ErgoBox>();
+        assert_eq!(b.get_register(RegisterId::R0).unwrap(), b.value.into());
+        assert_eq!(
+            b.get_register(RegisterId::R1).unwrap(),
+            b.script_bytes().unwrap().into()
+        );
+        assert_eq!(
+            b.get_register(RegisterId::R2).unwrap(),
+            b.tokens_raw().into()
+        );
+        assert_eq!(
+            b.get_register(RegisterId::R3).unwrap(),
+            b.creation_info().into()
+        );
+    }
+
+    #[test]
+    fn creation_info() {
+        let b = force_any_val::<ErgoBox>();
+        assert_eq!(b.creation_info().0, b.creation_height as i32);
+        let mut expected_bytes = Vec::new();
+        expected_bytes.extend_from_slice(b.transaction_id.0 .0.as_ref());
+        expected_bytes.extend_from_slice(&b.index.to_be_bytes());
+        assert_eq!(b.creation_info().1, expected_bytes.to_vec().as_vec_i8());
+    }
+
+    proptest! {
+
+        #[test]
+        fn ergo_box_candidate_ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+
+        #[test]
+        fn ergo_box_ser_roundtrip(v in any::<ErgoBox>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/ergo-lib/src/chain/ergo_box/box_id.rs b/ergotree-ir/src/chain/ergo_box/box_id.rs
similarity index 51%
rename from ergo-lib/src/chain/ergo_box/box_id.rs
rename to ergotree-ir/src/chain/ergo_box/box_id.rs
index 989a074e9..e3f409ee7 100644
--- a/ergo-lib/src/chain/ergo_box/box_id.rs
+++ b/ergotree-ir/src/chain/ergo_box/box_id.rs
@@ -1,29 +1,23 @@
 //! Box id type
-use std::convert::{TryFrom, TryInto};
+use std::convert::TryFrom;
 
-use ergotree_ir::chain::digest::Digest32;
-use ergotree_ir::ir_ergo_box::IrBoxId;
+use crate::chain::digest32::Digest32;
+use crate::chain::digest32::Digest32Error;
+use crate::serialization::SigmaSerializeResult;
 
-use ergotree_ir::serialization::SigmaSerializeResult;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
-
-use crate::chain::{Digest32Error, DigestRef};
-
-use derive_more::From;
-use derive_more::Into;
-use ergotree_ir::serialization::{
+use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
     SigmaSerializable,
 };
-#[cfg(test)]
-use proptest_derive::Arbitrary;
+use crate::util::AsVecI8;
+use derive_more::From;
+use derive_more::Into;
 
 /// newtype for box ids
 #[derive(PartialEq, Eq, Hash, Debug, Clone, From, Into)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(test, derive(Arbitrary))]
-pub struct BoxId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
+pub struct BoxId(Digest32);
 
 impl BoxId {
     /// Size in bytes
@@ -41,25 +35,9 @@ impl AsRef<[u8]> for BoxId {
     }
 }
 
-#[cfg(feature = "json")]
 impl From<BoxId> for String {
     fn from(v: BoxId) -> Self {
-        String::from(Into::<DigestRef<32>>::into(v.0))
-    }
-}
-
-impl From<&IrBoxId> for BoxId {
-    fn from(irb: &IrBoxId) -> Self {
-        let u8bytes: Vec<u8> = irb.0.iter().map(|b| *b as u8).collect();
-        let arr: [u8; Digest32::SIZE] = u8bytes.as_slice().try_into().unwrap();
-        BoxId(arr.into())
-    }
-}
-
-impl From<BoxId> for IrBoxId {
-    fn from(id: BoxId) -> Self {
-        let i8bytes: Vec<i8> = id.0 .0.iter().map(|b| *b as i8).collect();
-        IrBoxId::new(i8bytes.try_into().unwrap())
+        v.0.into()
     }
 }
 
@@ -67,9 +45,14 @@ impl TryFrom<String> for BoxId {
     type Error = Digest32Error;
 
     fn try_from(value: String) -> Result<Self, Self::Error> {
-        DigestRef::try_from(value)
-            .map(Into::<Digest32>::into)
-            .map(BoxId)
+        Ok(Digest32::try_from(value)?.into())
+    }
+}
+
+impl From<BoxId> for Vec<i8> {
+    fn from(b: BoxId) -> Self {
+        let bytes: Vec<u8> = b.0.into();
+        bytes.as_vec_i8()
     }
 }
 
@@ -83,10 +66,11 @@ impl SigmaSerializable for BoxId {
     }
 }
 
+#[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use super::*;
-    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
diff --git a/ergo-lib/src/chain/ergo_box/box_value.rs b/ergotree-ir/src/chain/ergo_box/box_value.rs
similarity index 94%
rename from ergo-lib/src/chain/ergo_box/box_value.rs
rename to ergotree-ir/src/chain/ergo_box/box_value.rs
index dacf4e15d..11de39426 100644
--- a/ergo-lib/src/chain/ergo_box/box_value.rs
+++ b/ergotree-ir/src/chain/ergo_box/box_value.rs
@@ -1,20 +1,18 @@
 //! Box value newtype
 
 use crate::chain::token::TokenAmountError;
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
-use ergotree_ir::serialization::SigmaSerializeResult;
-use ergotree_ir::serialization::{
+use crate::mir::constant::Constant;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaSerializeResult;
+use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
 use thiserror::Error;
 
 /// Box value in nanoERGs with bound checks
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 pub struct BoxValue(pub(crate) u64);
 
 impl BoxValue {
@@ -198,17 +196,16 @@ pub fn checked_sum<I: Iterator<Item = BoxValue>>(mut iter: I) -> Result<BoxValue
         })
 }
 
-#[cfg(test)]
-pub mod tests {
-    use std::convert::TryInto;
+/// Arbitrary
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use derive_more::{From, Into};
+    use proptest::{arbitrary::Arbitrary, prelude::*};
     use std::ops::Range;
 
     use super::*;
-    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
-
-    extern crate derive_more;
-    use derive_more::{From, Into};
 
+    /// Range for arbitrary BoxValue values
     #[derive(Debug, From, Into)]
     pub struct ArbBoxValueRange(Range<u64>);
 
@@ -225,6 +222,17 @@ pub mod tests {
             (args.0).prop_map(BoxValue).boxed()
         }
     }
+}
+
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+pub mod tests {
+    use std::convert::TryInto;
+
+    use super::*;
+    use proptest::{collection::vec, prelude::*};
+
+    extern crate derive_more;
 
     #[test]
     fn test_checked_add() {
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergotree-ir/src/chain/ergo_box/register.rs
similarity index 86%
rename from ergo-lib/src/chain/ergo_box/register.rs
rename to ergotree-ir/src/chain/ergo_box/register.rs
index 4f5e65ebc..de1c57408 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergotree-ir/src/chain/ergo_box/register.rs
@@ -1,16 +1,12 @@
 //! Box registers
 
-#[cfg(feature = "json")]
-use crate::chain::json::ergo_box::ConstantHolder;
-use crate::chain::Base16EncodedBytes;
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
-use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
-use ergotree_ir::serialization::SigmaParsingError;
-use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::serialization::SigmaSerializeResult;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
+use crate::chain::base16_bytes::Base16EncodedBytes;
+use crate::mir::constant::Constant;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializeResult;
 use std::convert::TryInto;
 use std::{collections::HashMap, convert::TryFrom};
 use thiserror::Error;
@@ -58,9 +54,17 @@ impl TryFrom<i8> for RegisterId {
     }
 }
 
+impl TryFrom<u8> for RegisterId {
+    type Error = RegisterIdOutOfBounds;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        RegisterId::try_from(value as i8)
+    }
+}
+
 /// newtype for additional registers R4 - R9
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[cfg_attr(feature = "json", serde(into = "String", try_from = "String"))]
 #[repr(u8)]
 pub enum NonMandatoryRegisterId {
@@ -157,7 +161,7 @@ pub struct NonMandatoryRegisterIdParsingError();
 
 /// Stores non-mandatory registers for the box
 #[derive(PartialEq, Eq, Debug, Clone)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[cfg_attr(
     feature = "json",
     serde(
@@ -183,17 +187,6 @@ impl NonMandatoryRegisters {
         NonMandatoryRegisters::try_from(regs)
     }
 
-    /// Create new from ordered values (first element will be R4, and so on)
-    pub fn from_ordered_values(
-        values: Vec<Constant>,
-    ) -> Result<NonMandatoryRegisters, NonMandatoryRegistersError> {
-        if values.len() > NonMandatoryRegisters::MAX_SIZE {
-            Err(NonMandatoryRegistersError::InvalidSize(values.len()))
-        } else {
-            Ok(NonMandatoryRegisters(values))
-        }
-    }
-
     /// Size of non-mandatory registers set
     pub fn len(&self) -> usize {
         self.0.len()
@@ -216,6 +209,19 @@ impl NonMandatoryRegisters {
     }
 }
 
+/// Create new from ordered values (first element will be R4, and so on)
+impl TryFrom<Vec<Constant>> for NonMandatoryRegisters {
+    type Error = NonMandatoryRegistersError;
+
+    fn try_from(values: Vec<Constant>) -> Result<Self, Self::Error> {
+        if values.len() > NonMandatoryRegisters::MAX_SIZE {
+            Err(NonMandatoryRegistersError::InvalidSize(values.len()))
+        } else {
+            Ok(NonMandatoryRegisters(values))
+        }
+    }
+}
+
 impl SigmaSerializable for NonMandatoryRegisters {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
         let regs_num = self.len();
@@ -233,7 +239,7 @@ impl SigmaSerializable for NonMandatoryRegisters {
             let v = Constant::sigma_parse(r)?;
             additional_regs.push(v);
         }
-        Ok(NonMandatoryRegisters::from_ordered_values(additional_regs)?)
+        Ok(additional_regs.try_into()?)
     }
 }
 
@@ -252,7 +258,14 @@ impl From<NonMandatoryRegisters> for HashMap<NonMandatoryRegisterId, Base16Encod
     fn from(v: NonMandatoryRegisters) -> Self {
         v.0.into_iter()
             .enumerate()
-            .map(|(i, c)| (NonMandatoryRegisterId::get_by_zero_index(i), c.into()))
+            .map(|(i, c)| {
+                (
+                    NonMandatoryRegisterId::get_by_zero_index(i),
+                    // no way of returning an error without writing custom JSON serializer
+                    #[allow(clippy::unwrap_used)]
+                    Base16EncodedBytes::new(&c.sigma_serialize_bytes().unwrap()),
+                )
+            })
             .collect()
     }
 }
@@ -287,10 +300,12 @@ impl TryFrom<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegister
 }
 
 #[cfg(feature = "json")]
-impl TryFrom<HashMap<NonMandatoryRegisterId, ConstantHolder>> for NonMandatoryRegisters {
+impl TryFrom<HashMap<NonMandatoryRegisterId, crate::chain::json::ergo_box::ConstantHolder>>
+    for NonMandatoryRegisters
+{
     type Error = NonMandatoryRegistersError;
     fn try_from(
-        value: HashMap<NonMandatoryRegisterId, ConstantHolder>,
+        value: HashMap<NonMandatoryRegisterId, crate::chain::json::ergo_box::ConstantHolder>,
     ) -> Result<Self, Self::Error> {
         let cm: HashMap<NonMandatoryRegisterId, Constant> =
             value.into_iter().map(|(k, v)| (k, v.into())).collect();
@@ -331,10 +346,10 @@ impl TryFrom<i8> for MandatoryRegisterId {
     }
 }
 
-#[cfg(test)]
-mod tests {
+#[allow(clippy::unwrap_used)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
     impl Arbitrary for NonMandatoryRegisters {
@@ -343,13 +358,19 @@ mod tests {
 
         fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
             vec(any::<Constant>(), 0..=NonMandatoryRegisterId::NUM_REGS)
-                .prop_map(|constants| {
-                    NonMandatoryRegisters::from_ordered_values(constants)
-                        .expect("error building registers")
-                })
+                .prop_map(|constants| NonMandatoryRegisters::try_from(constants).unwrap())
                 .boxed()
         }
     }
+}
+
+#[allow(clippy::unwrap_used)]
+#[allow(clippy::expect_used)]
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergotree-ir/src/chain/header.rs b/ergotree-ir/src/chain/header.rs
index e6a8405f1..87aa98754 100644
--- a/ergotree-ir/src/chain/header.rs
+++ b/ergotree-ir/src/chain/header.rs
@@ -5,43 +5,60 @@ use crate::mir::header::PreHeader;
 use crate::sigma_protocol::dlog_group;
 
 use super::block_id::BlockId;
-use super::digest::{ADDigest, Digest32};
+use super::digest32::ADDigest;
+use super::digest32::Digest32;
 use super::votes::Votes;
 
 /// Represents data of the block header available in Sigma propositions.
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Header {
     /// Block version, to be increased on every soft and hardfork.
+    #[cfg_attr(feature = "json", serde(rename = "version"))]
     pub version: u8,
     /// Bytes representation of ModifierId of this Header
+    #[cfg_attr(feature = "json", serde(rename = "id"))]
     pub id: BlockId,
     /// Bytes representation of ModifierId of the parent block
+    #[cfg_attr(feature = "json", serde(rename = "parentId"))]
     pub parent_id: BlockId,
     /// Hash of ADProofs for transactions in a block
+    #[cfg_attr(feature = "json", serde(rename = "adProofsRoot"))]
     pub ad_proofs_root: Digest32,
     /// AvlTree of a state after block application
+    #[cfg_attr(feature = "json", serde(rename = "stateRoot"))]
     pub state_root: ADDigest,
     /// Root hash (for a Merkle tree) of transactions in a block.
+    #[cfg_attr(feature = "json", serde(rename = "transactionsRoot"))]
     pub transaction_root: Digest32,
     /// Timestamp of a block in ms from UNIX epoch
+    #[cfg_attr(feature = "json", serde(rename = "timestamp"))]
     pub timestamp: u64,
     /// Current difficulty in a compressed view.
+    #[cfg_attr(feature = "json", serde(rename = "nBits"))]
     pub n_bits: u64,
     /// Block height
+    #[cfg_attr(feature = "json", serde(rename = "height"))]
     pub height: u32,
     /// Root hash of extension section
+    #[cfg_attr(feature = "json", serde(rename = "extensionHash"))]
     pub extension_root: Digest32,
     /// Public key of miner. Part of Autolykos solution.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
     pub miner_pk: Box<dlog_group::EcPoint>,
     /// One-time public key. Prevents revealing of miners secret.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
     pub pow_onetime_pk: Box<dlog_group::EcPoint>,
     /// nonce
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
     pub nonce: Vec<u8>,
     /// Distance between pseudo-random number, corresponding to nonce `nonce` and a secret,
     /// corresponding to `miner_pk`. The lower `pow_distance` is, the harder it was to find this solution.
+    #[cfg_attr(feature = "json", serde(skip_serializing, skip_deserializing))]
     pub pow_distance: BigInt,
     /// Miner votes for changing system parameters.
     /// 3 bytes in accordance to Scala implementation, but will use `Vec` until further improvements
+    #[cfg_attr(feature = "json", serde(rename = "votes"))]
     pub votes: Votes,
 }
 
@@ -90,7 +107,8 @@ mod arbitrary {
     use proptest::array::{uniform3, uniform32};
     use proptest::prelude::*;
 
-    use crate::chain::digest::{ADDigest, Digest};
+    use crate::chain::digest32::ADDigest;
+    use crate::chain::digest32::Digest;
     use crate::sigma_protocol::dlog_group::EcPoint;
 
     use super::{BlockId, Header, Votes};
@@ -157,3 +175,70 @@ mod arbitrary {
         type Strategy = BoxedStrategy<Header>;
     }
 }
+
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+mod tests {
+    use crate::chain::header::Header;
+
+    #[test]
+    fn parse_block_header() {
+        let json = r#"{
+            "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
+            "difficulty": "626412390187008",
+            "votes": "040000",
+            "timestamp": 1618929697400,
+            "size": 221,
+            "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
+            "height": 471746,
+            "nBits": 117586360,
+            "version": 2,
+            "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
+            "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
+            "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
+            "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
+            "powSolutions": {
+              "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
+              "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+              "n": "5939ecfee6b0d7f4",
+              "d": 0
+            },
+            "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
+            "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
+            "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
+        }"#;
+        let header: Header = serde_json::from_str(json).unwrap();
+        assert_eq!(header.height, 471746);
+    }
+
+    #[test]
+    fn parse_block_header_explorer_v1() {
+        // see https://api.ergoplatform.com/api/v1/blocks/de68a9cd727510d01eae3146f862261661f3bebdfd3c45c19d431b2ae81fb4b6
+        let json = r#"{
+            "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
+            "difficulty": "626412390187008",
+            "votes": [4,0,0],
+            "timestamp": 1618929697400,
+            "size": 221,
+            "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
+            "height": 471746,
+            "nBits": 117586360,
+            "version": 2,
+            "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
+            "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
+            "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
+            "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
+            "powSolutions": {
+              "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
+              "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+              "n": "5939ecfee6b0d7f4",
+              "d": 0
+            },
+            "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
+            "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
+            "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
+        }"#;
+        let header: Header = serde_json::from_str(json).unwrap();
+        assert_eq!(header.height, 471746);
+    }
+}
diff --git a/ergotree-ir/src/chain/json.rs b/ergotree-ir/src/chain/json.rs
new file mode 100644
index 000000000..e7db0df90
--- /dev/null
+++ b/ergotree-ir/src/chain/json.rs
@@ -0,0 +1,15 @@
+//! JSON serialization
+
+use serde::Serializer;
+
+pub(crate) mod ergo_box;
+pub(crate) 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>
+where
+    S: Serializer,
+    T: AsRef<[u8]>,
+{
+    serializer.serialize_str(&base16::encode_lower(bytes.as_ref()))
+}
diff --git a/ergo-lib/src/chain/json/ergo_box.rs b/ergotree-ir/src/chain/json/ergo_box.rs
similarity index 93%
rename from ergo-lib/src/chain/json/ergo_box.rs
rename to ergotree-ir/src/chain/json/ergo_box.rs
index 9af2425dd..80c05b917 100644
--- a/ergo-lib/src/chain/json/ergo_box.rs
+++ b/ergotree-ir/src/chain/json/ergo_box.rs
@@ -1,8 +1,15 @@
+use crate::chain::base16_bytes::Base16DecodedBytes;
+use crate::chain::ergo_box::box_value::BoxValue;
+use crate::chain::ergo_box::BoxId;
+use crate::chain::ergo_box::NonMandatoryRegisters;
+use crate::chain::token::Token;
+use crate::chain::tx_id::TxId;
+use crate::ergo_tree::ErgoTree;
+use crate::mir::constant::Constant;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializationError;
 use core::fmt;
-use ergotree_ir::ergo_tree::ErgoTree;
-use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::serialization::SigmaParsingError;
-use ergotree_ir::serialization::SigmaSerializable;
 use serde::de::{self, MapAccess, Visitor};
 use serde::Deserializer;
 use std::convert::TryFrom;
@@ -12,12 +19,6 @@ use std::str::FromStr;
 extern crate derive_more;
 use derive_more::From;
 
-use crate::chain::Base16DecodedBytes;
-use crate::chain::{
-    ergo_box::{BoxId, BoxValue, NonMandatoryRegisters},
-    token::Token,
-    transaction::TxId,
-};
 use serde::Deserialize;
 use thiserror::Error;
 
@@ -50,6 +51,17 @@ pub struct ErgoBoxFromJson {
     pub index: u16,
 }
 
+/// Errors on parsing ErgoBox from JSON
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+pub enum ErgoBoxFromJsonError {
+    /// Box id parsed from JSON differs from calculated from box serialized bytes
+    #[error("Box id parsed from JSON differs from calculated from box serialized bytes")]
+    InvalidBoxId,
+    /// Box serialization failed (id calculation)
+    #[error("Box serialization failed (id calculation): {0}")]
+    SerializationError(#[from] SigmaSerializationError),
+}
+
 #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
 pub struct ConstantHolder(#[serde(deserialize_with = "constant_as_string_or_struct")] RichConstant);
 
@@ -143,6 +155,7 @@ where
     deserializer.deserialize_any(StringOrStruct(PhantomData))
 }
 
+#[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use std::convert::TryInto;
diff --git a/ergo-lib/src/chain/json/ergo_tree.rs b/ergotree-ir/src/chain/json/ergo_tree.rs
similarity index 93%
rename from ergo-lib/src/chain/json/ergo_tree.rs
rename to ergotree-ir/src/chain/json/ergo_tree.rs
index 8e5160c1d..49d4b64f9 100644
--- a/ergo-lib/src/chain/json/ergo_tree.rs
+++ b/ergotree-ir/src/chain/json/ergo_tree.rs
@@ -1,5 +1,5 @@
-use ergotree_ir::ergo_tree::ErgoTree;
-use ergotree_ir::serialization::SigmaSerializable;
+use crate::ergo_tree::ErgoTree;
+use crate::serialization::SigmaSerializable;
 use serde::{Deserialize, Deserializer, Serializer};
 
 use super::serialize_bytes;
@@ -27,6 +27,7 @@ where
         })
 }
 
+#[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
     use crate::chain::json::ergo_box::ErgoBoxFromJson;
diff --git a/ergo-lib/src/chain/token.rs b/ergotree-ir/src/chain/token.rs
similarity index 77%
rename from ergo-lib/src/chain/token.rs
rename to ergotree-ir/src/chain/token.rs
index ad23d529d..7afb41782 100644
--- a/ergo-lib/src/chain/token.rs
+++ b/ergotree-ir/src/chain/token.rs
@@ -1,27 +1,22 @@
 //! Token related types
 
-use ergotree_ir::chain::digest::Digest32;
-use ergotree_ir::serialization::SigmaSerializeResult;
-use ergotree_ir::serialization::{
+use crate::serialization::SigmaSerializeResult;
+use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
     SigmaSerializable,
 };
 use std::convert::TryFrom;
 
-use super::digest32::DigestRef;
+use super::digest32::Digest32;
 use super::ergo_box::BoxId;
 use derive_more::From;
 use derive_more::Into;
-#[cfg(test)]
-use proptest_derive::Arbitrary;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
 /// newtype for token id
 #[derive(PartialEq, Eq, Hash, Debug, Clone, From, Into)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct TokenId(#[cfg_attr(feature = "json", serde(with = "DigestRef"))] Digest32);
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+pub struct TokenId(Digest32);
 
 impl TokenId {
     /// token id size in bytes
@@ -54,7 +49,7 @@ impl AsRef<[u8]> for TokenId {
 
 impl From<TokenId> for String {
     fn from(v: TokenId) -> Self {
-        String::from(Into::<DigestRef<32>>::into(v.0))
+        v.0.into()
     }
 }
 
@@ -69,7 +64,7 @@ impl SigmaSerializable for TokenId {
 
 /// Token amount with bound checks
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 pub struct TokenAmount(u64);
 
 impl TokenAmount {
@@ -145,10 +140,16 @@ impl From<TokenAmount> for i64 {
     }
 }
 
+impl From<Token> for (Vec<i8>, i64) {
+    fn from(t: Token) -> Self {
+        (t.token_id.into(), t.amount.into())
+    }
+}
+
 /// Token represented with token id paired with it's amount
 #[derive(PartialEq, Eq, Debug, Clone)]
-#[cfg_attr(test, derive(Arbitrary))]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[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"))]
@@ -167,15 +168,24 @@ impl From<(TokenId, TokenAmount)> for Token {
     }
 }
 
-#[cfg(test)]
-pub mod tests {
-    use super::*;
-    use crate::chain::Base16DecodedBytes;
-    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+/// Arbitrary
+#[allow(clippy::unwrap_used)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use proptest::prelude::*;
 
+    use crate::chain::digest32::Digest32;
+
+    use super::TokenAmount;
+    use super::TokenId;
+
+    use std::convert::TryFrom;
+
+    /// How to generate a token id
     pub enum ArbTokenIdParam {
+        /// From a list of predefined token ids
         Predef,
+        /// Arbitrary token id
         Arbitrary,
     }
 
@@ -193,31 +203,22 @@ pub mod tests {
                 ArbTokenIdParam::Predef => prop_oneof![
                     Just(TokenId::from(
                         Digest32::try_from(
-                            Base16DecodedBytes::try_from(
-                                "3130a82e45842aebb888742868e055e2f554ab7d92f233f2c828ed4a43793710"
-                                    .to_string()
-                            )
-                            .unwrap()
+                            "3130a82e45842aebb888742868e055e2f554ab7d92f233f2c828ed4a43793710"
+                                .to_string()
                         )
                         .unwrap()
                     )),
                     Just(TokenId::from(
                         Digest32::try_from(
-                            Base16DecodedBytes::try_from(
-                                "e7321ffb4ec5d71deb3110eb1ac09612b9cf57445acab1e0e3b1222d5b5a6c60"
-                                    .to_string()
-                            )
-                            .unwrap()
+                            "e7321ffb4ec5d71deb3110eb1ac09612b9cf57445acab1e0e3b1222d5b5a6c60"
+                                .to_string()
                         )
                         .unwrap()
                     )),
                     Just(TokenId::from(
                         Digest32::try_from(
-                            Base16DecodedBytes::try_from(
-                                "ad62f6dd92e7dc850bc406770dfac9a943dd221a7fb440b7b2bcc7d3149c1792"
-                                    .to_string()
-                            )
-                            .unwrap()
+                            "ad62f6dd92e7dc850bc406770dfac9a943dd221a7fb440b7b2bcc7d3149c1792"
+                                .to_string()
                         )
                         .unwrap()
                     ))
@@ -240,6 +241,14 @@ pub mod tests {
         }
         type Strategy = BoxedStrategy<Self>;
     }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use crate::chain::token::TokenId;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergotree-ir/src/chain/tx_id.rs b/ergotree-ir/src/chain/tx_id.rs
new file mode 100644
index 000000000..45ce6201f
--- /dev/null
+++ b/ergotree-ir/src/chain/tx_id.rs
@@ -0,0 +1,44 @@
+//! Transaction id type
+
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaParsingError;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializeResult;
+
+use super::digest32::Digest32;
+
+/// Transaction id (ModifierId in sigmastate)
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+pub struct TxId(pub Digest32);
+
+impl TxId {
+    /// All zeros
+    pub fn zero() -> TxId {
+        TxId(Digest32::zero())
+    }
+}
+
+impl SigmaSerializable for TxId {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
+        self.0.sigma_serialize(w)?;
+        Ok(())
+    }
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        Ok(Self(Digest32::sigma_parse(r)?))
+    }
+}
+
+impl From<TxId> for String {
+    fn from(v: TxId) -> Self {
+        v.0.into()
+    }
+}
+
+impl AsRef<[u8]> for TxId {
+    fn as_ref(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
diff --git a/ergotree-ir/src/chain/votes.rs b/ergotree-ir/src/chain/votes.rs
index 315a9f27a..247a5724a 100644
--- a/ergotree-ir/src/chain/votes.rs
+++ b/ergotree-ir/src/chain/votes.rs
@@ -3,10 +3,37 @@ use thiserror::Error;
 
 use std::convert::{TryFrom, TryInto};
 
+use super::base16_bytes::Base16DecodedBytes;
+use super::base16_bytes::Base16EncodedBytes;
+
 /// Votes for changing system parameters
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[derive(PartialEq, Eq, Debug, Clone)]
+#[cfg_attr(
+    feature = "json",
+    serde(into = "Base16EncodedBytes", try_from = "VotesEncodingVariants")
+)]
 pub struct Votes(pub [u8; 3]);
 
+#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Debug, Clone)]
+#[serde(untagged)]
+enum VotesEncodingVariants {
+    AsStr(Base16DecodedBytes),
+    AsByteArray(Vec<u8>), // explorer v1
+}
+
+impl TryFrom<VotesEncodingVariants> for Votes {
+    type Error = VotesError;
+
+    fn try_from(value: VotesEncodingVariants) -> Result<Self, Self::Error> {
+        match value {
+            VotesEncodingVariants::AsStr(bytes) => bytes.try_into(),
+            VotesEncodingVariants::AsByteArray(bytes) => bytes.try_into(),
+        }
+    }
+}
+
 impl From<Votes> for Vec<u8> {
     fn from(v: Votes) -> Self {
         v.0.to_vec()
@@ -29,3 +56,17 @@ pub enum VotesError {
     #[error("Votes: Invalid byte array size ({0})")]
     InvalidSize(#[from] std::array::TryFromSliceError),
 }
+
+impl TryFrom<Base16DecodedBytes> for Votes {
+    type Error = VotesError;
+
+    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
+        bytes.0.try_into()
+    }
+}
+
+impl From<Votes> for Base16EncodedBytes {
+    fn from(v: Votes) -> Self {
+        Base16EncodedBytes::new(v.0.as_ref())
+    }
+}
diff --git a/ergotree-ir/src/ergo_tree.rs b/ergotree-ir/src/ergo_tree.rs
index e1f8e68c8..341a16e1c 100644
--- a/ergotree-ir/src/ergo_tree.rs
+++ b/ergotree-ir/src/ergo_tree.rs
@@ -595,8 +595,8 @@ pub(crate) mod arbitrary {
 #[allow(clippy::expect_used)]
 mod tests {
     use super::*;
-    use crate::address::AddressEncoder;
-    use crate::address::NetworkPrefix;
+    use crate::chain::address::AddressEncoder;
+    use crate::chain::address::NetworkPrefix;
     use crate::mir::constant::Literal;
     use proptest::prelude::*;
 
diff --git a/ergotree-ir/src/ir_ergo_box.rs b/ergotree-ir/src/ir_ergo_box.rs
deleted file mode 100644
index 07987bfe3..000000000
--- a/ergotree-ir/src/ir_ergo_box.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-//! ErgoBox representation in IR
-use crate::mir::constant::Constant;
-use crate::serialization::SigmaSerializationError;
-use sigma_util::DIGEST32_SIZE;
-use std::fmt::Debug;
-use std::rc::Rc;
-use thiserror::Error;
-
-/// Ergo box id
-#[derive(PartialEq, Eq, Debug, Clone, Hash)]
-pub struct IrBoxId(pub [i8; DIGEST32_SIZE]);
-
-impl IrBoxId {
-    /// Make new box id
-    pub fn new(id: [i8; DIGEST32_SIZE]) -> Self {
-        IrBoxId(id)
-    }
-
-    /// Gets box with this id from box arena
-    pub fn get_box(
-        &self,
-        arena: &Rc<dyn IrErgoBoxArena>,
-    ) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError> {
-        arena.get(self)
-    }
-
-    /// Returns id as byte array
-    pub fn to_bytes(&self) -> Vec<i8> {
-        self.0.to_vec()
-    }
-}
-
-/// Arena (store) for boxes
-pub trait IrErgoBoxArena: Debug {
-    /// Returns a box with the given id
-    fn get(&self, id: &IrBoxId) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError>;
-}
-
-/// Box arena error
-#[derive(Error, PartialEq, Eq, Debug, Clone)]
-#[error("IrErgoBoxArenaError: {0}")]
-pub struct IrErgoBoxArenaError(pub String);
-
-/// Ergo box properties
-pub trait IrErgoBox: Debug {
-    /// Box id
-    fn id(&self) -> IrBoxId;
-    /// Box value
-    fn value(&self) -> i64;
-    /// Box tokens
-    fn tokens_raw(&self) -> Vec<(Vec<i8>, i64)>;
-    /// R4-R9 optional registers, where element with index 0 is R4, etc.
-    fn additional_registers(&self) -> &[Constant];
-    /// Returns a register value for the given register index (0 is R0, 9 is R9)
-    fn get_register(&self, id: i8) -> Option<Constant>;
-    /// Box creation height
-    fn creation_height(&self) -> i32;
-    /// Box guarding script serialized
-    fn script_bytes(&self) -> Result<Vec<i8>, SigmaSerializationError>;
-    /// Tuple of height when block got included into the blockchain and transaction identifier with
-    /// box index in the transaction outputs serialized to the byte array.
-    fn creation_info(&self) -> (i32, Vec<i8>);
-    /// Box serialized bytes
-    fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError>;
-    /// Box serialized bytes excluding txId & index
-    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError>;
-}
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
index 06c588e2e..a4607f643 100644
--- a/ergotree-ir/src/lib.rs
+++ b/ergotree-ir/src/lib.rs
@@ -11,7 +11,7 @@
 #![deny(missing_docs)]
 // Clippy exclusions
 #![allow(clippy::unit_arg)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 #![deny(clippy::unwrap_used)]
 #![deny(clippy::expect_used)]
 #![deny(clippy::todo)]
@@ -20,11 +20,10 @@
 
 mod has_opcode;
 
-pub mod address;
+pub mod base16_str;
 pub mod bigint256;
 pub mod chain;
 pub mod ergo_tree;
-pub mod ir_ergo_box;
 pub mod mir;
 pub mod serialization;
 pub mod sigma_protocol;
diff --git a/ergotree-ir/src/mir/avl_tree_data.rs b/ergotree-ir/src/mir/avl_tree_data.rs
index 275290902..3158d94fe 100644
--- a/ergotree-ir/src/mir/avl_tree_data.rs
+++ b/ergotree-ir/src/mir/avl_tree_data.rs
@@ -1,3 +1,4 @@
+use crate::chain::digest32::ADDigest;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
     SigmaSerializable, SigmaSerializeResult,
@@ -81,44 +82,14 @@ impl SigmaSerializable for AvlTreeData {
     }
 }
 
-// The following types were copied from `ergo-lib::chain::digest32` as a workaround until some
-// types are reorganized in the future.
-
-/// Digest
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Digest<const N: usize>(pub Box<[u8; N]>);
-
-/// 32 byte array used as ID of some value: block, transaction, etc.
-/// Usually this is as blake2b hash of serialized form
-pub type Digest32 = Digest<32>;
-
-/// AVL tree digest: root hash along with tree height (33 bytes)
-pub type ADDigest = Digest<33>;
-
-impl<const N: usize> Digest<N> {
-    /// Digest size 32 bytes
-    pub const SIZE: usize = N;
-}
-
-impl<const N: usize> SigmaSerializable for Digest<N> {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        w.write_all(self.0.as_ref())?;
-        Ok(())
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
-        let mut bytes = [0; N];
-        r.read_exact(&mut bytes)?;
-        Ok(Self(bytes.into()))
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::unwrap_used)]
 mod arbitrary {
 
+    use crate::chain::digest32::ADDigest;
+
     use super::*;
-    use proptest::{collection::vec, prelude::*};
-    use std::convert::TryInto;
+    use proptest::prelude::*;
 
     type OptBox = Option<Box<u32>>;
     impl Arbitrary for AvlTreeData {
@@ -156,17 +127,6 @@ mod arbitrary {
                 .boxed()
         }
     }
-
-    impl<const N: usize> Arbitrary for Digest<N> {
-        type Parameters = ();
-        type Strategy = BoxedStrategy<Self>;
-
-        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
-            vec(any::<u8>(), Self::SIZE)
-                .prop_map(|v| Digest(Box::new(v.try_into().unwrap())))
-                .boxed()
-        }
-    }
 }
 
 #[cfg(test)]
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index c4f48a022..622bea51a 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -1,8 +1,11 @@
 //! Constant(Literal) IR node
 
+use crate::base16_str::Base16Str;
 use crate::bigint256::BigInt256;
-use crate::ir_ergo_box::IrBoxId;
+use crate::chain::ergo_box::ErgoBox;
 use crate::mir::value::CollKind;
+use crate::serialization::SigmaSerializable;
+use crate::serialization::SigmaSerializationError;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 use crate::sigma_protocol::sigma_boolean::{ProveDhTuple, ProveDlog};
@@ -14,6 +17,7 @@ use crate::types::stype::SType;
 use impl_trait_for_tuples::impl_for_tuples;
 use std::convert::TryFrom;
 use std::convert::TryInto;
+use std::rc::Rc;
 
 mod constant_placeholder;
 
@@ -54,10 +58,10 @@ pub enum Literal {
     SigmaProp(Box<SigmaProp>),
     /// GroupElement
     GroupElement(Box<EcPoint>),
-    /// Ergo box ID
-    CBox(IrBoxId),
     /// AVL tree
     AvlTree(Box<AvlTreeData>),
+    /// Ergo box
+    CBox(Rc<ErgoBox>),
     /// Collection
     Coll(CollKind<Literal>),
     /// Option type
@@ -65,6 +69,7 @@ pub enum Literal {
     /// Tuple (arbitrary type values)
     Tup(TupleItems<Literal>),
 }
+
 impl From<bool> for Literal {
     fn from(v: bool) -> Literal {
         Literal::Boolean(v)
@@ -113,12 +118,18 @@ impl From<EcPoint> for Literal {
     }
 }
 
-impl From<IrBoxId> for Literal {
-    fn from(b: IrBoxId) -> Self {
+impl From<Rc<ErgoBox>> for Literal {
+    fn from(b: Rc<ErgoBox>) -> Self {
         Literal::CBox(b)
     }
 }
 
+impl From<ErgoBox> for Literal {
+    fn from(b: ErgoBox) -> Self {
+        Literal::CBox(Rc::new(b))
+    }
+}
+
 impl From<Vec<u8>> for Literal {
     fn from(v: Vec<u8>) -> Self {
         Literal::Coll(CollKind::NativeColl(NativeColl::CollByte(
@@ -284,8 +295,17 @@ impl From<EcPoint> for Constant {
     }
 }
 
-impl From<IrBoxId> for Constant {
-    fn from(b: IrBoxId) -> Self {
+impl From<Rc<ErgoBox>> for Constant {
+    fn from(b: Rc<ErgoBox>) -> Self {
+        Constant {
+            tpe: SType::SBox,
+            v: b.into(),
+        }
+    }
+}
+
+impl From<ErgoBox> for Constant {
+    fn from(b: ErgoBox) -> Self {
         Constant {
             tpe: SType::SBox,
             v: b.into(),
@@ -489,12 +509,24 @@ impl TryExtractFrom<Literal> for SigmaProp {
     }
 }
 
-impl TryExtractFrom<Literal> for IrBoxId {
+impl TryExtractFrom<Literal> for Rc<ErgoBox> {
     fn try_extract_from(c: Literal) -> Result<Self, TryExtractFromError> {
         match c {
             Literal::CBox(b) => Ok(b),
             _ => Err(TryExtractFromError(format!(
-                "expected IrErgoBox, found {:?}",
+                "expected ErgoBox, found {:?}",
+                c
+            ))),
+        }
+    }
+}
+
+impl TryExtractFrom<Literal> for ErgoBox {
+    fn try_extract_from(c: Literal) -> Result<Self, TryExtractFromError> {
+        match c {
+            Literal::CBox(b) => Ok((*b).clone()),
+            _ => Err(TryExtractFromError(format!(
+                "expected ErgoBox, found {:?}",
                 c
             ))),
         }
@@ -639,6 +671,20 @@ impl TryFrom<Literal> for ProveDlog {
     }
 }
 
+impl Base16Str for &Constant {
+    fn base16_str(&self) -> Result<String, SigmaSerializationError> {
+        self.sigma_serialize_bytes()
+            .map(|bytes| base16::encode_lower(&bytes))
+    }
+}
+
+impl Base16Str for Constant {
+    fn base16_str(&self) -> Result<String, SigmaSerializationError> {
+        self.sigma_serialize_bytes()
+            .map(|bytes| base16::encode_lower(&bytes))
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::unwrap_used)]
 #[allow(clippy::todo)]
@@ -706,7 +752,7 @@ pub(crate) mod arbitrary {
             SType::SBigInt => any::<i64>().prop_map(|v| BigInt256::from(v).into()).boxed(),
             SType::SGroupElement => any::<EcPoint>().prop_map_into().boxed(),
             SType::SSigmaProp => any::<SigmaProp>().prop_map_into().boxed(),
-            // SType::SBox => {}
+            SType::SBox => any::<ErgoBox>().prop_map_into().boxed(),
             // SType::SAvlTree => {}
             // SType::SOption(tpe) =>
             SType::SColl(elem_tpe) => match *elem_tpe {
diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs
index f87097268..d72760da9 100644
--- a/ergotree-ir/src/mir/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -1,12 +1,13 @@
 //! Ergo data type
 
 use std::convert::TryInto;
+use std::rc::Rc;
 
 use impl_trait_for_tuples::impl_for_tuples;
 
 use crate::bigint256::BigInt256;
+use crate::chain::ergo_box::ErgoBox;
 use crate::chain::header::Header;
-use crate::ir_ergo_box::IrBoxId;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::SigmaProp;
 use crate::types::stuple::TupleItems;
@@ -149,8 +150,8 @@ pub enum Value {
     GroupElement(Box<EcPoint>),
     /// Sigma property
     SigmaProp(Box<SigmaProp>),
-    /// Box
-    CBox(IrBoxId),
+    /// Ergo box
+    CBox(Rc<ErgoBox>),
     /// AVL tree
     AvlTree(Box<AvlTreeData>),
     /// Collection of values of the same type
@@ -160,7 +161,7 @@ pub enum Value {
     /// Transaction(and blockchain) context info
     Context,
     /// Block header
-    Header(Header),
+    Header(Box<Header>),
     /// Global which is used to define global methods
     Global,
     /// Optional value
@@ -217,7 +218,7 @@ impl From<Literal> for Value {
             Literal::BigInt(b) => Value::BigInt(b),
             Literal::SigmaProp(s) => Value::SigmaProp(s),
             Literal::GroupElement(e) => Value::GroupElement(e),
-            Literal::CBox(i) => Value::CBox(i),
+            Literal::CBox(b) => Value::CBox(b),
             Literal::Coll(coll) => {
                 let converted_coll = match coll {
                     CollKind::NativeColl(n) => CollKind::NativeColl(n),
@@ -243,8 +244,8 @@ impl StoreWrapped for i16 {}
 impl StoreWrapped for i32 {}
 impl StoreWrapped for i64 {}
 impl StoreWrapped for BigInt256 {}
-impl StoreWrapped for IrBoxId {}
 impl StoreWrapped for Header {}
+impl StoreWrapped for Rc<ErgoBox> {}
 impl StoreWrapped for EcPoint {}
 impl StoreWrapped for SigmaProp {}
 impl<T: StoreWrapped> StoreWrapped for Option<T> {}
@@ -273,6 +274,15 @@ impl Into<Value> for Tuple {
     }
 }
 
+impl From<Vec<Rc<ErgoBox>>> for Value {
+    fn from(v: Vec<Rc<ErgoBox>>) -> Self {
+        Value::Coll(CollKind::WrappedColl {
+            elem_tpe: SType::SBox,
+            items: v.into_iter().map(|i| i.into()).collect(),
+        })
+    }
+}
+
 impl TryExtractFrom<Value> for bool {
     fn try_extract_from(cv: Value) -> Result<bool, TryExtractFromError> {
         match cv {
@@ -345,12 +355,12 @@ impl TryExtractFrom<Value> for SigmaProp {
     }
 }
 
-impl TryExtractFrom<Value> for IrBoxId {
+impl TryExtractFrom<Value> for Rc<ErgoBox> {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
             Value::CBox(b) => Ok(b),
             _ => Err(TryExtractFromError(format!(
-                "expected IrErgoBox, found {:?}",
+                "expected ErgoBox, found {:?}",
                 c
             ))),
         }
@@ -360,7 +370,7 @@ impl TryExtractFrom<Value> for IrBoxId {
 impl TryExtractFrom<Value> for Header {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
-            Value::Header(h) => Ok(h),
+            Value::Header(h) => Ok(*h),
             _ => Err(TryExtractFromError(format!(
                 "expected Header, found {:?}",
                 c
diff --git a/ergotree-ir/src/serialization/constant.rs b/ergotree-ir/src/serialization/constant.rs
index 0fa535a9a..964a9a5cd 100644
--- a/ergotree-ir/src/serialization/constant.rs
+++ b/ergotree-ir/src/serialization/constant.rs
@@ -47,5 +47,10 @@ mod tests {
         fn ser_roundtrip(v in any_with::<Constant>(ArbConstantParams::AnyWithDepth(4))) {
             prop_assert_eq![sigma_serialize_roundtrip(&v), v];
         }
+
+        #[test]
+        fn ser_roundtrip_sbox(v in any_with::<Constant>(ArbConstantParams::Exact(SType::SBox))) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
     }
 }
diff --git a/ergotree-ir/src/serialization/data.rs b/ergotree-ir/src/serialization/data.rs
index b50ec339a..4f18333d8 100644
--- a/ergotree-ir/src/serialization/data.rs
+++ b/ergotree-ir/src/serialization/data.rs
@@ -1,3 +1,4 @@
+use crate::chain::ergo_box::ErgoBox;
 use crate::mir::avl_tree_data::AvlTreeData;
 use crate::mir::constant::Literal;
 use crate::mir::constant::TryExtractFromError;
@@ -18,6 +19,7 @@ use crate::util::AsVecU8;
 
 use super::sigma_byte_writer::SigmaByteWrite;
 use std::convert::TryInto;
+use std::rc::Rc;
 
 /// Used to serialize and parse `Literal` and `Value`.
 pub struct DataSerializer {}
@@ -38,8 +40,8 @@ impl DataSerializer {
             }
             Literal::GroupElement(ecp) => ecp.sigma_serialize(w)?,
             Literal::SigmaProp(s) => s.value().sigma_serialize(w)?,
-            Literal::CBox(_) => return Err(SigmaSerializationError::NotImplementedYet("Box")),
             Literal::AvlTree(a) => a.sigma_serialize(w)?,
+            Literal::CBox(b) => b.sigma_serialize(w)?,
             Literal::Coll(ct) => match ct {
                 CollKind::NativeColl(NativeColl::CollByte(b)) => {
                     w.put_usize_as_u16_unwrapped(b.len())?;
@@ -145,11 +147,7 @@ impl DataSerializer {
                 // is correct
                 Literal::Tup(items.try_into()?)
             }
-            SBox => {
-                return Err(SigmaParsingError::NotImplementedYet(
-                    "SBox data".to_string(),
-                ))
-            }
+            SBox => Literal::CBox(Rc::new(ErgoBox::sigma_parse(r)?)),
             SAvlTree => Literal::AvlTree(Box::new(AvlTreeData::sigma_parse(r)?)),
             STypeVar(_) => return Err(SigmaParsingError::NotSupported("TypeVar data")),
             SAny => return Err(SigmaParsingError::NotSupported("SAny data")),
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 73e4d6d76..0bd5e593b 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -299,8 +299,8 @@ impl SigmaSerializable for Expr {
 #[allow(clippy::unwrap_used)]
 #[allow(clippy::panic)]
 mod tests {
-    use crate::address::AddressEncoder;
-    use crate::address::NetworkPrefix;
+    use crate::chain::address::AddressEncoder;
+    use crate::chain::address::NetworkPrefix;
 
     use super::*;
     use crate::serialization::sigma_serialize_roundtrip;
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index 3a354f6c8..c9decc53b 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -6,7 +6,7 @@ use std::convert::TryInto;
 use impl_trait_for_tuples::impl_for_tuples;
 
 use crate::bigint256::BigInt256;
-use crate::ir_ergo_box::IrBoxId;
+use crate::chain::ergo_box::ErgoBox;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
@@ -165,7 +165,7 @@ impl LiftIntoSType for i64 {
     }
 }
 
-impl LiftIntoSType for IrBoxId {
+impl LiftIntoSType for ErgoBox {
     fn stype() -> SType {
         SType::SBox
     }
diff --git a/sigma-ser/src/vlq_encode.rs b/sigma-ser/src/vlq_encode.rs
index 243c49a86..2241c575b 100644
--- a/sigma-ser/src/vlq_encode.rs
+++ b/sigma-ser/src/vlq_encode.rs
@@ -29,8 +29,8 @@ impl From<io::Error> for VlqEncodingError {
 }
 
 /// Write encoded unsigned values using VLQ and signed values first with ZigZag, then using VLQ
-/// for VLQ see https://en.wikipedia.org/wiki/Variable-length_quantity (GLE)
-/// for ZigZag see https://developers.google.com/protocol-buffers/docs/encoding#types
+/// for VLQ see <https://en.wikipedia.org/wiki/Variable-length_quantity> (GLE)
+/// for ZigZag see <https://developers.google.com/protocol-buffers/docs/encoding#types>
 pub trait WriteSigmaVlqExt: io::Write {
     /// Write i8 without encoding
     fn put_i8(&mut self, v: i8) -> io::Result<()> {
@@ -122,8 +122,8 @@ pub trait WriteSigmaVlqExt: io::Write {
 impl<W: io::Write + ?Sized> WriteSigmaVlqExt for W {}
 
 /// Read and decode values using VLQ (+ ZigZag for signed values) encoded and written with [`WriteSigmaVlqExt`]
-/// for VLQ see https://en.wikipedia.org/wiki/Variable-length_quantity (GLE)
-/// for ZigZag see https://developers.google.com/protocol-buffers/docs/encoding#types
+/// for VLQ see <https://en.wikipedia.org/wiki/Variable-length_quantity> (GLE)
+/// for ZigZag see <https://developers.google.com/protocol-buffers/docs/encoding#types>
 pub trait ReadSigmaVlqExt: io::Read {
     /// Read i8 without decoding
     fn get_i8(&mut self) -> Result<i8, io::Error> {
diff --git a/sigma-util/src/lib.rs b/sigma-util/src/lib.rs
index da50b79c1..b34744692 100644
--- a/sigma-util/src/lib.rs
+++ b/sigma-util/src/lib.rs
@@ -11,7 +11,7 @@
 #![deny(missing_docs)]
 // Clippy exclusions
 #![allow(clippy::unit_arg)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 pub mod hash;
 


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_constant.js b/bindings/ergo-lib-wasm/tests/test_constant.js
index 32e6bb79a..a3098ed70 100644
--- a/bindings/ergo-lib-wasm/tests/test_constant.js
+++ b/bindings/ergo-lib-wasm/tests/test_constant.js
@@ -1,7 +1,7 @@
 import { expect, assert } from 'chai';
 
 import {
-  Constant, I64
+  Constant, I64, ErgoBoxes,
 } from '../pkg/ergo_lib_wasm';
 
 it('decode Constant i32', async () => {
@@ -74,3 +74,25 @@ it('roundtrip tuple of i64', async () => {
   expect(decoded_c_value.toString()).equal([value_str1, value_str2].toString());
 });
 
+it('roundtrip ErgoBox', async () => {
+  const boxes = ErgoBoxes.from_boxes_json([
+    {
+      "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+      "value": 67500000000,
+      "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+      "assets": [],
+      "creationHeight": 284761,
+      "additionalRegisters": {},
+      "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+      "index": 1
+    }
+  ]);
+  let box = boxes.get(0);
+  let c = Constant.from_ergo_box(box);
+  let encoded = c.encode_to_base16();
+  let decoded_c = Constant.decode_from_base16(encoded);
+  let decoded_c_value = decoded_c.to_ergo_box();
+  assert(decoded_c_value != null);
+  expect(decoded_c_value.to_json().toString()).equal(box.to_json().toString());
+});
+