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

Issue:
Draft the compiler pipeline (parser, binder, typer)
Draft parser, binder and typer to check if current AST design fits.


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index e07772e1b..c9c71342e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,14 @@
 [workspace]
 members = [
     "sigma-ser",
+    "sigma-util",
+    "ergoscript-compiler",
+    "ergotree-ir",
     "ergo-lib",
     "bindings/ergo-lib-wasm",
     "bindings/ergo-lib-c-core",
     "bindings/ergo-lib-c",
-    "bindings/ergo-lib-jni",
+    "bindings/ergo-lib-jni"
 ]
 
 [profile.release]
diff --git a/bindings/ergo-lib-c-core/Cargo.toml b/bindings/ergo-lib-c-core/Cargo.toml
index 34725e1ba..e8c9ef59e 100644
--- a/bindings/ergo-lib-c-core/Cargo.toml
+++ b/bindings/ergo-lib-c-core/Cargo.toml
@@ -11,6 +11,6 @@ repository = "https://github.com/ergoplatform/sigma-rust"
 crate-type = ["lib"]
 
 [dependencies]
-ergo-lib = { version = "^0.5.0", path = "../../ergo-lib" }
+ergo-lib = { version = "^0.5.1", path = "../../ergo-lib" }
 thiserror = "1"
 
diff --git a/bindings/ergo-lib-c-core/src/lib.rs b/bindings/ergo-lib-c-core/src/lib.rs
index 4dbe4607a..4095b998c 100644
--- a/bindings/ergo-lib-c-core/src/lib.rs
+++ b/bindings/ergo-lib-c-core/src/lib.rs
@@ -13,10 +13,9 @@
 mod error;
 pub use error::*;
 
-use chain::address::{AddressEncoder, NetworkPrefix};
-use ergo_lib::chain;
+use ergo_lib::ergotree_ir::address::{AddressEncoder, NetworkPrefix};
 
-pub struct Address(chain::address::Address);
+pub struct Address(ergo_lib::ergotree_ir::address::Address);
 pub type AddressPtr = *mut Address;
 
 pub unsafe fn address_from_testnet(
diff --git a/bindings/ergo-lib-wasm/Cargo.toml b/bindings/ergo-lib-wasm/Cargo.toml
index bcc166e34..033212162 100644
--- a/bindings/ergo-lib-wasm/Cargo.toml
+++ b/bindings/ergo-lib-wasm/Cargo.toml
@@ -15,10 +15,10 @@ default = ["console_error_panic_hook"]
 
 [dependencies]
 serde = { version = "1.0", features = ["derive"]}
+ergo-lib = { version = "^0.5.0", path = "../../ergo-lib" }
 serde_json = "1.0"
 js-sys = "0.3"
 
-ergo-lib = { version = "^0.5.0", path = "../../ergo-lib" }
 # used in elliptic-curve(in ergo-lib), compiled here with WASM support
 getrandom = {version = "0.1", features = ["wasm-bindgen"]}
 # The `console_error_panic_hook` crate provides better debugging of panics by
diff --git a/bindings/ergo-lib-wasm/src/address.rs b/bindings/ergo-lib-wasm/src/address.rs
index 7156e9f6e..132116463 100644
--- a/bindings/ergo-lib-wasm/src/address.rs
+++ b/bindings/ergo-lib-wasm/src/address.rs
@@ -1,9 +1,8 @@
 //! Address types
 
-use ergo_lib::chain;
-use ergo_lib::serialization::SigmaSerializable;
-use ergo_lib::sigma_protocol::dlog_group::EcPoint;
-use ergo_lib::sigma_protocol::sigma_boolean::ProveDlog;
+use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
+use ergo_lib::ergotree_ir::sigma_protocol::dlog_group::EcPoint;
+use ergo_lib::ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 use wasm_bindgen::prelude::*;
 
 use crate::ergo_tree::ErgoTree;
@@ -19,9 +18,9 @@ pub enum NetworkPrefix {
     Testnet = 16,
 }
 
-impl From<NetworkPrefix> for chain::address::NetworkPrefix {
+impl From<NetworkPrefix> for ergo_lib::ergotree_ir::address::NetworkPrefix {
     fn from(v: NetworkPrefix) -> Self {
-        use chain::address::NetworkPrefix::*;
+        use ergo_lib::ergotree_ir::address::NetworkPrefix::*;
         match v {
             NetworkPrefix::Mainnet => Mainnet,
             NetworkPrefix::Testnet => Testnet,
@@ -29,12 +28,12 @@ impl From<NetworkPrefix> for chain::address::NetworkPrefix {
     }
 }
 
-impl From<chain::address::NetworkPrefix> for NetworkPrefix {
-    fn from(v: chain::address::NetworkPrefix) -> Self {
+impl From<ergo_lib::ergotree_ir::address::NetworkPrefix> for NetworkPrefix {
+    fn from(v: ergo_lib::ergotree_ir::address::NetworkPrefix) -> Self {
         use NetworkPrefix::*;
         match v {
-            chain::address::NetworkPrefix::Mainnet => Mainnet,
-            chain::address::NetworkPrefix::Testnet => Testnet,
+            ergo_lib::ergotree_ir::address::NetworkPrefix::Mainnet => Mainnet,
+            ergo_lib::ergotree_ir::address::NetworkPrefix::Testnet => Testnet,
         }
     }
 }
@@ -51,9 +50,9 @@ pub enum AddressTypePrefix {
     Pay2S = 3,
 }
 
-impl From<AddressTypePrefix> for chain::address::AddressTypePrefix {
+impl From<AddressTypePrefix> for ergo_lib::ergotree_ir::address::AddressTypePrefix {
     fn from(v: AddressTypePrefix) -> Self {
-        use chain::address::AddressTypePrefix::*;
+        use ergo_lib::ergotree_ir::address::AddressTypePrefix::*;
         match v {
             AddressTypePrefix::P2PK => P2PK,
             AddressTypePrefix::Pay2SH => Pay2SH,
@@ -62,13 +61,13 @@ impl From<AddressTypePrefix> for chain::address::AddressTypePrefix {
     }
 }
 
-impl From<chain::address::AddressTypePrefix> for AddressTypePrefix {
-    fn from(v: chain::address::AddressTypePrefix) -> Self {
+impl From<ergo_lib::ergotree_ir::address::AddressTypePrefix> for AddressTypePrefix {
+    fn from(v: ergo_lib::ergotree_ir::address::AddressTypePrefix) -> Self {
         use AddressTypePrefix::*;
         match v {
-            chain::address::AddressTypePrefix::P2PK => P2PK,
-            chain::address::AddressTypePrefix::Pay2SH => Pay2SH,
-            chain::address::AddressTypePrefix::Pay2S => Pay2S,
+            ergo_lib::ergotree_ir::address::AddressTypePrefix::P2PK => P2PK,
+            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2SH => Pay2SH,
+            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2S => Pay2S,
         }
     }
 }
@@ -118,7 +117,7 @@ impl From<chain::address::AddressTypePrefix> for AddressTypePrefix {
  */
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Address(chain::address::Address);
+pub struct Address(ergo_lib::ergotree_ir::address::Address);
 
 #[wasm_bindgen]
 impl Address {
@@ -128,57 +127,67 @@ 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> {
-        chain::address::Address::recreate_from_ergo_tree(&ergo_tree.clone().into())
+        ergo_lib::ergotree_ir::address::Address::recreate_from_ergo_tree(&ergo_tree.clone().into())
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Create a P2PK address from serialized PK bytes(EcPoint/GroupElement)
     pub fn p2pk_from_pk_bytes(bytes: &[u8]) -> Result<Address, JsValue> {
-        chain::address::Address::p2pk_from_pk_bytes(bytes)
+        ergo_lib::ergotree_ir::address::Address::p2pk_from_pk_bytes(bytes)
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Decode (base58) testnet address from string, checking that address is from the testnet
     pub fn from_testnet_str(s: &str) -> Result<Address, JsValue> {
-        chain::address::AddressEncoder::new(chain::address::NetworkPrefix::Testnet)
-            .parse_address_from_str(s)
-            .map(Address)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        ergo_lib::ergotree_ir::address::AddressEncoder::new(
+            ergo_lib::ergotree_ir::address::NetworkPrefix::Testnet,
+        )
+        .parse_address_from_str(s)
+        .map(Address)
+        .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Decode (base58) mainnet address from string, checking that address is from the mainnet
     pub fn from_mainnet_str(s: &str) -> Result<Address, JsValue> {
-        chain::address::AddressEncoder::new(chain::address::NetworkPrefix::Mainnet)
-            .parse_address_from_str(s)
-            .map(Address)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        ergo_lib::ergotree_ir::address::AddressEncoder::new(
+            ergo_lib::ergotree_ir::address::NetworkPrefix::Mainnet,
+        )
+        .parse_address_from_str(s)
+        .map(Address)
+        .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Decode (base58) address from string without checking the network prefix
     #[allow(clippy::should_implement_trait)]
     pub fn from_base58(s: &str) -> Result<Address, JsValue> {
-        chain::address::AddressEncoder::unchecked_parse_address_from_str(s)
+        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_address_from_str(s)
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Encode (base58) address
     pub fn to_base58(&self, network_prefix: NetworkPrefix) -> String {
-        chain::address::AddressEncoder::encode_address_as_string(network_prefix.into(), &self.0)
+        ergo_lib::ergotree_ir::address::AddressEncoder::encode_address_as_string(
+            network_prefix.into(),
+            &self.0,
+        )
     }
 
     /// Decode from a serialized address (that includes the network prefix)
     pub fn from_bytes(data: Vec<u8>) -> Result<Address, JsValue> {
-        chain::address::AddressEncoder::unchecked_parse_address_from_bytes(&data)
+        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_address_from_bytes(&data)
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Encode address as serialized bytes (that includes the network prefix)
     pub fn to_bytes(&self, network_prefix: NetworkPrefix) -> Vec<u8> {
-        chain::address::AddressEncoder::encode_address_as_bytes(network_prefix.into(), &self.0)
+        ergo_lib::ergotree_ir::address::AddressEncoder::encode_address_as_bytes(
+            network_prefix.into(),
+            &self.0,
+        )
     }
 
     /// Get the type of the address
@@ -189,7 +198,7 @@ impl Address {
     /// Create an address from a public key
     pub fn from_public_key(bytes: &[u8]) -> Result<Address, JsValue> {
         EcPoint::sigma_parse_bytes(bytes.to_vec())
-            .map(|point| chain::address::Address::P2PK(ProveDlog::new(point)))
+            .map(|point| ergo_lib::ergotree_ir::address::Address::P2PK(ProveDlog::new(point)))
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
@@ -203,14 +212,14 @@ impl Address {
     }
 }
 
-impl Into<chain::address::Address> for Address {
-    fn into(self) -> chain::address::Address {
+impl Into<ergo_lib::ergotree_ir::address::Address> for Address {
+    fn into(self) -> ergo_lib::ergotree_ir::address::Address {
         self.0
     }
 }
 
-impl From<chain::address::Address> for Address {
-    fn from(a: chain::address::Address) -> Self {
+impl From<ergo_lib::ergotree_ir::address::Address> for Address {
+    fn from(a: ergo_lib::ergotree_ir::address::Address) -> Self {
         Address(a)
     }
 }
@@ -219,13 +228,13 @@ impl From<chain::address::Address> for Address {
 /// These two combined together form a base58 encoding
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct NetworkAddress(chain::address::NetworkAddress);
+pub struct NetworkAddress(ergo_lib::ergotree_ir::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(chain::address::NetworkAddress::new(
+        NetworkAddress(ergo_lib::ergotree_ir::address::NetworkAddress::new(
             network.into(),
             &address.clone().into(),
         ))
@@ -233,7 +242,7 @@ impl NetworkAddress {
 
     /// Decode (base58) a NetworkAddress (address + network prefix) from string
     pub fn from_base58(s: &str) -> Result<NetworkAddress, JsValue> {
-        chain::address::AddressEncoder::unchecked_parse_network_address_from_str(s)
+        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_network_address_from_str(s)
             .map(NetworkAddress)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
@@ -245,14 +254,16 @@ impl NetworkAddress {
 
     /// Decode from a serialized address
     pub fn from_bytes(data: Vec<u8>) -> Result<NetworkAddress, JsValue> {
-        chain::address::AddressEncoder::unchecked_parse_network_address_from_bytes(&data)
-            .map(NetworkAddress)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        ergo_lib::ergotree_ir::address::AddressEncoder::unchecked_parse_network_address_from_bytes(
+            &data,
+        )
+        .map(NetworkAddress)
+        .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Encode address as serialized bytes
     pub fn to_bytes(&self) -> Vec<u8> {
-        chain::address::AddressEncoder::encode_address_as_bytes(
+        ergo_lib::ergotree_ir::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 8cb0dc433..d8343a1d8 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -1,18 +1,19 @@
 //! Ergo constant values
 
-use std::convert::TryFrom;
-
+use crate::utils::I64;
+use ergo_lib::chain::Base16Str;
+use ergo_lib::ergotree_ir::mir::constant::TryExtractFrom;
 use js_sys::Uint8Array;
+use std::convert::TryFrom;
 use wasm_bindgen::prelude::*;
 
-use ergo_lib::ast::constant::TryExtractFrom;
-
-use crate::utils::I64;
+extern crate derive_more;
+use derive_more::{From, Into};
 
 /// Ergo constant(evaluated) values
 #[wasm_bindgen]
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Constant(ergo_lib::ast::constant::Constant);
+#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
+pub struct Constant(ergo_lib::ergotree_ir::mir::constant::Constant);
 
 #[wasm_bindgen]
 impl Constant {
@@ -25,7 +26,7 @@ impl Constant {
                     base16_bytes_str.clone()
                 ))
             })?;
-        ergo_lib::ast::constant::Constant::try_from(bytes)
+        ergo_lib::ergotree_ir::mir::constant::Constant::try_from(bytes)
             .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
             .map(Constant)
     }
@@ -69,15 +70,3 @@ impl Constant {
             .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
     }
 }
-
-impl From<ergo_lib::ast::constant::Constant> for Constant {
-    fn from(c: ergo_lib::ast::constant::Constant) -> Self {
-        Constant(c)
-    }
-}
-
-impl From<Constant> for ergo_lib::ast::constant::Constant {
-    fn from(c: Constant) -> Self {
-        c.0
-    }
-}
diff --git a/bindings/ergo-lib-wasm/src/context_extension.rs b/bindings/ergo-lib-wasm/src/context_extension.rs
index a98c9dba7..123fe7f0b 100644
--- a/bindings/ergo-lib-wasm/src/context_extension.rs
+++ b/bindings/ergo-lib-wasm/src/context_extension.rs
@@ -1,7 +1,6 @@
 //! ProverResult
 
 use crate::ast::Constant;
-use ergo_lib::sigma_protocol;
 use wasm_bindgen::prelude::*;
 
 extern crate derive_more;
@@ -10,21 +9,27 @@ use derive_more::{From, Into};
 /// Proof of correctness of tx spending
 #[wasm_bindgen]
 #[derive(PartialEq, Debug, Clone, From, Into)]
-pub struct ContextExtension(sigma_protocol::prover::ContextExtension);
+pub struct ContextExtension(ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension);
 
 #[wasm_bindgen]
 impl ContextExtension {
     /// Returns the number of elements in the collection
     pub fn len(&self) -> usize {
-        self.0.values.len()
+        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+            self.0.clone();
+        wrapped.values.len()
     }
     /// get from map or fail if key is missing
     pub fn get(&self, key: u8) -> Constant {
-        self.0.values.get(&key).unwrap().clone().into()
+        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+            self.0.clone();
+        wrapped.values.get(&key).unwrap().clone().into()
     }
 
     /// Returns all keys in the map
     pub fn keys(&self) -> Vec<u8> {
-        self.0.values.keys().cloned().collect()
+        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+            self.0.clone();
+        wrapped.values.keys().cloned().collect()
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/contract.rs b/bindings/ergo-lib-wasm/src/contract.rs
index 45044a9e1..267ef4aaf 100644
--- a/bindings/ergo-lib-wasm/src/contract.rs
+++ b/bindings/ergo-lib-wasm/src/contract.rs
@@ -1,5 +1,6 @@
 //! Contract, for easier ErgoTree generation
 use ergo_lib::chain;
+use ergo_lib::ergoscript_compiler::script_env::ScriptEnv;
 use wasm_bindgen::prelude::*;
 
 use crate::address::Address;
@@ -17,6 +18,13 @@ impl Contract {
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
             .map(Contract)
     }
+
+    /// Compiles a contract from ErgoScript source code
+    pub fn compile(source: &str) -> Result<Contract, JsValue> {
+        chain::contract::Contract::compile(source, ScriptEnv::new())
+            .map_err(|e| JsValue::from_str(e.pretty_desc(source).as_str()))
+            .map(Contract)
+    }
 }
 
 impl From<Contract> for chain::contract::Contract {
diff --git a/bindings/ergo-lib-wasm/src/ergo_tree.rs b/bindings/ergo-lib-wasm/src/ergo_tree.rs
index 1a3ea987e..63a39c79d 100644
--- a/bindings/ergo-lib-wasm/src/ergo_tree.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_tree.rs
@@ -3,7 +3,7 @@
 use std::convert::TryFrom;
 
 use ergo_lib::chain::Base16DecodedBytes;
-use ergo_lib::serialization::SigmaSerializable;
+use ergo_lib::ergotree_ir::serialization::SigmaSerializable;
 use wasm_bindgen::prelude::*;
 
 extern crate derive_more;
@@ -12,7 +12,7 @@ use derive_more::{From, Into};
 /// The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct ErgoTree(ergo_lib::ergo_tree::ErgoTree);
+pub struct ErgoTree(ergo_lib::ergotree_ir::ergo_tree::ErgoTree);
 
 #[wasm_bindgen]
 impl ErgoTree {
@@ -25,7 +25,7 @@ impl ErgoTree {
 
     /// Decode from encoded serialized ErgoTree
     pub fn from_bytes(data: Vec<u8>) -> Result<ErgoTree, JsValue> {
-        ergo_lib::ergo_tree::ErgoTree::sigma_parse_bytes(data)
+        ergo_lib::ergotree_ir::ergo_tree::ErgoTree::sigma_parse_bytes(data)
             .map(ErgoTree)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
diff --git a/bindings/ergo-lib-wasm/src/prover_result.rs b/bindings/ergo-lib-wasm/src/prover_result.rs
index 3a30121b7..2abefdbd9 100644
--- a/bindings/ergo-lib-wasm/src/prover_result.rs
+++ b/bindings/ergo-lib-wasm/src/prover_result.rs
@@ -1,7 +1,5 @@
 //! ProverResult
 
-use ergo_lib::serialization::SigmaSerializable;
-use ergo_lib::sigma_protocol;
 use wasm_bindgen::prelude::*;
 
 use crate::context_extension::ContextExtension;
@@ -11,13 +9,13 @@ use derive_more::{From, Into};
 /// Proof of correctness of tx spending
 #[wasm_bindgen]
 #[derive(PartialEq, Debug, Clone, From, Into)]
-pub struct ProverResult(sigma_protocol::prover::ProverResult);
+pub struct ProverResult(ergo_lib::chain::transaction::input::prover_result::ProverResult);
 
 #[wasm_bindgen]
 impl ProverResult {
     /// Get proof
     pub fn proof(&self) -> Vec<u8> {
-        self.0.proof.sigma_serialize_bytes()
+        self.0.proof.clone().into()
     }
 
     /// Get extension
diff --git a/bindings/ergo-lib-wasm/src/secret_key.rs b/bindings/ergo-lib-wasm/src/secret_key.rs
index d2461ec7d..fdd06286c 100644
--- a/bindings/ergo-lib-wasm/src/secret_key.rs
+++ b/bindings/ergo-lib-wasm/src/secret_key.rs
@@ -1,7 +1,7 @@
 //! Secret key
 use std::convert::TryInto;
 
-use ergo_lib::sigma_protocol::private_input::DlogProverInput;
+use ergo_lib::ergotree_ir::sigma_protocol::private_input::DlogProverInput;
 use ergo_lib::wallet;
 use wasm_bindgen::prelude::*;
 
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index 6ad1499ee..a9e783ccf 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -15,15 +15,17 @@ crate-type = ["cdylib", "rlib"]
 
 [dependencies]
 sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
+sigma-util = { version = "^0.1.0", path = "../sigma-util" }
+ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir" }
+ergoscript-compiler = { version = "^0.1.0", path = "../ergoscript-compiler" }
 indexmap = "1.3.2"
 base16 = "0.2.1"
 k256 = {version = "0.4.2", features = ["rand", "zeroize"]}
-blake2 = "0.9"
 elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 serde = { version = "1.0", features = ["derive"], optional = true }
 serde_json = "1.0"
 thiserror = "1"
-bs58 = "0.3.1"
+# bs58 = "0.4.0"
 num-bigint = "0.3.0"
 rand = "0.7"
 lazy_static = "1.4"
@@ -37,12 +39,13 @@ json = ["serde"]
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.10"
-proptest-derive = "0.2"
+proptest-derive = "0.3.0"
 rand = "0.7.3"
+ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir", features = ["arbitrary"] }
 
 [dev-dependencies.proptest]
 # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
-version = "0.10"
+version = "1.0.0"
 # The default feature set includes things like process forking which are not
 # supported in Web Assembly.
 default-features = false
diff --git a/ergo-lib/src/ast.rs b/ergo-lib/src/ast.rs
deleted file mode 100644
index d30021965..000000000
--- a/ergo-lib/src/ast.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! AST for ErgoTree
-
-pub(crate) mod and;
-pub(crate) mod apply;
-pub(crate) mod bin_op;
-pub(crate) mod block;
-pub(crate) mod bool_to_sigma;
-pub(crate) mod calc_blake2b256;
-pub(crate) mod coll_by_index;
-pub(crate) mod coll_filter;
-pub(crate) mod coll_fold;
-pub(crate) mod coll_map;
-pub(crate) mod coll_size;
-pub(crate) mod collection;
-pub(crate) mod expr;
-pub(crate) mod extract_amount;
-pub(crate) mod extract_reg_as;
-pub(crate) mod extract_script_bytes;
-pub(crate) mod func_value;
-pub(crate) mod global_vars;
-pub(crate) mod if_op;
-pub(crate) mod logical_not;
-pub(crate) mod method_call;
-pub(crate) mod option_get;
-pub(crate) mod or;
-pub(crate) mod property_call;
-pub(crate) mod select_field;
-pub(crate) mod upcast;
-pub(crate) mod val_def;
-pub(crate) mod val_use;
-
-pub mod constant;
-pub mod value;
diff --git a/ergo-lib/src/chain.rs b/ergo-lib/src/chain.rs
index 55aa38f15..caa7614b9 100644
--- a/ergo-lib/src/chain.rs
+++ b/ergo-lib/src/chain.rs
@@ -6,11 +6,9 @@ mod json;
 mod base16_bytes;
 mod digest32;
 
-pub use base16_bytes::Base16DecodedBytes;
-pub use base16_bytes::Base16EncodedBytes;
+pub use base16_bytes::*;
 pub use digest32::*;
 
-pub mod address;
 pub mod contract;
 pub mod ergo_box;
 pub mod ergo_state_context;
diff --git a/ergo-lib/src/chain/base16_bytes.rs b/ergo-lib/src/chain/base16_bytes.rs
index ecf8e73f6..9f23e0199 100644
--- a/ergo-lib/src/chain/base16_bytes.rs
+++ b/ergo-lib/src/chain/base16_bytes.rs
@@ -1,5 +1,8 @@
 //! Transitioning type for Base16 encoded bytes in JSON serialization
 
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
@@ -48,3 +51,35 @@ impl From<Base16DecodedBytes> for Vec<u8> {
         b.0
     }
 }
+
+impl From<Constant> for Base16EncodedBytes {
+    fn from(v: Constant) -> Base16EncodedBytes {
+        Base16EncodedBytes::new(&v.sigma_serialize_bytes())
+    }
+}
+
+impl TryFrom<Base16DecodedBytes> for Constant {
+    type Error = SerializationError;
+
+    fn try_from(value: Base16DecodedBytes) -> Result<Self, Self::Error> {
+        Constant::sigma_parse_bytes(value.0)
+    }
+}
+
+/// 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())
+    }
+}
+
+impl Base16Str for Constant {
+    fn base16_str(&self) -> String {
+        base16::encode_lower(&self.sigma_serialize_bytes())
+    }
+}
diff --git a/ergo-lib/src/chain/contract.rs b/ergo-lib/src/chain/contract.rs
index 4df9ff9fd..55dd4cfcf 100644
--- a/ergo-lib/src/chain/contract.rs
+++ b/ergo-lib/src/chain/contract.rs
@@ -1,8 +1,11 @@
 //! Ergo contract
 
-use super::address::Address;
-use crate::ergo_tree::ErgoTree;
-use crate::serialization::SerializationError;
+use ergoscript_compiler::compiler::compile;
+use ergoscript_compiler::compiler::CompileError;
+use ergoscript_compiler::script_env::ScriptEnv;
+use ergotree_ir::address::Address;
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::serialization::SerializationError;
 
 /// High-level wrapper for ErgoTree
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -25,4 +28,21 @@ impl Contract {
     pub fn ergo_tree(&self) -> ErgoTree {
         self.ergo_tree.clone()
     }
+
+    /// Compiles a contract from ErgoScript source code
+    pub fn compile(source: &str, env: ScriptEnv) -> Result<Contract, CompileError> {
+        let ergo_tree = compile(source, env)?;
+        Ok(Contract { ergo_tree })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn compile() {
+        let contract = Contract::compile("HEIGHT", ScriptEnv::new()).unwrap();
+        dbg!(&contract);
+    }
 }
diff --git a/ergo-lib/src/chain/digest32.rs b/ergo-lib/src/chain/digest32.rs
index 9321bcaf7..2ba90a884 100644
--- a/ergo-lib/src/chain/digest32.rs
+++ b/ergo-lib/src/chain/digest32.rs
@@ -1,10 +1,8 @@
-use crate::util::AsVecI8;
-use crate::{
-    chain::{Base16DecodedBytes, Base16EncodedBytes},
-    serialization::{sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable},
-};
-use blake2::digest::{Update, VariableOutput};
-use blake2::VarBlake2b;
+use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::util::AsVecI8;
 #[cfg(test)]
 use proptest_derive::Arbitrary;
 #[cfg(feature = "json")]
@@ -27,7 +25,7 @@ pub struct Digest32(pub Box<[u8; Digest32::SIZE]>);
 
 impl Digest32 {
     /// Digest size 32 bytes
-    pub const SIZE: usize = 32;
+    pub const SIZE: usize = sigma_util::DIGEST32_SIZE;
 
     /// All zeros
     pub fn zero() -> Digest32 {
@@ -37,12 +35,7 @@ impl Digest32 {
 
 /// Blake2b256 hash (256 bit)
 pub fn blake2b256_hash(bytes: &[u8]) -> Digest32 {
-    // unwrap is safe 32 bytes is a valid hash size (<= 512 && 32 % 8 == 0)
-    let mut hasher = VarBlake2b::new(Digest32::SIZE).unwrap();
-    hasher.update(bytes);
-    let hash = hasher.finalize_boxed();
-    // unwrap is safe due to hash size is expected to be Digest32::SIZE
-    Digest32(hash.try_into().unwrap())
+    Digest32(sigma_util::hash::blake2b256_hash(bytes))
 }
 
 impl From<[u8; Digest32::SIZE]> for Digest32 {
@@ -63,6 +56,12 @@ impl From<Digest32> for Vec<i8> {
     }
 }
 
+impl From<Digest32> for [u8; Digest32::SIZE] {
+    fn from(v: Digest32) -> Self {
+        *v.0
+    }
+}
+
 impl TryFrom<Base16DecodedBytes> for Digest32 {
     type Error = Digest32Error;
     fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index 984f17ce0..e13719cd9 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -7,7 +7,15 @@ mod register;
 
 pub use box_id::*;
 pub use box_value::*;
+use ergotree_ir::ergo_tree::ErgoTree;
+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::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 pub use register::*;
+use sigma_util::DIGEST32_SIZE;
 
 #[cfg(feature = "json")]
 use super::json;
@@ -18,26 +26,17 @@ use super::{
     transaction::TxId,
 };
 
-use crate::ast::constant::Constant;
-use crate::{
-    ergo_tree::ErgoTree,
-    serialization::{
-        ergo_box::{parse_box_with_indexed_digests, serialize_box_with_indexed_digests},
-        sigma_byte_reader::SigmaByteRead,
-        sigma_byte_writer::SigmaByteWrite,
-        SerializationError, SigmaSerializable,
-    },
-};
 use indexmap::IndexSet;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
-#[cfg(feature = "json")]
 use std::convert::TryFrom;
 use std::io;
 #[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),
@@ -163,6 +162,42 @@ impl ErgoBox {
     }
 }
 
+impl IrErgoBox for ErgoBox {
+    fn id(&self) -> &[u8; DIGEST32_SIZE] {
+        &self.box_id.0 .0
+    }
+
+    fn value(&self) -> i64 {
+        self.value.as_i64()
+    }
+
+    fn tokens(&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) -> Vec<u8> {
+        self.sigma_serialize_bytes()
+    }
+}
+
 /// Assets that ErgoBox holds
 pub trait ErgoBoxAssets {
     /// Box value
@@ -384,13 +419,110 @@ impl From<ErgoBox> for ErgoBoxCandidate {
     }
 }
 
+/// 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,
+) -> Result<(), io::Error> {
+    // 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) => 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(|()| w.put_u64(t.amount.into()))
+    })?;
+
+    let regs_num = additional_registers.len();
+    w.put_u8(regs_num as u8)?;
+
+    additional_registers
+        .get_ordered_values()
+        .iter()
+        .try_for_each(|c| c.sigma_serialize(w))?;
+
+    Ok(())
+}
+
+/// 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, SerializationError> {
+    // 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(SerializationError::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 regs_num = r.get_u8()?;
+    let mut additional_regs = Vec::with_capacity(regs_num as usize);
+    for _ in 0..regs_num {
+        let v = Constant::sigma_parse(r)?;
+        additional_regs.push(v);
+    }
+    let additional_registers = NonMandatoryRegisters::from_ordered_values(additional_regs)?;
+    Ok(ErgoBoxCandidate {
+        value,
+        ergo_tree,
+        tokens,
+        additional_registers,
+        creation_height,
+    })
+}
+
 #[cfg(test)]
 mod tests {
 
     use super::box_value::tests::ArbBoxValueRange;
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val;
+    use ergotree_ir::mir::constant::TryExtractInto;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
     impl Arbitrary for ErgoBoxCandidate {
@@ -464,6 +596,18 @@ mod tests {
         );
     }
 
+    #[test]
+    fn get_register_mandatory() {
+        let b = force_any_val::<ErgoBox>();
+        assert_eq!(
+            b.value.as_i64(),
+            b.get_register(RegisterId::R0)
+                .unwrap()
+                .try_extract_into::<i64>()
+                .unwrap()
+        );
+    }
+
     proptest! {
 
         #[test]
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index c916b29b4..5a81d845c 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -3,14 +3,14 @@
 use std::collections::HashMap;
 use std::convert::{TryFrom, TryInto};
 
-use crate::ast::constant::Constant;
 use crate::chain::token::Token;
-use crate::ergo_tree::ErgoTree;
-use crate::serialization::SigmaSerializable;
 
 use super::box_value::BoxValue;
 use super::register::{NonMandatoryRegisterId, NonMandatoryRegisters, NonMandatoryRegistersError};
 use super::ErgoBoxCandidate;
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::serialization::SigmaSerializable;
 use thiserror::Error;
 
 /// ErgoBoxCandidate builder errors
@@ -243,6 +243,7 @@ mod tests {
     use NonMandatoryRegisterId::*;
 
     use crate::chain::token::TokenId;
+    use crate::chain::Base16Str;
     use crate::test_util::force_any_val;
 
     use super::*;
diff --git a/ergo-lib/src/chain/ergo_box/box_id.rs b/ergo-lib/src/chain/ergo_box/box_id.rs
index 7737bb8c8..458361da4 100644
--- a/ergo-lib/src/chain/ergo_box/box_id.rs
+++ b/ergo-lib/src/chain/ergo_box/box_id.rs
@@ -1,11 +1,12 @@
 //! Box id type
 use std::io;
 
+use ergotree_ir::ir_ergo_box::IrBoxId;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
 use super::super::digest32::Digest32;
-use crate::serialization::{
+use ergotree_ir::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
 };
@@ -41,6 +42,12 @@ impl Into<String> for BoxId {
     }
 }
 
+impl From<&IrBoxId> for BoxId {
+    fn from(irb: &IrBoxId) -> Self {
+        BoxId(irb.0.into())
+    }
+}
+
 impl SigmaSerializable for BoxId {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.0.sigma_serialize(w)?;
@@ -54,7 +61,7 @@ impl SigmaSerializable for BoxId {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
diff --git a/ergo-lib/src/chain/ergo_box/box_value.rs b/ergo-lib/src/chain/ergo_box/box_value.rs
index 849365368..3bd1728bc 100644
--- a/ergo-lib/src/chain/ergo_box/box_value.rs
+++ b/ergo-lib/src/chain/ergo_box/box_value.rs
@@ -1,7 +1,8 @@
 //! Box value newtype
 
 use crate::chain::token::TokenAmountError;
-use crate::serialization::{
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
 #[cfg(feature = "json")]
@@ -142,6 +143,12 @@ impl From<BoxValue> for i64 {
     }
 }
 
+impl From<BoxValue> for Constant {
+    fn from(v: BoxValue) -> Self {
+        v.as_i64().into()
+    }
+}
+
 impl SigmaSerializable for BoxValue {
     fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.put_u64(self.0 as u64)
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergo-lib/src/chain/ergo_box/register.rs
index ca47490b0..9041a59ac 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergo-lib/src/chain/ergo_box/register.rs
@@ -2,10 +2,9 @@
 
 #[cfg(feature = "json")]
 use crate::chain::json::ergo_box::ConstantHolder;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SigmaSerializable;
-use crate::{ast::constant::Constant, serialization::SerializationError};
+use crate::chain::Base16EncodedBytes;
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::serialization::SerializationError;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::convert::TryInto;
@@ -49,23 +48,6 @@ impl TryFrom<i8> for RegisterId {
     }
 }
 
-impl SigmaSerializable for RegisterId {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        let byte = match self {
-            RegisterId::MandatoryRegisterId(id) => *id as i8,
-            RegisterId::NonMandatoryRegisterId(id) => *id as i8,
-        };
-        w.put_i8(byte)
-    }
-
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let reg_id = r.get_i8()?;
-        RegisterId::try_from(reg_id).map_err(|_| {
-            SerializationError::ValueOutOfBounds(format!("Register id out of bounds: {}", reg_id))
-        })
-    }
-}
-
 /// newtype for additional registers R4 - R9
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
@@ -169,7 +151,7 @@ pub struct NonMandatoryRegisterIdParsingError();
 #[cfg_attr(
     feature = "json",
     serde(
-        into = "HashMap<NonMandatoryRegisterId, Constant>",
+        into = "HashMap<NonMandatoryRegisterId, Base16EncodedBytes>",
         try_from = "HashMap<NonMandatoryRegisterId, crate::chain::json::ergo_box::ConstantHolder>"
     )
 )]
@@ -235,6 +217,16 @@ pub enum NonMandatoryRegistersError {
     NonDenselyPacked(u8),
 }
 
+impl Into<HashMap<NonMandatoryRegisterId, Base16EncodedBytes>> for NonMandatoryRegisters {
+    fn into(self) -> HashMap<NonMandatoryRegisterId, Base16EncodedBytes> {
+        self.0
+            .into_iter()
+            .enumerate()
+            .map(|(i, c)| (NonMandatoryRegisterId::get_by_zero_index(i), c.into()))
+            .collect()
+    }
+}
+
 impl Into<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegisters {
     fn into(self) -> HashMap<NonMandatoryRegisterId, Constant> {
         self.0
@@ -347,6 +339,11 @@ mod tests {
                 Ok(())
             })?;
         }
+
+        #[test]
+        fn reg_id_from_byte(reg_id_byte in 0i8..NonMandatoryRegisterId::END_INDEX as i8) {
+            assert!(RegisterId::try_from(reg_id_byte).is_ok());
+        }
     }
 
     #[test]
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index 4285c5f29..6f22f1c33 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -1,6 +1,12 @@
 //! JSON serialization
 
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::sigma_protocol::prover::ContextExtension;
+use ergotree_ir::sigma_protocol::prover::ProofBytes;
+use indexmap::IndexMap;
 use serde::Serializer;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
 
 pub fn serialize_bytes<S, T>(bytes: T, serializer: S) -> Result<S::Ok, S::Error>
 where
@@ -13,8 +19,8 @@ where
 pub mod ergo_tree {
 
     use super::*;
-    use crate::ergo_tree::ErgoTree;
-    use crate::serialization::SigmaSerializable;
+    use ergotree_ir::ergo_tree::ErgoTree;
+    use ergotree_ir::serialization::SigmaSerializable;
     use serde::{Deserialize, Deserializer, Serializer};
 
     pub fn serialize<S>(ergo_tree: &ErgoTree, serializer: S) -> Result<S::Ok, S::Error>
@@ -40,6 +46,10 @@ pub mod ergo_tree {
 
 pub mod ergo_box {
     use core::fmt;
+    use ergotree_ir::ergo_tree::ErgoTree;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::serialization::SerializationError;
+    use ergotree_ir::serialization::SigmaSerializable;
     use serde::de::{self, MapAccess, Visitor};
     use serde::Deserializer;
     use std::convert::TryFrom;
@@ -49,19 +59,14 @@ pub mod ergo_box {
     extern crate derive_more;
     use derive_more::From;
 
-    use crate::ast::constant::Constant;
     use crate::chain::Base16DecodedBytes;
-    use crate::serialization::SerializationError;
-    use crate::serialization::SigmaSerializable;
-    use crate::{
-        chain::{
-            ergo_box::{BoxId, BoxValue, NonMandatoryRegisters},
-            token::Token,
-            transaction::TxId,
-        },
-        ergo_tree::ErgoTree,
+    use crate::chain::{
+        ergo_box::{BoxId, BoxValue, NonMandatoryRegisters},
+        token::Token,
+        transaction::TxId,
     };
     use serde::Deserialize;
+    use thiserror::Error;
 
     #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
     pub struct ErgoBoxFromJson {
@@ -99,17 +104,19 @@ pub mod ergo_box {
 
     impl From<ConstantHolder> for Constant {
         fn from(ch: ConstantHolder) -> Self {
-            ch.0.raw_value
+            ch.0.raw_value.0
         }
     }
 
     #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
     struct RichConstant {
         #[serde(rename = "rawValue", alias = "serializedValue")]
-        raw_value: Constant,
+        raw_value: ConstantWrapper,
     }
 
-    use thiserror::Error;
+    #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
+    #[serde(try_from = "Base16DecodedBytes")]
+    struct ConstantWrapper(Constant);
 
     #[derive(Error, PartialEq, Eq, Debug, Clone, From)]
     pub enum ConstantParsingError {
@@ -119,12 +126,23 @@ pub mod ergo_box {
         DeserializationError(SerializationError),
     }
 
+    impl TryFrom<Base16DecodedBytes> for ConstantWrapper {
+        type Error = ConstantParsingError;
+
+        fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
+            let c = Constant::sigma_parse_bytes(bytes.into())?;
+            Ok(ConstantWrapper(c))
+        }
+    }
+
     impl FromStr for RichConstant {
         type Err = ConstantParsingError;
         fn from_str(s: &str) -> Result<Self, Self::Err> {
             let bytes = Base16DecodedBytes::try_from(s)?;
             let c = Constant::sigma_parse_bytes(bytes.into())?;
-            Ok(RichConstant { raw_value: c })
+            Ok(RichConstant {
+                raw_value: ConstantWrapper(c),
+            })
         }
     }
 
@@ -215,10 +233,34 @@ pub mod transaction {
     }
 }
 
+#[cfg_attr(
+    feature = "json",
+    derive(Serialize, Deserialize),
+    serde(into = "HashMap<String, String>", try_from = "HashMap<String, String>"),
+    serde(remote = "ContextExtension")
+)]
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub(crate) struct ContextExtensionSerde {
+    values: IndexMap<u8, Constant>,
+}
+
+#[cfg_attr(
+    feature = "json",
+    derive(Serialize, Deserialize),
+    serde(into = "String", try_from = "String"),
+    serde(remote = "ProofBytes")
+)]
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+pub enum ProofBytesSerde {
+    /// Empty proof
+    Empty,
+    /// Non-empty proof
+    Some(Vec<u8>),
+}
+
 #[cfg(test)]
 mod tests {
     use crate::chain::transaction::unsigned::UnsignedTransaction;
-    use crate::sigma_protocol::prover::ContextExtension;
     use std::convert::TryInto;
 
     use super::super::ergo_box::*;
@@ -300,7 +342,8 @@ mod tests {
 
     #[test]
     fn parse_empty_context_extension() {
-        let c: ContextExtension = serde_json::from_str("{}").unwrap();
+        let mut de = serde_json::Deserializer::from_str("{}");
+        let c: ContextExtension = ContextExtensionSerde::deserialize(&mut de).unwrap();
         assert_eq!(c, ContextExtension::empty());
     }
 
@@ -309,7 +352,8 @@ mod tests {
         let json = r#"
         {"1" :"05b0b5cad8e6dbaef44a", "3":"048ce5d4e505"}
         "#;
-        let c: ContextExtension = serde_json::from_str(json).unwrap();
+        let mut de = serde_json::Deserializer::from_str(json);
+        let c: ContextExtension = ContextExtensionSerde::deserialize(&mut de).unwrap();
         assert_eq!(c.values.len(), 2);
         assert!(c.values.get(&1u8).is_some());
         assert!(c.values.get(&3u8).is_some());
diff --git a/ergo-lib/src/chain/token.rs b/ergo-lib/src/chain/token.rs
index 9ea10de6f..6f33bbd8f 100644
--- a/ergo-lib/src/chain/token.rs
+++ b/ergo-lib/src/chain/token.rs
@@ -1,6 +1,6 @@
 //! Token related types
 
-use crate::serialization::{
+use ergotree_ir::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
 };
@@ -152,7 +152,7 @@ impl From<(TokenId, TokenAmount)> for Token {
 pub mod tests {
     use super::*;
     use crate::chain::Base16DecodedBytes;
-    use crate::serialization::sigma_serialize_roundtrip;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     pub enum ArbTokenIdParam {
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 79a74fbca..1f91ecc26 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -1,10 +1,14 @@
 //! Ergo transaction
 
 mod data_input;
-mod input;
+pub mod input;
 pub mod unsigned;
 
 pub use data_input::*;
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 pub use input::*;
 
 #[cfg(feature = "json")]
@@ -15,10 +19,6 @@ use super::{
     ergo_box::ErgoBoxCandidate,
     token::TokenId,
 };
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
-    SigmaSerializable,
-};
 use indexmap::IndexSet;
 #[cfg(test)]
 use proptest_derive::Arbitrary;
@@ -271,8 +271,8 @@ impl TryFrom<json::transaction::TransactionJson> for Transaction {
 pub mod tests {
 
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
 
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
     use proptest::{arbitrary::Arbitrary, collection::vec};
 
diff --git a/ergo-lib/src/chain/transaction/data_input.rs b/ergo-lib/src/chain/transaction/data_input.rs
index f0bcc48f8..f2a76a0fb 100644
--- a/ergo-lib/src/chain/transaction/data_input.rs
+++ b/ergo-lib/src/chain/transaction/data_input.rs
@@ -3,10 +3,10 @@
 use std::io;
 
 use crate::chain::ergo_box::BoxId;
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
-    SigmaSerializable,
-};
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 #[cfg(test)]
 use proptest::prelude::*;
 #[cfg(test)]
@@ -43,8 +43,9 @@ impl SigmaSerializable for DataInput {
 
 #[cfg(test)]
 mod tests {
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
 
     proptest! {
 
diff --git a/ergo-lib/src/chain/transaction/input.rs b/ergo-lib/src/chain/transaction/input.rs
index c198d22f3..c8a944c7b 100644
--- a/ergo-lib/src/chain/transaction/input.rs
+++ b/ergo-lib/src/chain/transaction/input.rs
@@ -1,17 +1,21 @@
 //! Transaction input
+
+pub mod prover_result;
+
 use std::io;
 
 use crate::chain::ergo_box::{BoxId, ErgoBoxId};
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
-    SigmaSerializable,
-};
-use crate::sigma_protocol::prover::ContextExtension;
-use crate::sigma_protocol::prover::ProofBytes;
-use crate::sigma_protocol::prover::ProverResult;
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::prover::ContextExtension;
+use ergotree_ir::sigma_protocol::prover::ProofBytes;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
+use self::prover_result::ProverResult;
+
 /// Unsigned (without proofs) transaction input
 #[derive(PartialEq, Debug, Clone)]
 #[cfg_attr(test, derive(proptest_derive::Arbitrary))]
@@ -21,16 +25,24 @@ pub struct UnsignedInput {
     #[cfg_attr(feature = "json", serde(rename = "boxId"))]
     pub box_id: BoxId,
     /// user-defined variables to be put into context
-    #[cfg_attr(feature = "json", serde(rename = "extension"))]
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "extension",),
+        serde(with = "crate::chain::json::ContextExtensionSerde")
+    )]
     pub extension: ContextExtension,
 }
 
+impl UnsignedInput {
+    /// Create new with empty ContextExtension
+    pub fn new(box_id: BoxId, extension: ContextExtension) -> Self {
+        UnsignedInput { box_id, extension }
+    }
+}
+
 impl<T: ErgoBoxId> From<T> for UnsignedInput {
     fn from(b: T) -> Self {
-        UnsignedInput {
-            box_id: b.box_id(),
-            extension: ContextExtension::empty(),
-        }
+        UnsignedInput::new(b.box_id(), ContextExtension::empty())
     }
 }
 
@@ -43,11 +55,19 @@ pub struct Input {
     #[cfg_attr(feature = "json", serde(rename = "boxId"))]
     pub box_id: BoxId,
     /// proof of spending correctness
-    #[cfg_attr(feature = "json", serde(rename = "spendingProof"))]
+    #[cfg_attr(feature = "json", serde(rename = "spendingProof",))]
     pub spending_proof: ProverResult,
 }
 
 impl Input {
+    /// Create new
+    pub fn new(box_id: BoxId, spending_proof: ProverResult) -> Self {
+        Self {
+            box_id,
+            spending_proof,
+        }
+    }
+
     /// input with an empty proof
     pub fn input_to_sign(&self) -> Input {
         Input {
@@ -79,7 +99,7 @@ impl SigmaSerializable for Input {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
diff --git a/ergo-lib/src/chain/transaction/input/prover_result.rs b/ergo-lib/src/chain/transaction/input/prover_result.rs
new file mode 100644
index 000000000..fbeac82fc
--- /dev/null
+++ b/ergo-lib/src/chain/transaction/input/prover_result.rs
@@ -0,0 +1,80 @@
+//! Proof generated by the prover in Sigma protocol
+
+use std::io;
+
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::prover::ContextExtension;
+use ergotree_ir::sigma_protocol::prover::ProofBytes;
+#[cfg(feature = "json")]
+use serde::{Deserialize, Serialize};
+
+/// Wrapped IR [`ProverResult`] for Serde
+#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[derive(PartialEq, Debug, Clone)]
+pub struct ProverResult {
+    /// proof that satisfies final sigma proposition
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "proofBytes"),
+        serde(with = "crate::chain::json::ProofBytesSerde")
+    )]
+    pub proof: ProofBytes,
+    /// user-defined variables to be put into context
+    #[cfg_attr(
+        feature = "json",
+        serde(rename = "extension"),
+        serde(with = "crate::chain::json::ContextExtensionSerde")
+    )]
+    pub extension: ContextExtension,
+}
+
+impl From<ergotree_ir::sigma_protocol::prover::ProverResult> for ProverResult {
+    fn from(ir: ergotree_ir::sigma_protocol::prover::ProverResult) -> Self {
+        ProverResult {
+            proof: ir.proof,
+            extension: ir.extension,
+        }
+    }
+}
+
+impl SigmaSerializable for ProverResult {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
+        self.proof.sigma_serialize(w)?;
+        self.extension.sigma_serialize(w)?;
+        Ok(())
+    }
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let proof = ProofBytes::sigma_parse(r)?;
+        let extension = ContextExtension::sigma_parse(r)?;
+        Ok(ProverResult { proof, extension })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    impl Arbitrary for ProverResult {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<ProofBytes>(), any::<ContextExtension>())
+                .prop_map(|(proof, extension)| Self { proof, extension })
+                .boxed()
+        }
+    }
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<ProverResult>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index a7a84ad39..408533536 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -3,6 +3,7 @@
 use super::input::{Input, UnsignedInput};
 #[cfg(feature = "json")]
 use super::json;
+use super::prover_result::ProverResult;
 use super::DataInput;
 use super::{
     super::{digest32::blake2b256_hash, ergo_box::ErgoBoxCandidate},
@@ -12,10 +13,10 @@ use super::{
 use crate::chain::transaction::ErgoBox;
 #[cfg(feature = "json")]
 use crate::chain::transaction::TransactionFromJsonError;
-use crate::serialization::SigmaSerializable;
-use crate::sigma_protocol::prover::{ProofBytes, ProverResult};
 #[cfg(feature = "json")]
 use core::convert::TryFrom;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::prover::ProofBytes;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
@@ -71,17 +72,19 @@ impl UnsignedTransaction {
         self.tx_id.clone()
     }
 
-    /// message to be signed by the [`crate::sigma_protocol::prover::Prover`] (serialized tx)
+    /// message to be signed by the [`ergotree_ir::sigma_protocol::prover::Prover`] (serialized tx)
     pub fn bytes_to_sign(&self) -> Vec<u8> {
         let empty_proofs_input = self
             .inputs
             .iter()
-            .map(|ui| Input {
-                box_id: ui.box_id.clone(),
-                spending_proof: ProverResult {
-                    proof: ProofBytes::Empty,
-                    extension: ui.extension.clone(),
-                },
+            .map(|ui| {
+                Input::new(
+                    ui.box_id.clone(),
+                    ProverResult {
+                        proof: ProofBytes::Empty,
+                        extension: ui.extension.clone(),
+                    },
+                )
             })
             .collect();
         let tx = Transaction::new(
diff --git a/ergo-lib/src/eval/context.rs b/ergo-lib/src/eval/context.rs
deleted file mode 100644
index 0343aaf4b..000000000
--- a/ergo-lib/src/eval/context.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use crate::chain::ergo_box::ErgoBox;
-use crate::chain::ergo_state_context::ErgoStateContext;
-use crate::wallet::signing::TransactionContext;
-use thiserror::Error;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Context {
-    pub height: i32,
-    pub self_box: ErgoBox,
-    pub outputs: Vec<ErgoBox>,
-    pub data_inputs: Vec<ErgoBox>,
-}
-
-impl Context {
-    /// Dummy instance intended for tests where actual values are not used
-    #[cfg(test)]
-    pub fn dummy() -> Self {
-        use crate::test_util::force_any_val;
-        Context {
-            height: 0,
-            self_box: force_any_val::<ErgoBox>(),
-            outputs: vec![force_any_val::<ErgoBox>()],
-            data_inputs: vec![],
-        }
-    }
-
-    /// Create new instance:
-    /// `self_index` - index of the SELF box in the tx_ctx.boxes_to_spend
-    pub fn new(
-        state_ctx: &ErgoStateContext,
-        tx_ctx: &TransactionContext,
-        self_index: usize,
-    ) -> Result<Self, ContextError> {
-        let height = state_ctx.pre_header.height;
-        let self_box = tx_ctx
-            .boxes_to_spend
-            .get(self_index)
-            .cloned()
-            .ok_or(ContextError::SelfIndexOutOfBounds)?;
-        let outputs: Vec<ErgoBox> = tx_ctx
-            .spending_tx
-            .output_candidates
-            .iter()
-            .enumerate()
-            .map(|(idx, b)| ErgoBox::from_box_candidate(b, tx_ctx.spending_tx.id(), idx as u16))
-            .collect();
-        let data_inputs: Vec<ErgoBox> = tx_ctx.data_boxes.clone();
-        Ok(Context {
-            height,
-            self_box,
-            outputs,
-            data_inputs,
-        })
-    }
-}
-
-#[derive(Error, PartialEq, Eq, Debug, Clone)]
-pub enum ContextError {
-    /// self_index is out of bounds for TransactionContext::boxes_to_spend
-    #[error("self_index is out of bounds for TransactionContext::boxes_to_spend")]
-    SelfIndexOutOfBounds,
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use proptest::collection::vec;
-    use proptest::prelude::*;
-
-    impl Arbitrary for Context {
-        type Parameters = ();
-
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (
-                0..i32::MAX,
-                any::<ErgoBox>(),
-                vec(any::<ErgoBox>(), 1..3),
-                vec(any::<ErgoBox>(), 0..3),
-            )
-                .prop_map(|(height, self_box, outputs, data_inputs)| Self {
-                    height,
-                    self_box,
-                    outputs,
-                    data_inputs,
-                })
-                .boxed()
-        }
-
-        type Strategy = BoxedStrategy<Self>;
-    }
-}
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index dcb4aa767..8bb435140 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -14,18 +14,14 @@
 #![deny(broken_intra_doc_links)]
 
 mod big_integer;
-mod eval;
 
-pub mod ast;
 pub mod chain;
 pub mod constants;
-pub mod serialization;
-pub mod sigma_protocol;
-pub mod types;
-pub mod util;
 pub mod wallet;
 
-pub mod ergo_tree;
-
 #[cfg(test)]
 pub mod test_util;
+
+/// Re-exported types from dependencies
+pub extern crate ergoscript_compiler;
+pub extern crate ergotree_ir;
diff --git a/ergo-lib/src/serialization/ergo_box.rs b/ergo-lib/src/serialization/ergo_box.rs
deleted file mode 100644
index 6532fccd5..000000000
--- a/ergo-lib/src/serialization/ergo_box.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-//! ErgoBox and ErgoBoxCandidate serialization
-use crate::ast::constant::Constant;
-use crate::{
-    chain::{
-        ergo_box::{BoxValue, ErgoBoxCandidate, NonMandatoryRegisters},
-        token::{Token, TokenId},
-    },
-    ergo_tree::ErgoTree,
-    serialization::{sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable},
-};
-use indexmap::IndexSet;
-
-use super::sigma_byte_writer::SigmaByteWrite;
-use std::convert::{TryFrom, TryInto};
-use std::io;
-
-/// 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,
-) -> Result<(), io::Error> {
-    // 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) => 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(|()| w.put_u64(t.amount.into()))
-    })?;
-
-    let regs_num = additional_registers.len();
-    w.put_u8(regs_num as u8)?;
-
-    additional_registers
-        .get_ordered_values()
-        .iter()
-        .try_for_each(|c| c.sigma_serialize(w))?;
-
-    Ok(())
-}
-
-/// 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, SerializationError> {
-    // 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(SerializationError::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 regs_num = r.get_u8()?;
-    let mut additional_regs = Vec::with_capacity(regs_num as usize);
-    for _ in 0..regs_num {
-        let v = Constant::sigma_parse(r)?;
-        additional_regs.push(v);
-    }
-    let additional_registers = NonMandatoryRegisters::from_ordered_values(additional_regs)?;
-    Ok(ErgoBoxCandidate {
-        value,
-        ergo_tree,
-        tokens,
-        additional_registers,
-        creation_height,
-    })
-}
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index 439a5d51f..28373f3ac 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -5,16 +5,15 @@ pub mod secret_key;
 pub mod signing;
 pub mod tx_builder;
 
+use ergotree_ir::sigma_protocol::private_input::PrivateInput;
+use ergotree_ir::sigma_protocol::prover::Prover;
+use ergotree_ir::sigma_protocol::prover::TestProver;
 use secret_key::SecretKey;
 use signing::{sign_transaction, TxSigningError};
 use thiserror::Error;
 
 use crate::chain::ergo_state_context::ErgoStateContext;
 use crate::chain::transaction::Transaction;
-use crate::sigma_protocol::{
-    private_input::PrivateInput,
-    prover::{Prover, TestProver},
-};
 
 use self::signing::TransactionContext;
 
diff --git a/ergo-lib/src/wallet/secret_key.rs b/ergo-lib/src/wallet/secret_key.rs
index d153db7c5..94133964f 100644
--- a/ergo-lib/src/wallet/secret_key.rs
+++ b/ergo-lib/src/wallet/secret_key.rs
@@ -1,6 +1,7 @@
 //! Secret types
-use crate::chain::address::Address;
-use crate::sigma_protocol::private_input::{DlogProverInput, PrivateInput};
+use ergotree_ir::address::Address;
+use ergotree_ir::sigma_protocol::private_input::DlogProverInput;
+use ergotree_ir::sigma_protocol::private_input::PrivateInput;
 
 /// Types of secrets
 #[derive(PartialEq, Debug, Clone)]
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 4d10b5cd6..a73a98c20 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -1,20 +1,24 @@
 //! Transaction signing
 
+use std::collections::HashMap;
 use std::rc::Rc;
 
+use crate::chain::ergo_box::BoxId;
 use crate::chain::transaction::Input;
-use crate::eval::context::Context;
-use crate::eval::context::ContextError;
-use crate::eval::env::Env;
-use crate::{
-    chain::{
-        ergo_box::ErgoBox,
-        ergo_state_context::ErgoStateContext,
-        transaction::{unsigned::UnsignedTransaction, Transaction},
-    },
-    sigma_protocol::prover::{Prover, ProverError},
+use crate::chain::{
+    ergo_box::ErgoBox,
+    ergo_state_context::ErgoStateContext,
+    transaction::{unsigned::UnsignedTransaction, Transaction},
 };
 
+use ergotree_ir::eval::context::Context;
+use ergotree_ir::eval::env::Env;
+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::sigma_protocol::prover::Prover;
+use ergotree_ir::sigma_protocol::prover::ProverError;
 use thiserror::Error;
 
 /// Errors on transaction signing
@@ -27,8 +31,8 @@ pub enum TxSigningError {
     #[error("Input box not found (index {0})")]
     InputBoxNotFound(usize),
     /// Context creation error
-    #[error("Context error: {0:?}")]
-    ContextError(#[from] ContextError),
+    #[error("Context error: {0}")]
+    ContextError(String),
 }
 
 /// Transaction and an additional info required for signing
@@ -42,6 +46,74 @@ pub struct TransactionContext {
     pub data_boxes: Vec<ErgoBox>,
 }
 
+/// Holding all ErgoBox needed for interpreter [`ergotree_ir::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>) -> 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);
+        });
+        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>)
+    }
+}
+
+impl From<BoxId> for IrBoxId {
+    fn from(id: BoxId) -> Self {
+        IrBoxId::new(*id.0 .0)
+    }
+}
+
+/// `self_index` - index of the SELF box in the tx_ctx.boxes_to_spend
+pub fn make_context(
+    state_ctx: &ErgoStateContext,
+    tx_ctx: &TransactionContext,
+    self_index: usize,
+) -> Result<Context, TxSigningError> {
+    let height = state_ctx.pre_header.height;
+    let self_box = tx_ctx
+        .boxes_to_spend
+        .get(self_index)
+        .cloned()
+        .ok_or_else(|| TxSigningError::ContextError("self_index is out of bounds".to_string()))?;
+    let outputs: Vec<ErgoBox> = tx_ctx
+        .spending_tx
+        .output_candidates
+        .iter()
+        .enumerate()
+        .map(|(idx, b)| ErgoBox::from_box_candidate(b, tx_ctx.spending_tx.id(), idx as u16))
+        .collect();
+    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 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));
+    Ok(Context {
+        box_arena,
+        height,
+        self_box: self_box_ir,
+        outputs: outputs_ir,
+        data_inputs: data_inputs_ir,
+    })
+}
+
 /// Signs a transaction (generating proofs for inputs)
 pub fn sign_transaction(
     prover: &dyn Prover,
@@ -57,7 +129,7 @@ pub fn sign_transaction(
         .enumerate()
         .try_for_each(|(idx, input_box)| {
             if let Some(unsigned_input) = tx.inputs.get(idx) {
-                let ctx = Rc::new(Context::new(state_context, &tx_context, idx)?);
+                let ctx = Rc::new(make_context(state_context, &tx_context, idx)?);
                 prover
                     .prove(
                         &input_box.ergo_tree,
@@ -66,10 +138,7 @@ pub fn sign_transaction(
                         message_to_sign.as_slice(),
                     )
                     .map(|proof| {
-                        let input = Input {
-                            box_id: unsigned_input.box_id.clone(),
-                            spending_proof: proof,
-                        };
+                        let input = Input::new(unsigned_input.box_id.clone(), proof.into());
                         signed_inputs.push(input);
                     })
                     .map_err(|e| TxSigningError::ProverError(e, idx))
@@ -87,22 +156,22 @@ pub fn sign_transaction(
 #[cfg(test)]
 mod tests {
     use super::*;
+    use ergotree_ir::sigma_protocol::private_input::DlogProverInput;
+    use ergotree_ir::sigma_protocol::private_input::PrivateInput;
+    use ergotree_ir::sigma_protocol::prover::TestProver;
+    use ergotree_ir::sigma_protocol::verifier::TestVerifier;
+    use ergotree_ir::sigma_protocol::verifier::Verifier;
+    use ergotree_ir::sigma_protocol::verifier::VerifierError;
+    use ergotree_ir::test_util::force_any_val;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
-    use crate::ast::expr::Expr;
-    use crate::{
-        chain::{
-            ergo_box::{box_builder::ErgoBoxCandidateBuilder, BoxValue, NonMandatoryRegisters},
-            transaction::{TxId, UnsignedInput},
-        },
-        ergo_tree::ErgoTree,
-        sigma_protocol::{
-            private_input::{DlogProverInput, PrivateInput},
-            prover::TestProver,
-            verifier::{TestVerifier, Verifier, VerifierError},
-        },
+    use crate::chain::{
+        ergo_box::{box_builder::ErgoBoxCandidateBuilder, BoxValue, NonMandatoryRegisters},
+        transaction::{TxId, UnsignedInput},
     };
+    use ergotree_ir::ergo_tree::ErgoTree;
+    use ergotree_ir::mir::expr::Expr;
     use std::rc::Rc;
 
     fn verify_tx_proofs(
@@ -118,7 +187,7 @@ mod tests {
                 let res = verifier.verify(
                     &b.ergo_tree,
                     &Env::empty(),
-                    Rc::new(Context::dummy()),
+                    Rc::new(force_any_val::<Context>()),
                     &input.spending_proof.proof,
                     &message,
                 )?;
@@ -134,7 +203,7 @@ mod tests {
         fn test_tx_signing(secrets in vec(any::<DlogProverInput>(), 1..10)) {
             let boxes_to_spend: Vec<ErgoBox> = secrets.iter().map(|secret|{
                 let pk = secret.public_image();
-                let tree = ErgoTree::from(Rc::new(Expr::Const(pk.into())));
+                let tree = ErgoTree::from(Expr::Const(pk.into()));
                 ErgoBox::new(BoxValue::SAFE_USER_MIN,
                              tree,
                              vec![],
@@ -147,7 +216,7 @@ mod tests {
                 secrets: secrets.clone().into_iter().map(PrivateInput::DlogProverInput).collect(),
             };
             let inputs = boxes_to_spend.clone().into_iter().map(UnsignedInput::from).collect();
-            let ergo_tree = ErgoTree::from(Rc::new(Expr::Const(secrets.get(0).unwrap().public_image().into())));
+            let ergo_tree = ErgoTree::from(Expr::Const(secrets.get(0).unwrap().public_image().into()));
             let candidate = ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, ergo_tree, 0)
                 .build().unwrap();
             let output_candidates = vec![candidate];
@@ -158,6 +227,5 @@ mod tests {
             let signed_tx = res.unwrap();
             prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
         }
-
     }
 }
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index 2cafd8c3e..88abc81cb 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -2,9 +2,13 @@
 
 use std::collections::HashSet;
 
+use ergotree_ir::address::{Address, AddressEncoder, NetworkPrefix};
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol;
+use ergotree_ir::sigma_protocol::prover::ProofBytes;
 use thiserror::Error;
 
-use crate::chain::address::{Address, AddressEncoder, NetworkPrefix};
 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};
@@ -15,9 +19,6 @@ use crate::chain::{
     transaction::unsigned::UnsignedTransaction,
 };
 use crate::constants::MINERS_FEE_MAINNET_ADDRESS;
-use crate::serialization::{SerializationError, SigmaSerializable};
-use crate::sigma_protocol;
-use crate::sigma_protocol::prover::{ProofBytes, ProverResult};
 
 use super::box_selector::{BoxSelection, BoxSelectorError};
 
@@ -114,13 +115,13 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
                 // mock proof of the size of ProveDlog's proof (P2PK box spending)
                 // as it's the most often used proof
                 let proof = ProofBytes::Some(vec![0u8, sigma_protocol::SOUNDNESS_BYTES as u8]);
-                Input {
-                    box_id: ui.box_id.clone(),
-                    spending_proof: ProverResult {
+                Input::new(
+                    ui.box_id.clone(),
+                    crate::chain::transaction::input::prover_result::ProverResult {
                         proof,
                         extension: ui.extension.clone(),
                     },
-                }
+                )
             })
             .collect();
         let signed_tx_mock = Transaction::new(inputs, tx.data_inputs, tx.output_candidates);
@@ -282,6 +283,7 @@ mod tests {
 
     use std::convert::TryInto;
 
+    use ergotree_ir::ergo_tree::ErgoTree;
     use proptest::{collection::vec, prelude::*};
 
     use crate::chain::{
@@ -289,7 +291,6 @@ mod tests {
         token::{tests::ArbTokenIdParam, Token, TokenAmount, TokenId},
         transaction::TxId,
     };
-    use crate::ergo_tree::ErgoTree;
     use crate::test_util::{force_any_val, force_any_val_with};
     use crate::wallet::box_selector::{BoxSelector, SimpleBoxSelector};
 
diff --git a/ergoscript-compiler/Cargo.toml b/ergoscript-compiler/Cargo.toml
new file mode 100644
index 000000000..bd881d549
--- /dev/null
+++ b/ergoscript-compiler/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "ergoscript-compiler"
+version = "0.1.0"
+license = "CC0-1.0"
+authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
+edition = "2018"
+description = "Compiler for ErgoScript"
+repository = "https://github.com/ergoplatform/sigma-rust"
+exclude = [
+    "proptest-regressions/*"
+]
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir" }
+logos = "0.12"
+text-size = "1.1.0"
+derive_more = "0.99"
+num-derive = "0.3.3"
+num-traits = "0.2.14"
+rowan = "0.12.1"
+drop_bomb = "0.1.5"
+line-col = "0.2.1"
+
+[dev-dependencies]
+expect-test = "1.0.1"
diff --git a/ergoscript-compiler/src/ast.rs b/ergoscript-compiler/src/ast.rs
new file mode 100644
index 000000000..49d0c0d24
--- /dev/null
+++ b/ergoscript-compiler/src/ast.rs
@@ -0,0 +1,178 @@
+use super::syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
+use text_size::TextRange;
+
+// #[derive(Debug)]
+// pub struct Root(SyntaxNode);
+
+// impl Root {
+//     pub fn cast(node: SyntaxNode) -> Option<Self> {
+//         if node.kind() == SyntaxKind::Root {
+//             Some(Self(node))
+//         } else {
+//             None
+//         }
+//     }
+
+//     pub fn expr(&self) -> impl Iterator<Item = Stmt> {
+//         self.0.children().filter_map(Stmt::cast)
+//     }
+// }
+
+// #[derive(Debug)]
+// pub enum Stmt {
+//     VariableDef(VariableDef),
+//     Expr(Expr),
+// }
+
+// impl Stmt {
+//     pub fn cast(node: SyntaxNode) -> Option<Self> {
+//         let result = match node.kind() {
+//             SyntaxKind::VariableDef => Self::VariableDef(VariableDef(node)),
+//             _ => Self::Expr(Expr::cast(node)?),
+//         };
+
+//         Some(result)
+//     }
+// }
+
+pub struct Root(SyntaxNode);
+
+impl Root {
+    pub fn cast(node: SyntaxNode) -> Option<Self> {
+        if node.kind() == SyntaxKind::Root {
+            Some(Self(node))
+        } else {
+            None
+        }
+    }
+
+    pub fn children(&self) -> impl Iterator<Item = Expr> {
+        self.0.children().filter_map(Expr::cast)
+    }
+}
+
+#[derive(Debug)]
+pub struct Ident(SyntaxNode);
+
+impl Ident {
+    pub fn name(&self) -> Option<SyntaxToken> {
+        self.0
+            .children_with_tokens()
+            .filter_map(SyntaxElement::into_token)
+            .find(|token| token.kind() == SyntaxKind::Ident)
+    }
+
+    pub fn span(&self) -> TextRange {
+        self.0.text_range()
+    }
+
+    // pub fn value(&self) -> Option<Expr> {
+    //     self.0.children().find_map(Expr::cast)
+    // }
+}
+
+#[derive(Debug)]
+pub enum Expr {
+    Ident(Ident),
+    BinaryExpr(BinaryExpr),
+    // Literal(Literal),
+    // ParenExpr(ParenExpr),
+    // UnaryExpr(UnaryExpr),
+}
+
+impl Expr {
+    pub fn cast(node: SyntaxNode) -> Option<Self> {
+        let result = match node.kind() {
+            SyntaxKind::Ident => Self::Ident(Ident(node)),
+            SyntaxKind::InfixExpr => Self::BinaryExpr(BinaryExpr(node)),
+            // SyntaxKind::Literal => Self::Literal(Literal(node)),
+            // SyntaxKind::ParenExpr => Self::ParenExpr(ParenExpr(node)),
+            // SyntaxKind::PrefixExpr => Self::UnaryExpr(UnaryExpr(node)),
+            _ => return None,
+        };
+
+        Some(result)
+    }
+
+    // pub fn span(&self) -> TextRange {
+    //     match self {
+    //         Expr::Ident(node) => node.0.text_range(),
+    //         _ => todo!(),
+    //     }
+    // }
+}
+
+#[derive(Debug)]
+pub struct BinaryExpr(SyntaxNode);
+
+impl BinaryExpr {
+    pub fn lhs(&self) -> Option<Expr> {
+        self.0.children().find_map(Expr::cast)
+    }
+
+    pub fn rhs(&self) -> Option<Expr> {
+        self.0.children().filter_map(Expr::cast).nth(1)
+    }
+
+    pub fn op(&self) -> Option<SyntaxToken> {
+        self.0
+            .children_with_tokens()
+            .filter_map(SyntaxElement::into_token)
+            .find(|token| {
+                matches!(
+                    token.kind(),
+                    SyntaxKind::Plus
+                        | SyntaxKind::Minus
+                        | SyntaxKind::Star
+                        | SyntaxKind::Slash
+                        | SyntaxKind::And,
+                )
+            })
+    }
+
+    pub fn span(&self) -> TextRange {
+        self.0.text_range()
+    }
+}
+
+// #[derive(Debug)]
+// pub struct Literal(SyntaxNode);
+
+// impl Literal {
+//     pub fn cast(node: SyntaxNode) -> Option<Self> {
+//         if node.kind() == SyntaxKind::Literal {
+//             Some(Self(node))
+//         } else {
+//             None
+//         }
+//     }
+
+//     pub fn parse(&self) -> Option<u64> {
+//         self.0.first_token().unwrap().text().parse().ok()
+//     }
+// }
+
+// #[derive(Debug)]
+// pub struct ParenExpr(SyntaxNode);
+
+// impl ParenExpr {
+//     pub fn expr(&self) -> Option<Expr> {
+//         self.0.children().find_map(Expr::cast)
+//     }
+// }
+
+// #[derive(Debug)]
+// pub struct UnaryExpr(SyntaxNode);
+
+// impl UnaryExpr {
+//     pub fn expr(&self) -> Option<Expr> {
+//         self.0.children().find_map(Expr::cast)
+//     }
+
+//     pub fn op(&self) -> Option<SyntaxToken> {
+//         self.0
+//             .children_with_tokens()
+//             .filter_map(SyntaxElement::into_token)
+//             .find(|token| token.kind() == SyntaxKind::Minus)
+//     }
+// }
diff --git a/ergoscript-compiler/src/binder.rs b/ergoscript-compiler/src/binder.rs
new file mode 100644
index 000000000..fb7f957e4
--- /dev/null
+++ b/ergoscript-compiler/src/binder.rs
@@ -0,0 +1,61 @@
+use rowan::TextRange;
+
+use crate::error::pretty_error_desc;
+use crate::hir;
+use crate::hir::Expr;
+use crate::hir::ExprKind;
+use crate::hir::GlobalVars;
+use crate::script_env::ScriptEnv;
+
+#[derive(Debug, PartialEq)]
+pub struct BinderError {
+    msg: String,
+    span: TextRange,
+}
+
+impl BinderError {
+    pub fn new(msg: String, span: TextRange) -> Self {
+        Self { msg, span }
+    }
+
+    pub fn pretty_desc(&self, source: &str) -> String {
+        pretty_error_desc(&source, self.span, &self.msg)
+    }
+}
+
+pub struct Binder {
+    env: ScriptEnv,
+}
+
+impl Binder {
+    pub fn new(env: ScriptEnv) -> Self {
+        Binder { env }
+    }
+
+    pub fn bind(&self, expr: Expr) -> Result<Expr, BinderError> {
+        rewrite(expr, &self.env)
+    }
+}
+
+fn rewrite(expr: Expr, env: &ScriptEnv) -> Result<Expr, BinderError> {
+    hir::rewrite(expr, |e| {
+        Ok(match &e.kind {
+            ExprKind::Ident(ident) => match env.get(ident) {
+                Some(_) => todo!(),
+                None => match ident.as_ref() {
+                    "HEIGHT" => {
+                        let v = GlobalVars::Height;
+                        let tpe = v.tpe();
+                        Some(Expr {
+                            kind: v.into(),
+                            span: e.span,
+                            tpe: tpe.into(),
+                        })
+                    }
+                    _ => None,
+                },
+            },
+            _ => None,
+        })
+    })
+}
diff --git a/ergoscript-compiler/src/compiler.rs b/ergoscript-compiler/src/compiler.rs
new file mode 100644
index 000000000..a29952556
--- /dev/null
+++ b/ergoscript-compiler/src/compiler.rs
@@ -0,0 +1,123 @@
+//! ErgoScript compiler
+
+use super::binder::BinderError;
+use super::hir::HirLoweringError;
+use crate::ast;
+use crate::binder::Binder;
+use crate::hir;
+use crate::mir;
+use crate::parser::parse_error::ParseError;
+use crate::script_env::ScriptEnv;
+use crate::type_infer::assign_type;
+use crate::type_infer::TypeInferenceError;
+
+extern crate derive_more;
+use derive_more::From;
+use ergotree_ir::ergo_tree::ErgoTree;
+use mir::lower::MirLoweringError;
+use mir::type_check::TypeCheckError;
+
+/// Compilation errors
+#[derive(Debug, PartialEq, From)]
+pub enum CompileError {
+    /// Parser error
+    ParseError(Vec<ParseError>),
+    /// Error on AST to HIR lowering
+    HirLoweringError(HirLoweringError),
+    /// Error on binder pass
+    BinderError(BinderError),
+    /// Error on type inference pass
+    TypeInferenceError(TypeInferenceError),
+    /// Error on HIT to MIR lowering
+    MirLoweringError(MirLoweringError),
+    /// Error on type checking
+    TypeCheckError(TypeCheckError),
+}
+
+impl CompileError {
+    /// Pretty formatted error with CST/AST/IR, etc.
+    pub fn pretty_desc(&self, source: &str) -> String {
+        match self {
+            CompileError::ParseError(errors) => {
+                errors.iter().map(|e| e.pretty_desc(source)).collect()
+            }
+            CompileError::HirLoweringError(e) => e.pretty_desc(source),
+            CompileError::BinderError(e) => e.pretty_desc(source),
+            CompileError::TypeInferenceError(e) => e.pretty_desc(source),
+            CompileError::MirLoweringError(e) => e.pretty_desc(source),
+            CompileError::TypeCheckError(e) => e.pretty_desc(),
+        }
+    }
+}
+
+/// Compiles given source code to [`ErgoTree`], or returns an error
+pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
+    let parse = super::parser::parse(&source);
+    dbg!(parse.debug_tree());
+    if !parse.errors.is_empty() {
+        return Err(CompileError::ParseError(parse.errors));
+    }
+    let syntax = parse.syntax();
+    dbg!(&syntax);
+    let root = ast::Root::cast(syntax).unwrap();
+    let hir = hir::lower(root)?;
+    dbg!(&hir);
+    let binder = Binder::new(env);
+    let bind = binder.bind(hir)?;
+    let typed = assign_type(bind)?;
+    dbg!(typed.debug_tree());
+    let mir = mir::lower::lower(typed)?;
+    let res = mir::type_check::type_check(mir)?;
+    Ok(res.into())
+}
+
+#[cfg(test)]
+fn check(input: &str, expected_tree: expect_test::Expect) {
+    let res = compile(input, ScriptEnv::new());
+
+    let expected_out = res
+        .map(|tree| tree.debug_tree())
+        .unwrap_or_else(|e| e.pretty_desc(input));
+    expected_tree.assert_eq(&expected_out);
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use expect_test::expect;
+
+    #[test]
+    fn test_height() {
+        check(
+            "HEIGHT",
+            expect![[r#"
+            ErgoTree {
+                header: ErgoTreeHeader(
+                    16,
+                ),
+                tree: Ok(
+                    ParsedTree {
+                        constants: [],
+                        root: Ok(
+                            GlobalVars(
+                                Height,
+                            ),
+                        ),
+                    },
+                ),
+            }"#]],
+        );
+    }
+
+    #[test]
+    fn test_parser_error() {
+        check(
+            "HSB.HEIGHT",
+            expect![[r#"
+            error: expected ‘+’, ‘-’, ‘*’, ‘/’, ‘val’, number, identifier, ‘-’ or ‘(’, but found an unrecognized token
+            line: 1
+            HSB.HEIGHT
+              ^^"#]],
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/error.rs b/ergoscript-compiler/src/error.rs
new file mode 100644
index 000000000..fbf9ee2db
--- /dev/null
+++ b/ergoscript-compiler/src/error.rs
@@ -0,0 +1,25 @@
+use line_col::LineColLookup;
+use rowan::TextRange;
+
+pub fn pretty_error_desc(source: &str, span: TextRange, error_msg: &str) -> String {
+    dbg!(&span);
+    let line_col_lookup = LineColLookup::new(source);
+    let start_zero_based: usize = usize::from(span.start()) - 1;
+    dbg!(&start_zero_based);
+    let end_zero_based: usize = usize::from(span.end()) - 1;
+    let (line_start, col_start) = line_col_lookup.get(start_zero_based);
+    let (line_end, col_end) = line_col_lookup.get(end_zero_based);
+    if line_end != line_start {
+        return "Multiline error spans are not yet supported".to_string();
+    }
+    let source_line = source.lines().nth(line_start - 1).unwrap();
+    let highlight = format!("{0:^>span$}", "^", span = col_end - col_start + 1);
+    format!(
+        "{0}\nline: {1}\n{2}\n{3:>ident$}",
+        error_msg,
+        line_start,
+        source_line,
+        highlight,
+        ident = col_start + 1,
+    )
+}
diff --git a/ergoscript-compiler/src/hir.rs b/ergoscript-compiler/src/hir.rs
new file mode 100644
index 000000000..43e7647f8
--- /dev/null
+++ b/ergoscript-compiler/src/hir.rs
@@ -0,0 +1,154 @@
+//! High-level Intermediate Representation
+//! Refered as frontend representation in sigmastate
+
+mod rewrite;
+
+use ergotree_ir::types::stype::SType;
+pub use rewrite::rewrite;
+
+use super::ast;
+use crate::error::pretty_error_desc;
+use crate::syntax::SyntaxKind;
+use text_size::TextRange;
+
+extern crate derive_more;
+use derive_more::From;
+
+pub fn lower(ast: ast::Root) -> Result<Expr, HirLoweringError> {
+    // TODO: return error if more than one expr is found
+    let first_expr = ast.children().next().unwrap();
+    Expr::lower(&first_expr)
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Expr {
+    pub kind: ExprKind,
+    pub span: TextRange,
+    pub tpe: Option<SType>,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct HirLoweringError {
+    msg: String,
+    span: TextRange,
+}
+
+impl HirLoweringError {
+    pub fn new(msg: String, span: TextRange) -> Self {
+        HirLoweringError { msg, span }
+    }
+
+    pub fn pretty_desc(&self, source: &str) -> String {
+        pretty_error_desc(&source, self.span, &self.msg)
+    }
+}
+
+impl Expr {
+    pub fn lower(expr: &ast::Expr) -> Result<Expr, HirLoweringError> {
+        match expr {
+            ast::Expr::BinaryExpr(ast) => Ok(Expr {
+                kind: Binary::lower(ast)?.into(),
+                span: ast.span(),
+                tpe: None,
+            }),
+            ast::Expr::Ident(ast) => ast
+                .name()
+                .map(|node| Expr {
+                    kind: ExprKind::Ident(node.text().to_string()),
+                    span: ast.span(),
+                    tpe: None,
+                })
+                .ok_or_else(|| {
+                    HirLoweringError::new(format!("Empty Ident.name: {:?}", ast), ast.span())
+                }),
+        }
+    }
+
+    pub fn debug_tree(&self) -> String {
+        let tree = format!("{:#?}", self);
+        tree
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Spanned<T: Clone> {
+    pub node: T,
+    pub span: TextRange,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Binary {
+    pub op: Spanned<BinaryOp>,
+    pub lhs: Box<Expr>,
+    pub rhs: Box<Expr>,
+}
+
+impl Binary {
+    fn lower(ast: &ast::BinaryExpr) -> Result<Binary, HirLoweringError> {
+        // TODO: unwraps -> errors
+        let op = match ast.op().unwrap().kind() {
+            SyntaxKind::Plus => BinaryOp::Plus,
+            SyntaxKind::Minus => BinaryOp::Minus,
+            SyntaxKind::Star => BinaryOp::Multiply,
+            SyntaxKind::Slash => BinaryOp::Divide,
+            _ => {
+                return Err(HirLoweringError::new(
+                    format!("unknown binary operator: {:?}", ast.op()),
+                    ast.op().unwrap().text_range(),
+                ))
+            }
+        };
+
+        let lhs = Expr::lower(&ast.lhs().unwrap());
+        let rhs = Expr::lower(&ast.rhs().unwrap());
+
+        Ok(Binary {
+            op: Spanned {
+                node: op,
+                span: ast.op().unwrap().text_range(),
+            },
+            lhs: Box::new(lhs?),
+            rhs: Box::new(rhs?),
+        })
+    }
+}
+
+#[derive(Debug, PartialEq, From, Clone)]
+pub enum ExprKind {
+    Ident(String),
+    Binary(Binary),
+    GlobalVars(GlobalVars),
+    // ...
+    // Block
+    // ValNode
+    // Select
+    // ApplyTypes
+    // MethodCallLike
+    // Lambda
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum BinaryOp {
+    Plus,
+    Minus,
+    Multiply,
+    Divide,
+}
+
+// #[derive(Debug, PartialEq, Clone)]
+// pub enum UnaryOp {
+//     Neg,
+// }
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum GlobalVars {
+    Height,
+}
+
+impl GlobalVars {
+    pub fn tpe(&self) -> SType {
+        match self {
+            GlobalVars::Height => SType::SInt,
+        }
+    }
+}
diff --git a/ergoscript-compiler/src/hir/rewrite.rs b/ergoscript-compiler/src/hir/rewrite.rs
new file mode 100644
index 000000000..2b6c84d03
--- /dev/null
+++ b/ergoscript-compiler/src/hir/rewrite.rs
@@ -0,0 +1,28 @@
+use super::Binary;
+use super::Expr;
+use super::ExprKind;
+
+// pub fn hir_rewrite_safe<F: Fn(&Expr) -> Option<Expr>>(e: Expr, f: F) -> Expr {
+//     let f_wrap = |e| Result::<Option<Expr>, BinderError>::Ok(f(e));
+//     hir_rewrite(e, f_wrap).unwrap()
+// }
+
+pub fn rewrite<E, F: Fn(&Expr) -> Result<Option<Expr>, E>>(e: Expr, f: F) -> Result<Expr, E> {
+    let e = f(&e)?.unwrap_or(e);
+    Ok(match &e.kind {
+        ExprKind::Ident(_) => f(&e)?.unwrap_or(e),
+        ExprKind::Binary(binary) => match (f(&binary.lhs)?, f(&binary.rhs)?) {
+            (None, None) => e,
+            (l, r) => Expr {
+                kind: Binary {
+                    op: binary.op.clone(),
+                    lhs: Box::new(l.unwrap_or(*binary.lhs.clone())),
+                    rhs: Box::new(r.unwrap_or(*binary.rhs.clone())),
+                }
+                .into(),
+                ..e
+            },
+        },
+        ExprKind::GlobalVars(_) => f(&e)?.unwrap_or(e),
+    })
+}
diff --git a/ergoscript-compiler/src/lexer.rs b/ergoscript-compiler/src/lexer.rs
new file mode 100644
index 000000000..69734f3d9
--- /dev/null
+++ b/ergoscript-compiler/src/lexer.rs
@@ -0,0 +1,47 @@
+// Initial version is copied from https://github.com/arzg/eldiro
+// Checkout https://arzg.github.io/lang/ for description
+mod token_kind;
+pub use token_kind::TokenKind;
+
+use logos::Logos;
+use std::convert::TryFrom;
+use std::ops::Range as StdRange;
+use text_size::{TextRange, TextSize};
+
+pub struct Lexer<'a> {
+    inner: logos::Lexer<'a, TokenKind>,
+}
+
+impl<'a> Lexer<'a> {
+    pub fn new(input: &'a str) -> Self {
+        Self {
+            inner: TokenKind::lexer(input),
+        }
+    }
+}
+
+impl<'a> Iterator for Lexer<'a> {
+    type Item = Token<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let kind = self.inner.next()?;
+        let text = self.inner.slice();
+
+        let range = {
+            let StdRange { start, end } = self.inner.span();
+            let start = TextSize::try_from(start).unwrap();
+            let end = TextSize::try_from(end).unwrap();
+
+            TextRange::new(start, end)
+        };
+
+        Some(Self::Item { kind, text, range })
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct Token<'a> {
+    pub kind: TokenKind,
+    pub text: &'a str,
+    pub range: TextRange,
+}
diff --git a/ergoscript-compiler/src/lexer/token_kind.rs b/ergoscript-compiler/src/lexer/token_kind.rs
new file mode 100644
index 000000000..78ebdedd9
--- /dev/null
+++ b/ergoscript-compiler/src/lexer/token_kind.rs
@@ -0,0 +1,192 @@
+// Initial version is copied from https://github.com/arzg/eldiro
+// Checkout https://arzg.github.io/lang/ for description
+use logos::Logos;
+use std::fmt;
+
+#[derive(Debug, Copy, Clone, PartialEq, Logos)]
+pub enum TokenKind {
+    #[regex("[ \n]+")]
+    Whitespace,
+
+    #[token("def")]
+    FnKw,
+
+    #[token("val")]
+    ValKw,
+
+    #[regex("[A-Za-z][A-Za-z0-9]*")]
+    Ident,
+
+    #[regex("[0-9]+")]
+    Number,
+
+    #[token("+")]
+    Plus,
+
+    #[token("-")]
+    Minus,
+
+    #[token("*")]
+    Star,
+
+    #[token("/")]
+    Slash,
+
+    #[token("&&")]
+    And,
+
+    #[token("=")]
+    Equals,
+
+    #[token("(")]
+    LParen,
+
+    #[token(")")]
+    RParen,
+
+    #[token("{")]
+    LBrace,
+
+    #[token("}")]
+    RBrace,
+
+    #[regex("//.*")]
+    Comment,
+
+    #[error]
+    Error,
+}
+
+impl TokenKind {
+    pub fn is_trivia(self) -> bool {
+        matches!(self, Self::Whitespace | Self::Comment)
+    }
+}
+
+impl fmt::Display for TokenKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(match self {
+            Self::Whitespace => "whitespace",
+            Self::FnKw => "‘def’",
+            Self::ValKw => "‘val’",
+            Self::Ident => "identifier",
+            Self::Number => "number",
+            Self::Plus => "‘+’",
+            Self::Minus => "‘-’",
+            Self::Star => "‘*’",
+            Self::Slash => "‘/’",
+            Self::And => "‘&&’",
+            Self::Equals => "‘=’",
+            Self::LParen => "‘(’",
+            Self::RParen => "‘)’",
+            Self::LBrace => "‘{’",
+            Self::RBrace => "‘}’",
+            Self::Comment => "comment",
+            Self::Error => "an unrecognized token",
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::Lexer;
+    use super::*;
+
+    fn check(input: &str, kind: TokenKind) {
+        let mut lexer = Lexer::new(input);
+
+        let token = lexer.next().unwrap();
+        assert_eq!(token.kind, kind);
+        assert_eq!(token.text, input);
+    }
+
+    #[test]
+    fn lex_spaces_and_newlines() {
+        check("  \n ", TokenKind::Whitespace);
+    }
+
+    #[test]
+    fn lex_fn_keyword() {
+        check("def", TokenKind::FnKw);
+    }
+
+    #[test]
+    fn lex_val_keyword() {
+        check("val", TokenKind::ValKw);
+    }
+
+    #[test]
+    fn lex_alphabetic_identifier() {
+        check("abcd", TokenKind::Ident);
+    }
+
+    #[test]
+    fn lex_alphanumeric_identifier() {
+        check("ab123cde456", TokenKind::Ident);
+    }
+
+    #[test]
+    fn lex_mixed_case_identifier() {
+        check("ABCdef", TokenKind::Ident);
+    }
+
+    #[test]
+    fn lex_single_char_identifier() {
+        check("x", TokenKind::Ident);
+    }
+
+    #[test]
+    fn lex_number() {
+        check("123456", TokenKind::Number);
+    }
+
+    #[test]
+    fn lex_plus() {
+        check("+", TokenKind::Plus);
+    }
+
+    #[test]
+    fn lex_minus() {
+        check("-", TokenKind::Minus);
+    }
+
+    #[test]
+    fn lex_star() {
+        check("*", TokenKind::Star);
+    }
+
+    #[test]
+    fn lex_slash() {
+        check("/", TokenKind::Slash);
+    }
+
+    #[test]
+    fn lex_equals() {
+        check("=", TokenKind::Equals);
+    }
+
+    #[test]
+    fn lex_left_parenthesis() {
+        check("(", TokenKind::LParen);
+    }
+
+    #[test]
+    fn lex_right_parenthesis() {
+        check(")", TokenKind::RParen);
+    }
+
+    #[test]
+    fn lex_left_brace() {
+        check("{", TokenKind::LBrace);
+    }
+
+    #[test]
+    fn lex_right_brace() {
+        check("}", TokenKind::RBrace);
+    }
+
+    #[test]
+    fn lex_comment() {
+        check("// foo", TokenKind::Comment);
+    }
+}
diff --git a/ergoscript-compiler/src/lib.rs b/ergoscript-compiler/src/lib.rs
new file mode 100644
index 000000000..f09d1ac08
--- /dev/null
+++ b/ergoscript-compiler/src/lib.rs
@@ -0,0 +1,27 @@
+//! ErgoScript compiler pipeline
+
+// Coding conventions
+#![forbid(unsafe_code)]
+#![deny(non_upper_case_globals)]
+#![deny(non_camel_case_types)]
+#![deny(non_snake_case)]
+#![deny(unused_mut)]
+#![deny(dead_code)]
+#![deny(unused_imports)]
+#![deny(missing_docs)]
+// Clippy exclusions
+#![allow(clippy::unit_arg)]
+#![deny(broken_intra_doc_links)]
+
+pub(crate) mod ast;
+pub(crate) mod binder;
+pub(crate) mod error;
+pub(crate) mod hir;
+pub(crate) mod lexer;
+pub(crate) mod mir;
+pub(crate) mod parser;
+pub(crate) mod syntax;
+pub(crate) mod type_infer;
+
+pub mod compiler;
+pub mod script_env;
diff --git a/ergoscript-compiler/src/mir.rs b/ergoscript-compiler/src/mir.rs
new file mode 100644
index 000000000..2ff8bd46c
--- /dev/null
+++ b/ergoscript-compiler/src/mir.rs
@@ -0,0 +1,2 @@
+pub mod lower;
+pub mod type_check;
diff --git a/ergoscript-compiler/src/mir/lower.rs b/ergoscript-compiler/src/mir/lower.rs
new file mode 100644
index 000000000..1972cc503
--- /dev/null
+++ b/ergoscript-compiler/src/mir/lower.rs
@@ -0,0 +1,120 @@
+use ergotree_ir::mir::bin_op::ArithOp;
+use ergotree_ir::mir::bin_op::BinOp;
+use ergotree_ir::mir::bin_op::BinOpKind;
+use ergotree_ir::mir::expr::Expr;
+use ergotree_ir::mir::global_vars::GlobalVars;
+use hir::BinaryOp;
+use rowan::TextRange;
+
+use crate::error::pretty_error_desc;
+use crate::hir;
+
+#[derive(Debug, PartialEq)]
+pub struct MirLoweringError {
+    msg: String,
+    span: TextRange,
+}
+
+impl MirLoweringError {
+    pub fn new(msg: String, span: TextRange) -> Self {
+        Self { msg, span }
+    }
+
+    pub fn pretty_desc(&self, source: &str) -> String {
+        pretty_error_desc(&source, self.span, &self.msg)
+    }
+}
+
+pub fn lower(hir_expr: hir::Expr) -> Result<Expr, MirLoweringError> {
+    let mir: Expr = match &hir_expr.kind {
+        hir::ExprKind::GlobalVars(hir) => match hir {
+            hir::GlobalVars::Height => GlobalVars::Height.into(),
+        },
+        hir::ExprKind::Ident(_) => {
+            return Err(MirLoweringError::new(
+                format!("MIR error: Unresolved Ident {0:?}", hir_expr),
+                hir_expr.span,
+            ))
+        }
+        hir::ExprKind::Binary(hir) => {
+            let l = lower(*hir.lhs.clone())?;
+            let r = lower(*hir.rhs.clone())?;
+            BinOp {
+                kind: hir.op.node.clone().into(),
+                left: l.into(),
+                right: r.into(),
+            }
+            .into()
+        }
+    };
+    let hir_tpe = hir_expr.tpe.clone().ok_or_else(|| {
+        MirLoweringError::new(
+            format!("MIR error: missing tpe for HIR: {0:?}", hir_expr),
+            hir_expr.span,
+        )
+    })?;
+    if mir.tpe() == hir_tpe {
+        Ok(mir)
+    } else {
+        Err(MirLoweringError::new(
+            format!(
+                "MIR error: lowered MIR type != HIR type ({0:?} != {1:?})",
+                mir.tpe(),
+                hir_expr.tpe
+            ),
+            hir_expr.span,
+        ))
+    }
+}
+
+impl From<hir::BinaryOp> for BinOpKind {
+    fn from(op: hir::BinaryOp) -> Self {
+        match op {
+            BinaryOp::Plus => ArithOp::Plus.into(),
+            BinaryOp::Minus => ArithOp::Minus.into(),
+            BinaryOp::Multiply => ArithOp::Multiply.into(),
+            BinaryOp::Divide => ArithOp::Divide.into(),
+        }
+    }
+}
+
+#[cfg(test)]
+pub fn check(input: &str, expected_tree: expect_test::Expect) {
+    let parse = crate::parser::parse(input);
+    let syntax = parse.syntax();
+    let root = crate::ast::Root::cast(syntax).unwrap();
+    let hir = hir::lower(root).unwrap();
+    let binder = crate::binder::Binder::new(crate::script_env::ScriptEnv::new());
+    let bind = binder.bind(hir).unwrap();
+    let typed = crate::type_infer::assign_type(bind).unwrap();
+    let res = lower(typed).unwrap();
+    expected_tree.assert_eq(&res.debug_tree());
+}
+
+#[cfg(test)]
+mod tests {
+    use expect_test::expect;
+
+    use super::*;
+
+    #[test]
+    fn bin_smoke() {
+        check(
+            "HEIGHT + HEIGHT",
+            expect![[r#"
+            BinOp(
+                BinOp {
+                    kind: Arith(
+                        Plus,
+                    ),
+                    left: GlobalVars(
+                        Height,
+                    ),
+                    right: GlobalVars(
+                        Height,
+                    ),
+                },
+            )"#]],
+        )
+    }
+}
diff --git a/ergoscript-compiler/src/mir/type_check.rs b/ergoscript-compiler/src/mir/type_check.rs
new file mode 100644
index 000000000..bb7e40267
--- /dev/null
+++ b/ergoscript-compiler/src/mir/type_check.rs
@@ -0,0 +1,34 @@
+use ergotree_ir::mir::expr::Expr;
+
+#[derive(Debug, PartialEq)]
+pub struct TypeCheckError {
+    msg: String,
+}
+
+impl TypeCheckError {
+    pub fn new(msg: String) -> Self {
+        Self { msg }
+    }
+
+    pub fn pretty_desc(&self) -> String {
+        self.msg.clone()
+    }
+}
+
+// TODO: move to ergotree_ir crate
+pub fn type_check(e: Expr) -> Result<Expr, TypeCheckError> {
+    // not really a relevant check, since such kind of check should be in BinOp::new()
+    match &e {
+        Expr::BinOp(bin) => {
+            if bin.left.tpe() == bin.right.tpe() {
+                Ok(e)
+            } else {
+                Err(TypeCheckError::new(format!(
+                    "Type check error: binary op operands types do not match: {0:?}",
+                    bin
+                )))
+            }
+        }
+        _ => Ok(e),
+    }
+}
diff --git a/ergoscript-compiler/src/parser.rs b/ergoscript-compiler/src/parser.rs
new file mode 100644
index 000000000..bff88cf4c
--- /dev/null
+++ b/ergoscript-compiler/src/parser.rs
@@ -0,0 +1,145 @@
+#![allow(dead_code)]
+
+mod event;
+mod grammar;
+mod marker;
+mod parse;
+pub(crate) mod parse_error;
+mod sink;
+mod source;
+
+pub(crate) use parse::parse;
+
+use std::mem;
+
+use crate::lexer::Token;
+use crate::lexer::TokenKind;
+use crate::syntax::SyntaxKind;
+
+use self::event::Event;
+use self::marker::Marker;
+use self::parse_error::ParseError;
+use self::source::Source;
+
+// const RECOVERY_SET: [TokenKind; 1] = [TokenKind::ValKw];
+
+pub struct Parser<'t, 'input> {
+    pub source: Source<'t, 'input>,
+    pub events: Vec<Event>,
+    pub expected_kinds: Vec<TokenKind>,
+}
+
+impl<'t, 'input> Parser<'t, 'input> {
+    pub fn new(source: Source<'t, 'input>) -> Self {
+        Self {
+            source,
+            events: Vec::new(),
+            expected_kinds: Vec::new(),
+        }
+    }
+
+    pub fn parse(mut self) -> Vec<Event> {
+        grammar::root(&mut self);
+        self.events
+    }
+
+    fn start(&mut self) -> Marker {
+        let pos = self.events.len();
+        self.events.push(Event::Placeholder);
+
+        Marker::new(pos)
+    }
+
+    fn expect(&mut self, kind: TokenKind) {
+        if self.at(kind) {
+            self.bump();
+        } else {
+            self.error();
+        }
+    }
+
+    fn error(&mut self) {
+        let current_token = self.source.peek_token();
+
+        let (found, range) = if let Some(Token { kind, range, .. }) = current_token {
+            (Some(*kind), *range)
+        } else {
+            // If we’re at the end of the input we use the range of the very last token in the
+            // input.
+            (None, self.source.last_token_range().unwrap())
+        };
+
+        self.events.push(Event::Error(ParseError {
+            expected: mem::take(&mut self.expected_kinds),
+            found,
+            span: range,
+        }));
+
+        // if !self.at_set(&RECOVERY_SET) && !self.at_end() {
+        if !self.at_end() {
+            let m = self.start();
+            self.bump();
+            m.complete(self, SyntaxKind::Error);
+        }
+    }
+
+    fn bump(&mut self) {
+        self.expected_kinds.clear();
+        self.source.next_token().unwrap();
+        self.events.push(Event::AddToken);
+    }
+
+    fn at(&mut self, kind: TokenKind) -> bool {
+        self.expected_kinds.push(kind);
+        self.peek() == Some(kind)
+    }
+
+    fn at_set(&mut self, set: &[TokenKind]) -> bool {
+        self.peek().map_or(false, |k| set.contains(&k))
+    }
+
+    fn at_end(&mut self) -> bool {
+        self.peek().is_none()
+    }
+
+    fn peek(&mut self) -> Option<TokenKind> {
+        self.source.peek_kind()
+    }
+}
+
+#[cfg(test)]
+fn check(input: &str, expected_tree: expect_test::Expect) {
+    let parse = parse::parse(input);
+    expected_tree.assert_eq(&parse.debug_tree());
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::parser::check;
+    use expect_test::expect;
+
+    #[test]
+    fn parse_nothing() {
+        check("", expect![[r#"Root@0..0"#]]);
+    }
+
+    #[test]
+    fn parse_whitespace() {
+        check(
+            "   ",
+            expect![[r#"
+            Root@0..3
+              Whitespace@0..3 "   ""#]],
+        );
+    }
+
+    #[test]
+    fn parse_comment() {
+        check(
+            "// hello!",
+            expect![[r#"
+            Root@0..9
+              Comment@0..9 "// hello!""#]],
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/parser/event.rs b/ergoscript-compiler/src/parser/event.rs
new file mode 100644
index 000000000..209414656
--- /dev/null
+++ b/ergoscript-compiler/src/parser/event.rs
@@ -0,0 +1,14 @@
+use crate::parser::ParseError;
+use crate::syntax::SyntaxKind;
+
+#[derive(Debug, PartialEq)]
+pub enum Event {
+    StartNode {
+        kind: SyntaxKind,
+        forward_parent: Option<usize>,
+    },
+    AddToken,
+    FinishNode,
+    Error(ParseError),
+    Placeholder,
+}
diff --git a/ergoscript-compiler/src/parser/grammar.rs b/ergoscript-compiler/src/parser/grammar.rs
new file mode 100644
index 000000000..71657800c
--- /dev/null
+++ b/ergoscript-compiler/src/parser/grammar.rs
@@ -0,0 +1,35 @@
+mod expr;
+mod stmt;
+
+use crate::lexer::TokenKind;
+use crate::parser::Parser;
+use crate::syntax::SyntaxKind;
+
+use super::marker::CompletedMarker;
+
+pub fn root(p: &mut Parser) -> CompletedMarker {
+    let m = p.start();
+
+    while !p.at_end() {
+        stmt::stmt(p);
+    }
+
+    m.complete(p, SyntaxKind::Root)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::parser::check;
+    use expect_test::expect;
+
+    #[test]
+    fn parse_ident() {
+        check(
+            "HEIGHT",
+            expect![[r#"
+            Root@0..6
+              Ident@0..6
+                Ident@0..6 "HEIGHT""#]],
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/parser/grammar/expr.rs b/ergoscript-compiler/src/parser/grammar/expr.rs
new file mode 100644
index 000000000..b965b1da2
--- /dev/null
+++ b/ergoscript-compiler/src/parser/grammar/expr.rs
@@ -0,0 +1,467 @@
+use super::*;
+
+pub(super) fn expr(p: &mut Parser) -> Option<CompletedMarker> {
+    expr_binding_power(p, 0)
+}
+
+// from https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
+//
+// From Precedence to Binding Power
+// I have a confession to make: I am always confused by “high precedence” and “low precedence”. In
+// a + b * c, addition has a lower precedence, but it is at the top of the parse tree…​
+//
+// So instead, I find thinking in terms of binding power more intuitive.
+//
+// expr:   A       +       B       *       C
+// power:      3       3       5       5
+// The * is stronger, it has more power to hold together B and C, and so the expression is parsed
+// as A + (B * C).
+//
+// What about associativity though? In A + B + C all operators seem to have the same power, and it
+// is unclear which + to fold first. But this can also be modelled with power, if we make it
+// slightly asymmetric:
+//
+// expr:      A       +       B       +       C
+// power:  0      3      3.1      3      3.1     0
+// Here, we pumped the right power of + just a little bit, so that it holds the right operand
+// tighter. We also added zeros at both ends, as there are no operators to bind from the sides.
+// Here, the first (and only the first) + holds both of its arguments tighter than the neighbors,
+// so we can reduce it:
+//
+// expr:     (A + B)     +     C
+// power:  0          3    3.1    0
+// Now we can fold the second plus and get (A + B) + C. Or, in terms of the syntax tree, the second
+// + really likes its right operand more than the left one, so it rushes to get hold of C. While he
+// does that, the first + captures both A and B, as they are uncontested.
+//
+// What Pratt parsing does is that it finds these badass, stronger than neighbors operators, by
+// processing the string left to right. We are almost at a point where we finally start writing
+// some code, but let’s first look at the other running example. We will use function composition
+// operator, . (dot) as a right associative operator with a high binding power. That is, f . g . h
+// is parsed as f . (g . h), or, in terms of power
+//
+//   f     .    g     .    h
+//   0   8.5    8   8.5    8   0
+//
+// ...
+//
+// And now comes the tricky bit, where we introduce recursion into the picture. Let’s think about
+// this example (with powers below):
+//
+// a   +   b   *   c   *   d   +   e
+//   1   2   3   4   3   4   1   2
+//   The cursor is at the first +, we know that the left bp is 1 and the right one is 2. The lhs
+//   stores a. The next operator after + is *, so we shouldn’t add b to a. The problem is that we
+//   haven’t yet seen the next operator, we are just past +. Can we add a lookahead? Looks like
+//   no — we’d have to look past all of b, c and d to find the next operator with lower binding
+//   power, which sounds pretty unbounded. But we are onto something! Our current right priority is
+//   2, and, to be able to fold the expression, we need to find the next operator with lower
+//   priority. So let’s recursively call expr_bp starting at b, but also tell it to stop as soon as
+//   bp drops below 2. This necessitates the addition of min_bp argument to the main function.
+//
+fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) -> Option<CompletedMarker> {
+    let mut lhs = lhs(p)?;
+
+    loop {
+        let op = if p.at(TokenKind::Plus) {
+            BinaryOp::Add
+        } else if p.at(TokenKind::Minus) {
+            BinaryOp::Sub
+        } else if p.at(TokenKind::Star) {
+            BinaryOp::Mul
+        } else if p.at(TokenKind::Slash) {
+            BinaryOp::Div
+        } else {
+            // We’re not at an operator; we don’t know what to do next, so we return and let the
+            // caller decide.
+            break;
+        };
+
+        let (left_binding_power, right_binding_power) = op.binding_power();
+
+        if left_binding_power < minimum_binding_power {
+            break;
+        }
+
+        // Eat the operator’s token.
+        p.bump();
+
+        //  And here we bump past the operator itself and make the recursive call. Note how we use
+        //  left_binding_power to check against minimum_binding_power, and right_binding_power
+        //  as the new minimum_binding_power of the recursive call. So, you can think
+        //  about minimum_binding_power as the binding power of the operator to the left of the current expressions.
+        //  https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
+        let m = lhs.precede(p);
+        let parsed_rhs = expr_binding_power(p, right_binding_power).is_some();
+        lhs = m.complete(p, SyntaxKind::InfixExpr);
+
+        if !parsed_rhs {
+            break;
+        }
+    }
+
+    Some(lhs)
+}
+
+fn lhs(p: &mut Parser) -> Option<CompletedMarker> {
+    let cm = if p.at(TokenKind::Number) {
+        literal(p)
+    } else if p.at(TokenKind::Ident) {
+        ident(p)
+        // variable_ref(p)
+        // } else if p.at(TokenKind::ValKw) {
+        //     variable_ref(p)
+    } else if p.at(TokenKind::Minus) {
+        prefix_expr(p)
+    } else if p.at(TokenKind::LParen) {
+        paren_expr(p)
+    } else {
+        p.error();
+        return None;
+    };
+
+    Some(cm)
+}
+
+enum BinaryOp {
+    Add,
+    Sub,
+    Mul,
+    Div,
+}
+
+impl BinaryOp {
+    fn binding_power(&self) -> (u8, u8) {
+        match self {
+            Self::Add | Self::Sub => (1, 2),
+            Self::Mul | Self::Div => (3, 4),
+        }
+    }
+}
+
+enum UnaryOp {
+    Neg,
+}
+
+impl UnaryOp {
+    fn binding_power(&self) -> ((), u8) {
+        match self {
+            Self::Neg => ((), 5),
+        }
+    }
+}
+
+fn literal(p: &mut Parser) -> CompletedMarker {
+    assert!(p.at(TokenKind::Number));
+
+    let m = p.start();
+    p.bump();
+    m.complete(p, SyntaxKind::Literal)
+}
+
+// fn variable_ref(p: &mut Parser) -> CompletedMarker {
+//     assert!(p.at(TokenKind::Ident));
+
+//     let m = p.start();
+//     p.bump();
+//     m.complete(p, SyntaxKind::VariableRef)
+// }
+
+fn ident(p: &mut Parser) -> CompletedMarker {
+    assert!(p.at(TokenKind::Ident));
+
+    let m = p.start();
+    p.bump();
+    m.complete(p, SyntaxKind::Ident)
+}
+
+fn prefix_expr(p: &mut Parser) -> CompletedMarker {
+    assert!(p.at(TokenKind::Minus));
+
+    let m = p.start();
+
+    let op = UnaryOp::Neg;
+    let ((), right_binding_power) = op.binding_power();
+
+    // Eat the operator’s token.
+    p.bump();
+
+    expr_binding_power(p, right_binding_power);
+
+    m.complete(p, SyntaxKind::PrefixExpr)
+}
+
+fn paren_expr(p: &mut Parser) -> CompletedMarker {
+    assert!(p.at(TokenKind::LParen));
+
+    let m = p.start();
+    p.bump();
+    expr_binding_power(p, 0);
+    p.expect(TokenKind::RParen);
+
+    m.complete(p, SyntaxKind::ParenExpr)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::parser::check;
+    use expect_test::expect;
+
+    #[test]
+    fn parse_number() {
+        check(
+            "123",
+            expect![[r#"
+Root@0..3
+  Literal@0..3
+    Number@0..3 "123""#]],
+        );
+    }
+
+    #[test]
+    fn parse_number_preceded_by_whitespace() {
+        check(
+            "  9876",
+            expect![[r#"
+                Root@0..6
+                  Whitespace@0..2 "  "
+                  Literal@2..6
+                    Number@2..6 "9876""#]],
+        );
+    }
+
+    #[test]
+    fn parse_number_followed_by_whitespace() {
+        check(
+            "999  ",
+            expect![[r#"
+                Root@0..5
+                  Literal@0..5
+                    Number@0..3 "999"
+                    Whitespace@3..5 "  ""#]],
+        );
+    }
+
+    #[test]
+    fn parse_number_surrounded_by_whitespace() {
+        check(
+            " 123    ",
+            expect![[r#"
+                Root@0..8
+                  Whitespace@0..1 " "
+                  Literal@1..8
+                    Number@1..4 "123"
+                    Whitespace@4..8 "    ""#]],
+        );
+    }
+
+    #[test]
+    fn parse_simple_infix_expression() {
+        check(
+            "1+3",
+            expect![[r#"
+                Root@0..3
+                  InfixExpr@0..3
+                    Literal@0..1
+                      Number@0..1 "1"
+                    Plus@1..2 "+"
+                    Literal@2..3
+                      Number@2..3 "3""#]],
+        );
+    }
+
+    #[test]
+    fn parse_left_associative_infix_expression() {
+        check(
+            "1+2+3",
+            expect![[r#"
+                Root@0..5
+                  InfixExpr@0..5
+                    InfixExpr@0..3
+                      Literal@0..1
+                        Number@0..1 "1"
+                      Plus@1..2 "+"
+                      Literal@2..3
+                        Number@2..3 "2"
+                    Plus@3..4 "+"
+                    Literal@4..5
+                      Number@4..5 "3""#]],
+        );
+    }
+
+    #[test]
+    fn parse_infix_expression_with_mixed_binding_power() {
+        check(
+            "1+2*3-5",
+            expect![[r#"
+                Root@0..7
+                  InfixExpr@0..7
+                    InfixExpr@0..5
+                      Literal@0..1
+                        Number@0..1 "1"
+                      Plus@1..2 "+"
+                      InfixExpr@2..5
+                        Literal@2..3
+                          Number@2..3 "2"
+                        Star@3..4 "*"
+                        Literal@4..5
+                          Number@4..5 "3"
+                    Minus@5..6 "-"
+                    Literal@6..7
+                      Number@6..7 "5""#]],
+        );
+    }
+
+    #[test]
+    fn parse_infix_expression_with_whitespace() {
+        check(
+            " 1 +  2* 3 ",
+            expect![[r#"
+                Root@0..11
+                  Whitespace@0..1 " "
+                  InfixExpr@1..11
+                    Literal@1..3
+                      Number@1..2 "1"
+                      Whitespace@2..3 " "
+                    Plus@3..4 "+"
+                    Whitespace@4..6 "  "
+                    InfixExpr@6..11
+                      Literal@6..7
+                        Number@6..7 "2"
+                      Star@7..8 "*"
+                      Whitespace@8..9 " "
+                      Literal@9..11
+                        Number@9..10 "3"
+                        Whitespace@10..11 " ""#]],
+        );
+    }
+
+    #[test]
+    fn parse_infix_expression_interspersed_with_comments() {
+        check(
+            "
+1
+  + 1 // Add one
+  + 10 // Add ten",
+            expect![[r#"
+                Root@0..37
+                  Whitespace@0..1 "\n"
+                  InfixExpr@1..37
+                    InfixExpr@1..22
+                      Literal@1..5
+                        Number@1..2 "1"
+                        Whitespace@2..5 "\n  "
+                      Plus@5..6 "+"
+                      Whitespace@6..7 " "
+                      Literal@7..22
+                        Number@7..8 "1"
+                        Whitespace@8..9 " "
+                        Comment@9..19 "// Add one"
+                        Whitespace@19..22 "\n  "
+                    Plus@22..23 "+"
+                    Whitespace@23..24 " "
+                    Literal@24..37
+                      Number@24..26 "10"
+                      Whitespace@26..27 " "
+                      Comment@27..37 "// Add ten""#]],
+        );
+    }
+
+    #[test]
+    fn do_not_parse_operator_if_gettting_rhs_failed() {
+        check(
+            "(2+",
+            expect![[r#"
+            Root@0..3
+              ParenExpr@0..3
+                LParen@0..1 "("
+                InfixExpr@1..3
+                  Literal@1..2
+                    Number@1..2 "2"
+                  Plus@2..3 "+"
+            error: expected number, identifier, ‘-’ or ‘(’
+            error: expected ‘)’"#]],
+        );
+    }
+
+    #[test]
+    fn parse_negation() {
+        check(
+            "-11",
+            expect![[r#"
+                Root@0..3
+                  PrefixExpr@0..3
+                    Minus@0..1 "-"
+                    Literal@1..3
+                      Number@1..3 "11""#]],
+        );
+    }
+
+    #[test]
+    fn negation_has_higher_binding_power_than_binary_operators() {
+        check(
+            "-20+21",
+            expect![[r#"
+                Root@0..6
+                  InfixExpr@0..6
+                    PrefixExpr@0..3
+                      Minus@0..1 "-"
+                      Literal@1..3
+                        Number@1..3 "20"
+                    Plus@3..4 "+"
+                    Literal@4..6
+                      Number@4..6 "21""#]],
+        );
+    }
+
+    #[test]
+    fn parse_nested_parentheses() {
+        check(
+            "((((((11))))))",
+            expect![[r#"
+                Root@0..14
+                  ParenExpr@0..14
+                    LParen@0..1 "("
+                    ParenExpr@1..13
+                      LParen@1..2 "("
+                      ParenExpr@2..12
+                        LParen@2..3 "("
+                        ParenExpr@3..11
+                          LParen@3..4 "("
+                          ParenExpr@4..10
+                            LParen@4..5 "("
+                            ParenExpr@5..9
+                              LParen@5..6 "("
+                              Literal@6..8
+                                Number@6..8 "11"
+                              RParen@8..9 ")"
+                            RParen@9..10 ")"
+                          RParen@10..11 ")"
+                        RParen@11..12 ")"
+                      RParen@12..13 ")"
+                    RParen@13..14 ")""#]],
+        );
+    }
+
+    #[test]
+    fn parentheses_affect_precedence() {
+        check(
+            "5*(2+3)",
+            expect![[r#"
+                Root@0..7
+                  InfixExpr@0..7
+                    Literal@0..1
+                      Number@0..1 "5"
+                    Star@1..2 "*"
+                    ParenExpr@2..7
+                      LParen@2..3 "("
+                      InfixExpr@3..6
+                        Literal@3..4
+                          Number@3..4 "2"
+                        Plus@4..5 "+"
+                        Literal@5..6
+                          Number@5..6 "3"
+                      RParen@6..7 ")""#]],
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/parser/grammar/stmt.rs b/ergoscript-compiler/src/parser/grammar/stmt.rs
new file mode 100644
index 000000000..c68156099
--- /dev/null
+++ b/ergoscript-compiler/src/parser/grammar/stmt.rs
@@ -0,0 +1,46 @@
+use super::*;
+
+pub(super) fn stmt(p: &mut Parser) -> Option<CompletedMarker> {
+    if p.at(TokenKind::ValKw) {
+        Some(variable_def(p))
+    } else {
+        expr::expr(p)
+    }
+}
+
+fn variable_def(p: &mut Parser) -> CompletedMarker {
+    assert!(p.at(TokenKind::ValKw));
+    let m = p.start();
+    p.bump();
+
+    p.expect(TokenKind::Ident);
+    p.expect(TokenKind::Equals);
+
+    expr::expr(p);
+
+    m.complete(p, SyntaxKind::VariableDef)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::parser::check;
+    use expect_test::expect;
+
+    #[test]
+    fn parse_variable_definition() {
+        check(
+            "val foo = bar",
+            expect![[r#"
+            Root@0..13
+              VariableDef@0..13
+                ValKw@0..3 "val"
+                Whitespace@3..4 " "
+                Ident@4..7 "foo"
+                Whitespace@7..8 " "
+                Equals@8..9 "="
+                Whitespace@9..10 " "
+                Ident@10..13
+                  Ident@10..13 "bar""#]],
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/parser/marker.rs b/ergoscript-compiler/src/parser/marker.rs
new file mode 100644
index 000000000..7e464a9dd
--- /dev/null
+++ b/ergoscript-compiler/src/parser/marker.rs
@@ -0,0 +1,57 @@
+use crate::parser::event::Event;
+use crate::syntax::SyntaxKind;
+
+use super::Parser;
+use drop_bomb::DropBomb;
+
+pub struct Marker {
+    pos: usize,
+    bomb: DropBomb,
+}
+
+impl Marker {
+    pub fn new(pos: usize) -> Self {
+        Self {
+            pos,
+            bomb: DropBomb::new("Markers need to be completed"),
+        }
+    }
+
+    pub fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
+        self.bomb.defuse();
+
+        let event_at_pos = &mut p.events[self.pos];
+        assert_eq!(*event_at_pos, Event::Placeholder);
+
+        *event_at_pos = Event::StartNode {
+            kind,
+            forward_parent: None,
+        };
+
+        p.events.push(Event::FinishNode);
+
+        CompletedMarker { pos: self.pos }
+    }
+}
+
+pub struct CompletedMarker {
+    pos: usize,
+}
+
+impl CompletedMarker {
+    pub fn precede(self, p: &mut Parser) -> Marker {
+        let new_m = p.start();
+
+        if let Event::StartNode {
+            ref mut forward_parent,
+            ..
+        } = p.events[self.pos]
+        {
+            *forward_parent = Some(new_m.pos - self.pos);
+        } else {
+            unreachable!();
+        }
+
+        new_m
+    }
+}
diff --git a/ergoscript-compiler/src/parser/parse.rs b/ergoscript-compiler/src/parser/parse.rs
new file mode 100644
index 000000000..7167b9e4d
--- /dev/null
+++ b/ergoscript-compiler/src/parser/parse.rs
@@ -0,0 +1,48 @@
+use rowan::GreenNode;
+
+use crate::lexer::Lexer;
+use crate::syntax::SyntaxNode;
+
+use super::parse_error::ParseError;
+use super::sink::Sink;
+use super::source::Source;
+use super::Parser;
+
+// Initial version is copied from https://github.com/arzg/eldiro
+// Checkout https://arzg.github.io/lang/ for description
+
+pub fn parse(input: &str) -> Parse {
+    let tokens: Vec<_> = Lexer::new(input).collect();
+    let source = Source::new(&tokens);
+    let parser = Parser::new(source);
+    let events = parser.parse();
+    let sink = Sink::new(&tokens, events);
+
+    sink.finish()
+}
+
+pub struct Parse {
+    pub green_node: GreenNode,
+    pub errors: Vec<ParseError>,
+}
+
+impl Parse {
+    pub fn debug_tree(&self) -> String {
+        let mut s = String::new();
+
+        let tree = format!("{:#?}", self.syntax());
+
+        // We cut off the last byte because formatting the SyntaxNode adds on a newline at the end.
+        s.push_str(&tree[0..tree.len() - 1]);
+
+        for error in &self.errors {
+            s.push_str(&format!("\n{}", error));
+        }
+
+        s
+    }
+
+    pub fn syntax(&self) -> SyntaxNode {
+        SyntaxNode::new_root(self.green_node.clone())
+    }
+}
diff --git a/ergoscript-compiler/src/parser/parse_error.rs b/ergoscript-compiler/src/parser/parse_error.rs
new file mode 100644
index 000000000..80a4d8efd
--- /dev/null
+++ b/ergoscript-compiler/src/parser/parse_error.rs
@@ -0,0 +1,109 @@
+use std::fmt;
+use text_size::TextRange;
+
+use crate::error::pretty_error_desc;
+use crate::lexer::TokenKind;
+
+#[derive(Debug, PartialEq)]
+pub struct ParseError {
+    pub expected: Vec<TokenKind>,
+    pub found: Option<TokenKind>,
+    pub span: TextRange,
+}
+
+impl ParseError {
+    pub fn pretty_desc(&self, source: &str) -> String {
+        pretty_error_desc(source, self.span, &self.to_string())
+    }
+}
+
+impl fmt::Display for ParseError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "error: expected ")?;
+
+        let num_expected = self.expected.len();
+        let is_first = |idx| idx == 0;
+        let is_last = |idx| idx == num_expected - 1;
+
+        for (idx, expected_kind) in self.expected.iter().enumerate() {
+            if is_first(idx) {
+                write!(f, "{}", expected_kind)?;
+            } else if is_last(idx) {
+                write!(f, " or {}", expected_kind)?;
+            } else {
+                write!(f, ", {}", expected_kind)?;
+            }
+        }
+
+        if let Some(found) = self.found {
+            write!(f, ", but found {}", found)?;
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::ops::Range as StdRange;
+
+    fn check(
+        expected: Vec<TokenKind>,
+        found: Option<TokenKind>,
+        range: StdRange<u32>,
+        output: &str,
+    ) {
+        let error = ParseError {
+            expected,
+            found,
+            span: {
+                let start = range.start.into();
+                let end = range.end.into();
+                TextRange::new(start, end)
+            },
+        };
+
+        assert_eq!(format!("{}", error), output);
+    }
+
+    #[test]
+    fn one_expected_did_find() {
+        check(
+            vec![TokenKind::Equals],
+            Some(TokenKind::Ident),
+            10..20,
+            "error: expected ‘=’, but found identifier",
+        );
+    }
+
+    #[test]
+    fn one_expected_did_not_find() {
+        check(vec![TokenKind::RParen], None, 5..6, "error: expected ‘)’");
+    }
+
+    #[test]
+    fn two_expected_did_find() {
+        check(
+            vec![TokenKind::Plus, TokenKind::Minus],
+            Some(TokenKind::Equals),
+            0..1,
+            "error: expected ‘+’ or ‘-’, but found ‘=’",
+        );
+    }
+
+    #[test]
+    fn multiple_expected_did_find() {
+        check(
+            vec![
+                TokenKind::Number,
+                TokenKind::Ident,
+                TokenKind::Minus,
+                TokenKind::LParen,
+            ],
+            Some(TokenKind::ValKw),
+            100..105,
+            "error: expected number, identifier, ‘-’ or ‘(’, but found ‘val’",
+        );
+    }
+}
diff --git a/ergoscript-compiler/src/parser/sink.rs b/ergoscript-compiler/src/parser/sink.rs
new file mode 100644
index 000000000..b382926a5
--- /dev/null
+++ b/ergoscript-compiler/src/parser/sink.rs
@@ -0,0 +1,98 @@
+use crate::lexer::Token;
+use crate::parser::ParseError;
+use crate::syntax::ErgoScriptLanguage;
+use rowan::{GreenNodeBuilder, Language};
+use std::mem;
+
+use super::event::Event;
+use super::parse::Parse;
+
+pub struct Sink<'t, 'input> {
+    builder: GreenNodeBuilder<'static>,
+    tokens: &'t [Token<'input>],
+    cursor: usize,
+    events: Vec<Event>,
+    errors: Vec<ParseError>,
+}
+
+impl<'t, 'input> Sink<'t, 'input> {
+    pub fn new(tokens: &'t [Token<'input>], events: Vec<Event>) -> Self {
+        Self {
+            builder: GreenNodeBuilder::new(),
+            tokens,
+            cursor: 0,
+            events,
+            errors: Vec::new(),
+        }
+    }
+
+    pub fn finish(mut self) -> Parse {
+        for idx in 0..self.events.len() {
+            match mem::replace(&mut self.events[idx], Event::Placeholder) {
+                Event::StartNode {
+                    kind,
+                    forward_parent,
+                } => {
+                    let mut kinds = vec![kind];
+
+                    let mut idx = idx;
+                    let mut forward_parent = forward_parent;
+
+                    // Walk through the forward parent of the forward parent and the forward parent
+                    // of that, and of that, etc. until we reach a StartNode event without a forward
+                    // parent.
+                    while let Some(fp) = forward_parent {
+                        idx += fp;
+
+                        forward_parent = if let Event::StartNode {
+                            kind,
+                            forward_parent,
+                        } =
+                            mem::replace(&mut self.events[idx], Event::Placeholder)
+                        {
+                            kinds.push(kind);
+                            forward_parent
+                        } else {
+                            unreachable!()
+                        };
+                    }
+
+                    for kind in kinds.into_iter().rev() {
+                        self.builder
+                            .start_node(ErgoScriptLanguage::kind_to_raw(kind));
+                    }
+                }
+                Event::AddToken => self.token(),
+                Event::FinishNode => self.builder.finish_node(),
+                Event::Error(error) => self.errors.push(error),
+                Event::Placeholder => {}
+            }
+
+            self.eat_trivia();
+        }
+
+        Parse {
+            green_node: self.builder.finish(),
+            errors: self.errors,
+        }
+    }
+
+    fn eat_trivia(&mut self) {
+        while let Some(token) = self.tokens.get(self.cursor) {
+            if !token.kind.is_trivia() {
+                break;
+            }
+
+            self.token();
+        }
+    }
+
+    fn token(&mut self) {
+        let Token { kind, text, .. } = self.tokens[self.cursor];
+
+        self.builder
+            .token(ErgoScriptLanguage::kind_to_raw(kind.into()), text);
+
+        self.cursor += 1;
+    }
+}
diff --git a/ergoscript-compiler/src/parser/source.rs b/ergoscript-compiler/src/parser/source.rs
new file mode 100644
index 000000000..efb7ff65d
--- /dev/null
+++ b/ergoscript-compiler/src/parser/source.rs
@@ -0,0 +1,56 @@
+use text_size::TextRange;
+
+use crate::lexer::Token;
+use crate::lexer::TokenKind;
+
+pub struct Source<'t, 'input> {
+    tokens: &'t [Token<'input>],
+    cursor: usize,
+}
+
+impl<'t, 'input> Source<'t, 'input> {
+    pub fn new(tokens: &'t [Token<'input>]) -> Self {
+        Self { tokens, cursor: 0 }
+    }
+
+    pub fn next_token(&mut self) -> Option<&'t Token<'input>> {
+        self.eat_trivia();
+
+        let token = self.tokens.get(self.cursor)?;
+        self.cursor += 1;
+
+        Some(token)
+    }
+
+    pub fn peek_kind(&mut self) -> Option<TokenKind> {
+        self.eat_trivia();
+        self.peek_kind_raw()
+    }
+
+    pub fn peek_token(&mut self) -> Option<&Token> {
+        self.eat_trivia();
+        self.peek_token_raw()
+    }
+
+    fn eat_trivia(&mut self) {
+        while self.at_trivia() {
+            self.cursor += 1;
+        }
+    }
+
+    fn at_trivia(&self) -> bool {
+        self.peek_kind_raw().map_or(false, TokenKind::is_trivia)
+    }
+
+    pub fn last_token_range(&self) -> Option<TextRange> {
+        self.tokens.last().map(|Token { range, .. }| *range)
+    }
+
+    fn peek_kind_raw(&self) -> Option<TokenKind> {
+        self.peek_token_raw().map(|Token { kind, .. }| *kind)
+    }
+
+    fn peek_token_raw(&self) -> Option<&Token> {
+        self.tokens.get(self.cursor)
+    }
+}
diff --git a/ergoscript-compiler/src/script_env.rs b/ergoscript-compiler/src/script_env.rs
new file mode 100644
index 000000000..ff7444540
--- /dev/null
+++ b/ergoscript-compiler/src/script_env.rs
@@ -0,0 +1,26 @@
+//! Compilation environment
+
+use std::collections::HashMap;
+
+use ergotree_ir::mir::constant::Constant;
+
+/// Environment with values substituted for identifiers during compilation
+pub struct ScriptEnv(HashMap<String, Constant>);
+
+impl Default for ScriptEnv {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ScriptEnv {
+    /// Empty environment
+    pub fn new() -> Self {
+        ScriptEnv(HashMap::new())
+    }
+
+    /// Returns the value([`Constant`]) for the given identifier (if any)
+    pub fn get(&self, ident: &str) -> Option<&Constant> {
+        self.0.get(ident)
+    }
+}
diff --git a/ergoscript-compiler/src/syntax.rs b/ergoscript-compiler/src/syntax.rs
new file mode 100644
index 000000000..4ca646e00
--- /dev/null
+++ b/ergoscript-compiler/src/syntax.rs
@@ -0,0 +1,75 @@
+// Initial version is copied from https://github.com/arzg/eldiro
+// Checkout https://arzg.github.io/lang/ for description
+use super::lexer::TokenKind;
+use num_derive::{FromPrimitive, ToPrimitive};
+use num_traits::{FromPrimitive, ToPrimitive};
+
+#[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
+pub enum SyntaxKind {
+    Whitespace,
+    FnKw,
+    ValKw,
+    Ident,
+    Number,
+    Plus,
+    Minus,
+    Star,
+    Slash,
+    And,
+    Equals,
+    LParen,
+    RParen,
+    LBrace,
+    RBrace,
+    Comment,
+    Error,
+    Root,
+    InfixExpr,
+    Literal,
+    ParenExpr,
+    PrefixExpr,
+    VariableDef,
+}
+
+impl From<TokenKind> for SyntaxKind {
+    fn from(token_kind: TokenKind) -> Self {
+        match token_kind {
+            TokenKind::Whitespace => Self::Whitespace,
+            TokenKind::FnKw => Self::FnKw,
+            TokenKind::ValKw => Self::ValKw,
+            TokenKind::Ident => Self::Ident,
+            TokenKind::Number => Self::Number,
+            TokenKind::Plus => Self::Plus,
+            TokenKind::Minus => Self::Minus,
+            TokenKind::Star => Self::Star,
+            TokenKind::Slash => Self::Slash,
+            TokenKind::Equals => Self::Equals,
+            TokenKind::LParen => Self::LParen,
+            TokenKind::RParen => Self::RParen,
+            TokenKind::LBrace => Self::LBrace,
+            TokenKind::RBrace => Self::RBrace,
+            TokenKind::Comment => Self::Comment,
+            TokenKind::Error => Self::Error,
+            TokenKind::And => Self::And,
+        }
+    }
+}
+
+pub type SyntaxNode = rowan::SyntaxNode<ErgoScriptLanguage>;
+pub type SyntaxElement = rowan::SyntaxElement<ErgoScriptLanguage>;
+pub type SyntaxToken = rowan::SyntaxToken<ErgoScriptLanguage>;
+
+#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
+pub enum ErgoScriptLanguage {}
+
+impl rowan::Language for ErgoScriptLanguage {
+    type Kind = SyntaxKind;
+
+    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
+        Self::Kind::from_u16(raw.0).unwrap()
+    }
+
+    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
+        rowan::SyntaxKind(kind.to_u16().unwrap())
+    }
+}
diff --git a/ergoscript-compiler/src/type_infer.rs b/ergoscript-compiler/src/type_infer.rs
new file mode 100644
index 000000000..8656b562f
--- /dev/null
+++ b/ergoscript-compiler/src/type_infer.rs
@@ -0,0 +1,107 @@
+use rowan::TextRange;
+
+use crate::error::pretty_error_desc;
+use crate::hir;
+use crate::hir::Binary;
+use crate::hir::Expr;
+use crate::hir::ExprKind;
+
+#[derive(Debug, PartialEq)]
+pub struct TypeInferenceError {
+    msg: String,
+    span: TextRange,
+}
+
+impl TypeInferenceError {
+    pub fn new(msg: String, span: TextRange) -> Self {
+        Self { msg, span }
+    }
+
+    pub fn pretty_desc(&self, source: &str) -> String {
+        pretty_error_desc(&source, self.span, &self.msg)
+    }
+}
+
+pub fn assign_type(expr: Expr) -> Result<Expr, TypeInferenceError> {
+    hir::rewrite(expr, |e| {
+        Ok(match &e.kind {
+            ExprKind::Binary(Binary { op, lhs, rhs }) => match op.node {
+                hir::BinaryOp::Plus => {
+                    let l = assign_type(*lhs.clone())?;
+                    let r = assign_type(*rhs.clone())?;
+                    let tpe = l.tpe.clone();
+                    Some(Expr {
+                        kind: Binary {
+                            op: op.clone(),
+                            lhs: l.into(),
+                            rhs: r.into(),
+                        }
+                        .into(),
+                        span: e.span,
+                        tpe,
+                    })
+                }
+                _ => todo!(),
+            },
+            _ => None,
+        })
+    })
+}
+
+#[cfg(test)]
+pub fn check(input: &str, expected_tree: expect_test::Expect) {
+    let parse = super::parser::parse(input);
+    let syntax = parse.syntax();
+    let root = crate::ast::Root::cast(syntax).unwrap();
+    let hir = hir::lower(root).unwrap();
+    let binder = crate::binder::Binder::new(crate::script_env::ScriptEnv::new());
+    let bind = binder.bind(hir).unwrap();
+    let res = assign_type(bind).unwrap();
+    expected_tree.assert_eq(&res.debug_tree());
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use expect_test::expect;
+
+    #[test]
+    fn bin_smoke() {
+        check(
+            "HEIGHT + HEIGHT",
+            expect![[r#"
+            Expr {
+                kind: Binary(
+                    Binary {
+                        op: Spanned {
+                            node: Plus,
+                            span: 7..8,
+                        },
+                        lhs: Expr {
+                            kind: GlobalVars(
+                                Height,
+                            ),
+                            span: 0..7,
+                            tpe: Some(
+                                SInt,
+                            ),
+                        },
+                        rhs: Expr {
+                            kind: GlobalVars(
+                                Height,
+                            ),
+                            span: 9..15,
+                            tpe: Some(
+                                SInt,
+                            ),
+                        },
+                    },
+                ),
+                span: 0..15,
+                tpe: Some(
+                    SInt,
+                ),
+            }"#]],
+        );
+    }
+}
diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml
new file mode 100644
index 000000000..ca1fbf342
--- /dev/null
+++ b/ergotree-ir/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "ergotree-ir"
+version = "0.5.1"
+license = "CC0-1.0"
+authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
+edition = "2018"
+description = "ErgoTree IR, serialization, interpreter"
+repository = "https://github.com/ergoplatform/sigma-rust"
+exclude = [
+    "proptest-regressions/*"
+]
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
+sigma-util = { version = "^0.1.0", path = "../sigma-util" }
+indexmap = "1.3.2"
+k256 = { version = "0.4.2", features = ["rand", "zeroize"] }
+blake2 = "0.9"
+elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
+thiserror = "1"
+num-bigint = "0.3.0"
+rand = "0.7"
+lazy_static = "1.4"
+derive_more = "0.99"
+impl-trait-for-tuples = "0.2.0"
+num = "0.3.1"
+proptest = {version = "1.0.0", optional = true }
+proptest-derive = {version = "0.3.0", optional = true }
+bs58 = "0.4.0"
+base16 = "0.2.1"
+
+[features]
+default = []
+arbitrary = ["proptest", "proptest-derive"]
+
+[dev-dependencies]
+proptest = "1.0.0"
+
diff --git a/ergo-lib/src/chain/address.rs b/ergotree-ir/src/address.rs
similarity index 94%
rename from ergo-lib/src/chain/address.rs
rename to ergotree-ir/src/address.rs
index c2216095a..940ceb063 100644
--- a/ergo-lib/src/chain/address.rs
+++ b/ergotree-ir/src/address.rs
@@ -1,21 +1,19 @@
 //! Address types
 
-use super::digest32;
-use crate::ast::constant::Constant;
-use crate::ast::expr::Expr;
+use crate::ergo_tree::ErgoTree;
+use crate::ergo_tree::ErgoTreeParsingError;
+use crate::mir::constant::Constant;
+use crate::mir::expr::Expr;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::sigma_protocol::dlog_group::EcPoint;
+use crate::sigma_protocol::sigma_boolean::ProveDlog;
+use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
+use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
+use crate::sigma_protocol::sigma_boolean::SigmaProp;
 use crate::types::stype::SType;
-use crate::{
-    ergo_tree::{ErgoTree, ErgoTreeParsingError},
-    serialization::{SerializationError, SigmaSerializable},
-    sigma_protocol::{
-        dlog_group::EcPoint,
-        sigma_boolean::{ProveDlog, SigmaBoolean, SigmaProofOfKnowledgeTree, SigmaProp},
-    },
-};
-use std::{
-    convert::{TryFrom, TryInto},
-    rc::Rc,
-};
+use sigma_util::hash::blake2b256_hash;
+use std::convert::{TryFrom, TryInto};
 use thiserror::Error;
 
 /**
@@ -118,12 +116,12 @@ impl Address {
     /// script encoded in the address
     pub fn script(&self) -> Result<ErgoTree, SerializationError> {
         match self {
-            Address::P2PK(prove_dlog) => Ok(ErgoTree::from(Rc::new(Expr::Const(
+            Address::P2PK(prove_dlog) => Ok(ErgoTree::from(Expr::Const(
                 SigmaProp::new(SigmaBoolean::ProofOfKnowledge(
                     SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog.clone()),
                 ))
                 .into(),
-            )))),
+            ))),
             Address::P2S(bytes) => ErgoTree::sigma_parse_bytes(bytes.to_vec()),
         }
     }
@@ -298,8 +296,7 @@ impl AddressEncoder {
     }
 
     fn calc_checksum(bytes: &[u8]) -> [u8; AddressEncoder::CHECKSUM_LENGTH] {
-        let v: Vec<u8> = digest32::blake2b256_hash(bytes)
-            .0
+        let v: Vec<u8> = blake2b256_hash(bytes)
             .to_vec()
             .into_iter()
             .take(AddressEncoder::CHECKSUM_LENGTH)
@@ -397,10 +394,8 @@ impl AddressEncoder {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::chain::Base16DecodedBytes;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
     use proptest::prelude::*;
 
@@ -415,15 +410,18 @@ mod tests {
                     Ok(dlog) => Address::P2PK(dlog),
                     Err(_) => Address::P2S(t.sigma_serialize_bytes()),
                 }),
-                Just(Address::P2S(
-                    Base16DecodedBytes::try_from(non_parseable_tree)
-                        .unwrap()
-                        .into()
-                ))
+                Just(Address::P2S(base16::decode(non_parseable_tree).unwrap()))
             ]
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ergo_tree.rs b/ergotree-ir/src/ergo_tree.rs
similarity index 90%
rename from ergo-lib/src/ergo_tree.rs
rename to ergotree-ir/src/ergo_tree.rs
index 55496a04c..237b8a8e8 100644
--- a/ergo-lib/src/ergo_tree.rs
+++ b/ergotree-ir/src/ergo_tree.rs
@@ -1,7 +1,7 @@
 //! ErgoTree
-use crate::ast::constant::Constant;
-use crate::ast::constant::TryExtractFromError;
-use crate::ast::expr::Expr;
+use crate::mir::constant::Constant;
+use crate::mir::constant::TryExtractFromError;
+use crate::mir::expr::Expr;
 use crate::serialization::{
     sigma_byte_reader::{SigmaByteRead, SigmaByteReader},
     sigma_byte_writer::{SigmaByteWrite, SigmaByteWriter},
@@ -106,18 +106,18 @@ impl ErgoTree {
     }
 
     /// Build ErgoTree using expr as is, without constants segregated
-    pub fn without_segregation(expr: Rc<Expr>) -> ErgoTree {
+    pub fn without_segregation(expr: Expr) -> ErgoTree {
         ErgoTree {
             header: ErgoTree::DEFAULT_HEADER,
             tree: Ok(ParsedTree {
                 constants: Vec::new(),
-                root: Ok(expr),
+                root: Ok(Rc::new(expr)),
             }),
         }
     }
 
     /// Build ErgoTree with constants segregated from expr
-    pub fn with_segregation(expr: Rc<Expr>) -> ErgoTree {
+    pub fn with_segregation(expr: &Expr) -> ErgoTree {
         let mut data = Vec::new();
         let mut cs = ConstantStore::empty();
         let mut w = SigmaByteWriter::new(&mut data, Some(&mut cs));
@@ -136,18 +136,24 @@ impl ErgoTree {
             }),
         }
     }
+
+    /// Prints with newlines
+    pub fn debug_tree(&self) -> String {
+        let tree = format!("{:#?}", self);
+        tree
+    }
 }
 
-impl From<Rc<Expr>> for ErgoTree {
-    fn from(expr: Rc<Expr>) -> Self {
-        match expr.as_ref() {
+impl From<Expr> for ErgoTree {
+    fn from(expr: Expr) -> Self {
+        match &expr {
             Expr::Const(c) => match c {
                 Constant { tpe, .. } if *tpe == SType::SSigmaProp => {
                     ErgoTree::without_segregation(expr)
                 }
-                _ => ErgoTree::with_segregation(expr),
+                _ => ErgoTree::with_segregation(&expr),
             },
-            _ => ErgoTree::with_segregation(expr),
+            _ => ErgoTree::with_segregation(&expr),
         }
     }
 }
@@ -291,17 +297,9 @@ impl TryFrom<ErgoTree> for ProveDlog {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    #![allow(unused_imports)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-    use crate::ast::value::Value;
-    use crate::chain;
-    use crate::chain::Base16DecodedBytes;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
-    use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
-    use crate::sigma_protocol::sigma_boolean::SigmaProp;
     use proptest::prelude::*;
 
     impl Arbitrary for ErgoTree {
@@ -311,11 +309,22 @@ mod tests {
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             prop_oneof![
                 // make sure that P2PK tree is included
-                any::<ProveDlog>().prop_map(|p| ErgoTree::from(Rc::new(Expr::Const(p.into())))),
+                any::<ProveDlog>().prop_map(|p| ErgoTree::from(Expr::Const(p.into()))),
             ]
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::address::AddressEncoder;
+    use crate::address::NetworkPrefix;
+    use crate::mir::value::Value;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
@@ -346,13 +355,11 @@ mod tests {
 
     #[test]
     fn test_constant_segregation_header_flag_support() {
-        let encoder = chain::address::AddressEncoder::new(chain::address::NetworkPrefix::Mainnet);
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
         let address = encoder
             .parse_address_from_str("9hzP24a2q8KLPVCUk7gdMDXYc7vinmGuxmLp5KU7k9UwptgYBYV")
             .unwrap();
-
-        let contract = chain::contract::Contract::pay_to_address(&address).unwrap();
-        let bytes = &contract.ergo_tree().sigma_serialize_bytes();
+        let bytes = address.script().unwrap().sigma_serialize_bytes();
         assert_eq!(&bytes[..2], vec![0u8, 8u8].as_slice());
     }
 
@@ -362,7 +369,7 @@ mod tests {
             tpe: SType::SBoolean,
             v: Value::Boolean(true),
         });
-        let ergo_tree = ErgoTree::with_segregation(Rc::new(expr.clone()));
+        let ergo_tree = ErgoTree::with_segregation(&expr);
         let bytes = ergo_tree.sigma_serialize_bytes();
         let parsed_expr = ErgoTree::sigma_parse_bytes(bytes)
             .unwrap()
diff --git a/ergo-lib/src/eval.rs b/ergotree-ir/src/eval.rs
similarity index 83%
rename from ergo-lib/src/eval.rs
rename to ergotree-ir/src/eval.rs
index 85edc458d..dbf137e2a 100644
--- a/ergo-lib/src/eval.rs
+++ b/ergotree-ir/src/eval.rs
@@ -1,10 +1,10 @@
 //! Interpreter
 use std::rc::Rc;
 
-use crate::ast::constant::TryExtractFromError;
-use crate::ast::expr::Expr;
-use crate::ast::value::Value;
-use crate::chain::ergo_box::RegisterIdOutOfBounds;
+use crate::ir_ergo_box::IrErgoBoxArenaError;
+use crate::mir::constant::TryExtractFromError;
+use crate::mir::expr::Expr;
+use crate::mir::value::Value;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 
 use cost_accum::CostAccumulator;
@@ -14,10 +14,11 @@ use self::context::Context;
 use self::cost_accum::CostError;
 use self::env::Env;
 
-pub(crate) mod context;
+pub mod context;
+pub mod env;
+
 pub(crate) mod cost_accum;
 pub(crate) mod costs;
-pub(crate) mod env;
 pub(crate) mod expr;
 pub(crate) mod global_vars;
 pub(crate) mod method_call;
@@ -30,7 +31,7 @@ pub enum EvalError {
     #[error("Only boolean or SigmaBoolean is a valid result expr type")]
     InvalidResultType,
     /// Unexpected Expr encountered during the evaluation
-    #[error("Unexpected Expr: {0:?}")]
+    #[error("Unexpected Expr: {0}")]
     UnexpectedExpr(String),
     /// Error on cost calculation
     #[error("Error on cost calculation: {0:?}")]
@@ -42,16 +43,19 @@ pub enum EvalError {
     #[error("Not found: {0}")]
     NotFound(String),
     /// Register id out of bounds
-    #[error("{0:?}")]
-    RegisterIdOutOfBounds(#[from] RegisterIdOutOfBounds),
+    #[error("{0}")]
+    RegisterIdOutOfBounds(String),
     /// Unexpected value
-    #[error("Unexpected value: {0:?}")]
+    #[error("Unexpected value: {0}")]
     UnexpectedValue(String),
     /// Arithmetic exception error
-    #[error("Arithmetic exception: {0:?}")]
+    #[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:?}")]
+    #[error("error: {0}")]
     Misc(String),
 }
 
@@ -91,6 +95,7 @@ pub trait Evaluator {
     }
 }
 
+#[derive(Debug)]
 pub struct EvalContext {
     pub ctx: Rc<Context>,
     pub cost_accum: CostAccumulator,
@@ -110,18 +115,19 @@ pub trait Evaluable {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 pub mod tests {
 
     #![allow(dead_code)]
 
-    use crate::ast::constant::TryExtractFrom;
-    use crate::ast::constant::TryExtractInto;
+    use crate::mir::constant::TryExtractInto;
+    use crate::{mir::constant::TryExtractFrom, test_util::force_any_val};
 
     use super::env::Env;
     use super::*;
 
     pub fn eval_out_wo_ctx<T: TryExtractFrom<Value>>(expr: &Expr) -> T {
-        let ctx = Rc::new(Context::dummy());
+        let ctx = Rc::new(force_any_val::<Context>());
         eval_out(expr, ctx)
     }
 
@@ -145,7 +151,7 @@ pub mod tests {
     }
 
     pub fn try_eval_out_wo_ctx<T: TryExtractFrom<Value>>(expr: &Expr) -> Result<T, EvalError> {
-        let ctx = Rc::new(Context::dummy());
+        let ctx = Rc::new(force_any_val::<Context>());
         try_eval_out(expr, ctx)
     }
 }
diff --git a/ergotree-ir/src/eval/context.rs b/ergotree-ir/src/eval/context.rs
new file mode 100644
index 000000000..5c8ea1430
--- /dev/null
+++ b/ergotree-ir/src/eval/context.rs
@@ -0,0 +1,66 @@
+use std::rc::Rc;
+
+use crate::ir_ergo_box::IrBoxId;
+use crate::ir_ergo_box::IrErgoBoxArena;
+
+#[derive(Debug)]
+pub struct Context {
+    pub box_arena: Rc<dyn IrErgoBoxArena>,
+    pub height: i32,
+    pub self_box: IrBoxId,
+    pub outputs: Vec<IrBoxId>,
+    pub data_inputs: Vec<IrBoxId>,
+}
+
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use std::collections::HashMap;
+
+    use crate::ir_ergo_box::IrErgoBoxDummy;
+    use crate::ir_ergo_box::IrErgoBoxDummyArena;
+
+    use super::*;
+    use proptest::collection::vec;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Context {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                0..i32::MAX,
+                any::<IrErgoBoxDummy>(),
+                vec(any::<IrErgoBoxDummy>(), 1..3),
+                vec(any::<IrErgoBoxDummy>(), 0..3),
+            )
+                .prop_map(|(height, self_box, outputs, data_inputs)| {
+                    let self_box_id = self_box.id.clone();
+                    let outputs_ids = outputs.iter().map(|b| b.id.clone()).collect();
+                    let data_inputs_ids = data_inputs.iter().map(|b| b.id.clone()).collect();
+                    let mut m = HashMap::new();
+                    m.insert(self_box_id.clone(), self_box);
+                    outputs.into_iter().for_each(|b| {
+                        m.insert(b.id.clone(), b);
+                    });
+                    data_inputs.into_iter().for_each(|b| {
+                        m.insert(b.id.clone(), 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,
+                    }
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+    }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {}
diff --git a/ergo-lib/src/eval/cost_accum.rs b/ergotree-ir/src/eval/cost_accum.rs
similarity index 95%
rename from ergo-lib/src/eval/cost_accum.rs
rename to ergotree-ir/src/eval/cost_accum.rs
index 8f476ec0c..99a191bf0 100644
--- a/ergo-lib/src/eval/cost_accum.rs
+++ b/ergotree-ir/src/eval/cost_accum.rs
@@ -1,7 +1,8 @@
 use super::costs::{Cost, Costs};
-use crate::ast::expr::Expr;
+use crate::mir::expr::Expr;
 use thiserror::Error;
 
+#[derive(Debug)]
 pub struct CostAccumulator {
     costs: Costs,
     accum: u64,
diff --git a/ergo-lib/src/eval/costs.rs b/ergotree-ir/src/eval/costs.rs
similarity index 88%
rename from ergo-lib/src/eval/costs.rs
rename to ergotree-ir/src/eval/costs.rs
index 1d330ec57..d93da89ee 100644
--- a/ergo-lib/src/eval/costs.rs
+++ b/ergotree-ir/src/eval/costs.rs
@@ -1,4 +1,4 @@
-use crate::ast::expr::Expr;
+use crate::mir::expr::Expr;
 
 extern crate derive_more;
 use derive_more::{From, Into};
@@ -6,6 +6,7 @@ use derive_more::{From, Into};
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct Cost(u32);
 
+#[derive(Debug)]
 pub struct Costs {
     pub eq_const_size: Cost,
 }
diff --git a/ergo-lib/src/eval/env.rs b/ergotree-ir/src/eval/env.rs
similarity index 92%
rename from ergo-lib/src/eval/env.rs
rename to ergotree-ir/src/eval/env.rs
index 78accbc9a..0b19740d5 100644
--- a/ergo-lib/src/eval/env.rs
+++ b/ergotree-ir/src/eval/env.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
-use crate::ast::val_def::ValId;
-use crate::ast::value::Value;
+use crate::mir::val_def::ValId;
+use crate::mir::value::Value;
 
 /// Environment for the interpreter
 #[derive(PartialEq, Eq, Debug, Clone)]
diff --git a/ergo-lib/src/eval/expr.rs b/ergotree-ir/src/eval/expr.rs
similarity index 93%
rename from ergo-lib/src/eval/expr.rs
rename to ergotree-ir/src/eval/expr.rs
index 3bcd2adcd..1982ca727 100644
--- a/ergo-lib/src/eval/expr.rs
+++ b/ergotree-ir/src/eval/expr.rs
@@ -1,5 +1,5 @@
-use crate::ast::expr::Expr;
-use crate::ast::value::Value;
+use crate::mir::expr::Expr;
+use crate::mir::value::Value;
 
 use super::Env;
 use super::EvalContext;
@@ -18,7 +18,7 @@ impl Evaluable for Expr {
             Expr::MethodCall(op) => op.eval(env, ctx),
             Expr::ProperyCall(op) => op.eval(env, ctx),
             Expr::BinOp(op) => op.eval(env, ctx),
-            Expr::Context => Ok(Value::Context(ctx.ctx.clone())),
+            Expr::Context => Ok(Value::Context),
             Expr::OptionGet(v) => v.eval(env, ctx),
             Expr::Apply(op) => op.eval(env, ctx),
             Expr::FuncValue(op) => op.eval(env, ctx),
diff --git a/ergo-lib/src/eval/global_vars.rs b/ergotree-ir/src/eval/global_vars.rs
similarity index 83%
rename from ergo-lib/src/eval/global_vars.rs
rename to ergotree-ir/src/eval/global_vars.rs
index 4e01d7dec..063e52d24 100644
--- a/ergo-lib/src/eval/global_vars.rs
+++ b/ergotree-ir/src/eval/global_vars.rs
@@ -1,6 +1,6 @@
-use crate::ast::global_vars::GlobalVars;
-use crate::ast::value::Value;
 use crate::eval::Env;
+use crate::mir::global_vars::GlobalVars;
+use crate::mir::value::Value;
 
 use super::EvalContext;
 use super::EvalError;
@@ -21,12 +21,13 @@ impl Evaluable for GlobalVars {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::chain::ergo_box::ErgoBox;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::ir_ergo_box::IrBoxId;
     use crate::test_util::force_any_val;
 
     use super::*;
@@ -44,7 +45,7 @@ mod tests {
     fn eval_self_box() {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<ErgoBox>(&GlobalVars::SelfBox.into(), ctx.clone()),
+            eval_out::<IrBoxId>(&GlobalVars::SelfBox.into(), ctx.clone()),
             ctx.self_box
         );
     }
@@ -53,7 +54,7 @@ mod tests {
     fn eval_outputs() {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<Vec<ErgoBox>>(&GlobalVars::Outputs.into(), ctx.clone()),
+            eval_out::<Vec<IrBoxId>>(&GlobalVars::Outputs.into(), ctx.clone()),
             ctx.outputs
         );
     }
diff --git a/ergo-lib/src/eval/method_call.rs b/ergotree-ir/src/eval/method_call.rs
similarity index 73%
rename from ergo-lib/src/eval/method_call.rs
rename to ergotree-ir/src/eval/method_call.rs
index 9bd572b37..c1f3f0500 100644
--- a/ergo-lib/src/eval/method_call.rs
+++ b/ergotree-ir/src/eval/method_call.rs
@@ -1,5 +1,5 @@
-use crate::ast::method_call::MethodCall;
-use crate::ast::value::Value;
+use crate::mir::method_call::MethodCall;
+use crate::mir::value::Value;
 
 use super::Env;
 use super::EvalContext;
@@ -11,20 +11,21 @@ impl Evaluable for MethodCall {
         let ov = self.obj.eval(env, ectx)?;
         let argsv: Result<Vec<Value>, EvalError> =
             self.args.iter().map(|arg| arg.eval(env, ectx)).collect();
-        self.method.eval_fn()(ov, argsv?)
+        self.method.eval_fn()(ectx.ctx.clone(), ov, argsv?)
     }
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::constant::Constant;
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::ast::option_get::OptionGet;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::constant::Constant;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::mir::option_get::OptionGet;
     use crate::test_util::force_any_val;
     use crate::types::sbox;
 
@@ -42,7 +43,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&option_get_expr, ctx.clone()),
-            ctx.self_box.value.as_i64()
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
         );
     }
 }
diff --git a/ergo-lib/src/eval/property_call.rs b/ergotree-ir/src/eval/property_call.rs
similarity index 72%
rename from ergo-lib/src/eval/property_call.rs
rename to ergotree-ir/src/eval/property_call.rs
index 9c9d84bdc..556179717 100644
--- a/ergo-lib/src/eval/property_call.rs
+++ b/ergotree-ir/src/eval/property_call.rs
@@ -1,5 +1,5 @@
-use crate::ast::property_call::PropertyCall;
-use crate::ast::value::Value;
+use crate::mir::property_call::PropertyCall;
+use crate::mir::value::Value;
 
 use super::Env;
 use super::EvalContext;
@@ -9,18 +9,19 @@ use super::Evaluable;
 impl Evaluable for PropertyCall {
     fn eval(&self, env: &Env, ectx: &mut EvalContext) -> Result<Value, EvalError> {
         let ov = self.obj.eval(env, ectx)?;
-        self.method.eval_fn()(ov, vec![])
+        self.method.eval_fn()(ectx.ctx.clone(), ov, vec![])
     }
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::expr::Expr;
-    use crate::chain::ergo_box::ErgoBox;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::ir_ergo_box::IrBoxId;
+    use crate::mir::expr::Expr;
     use crate::test_util::force_any_val;
     use crate::types::scontext;
 
@@ -34,6 +35,6 @@ mod tests {
         }
         .into();
         let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(eval_out::<Vec<ErgoBox>>(&pc, ctx.clone()), ctx.data_inputs);
+        assert_eq!(eval_out::<Vec<IrBoxId>>(&pc, ctx.clone()), ctx.data_inputs);
     }
 }
diff --git a/ergotree-ir/src/ir_ergo_box.rs b/ergotree-ir/src/ir_ergo_box.rs
new file mode 100644
index 000000000..f22a96244
--- /dev/null
+++ b/ergotree-ir/src/ir_ergo_box.rs
@@ -0,0 +1,139 @@
+use crate::mir::constant::Constant;
+use sigma_util::DIGEST32_SIZE;
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::rc::Rc;
+use thiserror::Error;
+
+// TODO: use Digest32 newtype
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
+pub struct IrBoxId(pub [u8; DIGEST32_SIZE]);
+
+impl IrBoxId {
+    pub fn new(id: [u8; DIGEST32_SIZE]) -> Self {
+        IrBoxId(id)
+    }
+
+    pub fn get_box(
+        &self,
+        arena: &Rc<dyn IrErgoBoxArena>,
+    ) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError> {
+        arena.get(self)
+    }
+}
+
+pub trait IrErgoBoxArena: Debug {
+    fn get(&self, id: &IrBoxId) -> Result<Rc<dyn IrErgoBox>, IrErgoBoxArenaError>;
+}
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub 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(Error, PartialEq, Eq, Debug, Clone)]
+#[error("IrErgoBoxArenaError: {0}")]
+pub struct IrErgoBoxArenaError(pub String);
+
+pub trait IrErgoBox: Debug {
+    fn id(&self) -> &[u8; DIGEST32_SIZE];
+    fn value(&self) -> i64;
+    fn tokens(&self) -> Vec<(Vec<i8>, i64)>;
+    /// R4-R9 optional registere, where element with index 0 is R4, etc.
+    fn additional_registers(&self) -> &[Constant];
+    fn get_register(&self, id: i8) -> Option<Constant>;
+    fn creation_height(&self) -> i32;
+    fn script_bytes(&self) -> Vec<u8>;
+}
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct IrErgoBoxDummy {
+    pub id: IrBoxId,
+    pub value: i64,
+    pub tokens: Vec<(Vec<i8>, i64)>,
+    pub additional_registers: Vec<Constant>,
+    pub creation_height: i32,
+    pub script_bytes: Vec<u8>,
+}
+
+impl IrErgoBox for IrErgoBoxDummy {
+    fn id(&self) -> &[u8; DIGEST32_SIZE] {
+        &self.id.0
+    }
+
+    fn value(&self) -> i64 {
+        self.value
+    }
+
+    fn tokens(&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()),
+            _ => self.additional_registers.get(id as usize).cloned(),
+        }
+    }
+
+    fn creation_height(&self) -> i32 {
+        self.creation_height
+    }
+
+    fn script_bytes(&self) -> Vec<u8> {
+        self.script_bytes.clone()
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::util::AsVecI8;
+
+    use super::*;
+    use num::abs;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    impl Arbitrary for IrErgoBoxDummy {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                any::<[u8; DIGEST32_SIZE]>(),
+                100000i64..999999999,
+                vec(any::<([u8; DIGEST32_SIZE], u64)>(), 0..3),
+                1i32..1000,
+                vec(any::<Constant>(), 0..5),
+                vec(any::<u8>(), 100..1000),
+            )
+                .prop_map(
+                    |(id, value, tokens, creation_height, additional_registers, script_bytes)| {
+                        Self {
+                            id: IrBoxId(id),
+                            value,
+                            tokens: tokens
+                                .into_iter()
+                                .map(|(id, amount)| (id.to_vec().as_vec_i8(), abs(amount as i64)))
+                                .collect(),
+                            additional_registers,
+                            creation_height,
+                            script_bytes,
+                        }
+                    },
+                )
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+}
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
new file mode 100644
index 000000000..6410806bc
--- /dev/null
+++ b/ergotree-ir/src/lib.rs
@@ -0,0 +1,26 @@
+// Coding conventions
+#![forbid(unsafe_code)]
+#![deny(non_upper_case_globals)]
+#![deny(non_camel_case_types)]
+#![deny(non_snake_case)]
+#![deny(unused_mut)]
+#![deny(dead_code)]
+#![deny(unused_imports)]
+// TODO: add docs and enable
+// #![deny(missing_docs)]
+// Clippy exclusions
+#![allow(clippy::unit_arg)]
+#![deny(broken_intra_doc_links)]
+
+pub mod address;
+pub mod ergo_tree;
+pub mod eval;
+pub mod ir_ergo_box;
+pub mod mir;
+pub mod serialization;
+pub mod sigma_protocol;
+pub mod types;
+pub mod util;
+
+#[cfg(feature = "arbitrary")]
+pub mod test_util;
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
new file mode 100644
index 000000000..1b956e084
--- /dev/null
+++ b/ergotree-ir/src/mir.rs
@@ -0,0 +1,32 @@
+//! Mid-level IR (ErgoTree)
+
+pub mod and;
+pub mod apply;
+pub mod bin_op;
+pub mod block;
+pub mod bool_to_sigma;
+pub mod calc_blake2b256;
+pub mod coll_by_index;
+pub mod coll_filter;
+pub mod coll_fold;
+pub mod coll_map;
+pub mod coll_size;
+pub mod collection;
+pub mod constant;
+pub mod expr;
+pub mod extract_amount;
+pub mod extract_reg_as;
+pub mod extract_script_bytes;
+pub mod func_value;
+pub mod global_vars;
+pub mod if_op;
+pub mod logical_not;
+pub mod method_call;
+pub mod option_get;
+pub mod or;
+pub mod property_call;
+pub mod select_field;
+pub mod upcast;
+pub mod val_def;
+pub mod val_use;
+pub mod value;
diff --git a/ergo-lib/src/ast/and.rs b/ergotree-ir/src/mir/and.rs
similarity index 93%
rename from ergo-lib/src/ast/and.rs
rename to ergotree-ir/src/mir/and.rs
index 45973fa16..da329a364 100644
--- a/ergo-lib/src/ast/and.rs
+++ b/ergotree-ir/src/mir/and.rs
@@ -50,20 +50,10 @@ impl SigmaSerializable for And {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-
-    use proptest::collection;
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use proptest::prelude::*;
 
     impl Arbitrary for And {
@@ -81,6 +71,22 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    use proptest::collection;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/apply.rs b/ergotree-ir/src/mir/apply.rs
similarity index 95%
rename from ergo-lib/src/ast/apply.rs
rename to ergotree-ir/src/mir/apply.rs
index c054e9679..123476081 100644
--- a/ergo-lib/src/ast/apply.rs
+++ b/ergotree-ir/src/mir/apply.rs
@@ -98,17 +98,18 @@ impl SigmaSerializable for Apply {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::bin_op::BinOp;
-    use crate::ast::bin_op::RelationOp;
-    use crate::ast::block::BlockValue;
-    use crate::ast::func_value::*;
-    use crate::ast::val_def::ValDef;
-    use crate::ast::val_use::ValUse;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::bin_op::BinOp;
+    use crate::mir::bin_op::RelationOp;
+    use crate::mir::block::BlockValue;
+    use crate::mir::func_value::*;
+    use crate::mir::val_def::ValDef;
+    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val;
     use crate::types::stype::SType;
diff --git a/ergo-lib/src/ast/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs
similarity index 96%
rename from ergo-lib/src/ast/bin_op.rs
rename to ergotree-ir/src/mir/bin_op.rs
index 54a8780d3..bfd71006d 100644
--- a/ergo-lib/src/ast/bin_op.rs
+++ b/ergotree-ir/src/mir/bin_op.rs
@@ -23,18 +23,24 @@ use super::value::Value;
 extern crate derive_more;
 use derive_more::From;
 
-#[cfg(test)]
+#[cfg(feature = "arbitrary")]
 use proptest_derive::Arbitrary;
 
 /// Operations for numerical types
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 pub enum ArithOp {
+    /// Addition
     Plus,
+    /// Subtraction
     Minus,
+    /// Multiplication
     Multiply,
+    /// Division
     Divide,
+    /// Max of two values
     Max,
+    /// Min of two values
     Min,
 }
 
@@ -51,16 +57,25 @@ impl From<ArithOp> for OpCode {
     }
 }
 
+/// Relational operations
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 pub enum RelationOp {
+    /// Equality
     Eq,
+    /// Non-equality
     NEq,
+    /// Greater of equal
     GE,
+    /// Greater than..
     GT,
+    /// Less or equal
     LE,
+    /// Less then
     LT,
+    /// Logical AND
     And,
+    /// Logical OR
     Or,
 }
 
@@ -81,9 +96,11 @@ impl From<RelationOp> for OpCode {
 
 /// Binary operations
 #[derive(PartialEq, Eq, Debug, Clone, Copy, From)]
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 pub enum BinOpKind {
+    /// Arithmetic operations
     Arith(ArithOp),
+    /// Relation operations (equality, comparison, etc.)
     Relation(RelationOp),
 }
 
@@ -96,18 +113,24 @@ impl From<BinOpKind> for OpCode {
     }
 }
 
+/// Binary operation
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BinOp {
+    /// Operation kind
     pub kind: BinOpKind,
+    /// Left operand
     pub left: Box<Expr>,
+    /// Right operand
     pub right: Box<Expr>,
 }
 
 impl BinOp {
+    /// Op code (serialization)
     pub fn op_code(&self) -> OpCode {
         self.kind.into()
     }
 
+    /// Type
     pub fn tpe(&self) -> SType {
         match self.kind {
             BinOpKind::Relation(_) => SType::SBoolean,
@@ -341,20 +364,12 @@ impl Evaluable for BinOp {
     }
 }
 
-#[cfg(test)]
-pub mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::constant::Constant;
-    use crate::ast::constant::TryExtractFrom;
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::eval::tests::try_eval_out;
-    use crate::test_util::force_any_val;
-    use crate::types::stype::SType;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
+    use proptest::prelude::*;
 
     impl Arbitrary for BinOp {
         type Parameters = ArbExprParams;
@@ -423,6 +438,20 @@ pub mod tests {
             }
         }
     }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::eval::tests::try_eval_out;
+    use crate::mir::constant::Constant;
+    use crate::mir::constant::TryExtractFrom;
+    use crate::test_util::force_any_val;
+
+    use super::*;
 
     fn check_eq_neq(left: Constant, right: Constant) -> bool {
         let eq_op: Expr = BinOp {
diff --git a/ergo-lib/src/ast/block.rs b/ergotree-ir/src/mir/block.rs
similarity index 96%
rename from ergo-lib/src/ast/block.rs
rename to ergotree-ir/src/mir/block.rs
index bd3a41473..eafcf0918 100644
--- a/ergo-lib/src/ast/block.rs
+++ b/ergotree-ir/src/mir/block.rs
@@ -69,9 +69,10 @@ impl SigmaSerializable for BlockValue {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::block::BlockValue;
-    use crate::ast::expr::Expr;
+    use crate::mir::block::BlockValue;
+    use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
 
     use proptest::collection::vec;
diff --git a/ergo-lib/src/ast/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs
similarity index 92%
rename from ergo-lib/src/ast/bool_to_sigma.rs
rename to ergotree-ir/src/mir/bool_to_sigma.rs
index 1c1bd5fea..de1611009 100644
--- a/ergo-lib/src/ast/bool_to_sigma.rs
+++ b/ergotree-ir/src/mir/bool_to_sigma.rs
@@ -52,16 +52,11 @@ impl Evaluable for BoolToSigmaProp {
     }
 }
 
-#[cfg(test)]
-mod tests {
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::serialization::sigma_serialize_roundtrip;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
-
     use proptest::prelude::*;
 
     impl Arbitrary for BoolToSigmaProp {
@@ -79,6 +74,19 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+
+    use crate::eval::tests::eval_out_wo_ctx;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/calc_blake2b256.rs b/ergotree-ir/src/mir/calc_blake2b256.rs
similarity index 93%
rename from ergo-lib/src/ast/calc_blake2b256.rs
rename to ergotree-ir/src/mir/calc_blake2b256.rs
index 5960727d4..07915176d 100644
--- a/ergo-lib/src/ast/calc_blake2b256.rs
+++ b/ergotree-ir/src/mir/calc_blake2b256.rs
@@ -1,4 +1,5 @@
-use crate::chain::blake2b256_hash;
+use sigma_util::hash::blake2b256_hash;
+
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
 use crate::eval::EvalError;
@@ -45,7 +46,7 @@ impl Evaluable for CalcBlake2b256 {
         match input_v.clone() {
             Value::Coll(CollKind::NativeColl(NativeColl::CollByte(coll_byte))) => {
                 let expected_hash: Vec<u8> =
-                    blake2b256_hash(coll_byte.as_vec_u8().as_slice()).0.to_vec();
+                    blake2b256_hash(coll_byte.as_vec_u8().as_slice()).to_vec();
                 Ok(expected_hash.into())
             }
             _ => Err(EvalError::UnexpectedValue(format!(
@@ -67,19 +68,11 @@ impl SigmaSerializable for CalcBlake2b256 {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-    use crate::util::AsVecU8;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
-
     use proptest::prelude::*;
 
     impl Arbitrary for CalcBlake2b256 {
@@ -97,12 +90,28 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+    use crate::util::AsVecU8;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
         #[test]
         fn eval(byte_array in any::<Vec<u8>>()) {
-            let expected_hash = blake2b256_hash(byte_array.as_slice()).0.to_vec();
+            let expected_hash = blake2b256_hash(byte_array.as_slice()).to_vec();
             let expr: Expr = CalcBlake2b256 {
                 input: Box::new(Expr::Const(byte_array.into())),
             }
diff --git a/ergo-lib/src/ast/coll_by_index.rs b/ergotree-ir/src/mir/coll_by_index.rs
similarity index 94%
rename from ergo-lib/src/ast/coll_by_index.rs
rename to ergotree-ir/src/mir/coll_by_index.rs
index d508bf21e..0c39d0720 100644
--- a/ergo-lib/src/ast/coll_by_index.rs
+++ b/ergotree-ir/src/mir/coll_by_index.rs
@@ -119,23 +119,10 @@ impl Evaluable for ByIndex {
     }
 }
 
-#[cfg(test)]
-mod tests {
-
-    use std::rc::Rc;
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::chain::ergo_box::ErgoBox;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use proptest::prelude::*;
     use proptest::result::Probability;
 
@@ -169,6 +156,26 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use crate::ir_ergo_box::IrBoxId;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
@@ -188,7 +195,7 @@ mod tests {
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
-            eval_out::<ErgoBox>(&expr, ctx.clone()),
+            eval_out::<IrBoxId>(&expr, ctx.clone()),
             ctx.outputs.get(0).unwrap().clone()
         );
     }
diff --git a/ergo-lib/src/ast/coll_filter.rs b/ergotree-ir/src/mir/coll_filter.rs
similarity index 91%
rename from ergo-lib/src/ast/coll_filter.rs
rename to ergotree-ir/src/mir/coll_filter.rs
index 11065bf14..e65068416 100644
--- a/ergo-lib/src/ast/coll_filter.rs
+++ b/ergotree-ir/src/mir/coll_filter.rs
@@ -135,28 +135,11 @@ impl Evaluable for Filter {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::bin_op::BinOp;
-    use crate::ast::bin_op::RelationOp;
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::ast::extract_amount::ExtractAmount;
-    use crate::ast::func_value::FuncArg;
-    use crate::ast::func_value::FuncValue;
-    use crate::ast::property_call::PropertyCall;
-    use crate::ast::val_use::ValUse;
-    use crate::chain::ergo_box::ErgoBox;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::scontext;
-    use crate::types::sfunc::SFunc;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-
+    use crate::mir::expr::arbitrary::ArbExprParams;
+    use crate::types::sfunc::SFunc;
     use proptest::prelude::*;
 
     impl Arbitrary for Filter {
@@ -182,6 +165,30 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::ir_ergo_box::IrBoxId;
+    use crate::mir::bin_op::BinOp;
+    use crate::mir::bin_op::RelationOp;
+    use crate::mir::expr::Expr;
+    use crate::mir::extract_amount::ExtractAmount;
+    use crate::mir::func_value::FuncArg;
+    use crate::mir::func_value::FuncValue;
+    use crate::mir::property_call::PropertyCall;
+    use crate::mir::val_use::ValUse;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::types::scontext;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
@@ -223,10 +230,10 @@ mod tests {
             .into();
             let ctx = Rc::new(ctx);
             assert_eq!(
-                eval_out::<Vec<ErgoBox>>(&expr, ctx.clone()),
+                eval_out::<Vec<IrBoxId>>(&expr, ctx.clone()),
                 ctx.data_inputs.clone()
                     .into_iter()
-                    .filter(| b| 1 <= b.value.as_i64()).collect::<Vec<ErgoBox>>()
+                    .filter(| b| 1 <= b.get_box(&ctx.box_arena).unwrap().value()).collect::<Vec<IrBoxId>>()
             );
         }
 
diff --git a/ergo-lib/src/ast/coll_fold.rs b/ergotree-ir/src/mir/coll_fold.rs
similarity index 93%
rename from ergo-lib/src/ast/coll_fold.rs
rename to ergotree-ir/src/mir/coll_fold.rs
index 6bb3e6f18..0962a5e8c 100644
--- a/ergo-lib/src/ast/coll_fold.rs
+++ b/ergotree-ir/src/mir/coll_fold.rs
@@ -126,21 +126,22 @@ impl Evaluable for Fold {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::convert::TryInto;
     use std::rc::Rc;
 
-    use crate::ast::bin_op::ArithOp;
-    use crate::ast::bin_op::BinOp;
-    use crate::ast::expr::Expr;
-    use crate::ast::extract_amount::ExtractAmount;
-    use crate::ast::func_value::FuncArg;
-    use crate::ast::func_value::FuncValue;
-    use crate::ast::property_call::PropertyCall;
-    use crate::ast::select_field::SelectField;
-    use crate::ast::val_use::ValUse;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::bin_op::ArithOp;
+    use crate::mir::bin_op::BinOp;
+    use crate::mir::expr::Expr;
+    use crate::mir::extract_amount::ExtractAmount;
+    use crate::mir::func_value::FuncArg;
+    use crate::mir::func_value::FuncValue;
+    use crate::mir::property_call::PropertyCall;
+    use crate::mir::select_field::SelectField;
+    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::types::scontext;
     use crate::types::stuple::STuple;
@@ -216,7 +217,7 @@ mod tests {
                 eval_out::<i64>(&expr, ctx.clone()),
                 ctx.data_inputs
                     .iter()
-                    .fold(0i64, |acc, b| acc + b.value.as_i64())
+                    .fold(0i64, |acc, b| acc + b.get_box(&ctx.box_arena).unwrap().value())
             );
         }
 
diff --git a/ergo-lib/src/ast/coll_map.rs b/ergotree-ir/src/mir/coll_map.rs
similarity index 92%
rename from ergo-lib/src/ast/coll_map.rs
rename to ergotree-ir/src/mir/coll_map.rs
index a5bb91f41..34e91b9eb 100644
--- a/ergo-lib/src/ast/coll_map.rs
+++ b/ergotree-ir/src/mir/coll_map.rs
@@ -132,27 +132,10 @@ impl Evaluable for Map {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::bin_op::ArithOp;
-    use crate::ast::bin_op::BinOp;
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::ast::extract_amount::ExtractAmount;
-    use crate::ast::func_value::FuncArg;
-    use crate::ast::func_value::FuncValue;
-    use crate::ast::property_call::PropertyCall;
-    use crate::ast::val_use::ValUse;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::scontext;
-    use crate::types::sfunc::SFunc;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use proptest::prelude::*;
 
     impl Arbitrary for Map {
@@ -178,6 +161,29 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::mir::bin_op::ArithOp;
+    use crate::mir::bin_op::BinOp;
+    use crate::mir::expr::Expr;
+    use crate::mir::extract_amount::ExtractAmount;
+    use crate::mir::func_value::FuncArg;
+    use crate::mir::func_value::FuncValue;
+    use crate::mir::property_call::PropertyCall;
+    use crate::mir::val_use::ValUse;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::types::scontext;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
@@ -222,7 +228,7 @@ mod tests {
                 eval_out::<Vec<i64>>(&expr, ctx.clone()),
                 ctx.data_inputs
                     .iter()
-                    .map(| b| b.value.as_i64() + 1).collect::<Vec<i64>>()
+                    .map(| b| b.get_box(&ctx.box_arena).unwrap().value() + 1).collect::<Vec<i64>>()
             );
         }
 
diff --git a/ergo-lib/src/ast/coll_size.rs b/ergotree-ir/src/mir/coll_size.rs
similarity index 92%
rename from ergo-lib/src/ast/coll_size.rs
rename to ergotree-ir/src/mir/coll_size.rs
index da97ef5bf..671948872 100644
--- a/ergo-lib/src/ast/coll_size.rs
+++ b/ergotree-ir/src/mir/coll_size.rs
@@ -67,21 +67,12 @@ impl Evaluable for SizeOf {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use crate::types::stype_param::STypeVar;
 
     use super::*;
-
     use proptest::prelude::*;
 
     impl Arbitrary for SizeOf {
@@ -99,6 +90,23 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/collection.rs b/ergotree-ir/src/mir/collection.rs
similarity index 96%
rename from ergo-lib/src/ast/collection.rs
rename to ergotree-ir/src/mir/collection.rs
index 2d0115fb5..2063febfe 100644
--- a/ergo-lib/src/ast/collection.rs
+++ b/ergotree-ir/src/mir/collection.rs
@@ -125,13 +125,10 @@ pub fn bool_const_coll_sigma_parse<R: SigmaByteRead>(
     Ok(Collection::BoolConstants(bools))
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::ast::constant::tests::ArbConstantParams;
-    use crate::ast::constant::Constant;
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::serialization::sigma_serialize_roundtrip;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::constant::arbitrary::ArbConstantParams;
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
     use proptest::collection::*;
@@ -154,6 +151,16 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use crate::mir::expr::arbitrary::ArbExprParams;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/constant.rs b/ergotree-ir/src/mir/constant.rs
similarity index 91%
rename from ergo-lib/src/ast/constant.rs
rename to ergotree-ir/src/mir/constant.rs
index a5d25e6d8..758d56c49 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -1,21 +1,13 @@
 //! Constant(Literal) IR node
 
-use crate::chain::ergo_box::BoxValue;
-use crate::chain::ergo_box::ErgoBox;
-use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
+use crate::ir_ergo_box::IrBoxId;
 use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
+use crate::sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp};
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
-use crate::{
-    serialization::{SerializationError, SigmaSerializable},
-    sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp},
-};
 use impl_trait_for_tuples::impl_for_tuples;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
-use std::convert::TryFrom;
 use std::convert::TryInto;
 
 mod constant_placeholder;
@@ -30,11 +22,6 @@ use super::value::Value;
 use thiserror::Error;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(
-    feature = "json",
-    serde(into = "Base16EncodedBytes", try_from = "Base16DecodedBytes")
-)]
 /// Constant
 pub struct Constant {
     /// Constant type
@@ -43,27 +30,6 @@ pub struct Constant {
     pub v: Value,
 }
 
-impl From<Constant> for Base16EncodedBytes {
-    fn from(v: Constant) -> Base16EncodedBytes {
-        Base16EncodedBytes::new(&v.sigma_serialize_bytes())
-    }
-}
-
-impl TryFrom<Base16DecodedBytes> for Constant {
-    type Error = SerializationError;
-    fn try_from(bytes: Base16DecodedBytes) -> Result<Self, Self::Error> {
-        Constant::sigma_parse_bytes(bytes.0)
-    }
-}
-
-impl Constant {
-    /// Serialized bytes encoded as Base16
-    pub fn base16_str(&self) -> String {
-        let base16_bytes: Base16EncodedBytes = self.clone().into();
-        base16_bytes.into()
-    }
-}
-
 impl From<bool> for Constant {
     fn from(v: bool) -> Constant {
         Constant {
@@ -127,8 +93,8 @@ impl From<EcPoint> for Constant {
     }
 }
 
-impl From<ErgoBox> for Constant {
-    fn from(b: ErgoBox) -> Self {
+impl From<IrBoxId> for Constant {
+    fn from(b: IrBoxId) -> Self {
         Constant {
             tpe: SType::SBox,
             v: b.into(),
@@ -136,12 +102,6 @@ impl From<ErgoBox> for Constant {
     }
 }
 
-impl From<BoxValue> for Constant {
-    fn from(v: BoxValue) -> Self {
-        v.as_i64().into()
-    }
-}
-
 impl From<Vec<u8>> for Constant {
     fn from(v: Vec<u8>) -> Self {
         Constant {
@@ -231,13 +191,12 @@ impl<T: TryExtractFrom<Value>> TryExtractFrom<Constant> for T {
     }
 }
 
-#[cfg(test)]
-pub mod tests {
-    use core::fmt;
-
-    use crate::types::stuple::STuple;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use std::convert::TryFrom;
 
     use super::*;
+    use crate::types::stuple::STuple;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
@@ -379,6 +338,13 @@ pub mod tests {
             }
         }
     }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+    use core::fmt;
+    use proptest::prelude::*;
 
     fn test_constant_roundtrip<T>(v: T)
     where
diff --git a/ergo-lib/src/ast/constant/constant_placeholder.rs b/ergotree-ir/src/mir/constant/constant_placeholder.rs
similarity index 100%
rename from ergo-lib/src/ast/constant/constant_placeholder.rs
rename to ergotree-ir/src/mir/constant/constant_placeholder.rs
diff --git a/ergo-lib/src/ast/expr.rs b/ergotree-ir/src/mir/expr.rs
similarity index 96%
rename from ergo-lib/src/ast/expr.rs
rename to ergotree-ir/src/mir/expr.rs
index 61305c5e2..6fed417f3 100644
--- a/ergo-lib/src/ast/expr.rs
+++ b/ergotree-ir/src/mir/expr.rs
@@ -1,3 +1,5 @@
+//! IR expression
+
 use std::convert::TryFrom;
 use std::convert::TryInto;
 
@@ -53,6 +55,7 @@ pub enum Expr {
     /// Predefined functions (global)
     /// Blake2b256 hash calculation
     CalcBlake2b256(CalcBlake2b256),
+    /// Context variables (external)
     Context,
     // Global(Global),
     /// Predefined global variables
@@ -180,6 +183,7 @@ impl Expr {
         }
     }
 
+    /// Type expected after the evaluation
     pub fn post_eval_tpe(&self) -> SType {
         match self.tpe() {
             SType::SFunc(sfunc) => *sfunc.t_range,
@@ -187,6 +191,7 @@ impl Expr {
         }
     }
 
+    /// Check if given expected_tpe type is the same as the expression's post-evaluation type
     pub fn check_post_eval_tpe(&self, expected_tpe: SType) -> Result<(), InvalidExprEvalTypeError> {
         let expr_tpe = self.post_eval_tpe();
         if expr_tpe == expected_tpe {
@@ -198,12 +203,19 @@ impl Expr {
             )))
         }
     }
+
+    /// Prints the tree with newlines
+    pub fn debug_tree(&self) -> String {
+        let tree = format!("{:#?}", self);
+        tree
+    }
 }
 
 /// Unexpected argument on node construction (i.e non-Option input in OptionGet)
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct InvalidArgumentError(pub String);
 
+/// Invalid (unexpected) expr type
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct InvalidExprEvalTypeError(pub String);
 
@@ -226,12 +238,10 @@ impl<T: TryFrom<Expr>> TryExtractFrom<Expr> for T {
     }
 }
 
-#[cfg(test)]
-pub mod tests {
-    #![allow(unused_imports)]
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-    use crate::ast::func_value::FuncArg;
-    use crate::sigma_protocol::sigma_boolean::SigmaProp;
+    use crate::mir::func_value::FuncArg;
     use crate::types::sfunc::SFunc;
     use proptest::collection::*;
     use proptest::prelude::*;
@@ -390,3 +400,6 @@ pub mod tests {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {}
diff --git a/ergo-lib/src/ast/extract_amount.rs b/ergotree-ir/src/mir/extract_amount.rs
similarity index 92%
rename from ergo-lib/src/ast/extract_amount.rs
rename to ergotree-ir/src/mir/extract_amount.rs
index 837516331..8493cb3c1 100644
--- a/ergo-lib/src/ast/extract_amount.rs
+++ b/ergotree-ir/src/mir/extract_amount.rs
@@ -39,7 +39,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(b.value.as_i64())),
+            Value::CBox(b) => Ok(Value::Long(ctx.ctx.box_arena.get(&b)?.value())),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractAmount input to be Value::CBox, got {0:?}",
                 input_v
@@ -61,8 +61,9 @@ impl SigmaSerializable for ExtractAmount {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::global_vars::GlobalVars;
+    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
 
     use super::*;
diff --git a/ergo-lib/src/ast/extract_reg_as.rs b/ergotree-ir/src/mir/extract_reg_as.rs
similarity index 83%
rename from ergo-lib/src/ast/extract_reg_as.rs
rename to ergotree-ir/src/mir/extract_reg_as.rs
index d16959f7a..3511a7588 100644
--- a/ergo-lib/src/ast/extract_reg_as.rs
+++ b/ergotree-ir/src/mir/extract_reg_as.rs
@@ -1,9 +1,8 @@
-use crate::chain::ergo_box::ErgoBox;
-use crate::chain::ergo_box::RegisterId;
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
 use crate::eval::EvalError;
 use crate::eval::Evaluable;
+use crate::ir_ergo_box::IrBoxId;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -21,7 +20,7 @@ pub struct ExtractRegisterAs {
     /// Box
     input: Box<Expr>,
     /// Register id to extract value from
-    register_id: RegisterId,
+    register_id: i8,
     /// Result type, to be wrapped in SOption
     elem_tpe: SType,
 }
@@ -29,11 +28,7 @@ pub struct ExtractRegisterAs {
 impl ExtractRegisterAs {
     pub const OP_CODE: OpCode = OpCode::EXTRACT_REGISTER_AS;
 
-    pub fn new(
-        input: Expr,
-        register_id: RegisterId,
-        tpe: SType,
-    ) -> Result<Self, InvalidArgumentError> {
+    pub fn new(input: Expr, register_id: i8, tpe: SType) -> Result<Self, InvalidArgumentError> {
         if input.post_eval_tpe() != SType::SBox {
             return Err(InvalidArgumentError(format!(
                 "expected input to be SBox, got {0:?}",
@@ -66,10 +61,11 @@ impl ExtractRegisterAs {
 
 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>()?;
         Ok(Value::Opt(Box::new(
-            self.input
-                .eval(env, ctx)?
-                .try_extract_into::<ErgoBox>()?
+            ctx.ctx
+                .box_arena
+                .get(&ir_box_id)?
                 .get_register(self.register_id)
                 .map(|c| c.v),
         )))
@@ -79,13 +75,13 @@ impl Evaluable for ExtractRegisterAs {
 impl SigmaSerializable for ExtractRegisterAs {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)?;
-        self.register_id.sigma_serialize(w)?;
+        w.put_i8(self.register_id)?;
         self.elem_tpe.sigma_serialize(w)
     }
 
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
         let input = Expr::sigma_parse(r)?;
-        let register_id = RegisterId::sigma_parse(r)?;
+        let register_id = r.get_i8()?;
         let elem_tpe = SType::sigma_parse(r)?;
         Ok(ExtractRegisterAs::new(
             input,
@@ -96,13 +92,14 @@ impl SigmaSerializable for ExtractRegisterAs {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::global_vars::GlobalVars;
-    use crate::ast::option_get::OptionGet;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::mir::option_get::OptionGet;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val;
 
@@ -112,7 +109,7 @@ mod tests {
     fn eval_box_get_reg() {
         let get_reg_expr: Expr = ExtractRegisterAs::new(
             GlobalVars::SelfBox.into(),
-            RegisterId::R0,
+            0,
             SType::SOption(SType::SLong.into()),
         )
         .unwrap()
@@ -120,14 +117,14 @@ mod tests {
         let option_get_expr: Expr = OptionGet::new(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.value.as_i64());
+        assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
     }
 
     #[test]
     fn ser_roundtrip() {
         let e: Expr = ExtractRegisterAs::new(
             GlobalVars::SelfBox.into(),
-            RegisterId::R0,
+            0,
             SType::SOption(SType::SLong.into()),
         )
         .unwrap()
diff --git a/ergo-lib/src/ast/extract_script_bytes.rs b/ergotree-ir/src/mir/extract_script_bytes.rs
similarity index 91%
rename from ergo-lib/src/ast/extract_script_bytes.rs
rename to ergotree-ir/src/mir/extract_script_bytes.rs
index 3296a5b5b..9fe910604 100644
--- a/ergo-lib/src/ast/extract_script_bytes.rs
+++ b/ergotree-ir/src/mir/extract_script_bytes.rs
@@ -41,7 +41,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(b.ergo_tree.sigma_serialize_bytes().into()),
+            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.script_bytes().into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected ExtractScriptBytes input to be Value::CBox, got {0:?}",
                 input_v
@@ -61,12 +61,13 @@ impl SigmaSerializable for ExtractScriptBytes {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::global_vars::GlobalVars;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val;
 
@@ -90,7 +91,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<u8>>(&e, ctx.clone()),
-            ctx.self_box.ergo_tree.sigma_serialize_bytes()
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().script_bytes()
         );
     }
 }
diff --git a/ergo-lib/src/ast/func_value.rs b/ergotree-ir/src/mir/func_value.rs
similarity index 98%
rename from ergo-lib/src/ast/func_value.rs
rename to ergotree-ir/src/mir/func_value.rs
index 52a31c36e..1daa15b1c 100644
--- a/ergo-lib/src/ast/func_value.rs
+++ b/ergotree-ir/src/mir/func_value.rs
@@ -102,8 +102,9 @@ impl SigmaSerializable for FuncValue {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::expr::Expr;
+    use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
 
     use proptest::collection::vec;
diff --git a/ergo-lib/src/ast/global_vars.rs b/ergotree-ir/src/mir/global_vars.rs
similarity index 93%
rename from ergo-lib/src/ast/global_vars.rs
rename to ergotree-ir/src/mir/global_vars.rs
index b68b7f4b4..45af88d09 100644
--- a/ergo-lib/src/ast/global_vars.rs
+++ b/ergotree-ir/src/mir/global_vars.rs
@@ -1,3 +1,5 @@
+//! Global variables
+
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -15,6 +17,7 @@ pub enum GlobalVars {
 }
 
 impl GlobalVars {
+    /// Op code (serialization)
     pub fn op_code(&self) -> OpCode {
         match self {
             GlobalVars::SelfBox => OpCode::SELF_BOX,
@@ -24,6 +27,7 @@ impl GlobalVars {
         }
     }
 
+    /// Type
     pub fn tpe(&self) -> SType {
         match self {
             GlobalVars::Inputs => SType::SColl(Box::new(SType::SBox)),
@@ -35,6 +39,7 @@ impl GlobalVars {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
     use proptest::prelude::*;
diff --git a/ergo-lib/src/ast/if_op.rs b/ergotree-ir/src/mir/if_op.rs
similarity index 93%
rename from ergo-lib/src/ast/if_op.rs
rename to ergotree-ir/src/mir/if_op.rs
index d80a0184e..489aec9f4 100644
--- a/ergo-lib/src/ast/if_op.rs
+++ b/ergotree-ir/src/mir/if_op.rs
@@ -63,18 +63,10 @@ impl SigmaSerializable for If {
     }
 }
 
-#[cfg(test)]
-mod tests {
-
-    use crate::ast::bin_op::ArithOp;
-    use crate::ast::bin_op::BinOp;
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::serialization::sigma_serialize_roundtrip;
-
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use proptest::prelude::*;
 
     impl Arbitrary for If {
@@ -97,6 +89,21 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+
+    use crate::eval::tests::eval_out_wo_ctx;
+    use crate::mir::bin_op::ArithOp;
+    use crate::mir::bin_op::BinOp;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+
+    use super::*;
+
+    use proptest::prelude::*;
 
     #[test]
     fn eval() {
diff --git a/ergo-lib/src/ast/logical_not.rs b/ergotree-ir/src/mir/logical_not.rs
similarity index 93%
rename from ergo-lib/src/ast/logical_not.rs
rename to ergotree-ir/src/mir/logical_not.rs
index 0bcceff48..18cb3e4fd 100644
--- a/ergo-lib/src/ast/logical_not.rs
+++ b/ergotree-ir/src/mir/logical_not.rs
@@ -50,16 +50,11 @@ impl Evaluable for LogicalNot {
     }
 }
 
-#[cfg(test)]
-mod tests {
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::serialization::sigma_serialize_roundtrip;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
-
     use proptest::prelude::*;
 
     impl Arbitrary for LogicalNot {
@@ -77,6 +72,16 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/method_call.rs b/ergotree-ir/src/mir/method_call.rs
similarity index 100%
rename from ergo-lib/src/ast/method_call.rs
rename to ergotree-ir/src/mir/method_call.rs
diff --git a/ergo-lib/src/ast/option_get.rs b/ergotree-ir/src/mir/option_get.rs
similarity index 91%
rename from ergo-lib/src/ast/option_get.rs
rename to ergotree-ir/src/mir/option_get.rs
index 5f114157c..23f5120a0 100644
--- a/ergo-lib/src/ast/option_get.rs
+++ b/ergotree-ir/src/mir/option_get.rs
@@ -1,10 +1,10 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use crate::ast::value::Value;
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
 use crate::eval::EvalError;
 use crate::eval::Evaluable;
+use crate::mir::value::Value;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -71,15 +71,15 @@ impl SigmaSerializable for OptionGet {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::expr::Expr;
-    use crate::ast::extract_reg_as::ExtractRegisterAs;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::chain::ergo_box::RegisterId;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::expr::Expr;
+    use crate::mir::extract_reg_as::ExtractRegisterAs;
+    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val;
     use crate::types::stype::SType;
@@ -90,7 +90,7 @@ mod tests {
     fn eval_get() {
         let get_reg_expr: Expr = ExtractRegisterAs::new(
             GlobalVars::SelfBox.into(),
-            RegisterId::R0,
+            0,
             SType::SOption(SType::SLong.into()),
         )
         .unwrap()
@@ -101,14 +101,14 @@ 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.value.as_i64());
+        assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
     }
 
     #[test]
     fn ser_roundtrip() {
         let get_reg_expr: Expr = ExtractRegisterAs::new(
             GlobalVars::SelfBox.into(),
-            RegisterId::R0,
+            0,
             SType::SOption(SType::SLong.into()),
         )
         .unwrap()
diff --git a/ergo-lib/src/ast/or.rs b/ergotree-ir/src/mir/or.rs
similarity index 93%
rename from ergo-lib/src/ast/or.rs
rename to ergotree-ir/src/mir/or.rs
index 39e9ad8ee..316e4f51c 100644
--- a/ergo-lib/src/ast/or.rs
+++ b/ergotree-ir/src/mir/or.rs
@@ -50,20 +50,12 @@ impl SigmaSerializable for Or {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::ast::expr::Expr;
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
 
-    use proptest::collection;
     use proptest::prelude::*;
 
     impl Arbitrary for Or {
@@ -81,6 +73,22 @@ mod tests {
             .boxed()
         }
     }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    use proptest::collection;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/ast/property_call.rs b/ergotree-ir/src/mir/property_call.rs
similarity index 100%
rename from ergo-lib/src/ast/property_call.rs
rename to ergotree-ir/src/mir/property_call.rs
diff --git a/ergo-lib/src/ast/select_field.rs b/ergotree-ir/src/mir/select_field.rs
similarity index 99%
rename from ergo-lib/src/ast/select_field.rs
rename to ergotree-ir/src/mir/select_field.rs
index 631d240bc..0f8d8b6cf 100644
--- a/ergo-lib/src/ast/select_field.rs
+++ b/ergotree-ir/src/mir/select_field.rs
@@ -131,6 +131,7 @@ impl SigmaSerializable for SelectField {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::convert::TryInto;
 
diff --git a/ergo-lib/src/ast/upcast.rs b/ergotree-ir/src/mir/upcast.rs
similarity index 95%
rename from ergo-lib/src/ast/upcast.rs
rename to ergotree-ir/src/mir/upcast.rs
index 121a1a64e..864ae53bb 100644
--- a/ergo-lib/src/ast/upcast.rs
+++ b/ergotree-ir/src/mir/upcast.rs
@@ -110,14 +110,12 @@ impl SigmaSerializable for Upcast {
     }
 }
 
-#[cfg(test)]
-pub mod tests {
-
-    use crate::ast::expr::tests::ArbExprParams;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::stype::SType;
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
 
     use super::*;
+    use proptest::prelude::*;
 
     impl Arbitrary for Upcast {
         type Parameters = ();
@@ -135,7 +133,14 @@ pub mod tests {
             .boxed()
         }
     }
+}
 
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+pub mod proptests {
+
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
diff --git a/ergo-lib/src/ast/val_def.rs b/ergotree-ir/src/mir/val_def.rs
similarity index 95%
rename from ergo-lib/src/ast/val_def.rs
rename to ergotree-ir/src/mir/val_def.rs
index 6c5a5534e..fe945b372 100644
--- a/ergo-lib/src/ast/val_def.rs
+++ b/ergotree-ir/src/mir/val_def.rs
@@ -12,11 +12,11 @@ use super::expr::Expr;
 extern crate derive_more;
 use derive_more::From;
 
-#[cfg(test)]
+#[cfg(feature = "arbitrary")]
 use proptest_derive::Arbitrary;
 
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, From)]
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 pub struct ValId(u32);
 
 impl SigmaSerializable for ValId {
@@ -70,6 +70,7 @@ impl SigmaSerializable for ValDef {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use crate::serialization::sigma_serialize_roundtrip;
 
diff --git a/ergo-lib/src/ast/val_use.rs b/ergotree-ir/src/mir/val_use.rs
similarity index 91%
rename from ergo-lib/src/ast/val_use.rs
rename to ergotree-ir/src/mir/val_use.rs
index 272f3fed6..69f7a4d1c 100644
--- a/ergo-lib/src/ast/val_use.rs
+++ b/ergotree-ir/src/mir/val_use.rs
@@ -52,12 +52,13 @@ impl SigmaSerializable for ValUse {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::block::BlockValue;
-    use crate::ast::expr::Expr;
-    use crate::ast::func_value::FuncArg;
-    use crate::ast::func_value::FuncValue;
-    use crate::ast::val_def::ValDef;
+    use crate::mir::block::BlockValue;
+    use crate::mir::expr::Expr;
+    use crate::mir::func_value::FuncArg;
+    use crate::mir::func_value::FuncValue;
+    use crate::mir::val_def::ValDef;
     use crate::serialization::sigma_serialize_roundtrip;
 
     use super::*;
diff --git a/ergo-lib/src/ast/value.rs b/ergotree-ir/src/mir/value.rs
similarity index 94%
rename from ergo-lib/src/ast/value.rs
rename to ergotree-ir/src/mir/value.rs
index dee2f40ff..90b61ae61 100644
--- a/ergo-lib/src/ast/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -2,12 +2,10 @@
 
 use std::convert::TryFrom;
 use std::convert::TryInto;
-use std::rc::Rc;
 
 use impl_trait_for_tuples::impl_for_tuples;
 
-use crate::chain::ergo_box::ErgoBox;
-use crate::eval::context::Context;
+use crate::ir_ergo_box::IrBoxId;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
@@ -113,7 +111,7 @@ pub enum Value {
     /// Sigma property
     SigmaProp(Box<SigmaProp>),
     /// Box
-    CBox(Box<ErgoBox>),
+    CBox(IrBoxId),
     /// AVL tree
     AvlTree,
     /// Collection of values of the same type
@@ -121,7 +119,7 @@ pub enum Value {
     /// Tuple (arbitrary type values)
     Tup(TupleItems<Value>),
     /// Transaction(and blockchain) context info
-    Context(Rc<Context>),
+    Context,
     /// Optional value
     Opt(Box<Option<Value>>),
     /// lambda
@@ -147,11 +145,11 @@ impl Into<Value> for EcPoint {
     }
 }
 
-impl From<ErgoBox> for Value {
-    fn from(b: ErgoBox) -> Self {
-        Value::CBox(Box::new(b))
-    }
-}
+// impl From<Box<dyn IrErgoBox>> for Value {
+//     fn from(b: dyn IrErgoBox) -> Self {
+//         Value::CBox(Box::new(b))
+//     }
+// }
 
 impl From<Vec<i8>> for Value {
     fn from(v: Vec<i8>) -> Self {
@@ -178,7 +176,7 @@ impl StoreWrapped for bool {}
 impl StoreWrapped for i16 {}
 impl StoreWrapped for i32 {}
 impl StoreWrapped for i64 {}
-impl StoreWrapped for ErgoBox {}
+impl StoreWrapped for IrBoxId {}
 impl StoreWrapped for EcPoint {}
 impl StoreWrapped for SigmaProp {}
 impl<T: StoreWrapped> StoreWrapped for Option<T> {}
@@ -276,12 +274,12 @@ impl TryExtractFrom<Value> for SigmaProp {
     }
 }
 
-impl TryExtractFrom<Value> for ErgoBox {
+impl TryExtractFrom<Value> for IrBoxId {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
-            Value::CBox(b) => Ok(*b),
+            Value::CBox(b) => Ok(b),
             _ => Err(TryExtractFromError(format!(
-                "expected ErgoBox, found {:?}",
+                "expected IrErgoBox, found {:?}",
                 c
             ))),
         }
@@ -365,17 +363,17 @@ impl TryFrom<Value> for ProveDlog {
     }
 }
 
-impl TryExtractFrom<Value> for Rc<Context> {
-    fn try_extract_from(v: Value) -> Result<Self, TryExtractFromError> {
-        match v {
-            Value::Context(ctx) => Ok(ctx),
-            _ => Err(TryExtractFromError(format!(
-                "expected Context, found {:?}",
-                v
-            ))),
-        }
-    }
-}
+// impl TryExtractFrom<Value> for Rc<Context> {
+//     fn try_extract_from(v: Value) -> Result<Self, TryExtractFromError> {
+//         match v {
+//             Value::Context(ctx) => Ok(ctx),
+//             _ => Err(TryExtractFromError(format!(
+//                 "expected Context, found {:?}",
+//                 v
+//             ))),
+//         }
+//     }
+// }
 
 impl<T: TryExtractFrom<Value>> TryExtractFrom<Value> for Option<T> {
     fn try_extract_from(v: Value) -> Result<Self, TryExtractFromError> {
@@ -413,6 +411,7 @@ impl TryExtractFrom<Value> for Tuple {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/serialization.rs b/ergotree-ir/src/serialization.rs
similarity index 76%
rename from ergo-lib/src/serialization.rs
rename to ergotree-ir/src/serialization.rs
index 5dfa8f989..75f2ffa99 100644
--- a/ergo-lib/src/serialization.rs
+++ b/ergotree-ir/src/serialization.rs
@@ -12,11 +12,11 @@ mod sigmaboolean;
 mod val_def_type_store;
 
 pub(crate) mod constant_store;
-pub(crate) mod ergo_box;
 pub(crate) mod op_code;
-pub(crate) mod sigma_byte_reader;
-pub(crate) mod sigma_byte_writer;
 pub(crate) mod types;
 
+pub mod sigma_byte_reader;
+pub mod sigma_byte_writer;
+
 mod serializable;
 pub use serializable::*;
diff --git a/ergo-lib/src/serialization/bin_op.rs b/ergotree-ir/src/serialization/bin_op.rs
similarity index 87%
rename from ergo-lib/src/serialization/bin_op.rs
rename to ergotree-ir/src/serialization/bin_op.rs
index fcb765980..b012a1791 100644
--- a/ergo-lib/src/serialization/bin_op.rs
+++ b/ergotree-ir/src/serialization/bin_op.rs
@@ -1,10 +1,10 @@
 use std::io::Error;
 
-use crate::ast::bin_op::BinOp;
-use crate::ast::bin_op::BinOpKind;
-use crate::ast::constant::Constant;
-use crate::ast::constant::TryExtractInto;
-use crate::ast::expr::Expr;
+use crate::mir::bin_op::BinOp;
+use crate::mir::bin_op::BinOpKind;
+use crate::mir::constant::Constant;
+use crate::mir::constant::TryExtractInto;
+use crate::mir::expr::Expr;
 use crate::types::stype::SType;
 
 use super::op_code::OpCode;
@@ -64,11 +64,29 @@ pub fn bin_op_sigma_parse<R: SigmaByteRead>(
     })
 }
 
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod proptests {
+    use super::*;
+    use crate::mir::expr::arbitrary::ArbExprParams;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any_with::<BinOp>(ArbExprParams {tpe: SType::SAny, depth: 0})) {
+            let expr: Expr = v.into();
+            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::ast::bin_op::RelationOp;
-    use crate::ast::expr::tests::ArbExprParams;
+    use crate::mir::bin_op::RelationOp;
+    use crate::mir::expr::arbitrary::ArbExprParams;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::test_util::force_any_val_with;
     use crate::types::stype::SType;
@@ -112,15 +130,4 @@ mod tests {
             }),
         )
     }
-
-    use proptest::prelude::*;
-
-    proptest! {
-
-        #[test]
-        fn ser_roundtrip(v in any_with::<BinOp>(ArbExprParams {tpe: SType::SAny, depth: 0})) {
-            let expr: Expr = v.into();
-            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
-        }
-    }
 }
diff --git a/ergo-lib/src/serialization/constant.rs b/ergotree-ir/src/serialization/constant.rs
similarity index 90%
rename from ergo-lib/src/serialization/constant.rs
rename to ergotree-ir/src/serialization/constant.rs
index ff5c82a5c..717a85ab6 100644
--- a/ergo-lib/src/serialization/constant.rs
+++ b/ergotree-ir/src/serialization/constant.rs
@@ -1,5 +1,5 @@
 use super::{data::DataSerializer, sigma_byte_writer::SigmaByteWrite};
-use crate::ast::constant::Constant;
+use crate::mir::constant::Constant;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
@@ -22,9 +22,10 @@ impl SigmaSerializable for Constant {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::ast::constant::tests::ArbConstantParams;
+    use crate::mir::constant::arbitrary::ArbConstantParams;
     use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
diff --git a/ergo-lib/src/serialization/constant_placeholder.rs b/ergotree-ir/src/serialization/constant_placeholder.rs
similarity index 93%
rename from ergo-lib/src/serialization/constant_placeholder.rs
rename to ergotree-ir/src/serialization/constant_placeholder.rs
index 74ed9540a..1fcea9eff 100644
--- a/ergo-lib/src/serialization/constant_placeholder.rs
+++ b/ergotree-ir/src/serialization/constant_placeholder.rs
@@ -1,5 +1,5 @@
 use super::sigma_byte_writer::SigmaByteWrite;
-use crate::ast::constant::ConstantPlaceholder;
+use crate::mir::constant::ConstantPlaceholder;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
@@ -25,9 +25,10 @@ impl SigmaSerializable for ConstantPlaceholder {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::ast::constant::Constant;
+    use crate::mir::constant::Constant;
     use crate::serialization::{
         constant_store::ConstantStore, sigma_byte_reader::SigmaByteReader,
         sigma_byte_writer::SigmaByteWriter,
diff --git a/ergo-lib/src/serialization/constant_store.rs b/ergotree-ir/src/serialization/constant_store.rs
similarity index 94%
rename from ergo-lib/src/serialization/constant_store.rs
rename to ergotree-ir/src/serialization/constant_store.rs
index 62a280b3d..df9500ae8 100644
--- a/ergo-lib/src/serialization/constant_store.rs
+++ b/ergotree-ir/src/serialization/constant_store.rs
@@ -1,6 +1,6 @@
 //! Constant store for Sigma byte reader
 
-use crate::ast::constant::{Constant, ConstantPlaceholder};
+use crate::mir::constant::{Constant, ConstantPlaceholder};
 
 /// Storage for constants used in ErgoTree constant segregation
 pub struct ConstantStore {
@@ -40,9 +40,10 @@ impl ConstantStore {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::ast::value::Value;
+    use crate::mir::value::Value;
     use crate::types::stype::SType;
 
     #[test]
diff --git a/ergo-lib/src/serialization/data.rs b/ergotree-ir/src/serialization/data.rs
similarity index 96%
rename from ergo-lib/src/serialization/data.rs
rename to ergotree-ir/src/serialization/data.rs
index e2e2db273..cdd3cd08c 100644
--- a/ergo-lib/src/serialization/data.rs
+++ b/ergotree-ir/src/serialization/data.rs
@@ -1,8 +1,8 @@
-use crate::ast::constant::TryExtractFromError;
-use crate::ast::constant::TryExtractInto;
-use crate::ast::value::CollKind;
-use crate::ast::value::NativeColl;
-use crate::ast::value::Value;
+use crate::mir::constant::TryExtractFromError;
+use crate::mir::constant::TryExtractInto;
+use crate::mir::value::CollKind;
+use crate::mir::value::NativeColl;
+use crate::mir::value::Value;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
diff --git a/ergo-lib/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
similarity index 93%
rename from ergo-lib/src/serialization/expr.rs
rename to ergotree-ir/src/serialization/expr.rs
index 2ff81a051..3bd32c849 100644
--- a/ergo-lib/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -1,39 +1,39 @@
 use super::bin_op::bin_op_sigma_parse;
 use super::bin_op::bin_op_sigma_serialize;
 use super::{op_code::OpCode, sigma_byte_writer::SigmaByteWrite};
-use crate::ast::and::And;
-use crate::ast::apply::Apply;
-use crate::ast::bin_op::ArithOp;
-use crate::ast::bin_op::RelationOp;
-use crate::ast::block::BlockValue;
-use crate::ast::bool_to_sigma::BoolToSigmaProp;
-use crate::ast::calc_blake2b256::CalcBlake2b256;
-use crate::ast::coll_by_index::ByIndex;
-use crate::ast::coll_filter::Filter;
-use crate::ast::coll_fold::Fold;
-use crate::ast::coll_map::Map;
-use crate::ast::coll_size::SizeOf;
-use crate::ast::collection::bool_const_coll_sigma_parse;
-use crate::ast::collection::coll_sigma_parse;
-use crate::ast::collection::coll_sigma_serialize;
-use crate::ast::constant::Constant;
-use crate::ast::constant::ConstantPlaceholder;
-use crate::ast::expr::Expr;
-use crate::ast::extract_amount::ExtractAmount;
-use crate::ast::extract_reg_as::ExtractRegisterAs;
-use crate::ast::extract_script_bytes::ExtractScriptBytes;
-use crate::ast::func_value::FuncValue;
-use crate::ast::global_vars::GlobalVars;
-use crate::ast::if_op::If;
-use crate::ast::logical_not::LogicalNot;
-use crate::ast::method_call::MethodCall;
-use crate::ast::option_get::OptionGet;
-use crate::ast::or::Or;
-use crate::ast::property_call::PropertyCall;
-use crate::ast::select_field::SelectField;
-use crate::ast::upcast::Upcast;
-use crate::ast::val_def::ValDef;
-use crate::ast::val_use::ValUse;
+use crate::mir::and::And;
+use crate::mir::apply::Apply;
+use crate::mir::bin_op::ArithOp;
+use crate::mir::bin_op::RelationOp;
+use crate::mir::block::BlockValue;
+use crate::mir::bool_to_sigma::BoolToSigmaProp;
+use crate::mir::calc_blake2b256::CalcBlake2b256;
+use crate::mir::coll_by_index::ByIndex;
+use crate::mir::coll_filter::Filter;
+use crate::mir::coll_fold::Fold;
+use crate::mir::coll_map::Map;
+use crate::mir::coll_size::SizeOf;
+use crate::mir::collection::bool_const_coll_sigma_parse;
+use crate::mir::collection::coll_sigma_parse;
+use crate::mir::collection::coll_sigma_serialize;
+use crate::mir::constant::Constant;
+use crate::mir::constant::ConstantPlaceholder;
+use crate::mir::expr::Expr;
+use crate::mir::extract_amount::ExtractAmount;
+use crate::mir::extract_reg_as::ExtractRegisterAs;
+use crate::mir::extract_script_bytes::ExtractScriptBytes;
+use crate::mir::func_value::FuncValue;
+use crate::mir::global_vars::GlobalVars;
+use crate::mir::if_op::If;
+use crate::mir::logical_not::LogicalNot;
+use crate::mir::method_call::MethodCall;
+use crate::mir::option_get::OptionGet;
+use crate::mir::or::Or;
+use crate::mir::property_call::PropertyCall;
+use crate::mir::select_field::SelectField;
+use crate::mir::upcast::Upcast;
+use crate::mir::val_def::ValDef;
+use crate::mir::val_use::ValUse;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
@@ -175,12 +175,14 @@ impl SigmaSerializable for Expr {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
+    use crate::address::AddressEncoder;
+    use crate::address::NetworkPrefix;
     use std::convert::TryInto;
+    use std::rc::Rc;
 
     use super::*;
-    use crate::chain::address::AddressEncoder;
-    use crate::chain::address::NetworkPrefix;
     use crate::eval::tests::eval_out_wo_ctx;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::sigma_protocol::sigma_boolean::SigmaProp;
@@ -345,7 +347,7 @@ mod tests {
         let p2s_addr_str = "7Nq5tKsVYCgneNgEfA2BJKwGsWozezNLhCNsRBihcHVFkDTuTThd4Qt1bi7NfCK1HuuVfjksMrEftV6MEFajjuyp1TMD2PX7SYWvkg9zH4CtgpdoBjekCNXs5XawxXnW6FT7GCqXTpJUP2TkkuqBh1df99PTigehys36uZz9wQnkrJXrv3mw3Yy4CM622qe5wdqLtpEonjazEmsw8weqEYegDyfJnswDvDkLPXtcCB86i19jik4fnSTtCcYj3jpWCQ7WL5dZn1ivs5JGRsR2ioNCRiZd3Gu1zJBgbHkMg41Z6VeCRWXjGY99BUtgtQiepSHGHajFCVcFAHhVxccdVUPCxGeEL6c2dNx6qzEkVfTfHs5qBgJewR8KCZTCVTurNBHeqCSVdxnfFvhW3f72cNrae5E1UhTAXU2iX4LZMHQsKyefY24Aq1b1srTyRWLpixjbcezFqA2TKjGSn1p1ruxbR7AQpW24ByPKT9sFE9ii4qNeXDnLcGtAAGS9FC5SD1s516a4NCu6v9zZfTvRKGkCwt78J8DEVnhTbttjcsvqFsUXQrvAv7TGVsaT4mL6B7F5BhRoZwFkgRXqFUVCWvgqJrwwjFRtbc5aZz";
         let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
         let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
-        let script = addr.script().unwrap().proposition().unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
         dbg!(&script);
         let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
             .try_into()
@@ -511,7 +513,5 @@ mod tests {
         let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
         let script = addr.script().unwrap().proposition().unwrap();
         dbg!(&script);
-        // let res = try_eval_out_wo_ctx::<SigmaProp>(script.as_ref());
-        // assert!(res.is_err(), "ByIndex should fail on empty collection");
     }
 }
diff --git a/ergo-lib/src/serialization/global_vars.rs b/ergotree-ir/src/serialization/global_vars.rs
similarity index 75%
rename from ergo-lib/src/serialization/global_vars.rs
rename to ergotree-ir/src/serialization/global_vars.rs
index 83498be41..5e7d21ce7 100644
--- a/ergo-lib/src/serialization/global_vars.rs
+++ b/ergotree-ir/src/serialization/global_vars.rs
@@ -1,7 +1,8 @@
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
 
     use proptest::prelude::*;
diff --git a/ergo-lib/src/serialization/method_call.rs b/ergotree-ir/src/serialization/method_call.rs
similarity index 85%
rename from ergo-lib/src/serialization/method_call.rs
rename to ergotree-ir/src/serialization/method_call.rs
index b5a18e956..5774e2a1c 100644
--- a/ergo-lib/src/serialization/method_call.rs
+++ b/ergotree-ir/src/serialization/method_call.rs
@@ -1,7 +1,7 @@
 use std::io::Error;
 
-use crate::ast::expr::Expr;
-use crate::ast::method_call::MethodCall;
+use crate::mir::expr::Expr;
+use crate::mir::method_call::MethodCall;
 use crate::types::smethod::MethodId;
 use crate::types::smethod::SMethod;
 use crate::types::stype_companion::TypeId;
@@ -35,11 +35,12 @@ impl SigmaSerializable for MethodCall {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::constant::Constant;
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::ast::method_call::MethodCall;
+    use crate::mir::constant::Constant;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::mir::method_call::MethodCall;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::types::sbox;
 
diff --git a/ergo-lib/src/serialization/op_code.rs b/ergotree-ir/src/serialization/op_code.rs
similarity index 100%
rename from ergo-lib/src/serialization/op_code.rs
rename to ergotree-ir/src/serialization/op_code.rs
diff --git a/ergo-lib/src/serialization/property_call.rs b/ergotree-ir/src/serialization/property_call.rs
similarity index 88%
rename from ergo-lib/src/serialization/property_call.rs
rename to ergotree-ir/src/serialization/property_call.rs
index 89a126353..2ee3497ca 100644
--- a/ergo-lib/src/serialization/property_call.rs
+++ b/ergotree-ir/src/serialization/property_call.rs
@@ -1,7 +1,7 @@
 use std::io::Error;
 
-use crate::ast::expr::Expr;
-use crate::ast::property_call::PropertyCall;
+use crate::mir::expr::Expr;
+use crate::mir::property_call::PropertyCall;
 use crate::types::smethod::MethodId;
 use crate::types::smethod::SMethod;
 use crate::types::stype_companion::TypeId;
@@ -31,9 +31,10 @@ impl SigmaSerializable for PropertyCall {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
-    use crate::ast::expr::Expr;
-    use crate::ast::property_call::PropertyCall;
+    use crate::mir::expr::Expr;
+    use crate::mir::property_call::PropertyCall;
     use crate::serialization::sigma_serialize_roundtrip;
     use crate::types::scontext;
 
diff --git a/ergo-lib/src/serialization/serializable.rs b/ergotree-ir/src/serialization/serializable.rs
similarity index 98%
rename from ergo-lib/src/serialization/serializable.rs
rename to ergotree-ir/src/serialization/serializable.rs
index 86eaffbb7..5df5cfa3b 100644
--- a/ergo-lib/src/serialization/serializable.rs
+++ b/ergotree-ir/src/serialization/serializable.rs
@@ -1,6 +1,6 @@
 //! Serialization of Ergo types
-use crate::ast::expr::InvalidArgumentError;
-use crate::ast::val_def::ValId;
+use crate::mir::expr::InvalidArgumentError;
+use crate::mir::val_def::ValId;
 
 use super::{
     constant_store::ConstantStore,
@@ -150,7 +150,6 @@ impl<T: SigmaSerializable> SigmaSerializable for Option<Box<T>> {
 }
 
 /// serialization roundtrip
-#[cfg(test)]
 pub fn sigma_serialize_roundtrip<T: SigmaSerializable>(v: &T) -> T {
     let mut data = Vec::new();
     let mut w = SigmaByteWriter::new(&mut data, None);
diff --git a/ergo-lib/src/serialization/sigma_byte_reader.rs b/ergotree-ir/src/serialization/sigma_byte_reader.rs
similarity index 100%
rename from ergo-lib/src/serialization/sigma_byte_reader.rs
rename to ergotree-ir/src/serialization/sigma_byte_reader.rs
diff --git a/ergo-lib/src/serialization/sigma_byte_writer.rs b/ergotree-ir/src/serialization/sigma_byte_writer.rs
similarity index 100%
rename from ergo-lib/src/serialization/sigma_byte_writer.rs
rename to ergotree-ir/src/serialization/sigma_byte_writer.rs
diff --git a/ergo-lib/src/serialization/sigmaboolean.rs b/ergotree-ir/src/serialization/sigmaboolean.rs
similarity index 100%
rename from ergo-lib/src/serialization/sigmaboolean.rs
rename to ergotree-ir/src/serialization/sigmaboolean.rs
diff --git a/ergo-lib/src/serialization/types.rs b/ergotree-ir/src/serialization/types.rs
similarity index 99%
rename from ergo-lib/src/serialization/types.rs
rename to ergotree-ir/src/serialization/types.rs
index d79f0f2be..40cc949f9 100644
--- a/ergo-lib/src/serialization/types.rs
+++ b/ergotree-ir/src/serialization/types.rs
@@ -337,6 +337,7 @@ fn get_arg_type<R: SigmaByteRead>(r: &mut R, prim_id: u8) -> Result<SType, Seria
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
     use crate::serialization::sigma_serialize_roundtrip;
diff --git a/ergo-lib/src/serialization/val_def_type_store.rs b/ergotree-ir/src/serialization/val_def_type_store.rs
similarity index 94%
rename from ergo-lib/src/serialization/val_def_type_store.rs
rename to ergotree-ir/src/serialization/val_def_type_store.rs
index 0acfc98fd..c836f9e46 100644
--- a/ergo-lib/src/serialization/val_def_type_store.rs
+++ b/ergotree-ir/src/serialization/val_def_type_store.rs
@@ -1,6 +1,6 @@
 use std::collections::HashMap;
 
-use crate::ast::val_def::ValId;
+use crate::mir::val_def::ValId;
 use crate::types::stype::SType;
 
 pub struct ValDefTypeStore(HashMap<ValId, SType>);
diff --git a/ergo-lib/src/sigma_protocol.rs b/ergotree-ir/src/sigma_protocol.rs
similarity index 99%
rename from ergo-lib/src/sigma_protocol.rs
rename to ergotree-ir/src/sigma_protocol.rs
index 88bb2faa3..7e4d577bc 100644
--- a/ergo-lib/src/sigma_protocol.rs
+++ b/ergotree-ir/src/sigma_protocol.rs
@@ -120,6 +120,7 @@ pub const SOUNDNESS_BITS: usize = 192;
 pub const SOUNDNESS_BYTES: usize = SOUNDNESS_BITS / 8;
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/sigma_protocol/challenge.rs b/ergotree-ir/src/sigma_protocol/challenge.rs
similarity index 91%
rename from ergo-lib/src/sigma_protocol/challenge.rs
rename to ergotree-ir/src/sigma_protocol/challenge.rs
index 71b4d6a79..48a6ba06b 100644
--- a/ergo-lib/src/sigma_protocol/challenge.rs
+++ b/ergotree-ir/src/sigma_protocol/challenge.rs
@@ -1,11 +1,11 @@
 use super::{fiat_shamir::FiatShamirHash, SOUNDNESS_BYTES};
 use k256::Scalar;
-#[cfg(test)]
+#[cfg(feature = "arbitrary")]
 use proptest_derive::Arbitrary;
 use std::convert::TryInto;
 
 /// Challenge in Sigma protocol
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Challenge(FiatShamirHash);
 
diff --git a/ergo-lib/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs
similarity index 98%
rename from ergo-lib/src/sigma_protocol/dlog_group.rs
rename to ergotree-ir/src/sigma_protocol/dlog_group.rs
index e5d8565ac..5aa763a85 100644
--- a/ergo-lib/src/sigma_protocol/dlog_group.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs
@@ -135,10 +135,9 @@ impl SigmaSerializable for EcPoint {
     }
 }
 
-#[cfg(test)]
-mod tests {
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     impl Arbitrary for EcPoint {
@@ -149,6 +148,13 @@ mod tests {
             prop_oneof![Just(generator()), Just(identity()), Just(random_element()),].boxed()
         }
     }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/sigma_protocol/dlog_protocol.rs b/ergotree-ir/src/sigma_protocol/dlog_protocol.rs
similarity index 98%
rename from ergo-lib/src/sigma_protocol/dlog_protocol.rs
rename to ergotree-ir/src/sigma_protocol/dlog_protocol.rs
index 9389aa033..a3989db62 100644
--- a/ergo-lib/src/sigma_protocol/dlog_protocol.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_protocol.rs
@@ -98,10 +98,12 @@ pub mod interactive_prover {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::super::*;
     use super::*;
     use crate::sigma_protocol::private_input::DlogProverInput;
+
     use proptest::prelude::*;
 
     proptest! {
@@ -109,6 +111,7 @@ mod tests {
         #![proptest_config(ProptestConfig::with_cases(16))]
 
         #[test]
+        #[cfg(feature = "arbitrary")]
         fn test_compute_commitment(secret in any::<DlogProverInput>(), challenge in any::<Challenge>()) {
             let pk = secret.public_image();
             let (r, commitment) = interactive_prover::first_message();
diff --git a/ergo-lib/src/sigma_protocol/fiat_shamir.rs b/ergotree-ir/src/sigma_protocol/fiat_shamir.rs
similarity index 91%
rename from ergo-lib/src/sigma_protocol/fiat_shamir.rs
rename to ergotree-ir/src/sigma_protocol/fiat_shamir.rs
index 54f6cb477..a56d78caa 100644
--- a/ergo-lib/src/sigma_protocol/fiat_shamir.rs
+++ b/ergotree-ir/src/sigma_protocol/fiat_shamir.rs
@@ -6,20 +6,17 @@ use super::{
     unproven_tree::UnprovenTree,
     ProofTree, ProofTreeLeaf, ProverMessage, GROUP_SIZE, SOUNDNESS_BYTES,
 };
-use crate::{ast::expr::Expr, ergo_tree::ErgoTree, serialization::SigmaSerializable};
+use crate::{ergo_tree::ErgoTree, mir::expr::Expr, serialization::SigmaSerializable};
 use blake2::digest::{Update, VariableOutput};
 use blake2::VarBlake2b;
-use std::{
-    convert::{TryFrom, TryInto},
-    rc::Rc,
-};
+use std::convert::{TryFrom, TryInto};
 use thiserror::Error;
 
-#[cfg(test)]
+#[cfg(feature = "arbitrary")]
 use proptest_derive::Arbitrary;
 
 /// Hash type for Fiat-Shamir hash function (24-bytes)
-#[cfg_attr(test, derive(Arbitrary))]
+#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct FiatShamirHash(pub Box<[u8; SOUNDNESS_BYTES]>);
 
@@ -76,9 +73,8 @@ pub fn fiat_shamir_tree_to_bytes(tree: &ProofTree) -> Vec<u8> {
         _ => todo!(),
     };
 
-    let prop_tree = ErgoTree::with_segregation(Rc::new(Expr::Const(
-        SigmaProp::new(leaf.proposition()).into(),
-    )));
+    let prop_tree =
+        ErgoTree::with_segregation(&Expr::Const(SigmaProp::new(leaf.proposition()).into()));
     let mut prop_bytes = prop_tree.sigma_serialize_bytes();
     // TODO: is unwrap safe here? Create new type with non-optional commitment? Decide when other scenarios
     // are implemented (leafs and trees)
diff --git a/ergo-lib/src/sigma_protocol/private_input.rs b/ergotree-ir/src/sigma_protocol/private_input.rs
similarity index 95%
rename from ergo-lib/src/sigma_protocol/private_input.rs
rename to ergotree-ir/src/sigma_protocol/private_input.rs
index 837631fe4..874092dee 100644
--- a/ergo-lib/src/sigma_protocol/private_input.rs
+++ b/ergotree-ir/src/sigma_protocol/private_input.rs
@@ -58,8 +58,8 @@ pub enum PrivateInput {
     DiffieHellmanTupleProverInput,
 }
 
-#[cfg(test)]
-mod tests {
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
     use proptest::prelude::*;
 
@@ -71,3 +71,7 @@ mod tests {
         }
     }
 }
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {}
diff --git a/ergo-lib/src/sigma_protocol/prover.rs b/ergotree-ir/src/sigma_protocol/prover.rs
similarity index 96%
rename from ergo-lib/src/sigma_protocol/prover.rs
rename to ergotree-ir/src/sigma_protocol/prover.rs
index 5d36f6956..a07864782 100644
--- a/ergo-lib/src/sigma_protocol/prover.rs
+++ b/ergotree-ir/src/sigma_protocol/prover.rs
@@ -301,28 +301,30 @@ impl Prover for TestProver {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::ast::constant::Constant;
-    use crate::ast::expr::Expr;
-    use crate::ast::value::Value;
+    use crate::mir::constant::Constant;
+    use crate::mir::expr::Expr;
+    use crate::mir::value::Value;
     use crate::sigma_protocol::private_input::DlogProverInput;
+    use crate::test_util::force_any_val;
     use crate::types::stype::SType;
     use std::rc::Rc;
 
     #[test]
     fn test_prove_true_prop() {
-        let bool_true_tree = ErgoTree::from(Rc::new(Expr::Const(Constant {
+        let bool_true_tree = ErgoTree::from(Expr::Const(Constant {
             tpe: SType::SBoolean,
             v: Value::Boolean(true),
-        })));
+        }));
         let message = vec![0u8; 100];
 
         let prover = TestProver { secrets: vec![] };
         let res = prover.prove(
             &bool_true_tree,
             &Env::empty(),
-            Rc::new(Context::dummy()),
+            Rc::new(force_any_val::<Context>()),
             message.as_slice(),
         );
         assert!(res.is_ok());
@@ -331,17 +333,17 @@ mod tests {
 
     #[test]
     fn test_prove_false_prop() {
-        let bool_false_tree = ErgoTree::from(Rc::new(Expr::Const(Constant {
+        let bool_false_tree = ErgoTree::from(Expr::Const(Constant {
             tpe: SType::SBoolean,
             v: Value::Boolean(false),
-        })));
+        }));
         let message = vec![0u8; 100];
 
         let prover = TestProver { secrets: vec![] };
         let res = prover.prove(
             &bool_false_tree,
             &Env::empty(),
-            Rc::new(Context::dummy()),
+            Rc::new(force_any_val::<Context>()),
             message.as_slice(),
         );
         assert!(res.is_err());
@@ -352,7 +354,7 @@ mod tests {
     fn test_prove_pk_prop() {
         let secret = DlogProverInput::random();
         let pk = secret.public_image();
-        let tree = ErgoTree::from(Rc::new(Expr::Const(pk.into())));
+        let tree = ErgoTree::from(Expr::Const(pk.into()));
         let message = vec![0u8; 100];
 
         let prover = TestProver {
@@ -361,7 +363,7 @@ mod tests {
         let res = prover.prove(
             &tree,
             &Env::empty(),
-            Rc::new(Context::dummy()),
+            Rc::new(force_any_val::<Context>()),
             message.as_slice(),
         );
         assert!(res.is_ok());
diff --git a/ergo-lib/src/sigma_protocol/prover/context_extension.rs b/ergotree-ir/src/sigma_protocol/prover/context_extension.rs
similarity index 63%
rename from ergo-lib/src/sigma_protocol/prover/context_extension.rs
rename to ergotree-ir/src/sigma_protocol/prover/context_extension.rs
index 404c480c5..66c6ce284 100644
--- a/ergo-lib/src/sigma_protocol/prover/context_extension.rs
+++ b/ergotree-ir/src/sigma_protocol/prover/context_extension.rs
@@ -1,25 +1,16 @@
 //! ContextExtension type
-use crate::ast::constant::Constant;
+use crate::mir::constant::Constant;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
 };
 use indexmap::IndexMap;
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
-use std::{convert::TryFrom, io, num::ParseIntError};
+use std::{convert::TryFrom, io};
+use thiserror::Error;
 
 /// User-defined variables to be put into context
 #[derive(Debug, PartialEq, Eq, Clone)]
-#[cfg_attr(
-    feature = "json",
-    derive(Serialize, Deserialize),
-    serde(
-        into = "HashMap<String, Constant>",
-        try_from = "HashMap<String, Constant>"
-    )
-)]
 pub struct ContextExtension {
     /// key-value pairs of variable id and it's value
     pub values: IndexMap<u8, Constant>,
@@ -61,23 +52,51 @@ impl SigmaSerializable for ContextExtension {
     }
 }
 
-impl Into<HashMap<String, Constant>> for ContextExtension {
-    fn into(self) -> HashMap<String, Constant> {
+// for JSON encoding in ergo-lib
+impl Into<HashMap<String, String>> for ContextExtension {
+    fn into(self) -> HashMap<String, String> {
         self.values
             .into_iter()
-            .map(|(k, v)| (format!("{}", k), v))
+            .map(|(k, v)| {
+                (
+                    format!("{}", k),
+                    base16::encode_lower(&v.sigma_serialize_bytes()),
+                )
+            })
             .collect()
     }
 }
 
-impl TryFrom<HashMap<String, Constant>> for ContextExtension {
-    type Error = ParseIntError;
-    fn try_from(values_str: HashMap<String, Constant>) -> Result<Self, Self::Error> {
+/// Error parsing Constant from base16-encoded string
+#[derive(Error, Eq, PartialEq, Debug, Clone)]
+#[error("Error parsing constant: {0}")]
+pub struct ConstantParsingError(pub String);
+
+// for JSON encoding in ergo-lib
+impl TryFrom<HashMap<String, String>> for ContextExtension {
+    type Error = ConstantParsingError;
+    fn try_from(values_str: HashMap<String, String>) -> Result<Self, Self::Error> {
         let values = values_str.iter().try_fold(
             IndexMap::with_capacity(values_str.len()),
             |mut acc, pair| {
-                let idx: u8 = pair.0.parse()?;
-                acc.insert(idx, pair.1.clone());
+                let idx: u8 = pair.0.parse().map_err(|_| {
+                    ConstantParsingError(format!("cannot parse index from {0:?}", pair.0))
+                })?;
+                let constant_bytes = base16::decode(pair.1).map_err(|_| {
+                    ConstantParsingError(format!(
+                        "cannot decode base16 constant bytes from {0:?}",
+                        pair.1
+                    ))
+                })?;
+                acc.insert(
+                    idx,
+                    Constant::sigma_parse_bytes(constant_bytes).map_err(|_| {
+                        ConstantParsingError(format!(
+                            "cannot deserialize constant bytes from {0:?}",
+                            pair.1
+                        ))
+                    })?,
+                );
                 Ok(acc)
             },
         )?;
@@ -85,10 +104,9 @@ impl TryFrom<HashMap<String, Constant>> for ContextExtension {
     }
 }
 
-#[cfg(test)]
-mod tests {
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
     impl Arbitrary for ContextExtension {
@@ -110,6 +128,14 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergo-lib/src/sigma_protocol/prover/prover_result.rs b/ergotree-ir/src/sigma_protocol/prover/prover_result.rs
similarity index 51%
rename from ergo-lib/src/sigma_protocol/prover/prover_result.rs
rename to ergotree-ir/src/sigma_protocol/prover/prover_result.rs
index c1970def5..060d752d2 100644
--- a/ergo-lib/src/sigma_protocol/prover/prover_result.rs
+++ b/ergotree-ir/src/sigma_protocol/prover/prover_result.rs
@@ -1,22 +1,16 @@
 //! ProverResult
+use std::convert::TryFrom;
 use std::io;
 
-use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
+// use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
 };
-#[cfg(feature = "json")]
-use serde::{Deserialize, Serialize};
 
 use super::ContextExtension;
 
 /// Serialized proof generated by ['Prover']
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-#[cfg_attr(
-    feature = "json",
-    serde(into = "Base16EncodedBytes", try_from = "Base16DecodedBytes")
-)]
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
 pub enum ProofBytes {
     /// Empty proof
@@ -25,25 +19,44 @@ pub enum ProofBytes {
     Some(Vec<u8>),
 }
 
-impl Into<Base16EncodedBytes> for ProofBytes {
-    fn into(self) -> Base16EncodedBytes {
+impl Into<Vec<u8>> for ProofBytes {
+    fn into(self) -> Vec<u8> {
         match self {
-            ProofBytes::Empty => Base16EncodedBytes::new(vec![].as_slice()),
-            ProofBytes::Some(bytes) => Base16EncodedBytes::new(&bytes),
+            ProofBytes::Empty => Vec::new(),
+            ProofBytes::Some(bytes) => bytes,
         }
     }
 }
 
-impl From<Base16DecodedBytes> for ProofBytes {
-    fn from(v: Base16DecodedBytes) -> Self {
-        if v.0.is_empty() {
+impl From<Vec<u8>> for ProofBytes {
+    fn from(bytes: Vec<u8>) -> Self {
+        if bytes.is_empty() {
             ProofBytes::Empty
         } else {
-            ProofBytes::Some(v.0)
+            ProofBytes::Some(bytes)
         }
     }
 }
 
+// for JSON encoding in ergo-lib as Base16-encoded string
+impl Into<String> for ProofBytes {
+    fn into(self) -> String {
+        match self {
+            ProofBytes::Empty => "".to_string(),
+            ProofBytes::Some(bytes) => base16::encode_lower(&bytes),
+        }
+    }
+}
+
+// for JSON encoding in ergo-lib
+impl TryFrom<String> for ProofBytes {
+    type Error = base16::DecodeError;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        base16::decode(&value).map(|bytes| bytes.into())
+    }
+}
+
 impl SigmaSerializable for ProofBytes {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         match self {
@@ -70,33 +83,16 @@ impl SigmaSerializable for ProofBytes {
 
 /// Proof of correctness of tx spending
 #[derive(Debug, PartialEq, Eq, Clone)]
-#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 pub struct ProverResult {
     /// proof that satisfies final sigma proposition
-    #[cfg_attr(feature = "json", serde(rename = "proofBytes"))]
     pub proof: ProofBytes,
     /// user-defined variables to be put into context
-    #[cfg_attr(feature = "json", serde(rename = "extension"))]
     pub extension: ContextExtension,
 }
 
-impl SigmaSerializable for ProverResult {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
-        self.proof.sigma_serialize(w)?;
-        self.extension.sigma_serialize(w)?;
-        Ok(())
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let proof = ProofBytes::sigma_parse(r)?;
-        let extension = ContextExtension::sigma_parse(r)?;
-        Ok(ProverResult { proof, extension })
-    }
-}
-
-#[cfg(test)]
-mod tests {
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::{collection::vec, prelude::*};
 
     impl Arbitrary for ProofBytes {
@@ -113,22 +109,4 @@ mod tests {
             .boxed()
         }
     }
-
-    impl Arbitrary for ProverResult {
-        type Parameters = ();
-        type Strategy = BoxedStrategy<Self>;
-
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (any::<ProofBytes>(), any::<ContextExtension>())
-                .prop_map(|(proof, extension)| Self { proof, extension })
-                .boxed()
-        }
-    }
-    proptest! {
-
-        #[test]
-        fn ser_roundtrip(v in any::<ProverResult>()) {
-            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
-        }
-    }
 }
diff --git a/ergo-lib/src/sigma_protocol/sig_serializer.rs b/ergotree-ir/src/sigma_protocol/sig_serializer.rs
similarity index 100%
rename from ergo-lib/src/sigma_protocol/sig_serializer.rs
rename to ergotree-ir/src/sigma_protocol/sig_serializer.rs
diff --git a/ergo-lib/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
similarity index 98%
rename from ergo-lib/src/sigma_protocol/sigma_boolean.rs
rename to ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index 71464565b..1ea4a5bf5 100644
--- a/ergo-lib/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -113,8 +113,8 @@ impl TryFrom<SigmaProp> for bool {
     }
 }
 
-#[cfg(test)]
-mod tests {
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
     use super::*;
     use proptest::prelude::*;
 
@@ -149,3 +149,6 @@ mod tests {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {}
diff --git a/ergo-lib/src/sigma_protocol/unchecked_tree.rs b/ergotree-ir/src/sigma_protocol/unchecked_tree.rs
similarity index 100%
rename from ergo-lib/src/sigma_protocol/unchecked_tree.rs
rename to ergotree-ir/src/sigma_protocol/unchecked_tree.rs
diff --git a/ergo-lib/src/sigma_protocol/unproven_tree.rs b/ergotree-ir/src/sigma_protocol/unproven_tree.rs
similarity index 100%
rename from ergo-lib/src/sigma_protocol/unproven_tree.rs
rename to ergotree-ir/src/sigma_protocol/unproven_tree.rs
diff --git a/ergo-lib/src/sigma_protocol/verifier.rs b/ergotree-ir/src/sigma_protocol/verifier.rs
similarity index 97%
rename from ergo-lib/src/sigma_protocol/verifier.rs
rename to ergotree-ir/src/sigma_protocol/verifier.rs
index 2f88b96d9..f4844f9e4 100644
--- a/ergo-lib/src/sigma_protocol/verifier.rs
+++ b/ergotree-ir/src/sigma_protocol/verifier.rs
@@ -120,13 +120,15 @@ impl Evaluator for TestVerifier {}
 impl Verifier for TestVerifier {}
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::ast::expr::Expr;
+    use crate::mir::expr::Expr;
     use crate::sigma_protocol::{
         private_input::{DlogProverInput, PrivateInput},
         prover::{Prover, TestProver},
     };
+    use crate::test_util::force_any_val;
     use proptest::prelude::*;
     use std::rc::Rc;
 
@@ -138,16 +140,16 @@ mod tests {
         fn test_prover_verifier_p2pk(secret in any::<DlogProverInput>(), message in any::<Vec<u8>>()) {
             prop_assume!(!message.is_empty());
             let pk = secret.public_image();
-            let tree = ErgoTree::from(Rc::new(Expr::Const(pk.into())));
+            let tree = ErgoTree::from(Expr::Const(pk.into()));
 
             let prover = TestProver {
                 secrets: vec![PrivateInput::DlogProverInput(secret)],
             };
-            let res = prover.prove(&tree, &Env::empty(), Rc::new(Context::dummy()), message.as_slice());
+            let res = prover.prove(&tree, &Env::empty(), Rc::new(force_any_val::<Context>()), message.as_slice());
             let proof = res.unwrap().proof;
 
             let verifier = TestVerifier;
-            let ver_res = verifier.verify(&tree, &Env::empty(), Rc::new(Context::dummy()),  &proof, message.as_slice());
+            let ver_res = verifier.verify(&tree, &Env::empty(), Rc::new(force_any_val::<Context>()),  &proof, message.as_slice());
             prop_assert_eq!(ver_res.unwrap().result, true);
         }
     }
diff --git a/ergo-lib/src/types.rs b/ergotree-ir/src/types.rs
similarity index 100%
rename from ergo-lib/src/types.rs
rename to ergotree-ir/src/types.rs
diff --git a/ergo-lib/src/types/sbox.rs b/ergotree-ir/src/types/sbox.rs
similarity index 77%
rename from ergo-lib/src/types/sbox.rs
rename to ergotree-ir/src/types/sbox.rs
index 9549885d3..c68a4b4da 100644
--- a/ergo-lib/src/types/sbox.rs
+++ b/ergotree-ir/src/types/sbox.rs
@@ -1,9 +1,7 @@
-use std::convert::TryInto;
-
-use crate::ast::constant::TryExtractInto;
-use crate::ast::value::Value;
-use crate::chain::ergo_box::ErgoBox;
 use crate::eval::EvalError;
+use crate::ir_ergo_box::IrBoxId;
+use crate::mir::constant::TryExtractInto;
+use crate::mir::value::Value;
 
 use super::sfunc::SFunc;
 use super::smethod::EvalFn;
@@ -34,9 +32,11 @@ lazy_static! {
     );
 }
 
-static VALUE_EVAL_FN: EvalFn = |obj, _args| {
+static VALUE_EVAL_FN: EvalFn = |ctx, obj, _args| {
     Ok(Value::Long(
-        obj.try_extract_into::<ErgoBox>()?.value.as_i64(),
+        obj.try_extract_into::<IrBoxId>()?
+            .get_box(&ctx.box_arena)?
+            .value(),
     ))
 };
 
@@ -54,15 +54,15 @@ lazy_static! {
     pub static ref VALUE_METHOD: SMethod = SMethod::new(&S_BOX_TYPE_COMPANION, &VALUE_METHOD_DESC,);
 }
 
-static GET_REG_EVAL_FN: EvalFn = |obj, args| {
+static GET_REG_EVAL_FN: EvalFn = |ctx, obj, args| {
     Ok(Value::Opt(Box::new(
-        obj.try_extract_into::<ErgoBox>()?
+        obj.try_extract_into::<IrBoxId>()?
+            .get_box(&ctx.box_arena)?
             .get_register(
                 args.get(0)
                     .cloned()
                     .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
-                    .try_extract_into::<i8>()?
-                    .try_into()?,
+                    .try_extract_into::<i8>()?,
             )
             .map(|c| c.v),
     )))
@@ -83,13 +83,11 @@ lazy_static! {
         SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
 }
 
-static TOKENS_EVAL_FN: EvalFn = |obj, _args| {
+static TOKENS_EVAL_FN: EvalFn = |ctx, obj, _args| {
     let res: Value = obj
-        .try_extract_into::<ErgoBox>()?
-        .tokens
-        .into_iter()
-        .map(|t| (t.token_id.into(), t.amount.into()))
-        .collect::<Vec<(Vec<i8>, i64)>>()
+        .try_extract_into::<IrBoxId>()?
+        .get_box(&ctx.box_arena)?
+        .tokens()
         .into();
     Ok(res)
 };
@@ -113,14 +111,15 @@ lazy_static! {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
-    use crate::ast::expr::Expr;
-    use crate::ast::global_vars::GlobalVars;
-    use crate::ast::property_call::PropertyCall;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
+    use crate::mir::expr::Expr;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::mir::property_call::PropertyCall;
     use crate::test_util::force_any_val;
 
     use super::*;
@@ -135,7 +134,7 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&expr, ctx.clone()),
-            ctx.self_box.value.as_i64()
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
         );
     }
 
@@ -149,12 +148,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
-                .tokens
-                .clone()
-                .into_iter()
-                .map(|t| (t.token_id.into(), t.amount.into()))
-                .collect::<Vec<(Vec<i8>, i64)>>()
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().tokens()
         );
     }
 }
diff --git a/ergo-lib/src/types/scontext.rs b/ergotree-ir/src/types/scontext.rs
similarity index 81%
rename from ergo-lib/src/types/scontext.rs
rename to ergotree-ir/src/types/scontext.rs
index 4374082df..0d8f2696d 100644
--- a/ergo-lib/src/types/scontext.rs
+++ b/ergotree-ir/src/types/scontext.rs
@@ -1,9 +1,5 @@
-use std::rc::Rc;
-
-use crate::ast::constant::TryExtractInto;
-use crate::ast::value::CollKind;
-use crate::ast::value::Value;
-use crate::eval::context::Context;
+use crate::mir::value::CollKind;
+use crate::mir::value::Value;
 
 use super::sfunc::SFunc;
 use super::smethod::EvalFn;
@@ -24,14 +20,14 @@ static S_CONTEXT_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
     type_name: "Context",
 };
 
-static DATA_INPUTS_EVAL_FN: EvalFn = |obj, _args| {
+static DATA_INPUTS_EVAL_FN: EvalFn = |ctx, _obj, _args| {
+    // TODO: check that obj is Value::Context
     Ok(Value::Coll(CollKind::WrappedColl {
-        items: obj
-            .try_extract_into::<Rc<Context>>()?
+        items: ctx
             .data_inputs
             .clone()
             .into_iter()
-            .map(|b| Value::CBox(Box::new(b)))
+            .map(Value::CBox)
             .collect(),
         elem_tpe: SType::SBox,
     }))
diff --git a/ergo-lib/src/types/sfunc.rs b/ergotree-ir/src/types/sfunc.rs
similarity index 100%
rename from ergo-lib/src/types/sfunc.rs
rename to ergotree-ir/src/types/sfunc.rs
diff --git a/ergo-lib/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
similarity index 92%
rename from ergo-lib/src/types/smethod.rs
rename to ergotree-ir/src/types/smethod.rs
index a7f5d682a..78e1c3456 100644
--- a/ergo-lib/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -1,7 +1,9 @@
 use std::io::Error;
+use std::rc::Rc;
 
-use crate::ast::value::Value;
+use crate::eval::context::Context;
 use crate::eval::EvalError;
+use crate::mir::value::Value;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
 use crate::serialization::SerializationError;
@@ -67,7 +69,7 @@ impl SMethod {
     }
 }
 
-pub type EvalFn = fn(Value, Vec<Value>) -> Result<Value, EvalError>;
+pub type EvalFn = fn(ctx: Rc<Context>, Value, Vec<Value>) -> Result<Value, EvalError>;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SMethodDesc {
diff --git a/ergo-lib/src/types/stuple.rs b/ergotree-ir/src/types/stuple.rs
similarity index 97%
rename from ergo-lib/src/types/stuple.rs
rename to ergotree-ir/src/types/stuple.rs
index 4bb9da24e..e75974365 100644
--- a/ergo-lib/src/types/stuple.rs
+++ b/ergotree-ir/src/types/stuple.rs
@@ -2,7 +2,7 @@ use std::convert::TryFrom;
 use std::convert::TryInto;
 use std::slice::Iter;
 
-use crate::ast::select_field::TupleFieldIndex;
+use crate::mir::select_field::TupleFieldIndex;
 
 use super::stype::SType;
 
diff --git a/ergo-lib/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
similarity index 98%
rename from ergo-lib/src/types/stype.rs
rename to ergotree-ir/src/types/stype.rs
index f2af5883e..349fac6ae 100644
--- a/ergo-lib/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -4,7 +4,7 @@ use std::convert::TryInto;
 
 use impl_trait_for_tuples::impl_for_tuples;
 
-use crate::chain::ergo_box::ErgoBox;
+use crate::ir_ergo_box::IrBoxId;
 use crate::serialization::types::TypeCode;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::ProveDlog;
@@ -135,7 +135,7 @@ impl LiftIntoSType for i64 {
     }
 }
 
-impl LiftIntoSType for ErgoBox {
+impl LiftIntoSType for IrBoxId {
     fn stype() -> SType {
         SType::SBox
     }
@@ -186,6 +186,7 @@ impl LiftIntoSType for Tuple {
 }
 
 #[cfg(test)]
+#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
     use proptest::prelude::*;
diff --git a/ergo-lib/src/types/stype_companion.rs b/ergotree-ir/src/types/stype_companion.rs
similarity index 100%
rename from ergo-lib/src/types/stype_companion.rs
rename to ergotree-ir/src/types/stype_companion.rs
diff --git a/ergo-lib/src/types/stype_param.rs b/ergotree-ir/src/types/stype_param.rs
similarity index 100%
rename from ergo-lib/src/types/stype_param.rs
rename to ergotree-ir/src/types/stype_param.rs
diff --git a/ergo-lib/src/util.rs b/ergotree-ir/src/util.rs
similarity index 100%
rename from ergo-lib/src/util.rs
rename to ergotree-ir/src/util.rs
diff --git a/sigma-util/Cargo.toml b/sigma-util/Cargo.toml
new file mode 100644
index 000000000..ca0649135
--- /dev/null
+++ b/sigma-util/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "sigma-util"
+version = "0.1.0"
+license = "CC0-1.0"
+authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
+edition = "2018"
+description = "Ergo primitives"
+repository = "https://github.com/ergoplatform/sigma-rust"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+blake2 = "0.9"
+
+
+[dev-dependencies]
diff --git a/sigma-util/src/hash.rs b/sigma-util/src/hash.rs
new file mode 100644
index 000000000..2423915dc
--- /dev/null
+++ b/sigma-util/src/hash.rs
@@ -0,0 +1,16 @@
+//! Hash functions
+
+use std::convert::TryInto;
+
+use blake2::digest::{Update, VariableOutput};
+use blake2::VarBlake2b;
+
+/// Blake2b256 hash (256 bit)
+pub fn blake2b256_hash(bytes: &[u8]) -> Box<[u8; 32]> {
+    // unwrap is safe 32 bytes is a valid hash size (<= 512 && 32 % 8 == 0)
+    let mut hasher = VarBlake2b::new(32).unwrap();
+    hasher.update(bytes);
+    let hash = hasher.finalize_boxed();
+    // unwrap is safe due to hash size is expected to be 32
+    hash.try_into().unwrap()
+}
diff --git a/sigma-util/src/lib.rs b/sigma-util/src/lib.rs
new file mode 100644
index 000000000..da50b79c1
--- /dev/null
+++ b/sigma-util/src/lib.rs
@@ -0,0 +1,19 @@
+//! Ergo primitives
+
+// Coding conventions
+#![forbid(unsafe_code)]
+#![deny(non_upper_case_globals)]
+#![deny(non_camel_case_types)]
+#![deny(non_snake_case)]
+#![deny(unused_mut)]
+#![deny(dead_code)]
+#![deny(unused_imports)]
+#![deny(missing_docs)]
+// Clippy exclusions
+#![allow(clippy::unit_arg)]
+#![deny(broken_intra_doc_links)]
+
+pub mod hash;
+
+/// 32 byte array used in box, transaction ids (hash)
+pub const DIGEST32_SIZE: usize = 32;


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_contract.js b/bindings/ergo-lib-wasm/tests/test_contract.js
new file mode 100644
index 000000000..17eb320ac
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/test_contract.js
@@ -0,0 +1,12 @@
+import { expect, assert } from 'chai';
+
+import {
+  Contract,
+  ErgoTree
+} from '../pkg/ergo_lib_wasm';
+
+it('Contract compiles from ErgoScript', async () => {
+  let contract = Contract.compile("HEIGHT");
+  assert(contract!= null);
+});
+
diff --git a/ergotree-ir/proptest-regressions/mir/and.txt b/ergotree-ir/proptest-regressions/mir/and.txt
new file mode 100644
index 000000000..d2c072b7b
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/and.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 03153683d16fb1d2c578cb66905019232e2472934f3794421c33820a9711f608 # shrinks to bools = []
diff --git a/ergotree-ir/proptest-regressions/mir/bin_op.txt b/ergotree-ir/proptest-regressions/mir/bin_op.txt
new file mode 100644
index 000000000..412f7f481
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/bin_op.txt
@@ -0,0 +1,8 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 6f6d687c553742d7c5cc0b54d17b72294abdc81396e46cf72e5d3161e7e79ddd # shrinks to l = false, r = false
+cc 081eef2135a3c46f05fbcb09de860acd736803ae4d7623c21b856ebef0358677 # shrinks to l = 0, r = 0
diff --git a/ergotree-ir/proptest-regressions/mir/bool_to_sigma.txt b/ergotree-ir/proptest-regressions/mir/bool_to_sigma.txt
new file mode 100644
index 000000000..8654b3ccb
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/bool_to_sigma.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc d0c516949039417ac052f1b610c4eb392092f5c2bdaeee0fc6eeaec8e0d41a11 # shrinks to b = false
diff --git a/ergotree-ir/proptest-regressions/mir/calc_blake2b256.txt b/ergotree-ir/proptest-regressions/mir/calc_blake2b256.txt
new file mode 100644
index 000000000..21397594c
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/calc_blake2b256.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc e1b1ee83b0204998087b46fc68b9e8a80dd426bf31067a910b405d27ddc6f7c0 # shrinks to byte_array = []
diff --git a/ergotree-ir/proptest-regressions/mir/collection.txt b/ergotree-ir/proptest-regressions/mir/collection.txt
new file mode 100644
index 000000000..84c76f0c3
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/collection.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc f324d62d34a1f9035e80a8943e2daabebe8a2eae8865d6bdeb27843281ff213c # shrinks to bools = []
diff --git a/ergotree-ir/proptest-regressions/mir/logical_not.txt b/ergotree-ir/proptest-regressions/mir/logical_not.txt
new file mode 100644
index 000000000..7a8d1ffc9
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/logical_not.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 1f189146c3716c3fcc199fe6925600fb6bf4e0e3554ee084e3bab424edc8a20f # shrinks to b = false
diff --git a/ergotree-ir/proptest-regressions/mir/or.txt b/ergotree-ir/proptest-regressions/mir/or.txt
new file mode 100644
index 000000000..31237aa5b
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/mir/or.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 70afee9eeccca0f532f78afe741fba0539c96da04d2742408115cb506d13919b # shrinks to bools = []
diff --git a/ergotree-ir/proptest-regressions/sigma_protocol/verifier.txt b/ergotree-ir/proptest-regressions/sigma_protocol/verifier.txt
new file mode 100644
index 000000000..7fe84e433
--- /dev/null
+++ b/ergotree-ir/proptest-regressions/sigma_protocol/verifier.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 1be7f07ca120b9e4145f314b5459f9c0b5a31fe7d5b24b0a8f70def443e60f54 # shrinks to secret = DlogProverInput { w: Scalar(Scalar4x64([13555793721159026511, 12393189365136756343, 10382562948891742109, 16656669716679916437])) }, message = [0]
diff --git a/ergotree-ir/src/test_util.rs b/ergotree-ir/src/test_util.rs
new file mode 100644
index 000000000..a6ecaa597
--- /dev/null
+++ b/ergotree-ir/src/test_util.rs
@@ -0,0 +1,13 @@
+use proptest::strategy::ValueTree;
+use proptest::test_runner::TestRunner;
+use proptest::{arbitrary::Arbitrary, prelude::*};
+
+pub fn force_any_val<T: Arbitrary>() -> T {
+    let mut runner = TestRunner::default();
+    any::<T>().new_tree(&mut runner).unwrap().current()
+}
+
+pub fn force_any_val_with<T: Arbitrary>(args: T::Parameters) -> T {
+    let mut runner = TestRunner::default();
+    any_with::<T>(args).new_tree(&mut runner).unwrap().current()
+}