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

Issue:
Transaction signing (only P2PK)



Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 73de0598f..c6d6d856b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,6 @@
 members = [
     "sigma-ser",
     "sigma-tree",
-    "ergo-wallet-lib",
     "bindings/ergo-wallet-lib-wasm",
     "bindings/ergo-wallet-lib-c-core",
     "bindings/ergo-wallet-lib-c",
diff --git a/bindings/ergo-wallet-lib-c-core/Cargo.toml b/bindings/ergo-wallet-lib-c-core/Cargo.toml
index fbad0ea45..d1e767aac 100644
--- a/bindings/ergo-wallet-lib-c-core/Cargo.toml
+++ b/bindings/ergo-wallet-lib-c-core/Cargo.toml
@@ -10,6 +10,6 @@ crate-type = ["lib"]
 
 [dependencies]
 sigma-tree = { path = "../../sigma-tree"}
-ergo-wallet-lib = { path = "../../ergo-wallet-lib"}
+
 thiserror = "1"
 
diff --git a/bindings/ergo-wallet-lib-c-core/src/lib.rs b/bindings/ergo-wallet-lib-c-core/src/lib.rs
index 44ce5d28e..9fb4ab5ff 100644
--- a/bindings/ergo-wallet-lib-c-core/src/lib.rs
+++ b/bindings/ergo-wallet-lib-c-core/src/lib.rs
@@ -13,10 +13,10 @@
 mod error;
 pub use error::*;
 
-use chain::{AddressEncoder, NetworkPrefix};
+use chain::address::{AddressEncoder, NetworkPrefix};
 use sigma_tree::chain;
 
-pub struct Address(chain::Address);
+pub struct Address(chain::address::Address);
 pub type AddressPtr = *mut Address;
 
 pub unsafe fn address_from_testnet(
diff --git a/bindings/ergo-wallet-lib-c/Cargo.toml b/bindings/ergo-wallet-lib-c/Cargo.toml
index 2ca0010f2..52de53668 100644
--- a/bindings/ergo-wallet-lib-c/Cargo.toml
+++ b/bindings/ergo-wallet-lib-c/Cargo.toml
@@ -13,7 +13,7 @@ crate-type = ["staticlib"]
 ffi_helpers = "0.2"
 sigma-tree = { path = "../../sigma-tree"}
 ergo-wallet-lib-c-core = { path = "../ergo-wallet-lib-c-core"}
-ergo-wallet-lib = { path = "../../ergo-wallet-lib"}
+
 
 [build-dependencies]
 cbindgen = "0.14"
diff --git a/bindings/ergo-wallet-lib-c/src/lib.rs b/bindings/ergo-wallet-lib-c/src/lib.rs
index 6f7d44692..8667ff59d 100644
--- a/bindings/ergo-wallet-lib-c/src/lib.rs
+++ b/bindings/ergo-wallet-lib-c/src/lib.rs
@@ -1,4 +1,4 @@
-//! WASM bindings for sigma-tree
+//! C bindings for sigma-tree
 
 // Coding conventions
 #![deny(non_upper_case_globals)]
@@ -22,7 +22,7 @@ use std::{
 pub type ErrorPtr = *mut Error;
 pub type AddressPtr = *mut Address;
 
-pub struct ErgoStateContext(ergo_wallet_lib::ErgoStateContext);
+pub struct ErgoStateContext(chain::ergo_state_context::ErgoStateContext);
 pub type ErgoStateContextPtr = *mut ErgoStateContext;
 
 #[no_mangle]
@@ -40,7 +40,7 @@ pub unsafe extern "C" fn ergo_wallet_ergo_state_context_delete(
     todo!()
 }
 
-pub struct ErgoBoxCandidate(chain::ErgoBoxCandidate);
+pub struct ErgoBoxCandidate(chain::ergo_box::ErgoBoxCandidate);
 pub type ErgoBoxCandidatePtr = *mut ErgoBoxCandidate;
 
 #[no_mangle]
@@ -75,7 +75,7 @@ pub extern "C" fn ergo_wallet_address_delete(address: AddressPtr) {
     address_delete(address)
 }
 
-pub struct UnspentBoxes(Vec<chain::ErgoBoxCandidate>);
+pub struct UnspentBoxes(Vec<chain::ergo_box::ErgoBoxCandidate>);
 pub type UnspentBoxesPtr = *mut UnspentBoxes;
 
 #[no_mangle]
@@ -91,10 +91,10 @@ pub extern "C" fn ergo_wallet_unspent_boxes_delete(_unspent_boxes: UnspentBoxesP
     todo!()
 }
 
-pub struct DataInputBoxes(Vec<chain::ErgoBoxCandidate>);
+pub struct DataInputBoxes(Vec<chain::ergo_box::ErgoBoxCandidate>);
 pub type DataInputBoxesPtr = *mut DataInputBoxes;
 
-pub struct OutputBoxes(Vec<chain::ErgoBoxCandidate>);
+pub struct OutputBoxes(Vec<chain::ergo_box::ErgoBoxCandidate>);
 pub type OutputBoxesPtr = *mut OutputBoxes;
 
 #[no_mangle]
@@ -143,7 +143,7 @@ pub extern "C" fn ergo_wallet_wallet_new_signed_tx(
     todo!()
 }
 
-pub struct Transaction(chain::Transaction);
+pub struct Transaction(chain::transaction::Transaction);
 pub type TransactionPtr = *mut Transaction;
 
 #[no_mangle]
diff --git a/bindings/ergo-wallet-lib-wasm/Cargo.toml b/bindings/ergo-wallet-lib-wasm/Cargo.toml
index f669b9f5f..78ccac501 100644
--- a/bindings/ergo-wallet-lib-wasm/Cargo.toml
+++ b/bindings/ergo-wallet-lib-wasm/Cargo.toml
@@ -13,7 +13,8 @@ default = ["console_error_panic_hook"]
 
 [dependencies]
 serde = { version = "1.0", features = ["derive"]}
-ergo-wallet-lib = { path = "../../ergo-wallet-lib", features = ["with-serde"] }
+serde_json = "1.0"
+
 sigma-tree = { path = "../../sigma-tree", features = ["with-serde"] }
 # used in elliptic-curve(in sigma-tree), compiled here with WASM support
 getrandom = {version = "0.1", features = ["wasm-bindgen"]}
diff --git a/bindings/ergo-wallet-lib-wasm/src/address.rs b/bindings/ergo-wallet-lib-wasm/src/address.rs
new file mode 100644
index 000000000..a690cbb09
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/address.rs
@@ -0,0 +1,74 @@
+//! Address types
+
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+/**
+ * An address is a short string corresponding to some script used to protect a box. Unlike (string-encoded) binary
+ * representation of a script, an address has some useful characteristics:
+ *
+ * - Integrity of an address could be checked., as it is incorporating a checksum.
+ * - A prefix of address is showing network and an address type.
+ * - An address is using an encoding (namely, Base58) which is avoiding similarly l0Oking characters, friendly to
+ * double-clicking and line-breaking in emails.
+ *
+ *
+ *
+ * An address is encoding network type, address type, checksum, and enough information to watch for a particular scripts.
+ *
+ * Possible network types are:
+ * Mainnet - 0x00
+ * Testnet - 0x10
+ *
+ * For an address type, we form content bytes as follows:
+ *
+ * P2PK - serialized (compressed) public key
+ * P2SH - first 192 bits of the Blake2b256 hash of serialized script bytes
+ * P2S  - serialized script
+ *
+ * Address examples for testnet:
+ *
+ * 3   - P2PK (3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN)
+ * ?   - P2SH (rbcrmKEYduUvADj9Ts3dSVSG27h54pgrq5fPuwB)
+ * ?   - P2S (Ms7smJwLGbUAjuWQ)
+ *
+ * for mainnet:
+ *
+ * 9  - P2PK (9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA)
+ * ?  - P2SH (8UApt8czfFVuTgQmMwtsRBZ4nfWquNiSwCWUjMg)
+ * ?  - P2S (4MQyML64GnzMxZgm, BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ)
+ *
+ *
+ * Prefix byte = network type + address type
+ *
+ * checksum = blake2b256(prefix byte ++ content bytes)
+ *
+ * address = prefix byte ++ content bytes ++ checksum
+ *
+ */
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Address(chain::address::Address);
+
+#[wasm_bindgen]
+impl Address {
+    /// Decode (base58) testnet address from string
+    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)))
+    }
+}
+
+impl Into<chain::address::Address> for Address {
+    fn into(self) -> chain::address::Address {
+        self.0
+    }
+}
+
+impl From<chain::address::Address> for Address {
+    fn from(a: chain::address::Address) -> Self {
+        Address(a)
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/box_coll.rs b/bindings/ergo-wallet-lib-wasm/src/box_coll.rs
new file mode 100644
index 000000000..1aa640284
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/box_coll.rs
@@ -0,0 +1,76 @@
+//! Box collection types
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+use crate::ergo_box::{ErgoBox, ErgoBoxCandidate};
+
+/// Collection of ErgoBox'es
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBoxes(Vec<chain::ergo_box::ErgoBox>);
+
+#[wasm_bindgen]
+impl ErgoBoxes {
+    /// parse ErgoBox array from json
+    #[allow(clippy::boxed_local, clippy::or_fun_call)]
+    pub fn from_boxes_json(boxes: Box<[JsValue]>) -> Result<ErgoBoxes, JsValue> {
+        boxes
+            .iter()
+            .try_fold(vec![], |mut acc, jb| {
+                let b: chain::ergo_box::ErgoBox = if jb.is_string() {
+                    let jb_str = jb
+                        .as_string()
+                        .ok_or(JsValue::from_str("Expected ErgoBox JSON as string"))?;
+                    serde_json::from_str(jb_str.as_str())
+                } else {
+                    jb.into_serde::<chain::ergo_box::ErgoBox>()
+                }
+                .map_err(|e| {
+                    JsValue::from_str(&format!(
+                        "Failed to parse ErgoBox from JSON string: {:?} \n with error: {}",
+                        jb, e
+                    ))
+                })?;
+                acc.push(b);
+                Ok(acc)
+            })
+            .map(ErgoBoxes)
+    }
+
+    /// Create new collection with one element
+    #[wasm_bindgen(constructor)]
+    pub fn new(b: &ErgoBox) -> ErgoBoxes {
+        ErgoBoxes(vec![b.clone().into()])
+    }
+
+    /// Add an element to the collection
+    pub fn add(&mut self, b: &ErgoBox) {
+        self.0.push(b.clone().into());
+    }
+}
+
+impl Into<Vec<chain::ergo_box::ErgoBox>> for ErgoBoxes {
+    fn into(self) -> Vec<chain::ergo_box::ErgoBox> {
+        self.0
+    }
+}
+
+/// Collection of ErgoBoxCandidates
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBoxCandidates(Vec<chain::ergo_box::ErgoBoxCandidate>);
+
+#[wasm_bindgen]
+impl ErgoBoxCandidates {
+    /// Create new outputs
+    #[wasm_bindgen(constructor)]
+    pub fn new(box_candidate: &ErgoBoxCandidate) -> ErgoBoxCandidates {
+        ErgoBoxCandidates(vec![box_candidate.clone().into()])
+    }
+}
+
+impl Into<Vec<chain::ergo_box::ErgoBoxCandidate>> for ErgoBoxCandidates {
+    fn into(self) -> Vec<chain::ergo_box::ErgoBoxCandidate> {
+        self.0
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/box_selector.rs b/bindings/ergo-wallet-lib-wasm/src/box_selector.rs
new file mode 100644
index 000000000..492f73de4
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/box_selector.rs
@@ -0,0 +1,23 @@
+//! Box selection algorithms
+use sigma_tree::chain::ergo_box::ErgoBoxAssets;
+use wasm_bindgen::prelude::*;
+
+/// Box selector implementations
+#[wasm_bindgen]
+pub enum BoxSelector {
+    /// Naive box selector, collects inputs until target balance is reached
+    Simple = 0,
+}
+
+impl BoxSelector {
+    /// Get underlying sigma-tree BoxSelector implementation
+    pub fn inner<T: ErgoBoxAssets>(
+        &self,
+    ) -> Box<dyn sigma_tree::wallet::box_selector::BoxSelector<T>> {
+        match self {
+            BoxSelector::Simple => {
+                Box::new(sigma_tree::wallet::box_selector::simple::SimpleBoxSelector {})
+            }
+        }
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/contract.rs b/bindings/ergo-wallet-lib-wasm/src/contract.rs
new file mode 100644
index 000000000..c0721fc07
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/contract.rs
@@ -0,0 +1,26 @@
+//! Contract, for easier ErgoTree generation
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+use crate::address::Address;
+
+/// Defines the contract(script) that will be guarding box contents
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Contract(chain::contract::Contract);
+
+#[wasm_bindgen]
+impl Contract {
+    /// create new contract that allow spending of the guarded box by a given recipient ([`Address`])
+    pub fn pay_to_address(recipient: &Address) -> Result<Contract, JsValue> {
+        chain::contract::Contract::pay_to_address(&recipient.clone().into())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+            .map(Contract)
+    }
+}
+
+impl Into<chain::contract::Contract> for Contract {
+    fn into(self) -> chain::contract::Contract {
+        self.0
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/ergo_box.rs b/bindings/ergo-wallet-lib-wasm/src/ergo_box.rs
new file mode 100644
index 000000000..29e9134b9
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/ergo_box.rs
@@ -0,0 +1,142 @@
+//! Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
+//! In Bitcoin, such an object is associated with some monetary value (arbitrary,
+//! but with predefined precision, so we use integer arithmetic to work with the value),
+//! and also a guarding script (aka proposition) to protect the box from unauthorized opening.
+//!
+//! In other way, a box is a state element locked by some proposition (ErgoTree).
+//!
+//! In Ergo, box is just a collection of registers, some with mandatory types and semantics,
+//! others could be used by applications in any way.
+//! We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
+//! Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
+//! Register R3 contains height when block got included into the blockchain and also transaction
+//! identifier and box index in the transaction outputs.
+//! Registers R4-R9 are free for arbitrary usage.
+//!
+//! A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
+//! can not be linked to the same box.
+
+use std::convert::TryFrom;
+
+use chain::ergo_box::register::NonMandatoryRegisters;
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+use crate::{contract::Contract, transaction::TxId};
+
+/// ErgoBox candidate not yet included in any transaction on the chain
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBoxCandidate(chain::ergo_box::ErgoBoxCandidate);
+
+#[wasm_bindgen]
+impl ErgoBoxCandidate {
+    /// make a new box with:
+    /// `value` - amount of money associated with the box
+    /// `contract` - guarding contract([`Contract`]), which should be evaluated to true in order
+    /// to open(spend) this box
+    /// `creation_height` - height when a transaction containing the box is created.
+    /// It should not exceed height of the block, containing the transaction with this box.
+    #[wasm_bindgen(constructor)]
+    pub fn new(
+        value: &BoxValue,
+        creation_height: u32,
+        contract: &Contract,
+    ) -> Result<ErgoBoxCandidate, JsValue> {
+        let chain_contract: chain::contract::Contract = contract.clone().into();
+        chain::ergo_box::ErgoBoxCandidate::new(
+            value.0,
+            chain_contract.get_ergo_tree(),
+            creation_height,
+        )
+        .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        .map(ErgoBoxCandidate)
+    }
+
+    // JSON representation
+    // pub fn to_json(&self) -> Result<JsValue, JsValue> {
+    //     JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
+    // }
+}
+
+impl Into<chain::ergo_box::ErgoBoxCandidate> for ErgoBoxCandidate {
+    fn into(self) -> chain::ergo_box::ErgoBoxCandidate {
+        self.0
+    }
+}
+
+/// Ergo box, that is taking part in some transaction on the chain
+/// Differs with [`ErgoBoxCandidate`] by added transaction id and an index in the input of that transaction
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBox(chain::ergo_box::ErgoBox);
+
+#[wasm_bindgen]
+impl ErgoBox {
+    /// make a new box with:
+    /// `value` - amount of money associated with the box
+    /// `contract` - guarding contract([`Contract`]), which should be evaluated to true in order
+    /// to open(spend) this box
+    /// `creation_height` - height when a transaction containing the box is created.
+    /// `tx_id` - transaction id in which this box was "created" (participated in outputs)
+    /// `index` - index (in outputs) in the transaction
+    #[wasm_bindgen(constructor)]
+    pub fn new(
+        value: &BoxValue,
+        creation_height: u32,
+        contract: &Contract,
+        tx_id: &TxId,
+        index: u16,
+    ) -> ErgoBox {
+        let chain_contract: chain::contract::Contract = contract.clone().into();
+        let b = chain::ergo_box::ErgoBox::new(
+            value.0,
+            chain_contract.get_ergo_tree(),
+            vec![],
+            NonMandatoryRegisters::empty(),
+            creation_height,
+            tx_id.clone().into(),
+            index,
+        );
+        ErgoBox(b)
+    }
+
+    // JSON representation
+    // pub fn to_json(&self) -> Result<JsValue, JsValue> {
+    //     JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
+    // }
+}
+
+impl Into<chain::ergo_box::ErgoBox> for ErgoBox {
+    fn into(self) -> chain::ergo_box::ErgoBox {
+        self.0
+    }
+}
+
+/// Box value with with bound checks
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct BoxValue(chain::ergo_box::box_value::BoxValue);
+
+#[wasm_bindgen]
+impl BoxValue {
+    /// Minimal value, calculated from smallest possible box size and original value per byte requirement
+    #[allow(non_snake_case)]
+    pub fn MIN() -> BoxValue {
+        BoxValue(chain::ergo_box::box_value::BoxValue::MIN)
+    }
+
+    /// Create from u32 with bounds check
+    pub fn from_u32(v: u32) -> Result<BoxValue, JsValue> {
+        Ok(BoxValue(
+            chain::ergo_box::box_value::BoxValue::try_from(v as u64)
+                .map_err(|e| JsValue::from_str(&format!("{}", e)))?,
+        ))
+    }
+}
+
+impl Into<chain::ergo_box::box_value::BoxValue> for BoxValue {
+    fn into(self) -> chain::ergo_box::box_value::BoxValue {
+        self.0
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/ergo_state_ctx.rs b/bindings/ergo-wallet-lib-wasm/src/ergo_state_ctx.rs
new file mode 100644
index 000000000..3423d6442
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/ergo_state_ctx.rs
@@ -0,0 +1,22 @@
+//! Ergo blockchain state (for ErgoTree evaluation)
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+/// TBD
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoStateContext(chain::ergo_state_context::ErgoStateContext);
+
+#[wasm_bindgen]
+impl ErgoStateContext {
+    /// empty (dummy) context (for signing P2PK tx only)
+    pub fn dummy() -> ErgoStateContext {
+        ErgoStateContext(chain::ergo_state_context::ErgoStateContext::dummy())
+    }
+}
+
+impl Into<chain::ergo_state_context::ErgoStateContext> for ErgoStateContext {
+    fn into(self) -> chain::ergo_state_context::ErgoStateContext {
+        self.0
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/lib.rs b/bindings/ergo-wallet-lib-wasm/src/lib.rs
index 75d4c3ba8..1b569cf3f 100644
--- a/bindings/ergo-wallet-lib-wasm/src/lib.rs
+++ b/bindings/ergo-wallet-lib-wasm/src/lib.rs
@@ -9,253 +9,16 @@
 #![deny(dead_code)]
 #![deny(unused_imports)]
 #![deny(missing_docs)]
-
-use sigma_tree::chain;
-
-mod utils;
-
-use chain::box_value::BoxValue;
-use wasm_bindgen::prelude::*;
-
-/**
- * An address is a short string corresponding to some script used to protect a box. Unlike (string-encoded) binary
- * representation of a script, an address has some useful characteristics:
- *
- * - Integrity of an address could be checked., as it is incorporating a checksum.
- * - A prefix of address is showing network and an address type.
- * - An address is using an encoding (namely, Base58) which is avoiding similarly l0Oking characters, friendly to
- * double-clicking and line-breaking in emails.
- *
- *
- *
- * An address is encoding network type, address type, checksum, and enough information to watch for a particular scripts.
- *
- * Possible network types are:
- * Mainnet - 0x00
- * Testnet - 0x10
- *
- * For an address type, we form content bytes as follows:
- *
- * P2PK - serialized (compressed) public key
- * P2SH - first 192 bits of the Blake2b256 hash of serialized script bytes
- * P2S  - serialized script
- *
- * Address examples for testnet:
- *
- * 3   - P2PK (3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN)
- * ?   - P2SH (rbcrmKEYduUvADj9Ts3dSVSG27h54pgrq5fPuwB)
- * ?   - P2S (Ms7smJwLGbUAjuWQ)
- *
- * for mainnet:
- *
- * 9  - P2PK (9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA)
- * ?  - P2SH (8UApt8czfFVuTgQmMwtsRBZ4nfWquNiSwCWUjMg)
- * ?  - P2S (4MQyML64GnzMxZgm, BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ)
- *
- *
- * Prefix byte = network type + address type
- *
- * checksum = blake2b256(prefix byte ++ content bytes)
- *
- * address = prefix byte ++ content bytes ++ checksum
- *
- */
-#[wasm_bindgen]
-pub struct Address(chain::Address);
-
-#[wasm_bindgen]
-impl Address {
-    /// Decode (base58) testnet address from string
-    pub fn from_testnet_str(s: &str) -> Result<Address, JsValue> {
-        chain::AddressEncoder::new(chain::NetworkPrefix::Testnet)
-            .parse_address_from_str(s)
-            .map(Address)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
-    }
-}
-
-/// Secret key for the prover
-#[wasm_bindgen]
-#[derive(PartialEq, Debug, Clone)]
-pub struct SecretKey(ergo_wallet_lib::SecretKey);
-
-#[wasm_bindgen]
-impl SecretKey {
-    /// generate random key
-    pub fn random_dlog() -> SecretKey {
-        SecretKey(ergo_wallet_lib::SecretKey::random_dlog())
-    }
-}
-
-/// Transaction inputs, array of ErgoBoxCandidate
-#[wasm_bindgen]
-pub struct UnspentBoxes(Vec<chain::ErgoBoxCandidate>);
-
-#[wasm_bindgen]
-impl UnspentBoxes {
-    /// parse ErgoBoxCandidate array from json
-    #[allow(clippy::boxed_local)]
-    pub fn from_boxes(_boxes: Box<[JsValue]>) -> UnspentBoxes {
-        // box in boxes.into_iter() {
-        //     let _box: chain::ErgoBoxCandidate = jbox.into_serde().unwrap();
-        // }
-        UnspentBoxes(vec![])
-    }
-}
-///
-/// Transaction data inputs, array of ErgoBoxCandidate
-#[wasm_bindgen]
-pub struct TxDataInputs(Vec<chain::ErgoBoxCandidate>);
-
-#[wasm_bindgen]
-impl TxDataInputs {
-    /// parse ErgoBoxCandidate array from json
-    #[allow(clippy::boxed_local)]
-    pub fn from_boxes(_boxes: Box<[JsValue]>) -> TxDataInputs {
-        // box in boxes.into_iter() {
-        //     let _box: chain::ErgoBoxCandidate = jbox.into_serde().unwrap();
-        // }
-        TxDataInputs(vec![])
-    }
-}
-
-/// Transaction outputs, array of ErgoBoxCandidate
-#[wasm_bindgen]
-pub struct TxOutputCandidates(Vec<chain::ErgoBoxCandidate>);
-
-#[wasm_bindgen]
-impl TxOutputCandidates {
-    /// Create new outputs
-    #[wasm_bindgen(constructor)]
-    pub fn new(box_candidate: ErgoBoxCandidate) -> TxOutputCandidates {
-        TxOutputCandidates(vec![box_candidate.0])
-    }
-}
-
-/// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
-/// In Bitcoin, such an object is associated with some monetary value (arbitrary,
-/// but with predefined precision, so we use integer arithmetic to work with the value),
-/// and also a guarding script (aka proposition) to protect the box from unauthorized opening.
-///
-/// In other way, a box is a state element locked by some proposition (ErgoTree).
-///
-/// In Ergo, box is just a collection of registers, some with mandatory types and semantics,
-/// others could be used by applications in any way.
-/// We add additional fields in addition to amount and proposition~(which stored in the registers R0 and R1).
-/// Namely, register R2 contains additional tokens (a sequence of pairs (token identifier, value)).
-/// Register R3 contains height when block got included into the blockchain and also transaction
-/// identifier and box index in the transaction outputs.
-/// Registers R4-R9 are free for arbitrary usage.
-///
-/// A transaction is unsealing a box. As a box can not be open twice, any further valid transaction
-/// can not be linked to the same box.
-#[wasm_bindgen]
-pub struct ErgoBoxCandidate(chain::ErgoBoxCandidate);
-
-#[wasm_bindgen]
-impl ErgoBoxCandidate {
-    /// make a new box with:
-    /// `value` - amount of money associated with the box
-    /// `contract` - guarding contract([`Contract`]), which should be evaluated to true in order
-    /// to open(spend) this box
-    /// `creation_height` - height when a transaction containing the box is created.
-    /// It should not exceed height of the block, containing the transaction with this box.
-    #[wasm_bindgen(constructor)]
-    pub fn new(value: u32, creation_height: u32, contract: Contract) -> ErgoBoxCandidate {
-        // value is u32, because u64 makes in BigInt in JS
-        let ergo_tree = contract.0.get_ergo_tree();
-        let b = chain::ErgoBoxCandidate::new(
-            BoxValue::new(value as u64).expect("value out of bounds"),
-            ergo_tree,
-            creation_height,
-        );
-        ErgoBoxCandidate(b)
-    }
-
-    // JSON representation
-    // pub fn to_json(&self) -> Result<JsValue, JsValue> {
-    //     JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
-    // }
-}
-
-/// Defines the contract(script) that will be guarding box contents
-#[wasm_bindgen]
-pub struct Contract(chain::Contract);
-
-#[wasm_bindgen]
-impl Contract {
-    /// create new contract that allow spending of the guarded box by a given recipient ([`Address`])
-    pub fn pay_to_address(recipient: Address) -> Result<Contract, JsValue> {
-        chain::Contract::pay_to_address(recipient.0)
-            .map_err(|e| JsValue::from_str(&format!("{}", e)))
-            .map(Contract)
-    }
-}
-
-/**
- * ErgoTransaction is an atomic state transition operation. It destroys Boxes from the state
- * and creates new ones. If transaction is spending boxes protected by some non-trivial scripts,
- * its inputs should also contain proof of spending correctness - context extension (user-defined
- * key-value map) and data inputs (links to existing boxes in the state) that may be used during
- * script reduction to crypto, signatures that satisfies the remaining cryptographic protection
- * of the script.
- * Transactions are not encrypted, so it is possible to browse and view every transaction ever
- * collected into a block.
- */
-#[wasm_bindgen]
-pub struct Transaction(chain::Transaction);
-
-#[wasm_bindgen]
-impl Transaction {
-    /// JSON representation
-    pub fn to_json(&self) -> Result<JsValue, JsValue> {
-        JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
-    }
-}
-
-/// TBD
-#[wasm_bindgen]
-pub struct ErgoStateContext(ergo_wallet_lib::ErgoStateContext);
-
-#[wasm_bindgen]
-impl ErgoStateContext {
-    /// empty (dummy) context (for signing P2PK tx only)
-    pub fn dummy() -> ErgoStateContext {
-        ErgoStateContext(ergo_wallet_lib::ErgoStateContext::dummy())
-    }
-}
-
-/// TBD
-#[wasm_bindgen]
-pub struct Wallet();
-
-#[wasm_bindgen]
-impl Wallet {
-    /// Create wallet instance loading secret key from mnemonic
-    pub fn from_mnemonic(_mnemonic_phrase: &str, _mnemonic_pass: &str) -> Wallet {
-        Wallet()
-    }
-
-    /// Create a signed transaction from:
-    /// `unspent_boxes` - unspent boxes [`ErgoBoxCandidate`] from which transaction
-    /// inputs (boxes to spend) will be selected
-    /// `outputs` - boxes that will be created in this transaction
-    /// `send_change_to` - address for the change (total value of input - total value of outputs)
-    /// that will be put in a new box that will be added to `outputs`
-    /// `sk` - secret key to sign the transaction (make proofs for inputs)
-    #[allow(clippy::too_many_arguments)]
-    #[wasm_bindgen]
-    pub fn new_signed_transaction(
-        &self,
-        _state_context: ErgoStateContext,
-        _unspent_boxes: UnspentBoxes,
-        _data_inputs: TxDataInputs,
-        _outputs: TxOutputCandidates,
-        _send_change_to: Address,
-        _min_change_value: u32,
-        _tx_fee_amount: u32,
-    ) -> Result<Transaction, JsValue> {
-        // not implemented, see https://github.com/ergoplatform/sigma-rust/issues/34
-        Err(JsValue::from_str("Not yet implemented"))
-    }
-}
+#![allow(unused_variables)]
+
+pub mod address;
+pub mod box_coll;
+pub mod box_selector;
+pub mod contract;
+pub mod ergo_box;
+pub mod ergo_state_ctx;
+pub mod secret_key;
+pub mod transaction;
+pub mod tx_builder;
+pub mod utils;
+pub mod wallet;
diff --git a/bindings/ergo-wallet-lib-wasm/src/secret_key.rs b/bindings/ergo-wallet-lib-wasm/src/secret_key.rs
new file mode 100644
index 000000000..97cda4637
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/secret_key.rs
@@ -0,0 +1,35 @@
+//! Secret key
+use sigma_tree::wallet;
+use wasm_bindgen::prelude::*;
+
+use crate::address::Address;
+
+/// Secret key for the prover
+#[wasm_bindgen]
+#[derive(PartialEq, Debug, Clone)]
+pub struct SecretKey(wallet::secret_key::SecretKey);
+
+#[wasm_bindgen]
+impl SecretKey {
+    /// generate random key
+    pub fn random_dlog() -> SecretKey {
+        SecretKey(wallet::secret_key::SecretKey::random_dlog())
+    }
+
+    /// Address (encoded public image)
+    pub fn get_address(&self) -> Address {
+        self.0.get_address_from_public_image().into()
+    }
+}
+
+impl From<SecretKey> for wallet::secret_key::SecretKey {
+    fn from(s: SecretKey) -> Self {
+        s.0
+    }
+}
+
+impl From<wallet::secret_key::SecretKey> for SecretKey {
+    fn from(sk: wallet::secret_key::SecretKey) -> Self {
+        SecretKey(sk)
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/transaction.rs b/bindings/ergo-wallet-lib-wasm/src/transaction.rs
new file mode 100644
index 000000000..d43029717
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/transaction.rs
@@ -0,0 +1,75 @@
+//! Ergo transaction
+use sigma_tree::chain;
+use wasm_bindgen::prelude::*;
+
+/// Transaction id
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct TxId(chain::transaction::TxId);
+
+#[wasm_bindgen]
+impl TxId {
+    /// Zero (empty) transaction id (to use as dummy value in tests)
+    pub fn zero() -> TxId {
+        chain::transaction::TxId::zero().into()
+    }
+}
+
+impl Into<chain::transaction::TxId> for TxId {
+    fn into(self) -> chain::transaction::TxId {
+        self.0
+    }
+}
+
+impl From<chain::transaction::TxId> for TxId {
+    fn from(tx_id: chain::transaction::TxId) -> Self {
+        TxId(tx_id)
+    }
+}
+
+/**
+ * ErgoTransaction is an estroys Boxes from the state
+ * and creates new ones. If transaction is spending boxes protected by some non-trivial scripts,
+ * its inputs should also contain proof of spending correctness - context extension (user-defined
+ * key-value map) and data inputs (links to existing boxes in the state) that may be used during
+ * script reduction to crypto, signatures that satisfies the remaining cryptographic protection
+ * of the script.
+ * Transactions are not encrypted, so it is possible to browse and view every transaction ever
+ * collected into a block.
+ */
+#[wasm_bindgen]
+pub struct Transaction(chain::transaction::Transaction);
+
+#[wasm_bindgen]
+impl Transaction {
+    /// JSON representation
+    pub fn to_json(&self) -> Result<JsValue, JsValue> {
+        JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+}
+
+impl From<chain::transaction::Transaction> for Transaction {
+    fn from(t: chain::transaction::Transaction) -> Self {
+        Transaction(t)
+    }
+}
+
+/// Unsigned (inputs without proofs) transaction
+#[wasm_bindgen]
+#[derive(PartialEq, Debug, Clone)]
+pub struct UnsignedTransaction(chain::transaction::unsigned::UnsignedTransaction);
+
+#[wasm_bindgen]
+impl UnsignedTransaction {}
+
+impl From<chain::transaction::unsigned::UnsignedTransaction> for UnsignedTransaction {
+    fn from(t: chain::transaction::unsigned::UnsignedTransaction) -> Self {
+        UnsignedTransaction(t)
+    }
+}
+
+impl From<UnsignedTransaction> for chain::transaction::unsigned::UnsignedTransaction {
+    fn from(t: UnsignedTransaction) -> Self {
+        t.0
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/tx_builder.rs b/bindings/ergo-wallet-lib-wasm/src/tx_builder.rs
new file mode 100644
index 000000000..89d82de0b
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/tx_builder.rs
@@ -0,0 +1,57 @@
+//! Unsigned transaction builder
+use sigma_tree::{chain::ergo_box::ErgoBox, wallet};
+use wasm_bindgen::prelude::*;
+
+use crate::{
+    address::Address,
+    box_coll::{ErgoBoxCandidates, ErgoBoxes},
+    box_selector::BoxSelector,
+    ergo_box::BoxValue,
+    transaction::UnsignedTransaction,
+};
+
+/// Unsigned transaction builder
+#[wasm_bindgen]
+pub struct TxBuilder(wallet::tx_builder::TxBuilder<ErgoBox>);
+
+#[wasm_bindgen]
+impl TxBuilder {
+    /// Creates new TxBuilder
+    /// `box_selector` - input box selection algorithm to choose inputs from `boxes_to_spend`,
+    /// `boxes_to_spend` - spendable boxes,
+    /// `output_candidates` - output boxes to be "created" in this transaction,
+    /// `current_height` - chain height that will be used in additionally created boxes (change, miner's fee, etc.),
+    /// `fee_amount` - miner's fee,
+    /// `change_address` - change (inputs - outputs) will be sent to this address,
+    /// `min_change_value` - minimal value of the change to be sent to `change_address`, value less than that
+    /// will be given to miners,
+    pub fn new(
+        box_selector: BoxSelector,
+        inputs: &ErgoBoxes,
+        output_candidates: &ErgoBoxCandidates,
+        current_height: u32,
+        fee_amount: &BoxValue,
+        change_address: &Address,
+        min_change_value: &BoxValue,
+    ) -> Result<TxBuilder, JsValue> {
+        sigma_tree::wallet::tx_builder::TxBuilder::new(
+            box_selector.inner::<ErgoBox>(),
+            inputs.clone().into(),
+            output_candidates.clone().into(),
+            current_height,
+            fee_amount.clone().into(),
+            change_address.clone().into(),
+            min_change_value.clone().into(),
+        )
+        .map_err(|e| JsValue::from_str(&format!("{}", e)))
+        .map(TxBuilder)
+    }
+
+    /// Build the unsigned transaction
+    pub fn build(&self) -> Result<UnsignedTransaction, JsValue> {
+        self.0
+            .build()
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+            .map(UnsignedTransaction::from)
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/utils.rs b/bindings/ergo-wallet-lib-wasm/src/utils.rs
index c4be847ee..1287e621d 100644
--- a/bindings/ergo-wallet-lib-wasm/src/utils.rs
+++ b/bindings/ergo-wallet-lib-wasm/src/utils.rs
@@ -1,4 +1,5 @@
-#[allow(dead_code)]
+//! Utilities
+#[allow(dead_code, missing_docs)]
 pub fn set_panic_hook() {
     // When the `console_error_panic_hook` feature is enabled, we can call the
     // `set_panic_hook` function at least once during initialization, and then
diff --git a/bindings/ergo-wallet-lib-wasm/src/wallet.rs b/bindings/ergo-wallet-lib-wasm/src/wallet.rs
new file mode 100644
index 000000000..fa09e894d
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/wallet.rs
@@ -0,0 +1,52 @@
+//! Wallet-like features
+use sigma_tree::chain::ergo_box::ErgoBox;
+use wasm_bindgen::prelude::*;
+
+use crate::{
+    box_coll::ErgoBoxes, ergo_state_ctx::ErgoStateContext, secret_key::SecretKey,
+    transaction::Transaction, transaction::UnsignedTransaction,
+};
+
+/// TBD
+#[wasm_bindgen]
+pub struct Wallet(sigma_tree::wallet::Wallet);
+
+#[wasm_bindgen]
+impl Wallet {
+    /// Create wallet instance loading secret key from mnemonic
+    #[wasm_bindgen]
+    pub fn from_mnemonic(_mnemonic_phrase: &str, _mnemonic_pass: &str) -> Wallet {
+        todo!()
+    }
+
+    /// Create wallet using provided secret key
+    #[wasm_bindgen]
+    pub fn from_secret(secret: &SecretKey) -> Wallet {
+        Wallet(sigma_tree::wallet::Wallet::from_secrets(vec![secret
+            .clone()
+            .into()]))
+    }
+
+    /// Sign a transaction:
+    /// `boxes_to_spend` - unspent boxes [`ErgoBoxCandidate`] used as inputs in the transaction
+    #[wasm_bindgen]
+    pub fn sign_transaction(
+        &self,
+        _state_context: &ErgoStateContext,
+        tx: &UnsignedTransaction,
+        boxes_to_spend: &ErgoBoxes,
+        data_boxes: &ErgoBoxes,
+    ) -> Result<Transaction, JsValue> {
+        let boxes_to_spend: Vec<ErgoBox> = boxes_to_spend.clone().into();
+        let data_boxes: Vec<ErgoBox> = data_boxes.clone().into();
+        self.0
+            .sign_transaction(
+                tx.clone().into(),
+                boxes_to_spend.as_slice(),
+                data_boxes.as_slice(),
+                &_state_context.clone().into(),
+            )
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+            .map(Transaction::from)
+    }
+}
diff --git a/ergo-wallet-lib/Cargo.toml b/ergo-wallet-lib/Cargo.toml
deleted file mode 100644
index b41075f11..000000000
--- a/ergo-wallet-lib/Cargo.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[package]
-name = "ergo-wallet-lib"
-version = "0.1.0"
-authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
-edition = "2018"
-
-[lib]
-crate-type = ["cdylib", "rlib"]
-
-[dependencies]
-serde = { version = "1.0", features = ["derive"], optional = true }
-sigma-tree = { path = "../sigma-tree"}
-
-[features]
-with-serde = ["serde"]
diff --git a/ergo-wallet-lib/src/ergo_state_context.rs b/ergo-wallet-lib/src/ergo_state_context.rs
deleted file mode 100644
index 2cff90cad..000000000
--- a/ergo-wallet-lib/src/ergo_state_context.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-/// TBD
-pub struct ErgoStateContext();
-
-impl ErgoStateContext {
-    /// Dummy "empty" context
-    pub fn dummy() -> ErgoStateContext {
-        ErgoStateContext()
-    }
-
-    /// build from last block headers from node REST API "blocks/lastHeaders/{count}"
-    pub fn from_last_headers(_json_str: &str) -> Result<ErgoStateContext, ErgoStateContextError> {
-        todo!()
-    }
-}
-
-/// Errors on parsing and building from node REST API blocks/lastHeaders
-pub enum ErgoStateContextError {}
diff --git a/ergo-wallet-lib/src/lib.rs b/ergo-wallet-lib/src/lib.rs
deleted file mode 100644
index c98c5d5b6..000000000
--- a/ergo-wallet-lib/src/lib.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-//! Ergo wallet
-
-// 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)]
-
-mod ergo_state_context;
-mod secret_key;
-
-pub use ergo_state_context::*;
-pub use secret_key::*;
diff --git a/ergo-wallet-lib/src/secret_key.rs b/ergo-wallet-lib/src/secret_key.rs
deleted file mode 100644
index eae7f5672..000000000
--- a/ergo-wallet-lib/src/secret_key.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use sigma_tree::sigma_protocol::DlogProverInput;
-
-/// Types of secrets
-#[derive(PartialEq, Debug, Clone)]
-pub enum SecretKey {
-    /// Secret exponent of a group element, i.e. secret w such as h = g^^w, where g is group generator, h is a public key.
-    DlogSecretKey(DlogProverInput),
-}
-
-impl SecretKey {
-    /// Generates random DlogProverInput
-    pub fn random_dlog() -> SecretKey {
-        SecretKey::DlogSecretKey(DlogProverInput::random())
-    }
-}
diff --git a/sigma-tree/Cargo.toml b/sigma-tree/Cargo.toml
index 15ad57f98..e52acdded 100644
--- a/sigma-tree/Cargo.toml
+++ b/sigma-tree/Cargo.toml
@@ -21,6 +21,10 @@ bs58 = "0.3.1"
 num-bigint = "0.3.0"
 rand = "0.7"
 
+[dependencies.num-traits]
+version = "0.2"
+default-features = false
+
 [features]
 default = []
 with-serde = ["serde"]
diff --git a/sigma-tree/src/ast/constant.rs b/sigma-tree/src/ast/constant.rs
index 662629b0a..0dbe6887d 100644
--- a/sigma-tree/src/ast/constant.rs
+++ b/sigma-tree/src/ast/constant.rs
@@ -1,6 +1,6 @@
 use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
 use crate::{
-    chain::ErgoBox,
+    chain::ergo_box::ErgoBox,
     serialization::{op_code::OpCode, SerializationError, SigmaSerializable},
     sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp},
     types::{LiftIntoSType, SType},
diff --git a/sigma-tree/src/chain.rs b/sigma-tree/src/chain.rs
index 02a0f426f..369e78254 100644
--- a/sigma-tree/src/chain.rs
+++ b/sigma-tree/src/chain.rs
@@ -1,30 +1,22 @@
 //! Ergo chain types
 
-mod address;
-mod base16_bytes;
-mod box_id;
-mod context_extension;
-mod contract;
-mod data_input;
-mod digest32;
-mod ergo_box;
-mod input;
 #[cfg(feature = "with-serde")]
 mod json;
-mod prover_result;
-mod token;
-mod transaction;
 
-pub use address::*;
+mod base16_bytes;
+mod digest32;
+
 pub use base16_bytes::Base16DecodedBytes;
 pub use base16_bytes::Base16EncodedBytes;
-pub use box_id::*;
-pub use context_extension::*;
-pub use contract::*;
 pub use digest32::*;
-pub use ergo_box::*;
-pub use input::*;
-pub use prover_result::*;
-pub use prover_result::*;
-pub use token::*;
-pub use transaction::*;
+
+pub mod address;
+pub mod context_extension;
+pub mod contract;
+pub mod data_input;
+pub mod ergo_box;
+pub mod ergo_state_context;
+pub mod input;
+pub mod prover_result;
+pub mod token;
+pub mod transaction;
diff --git a/sigma-tree/src/chain/address.rs b/sigma-tree/src/chain/address.rs
index 082708760..bb725c65e 100644
--- a/sigma-tree/src/chain/address.rs
+++ b/sigma-tree/src/chain/address.rs
@@ -1,3 +1,5 @@
+//! Address types
+
 use super::digest32;
 use crate::{
     ast::Expr,
diff --git a/sigma-tree/src/chain/contract.rs b/sigma-tree/src/chain/contract.rs
index 241517259..e0f02c9ff 100644
--- a/sigma-tree/src/chain/contract.rs
+++ b/sigma-tree/src/chain/contract.rs
@@ -1,9 +1,10 @@
 //! Ergo contract
 
-use super::Address;
+use super::address::Address;
 use crate::{serialization::SerializationError, ErgoTree};
 
 /// High-level wrapper for ErgoTree
+#[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Contract {
     ergo_tree: ErgoTree,
 }
@@ -15,7 +16,7 @@ impl Contract {
     }
 
     /// create new contract that allow spending for a given Address
-    pub fn pay_to_address(address: Address) -> Result<Contract, SerializationError> {
+    pub fn pay_to_address(address: &Address) -> Result<Contract, SerializationError> {
         Ok(Contract::new(address.script()?))
     }
 
diff --git a/sigma-tree/src/chain/data_input.rs b/sigma-tree/src/chain/data_input.rs
index a4491fdd2..2a3c36135 100644
--- a/sigma-tree/src/chain/data_input.rs
+++ b/sigma-tree/src/chain/data_input.rs
@@ -2,7 +2,7 @@
 
 use std::io;
 
-use super::box_id::BoxId;
+use super::ergo_box::{box_id::BoxId, ErgoBox};
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
@@ -24,6 +24,12 @@ pub struct DataInput {
     pub box_id: BoxId,
 }
 
+impl From<&ErgoBox> for DataInput {
+    fn from(b: &ErgoBox) -> Self {
+        DataInput { box_id: b.box_id() }
+    }
+}
+
 impl SigmaSerializable for DataInput {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.box_id.sigma_serialize(w)?;
diff --git a/sigma-tree/src/chain/ergo_box.rs b/sigma-tree/src/chain/ergo_box.rs
index 0e9547296..3511c48c4 100644
--- a/sigma-tree/src/chain/ergo_box.rs
+++ b/sigma-tree/src/chain/ergo_box.rs
@@ -1,5 +1,6 @@
 //! Ergo box
 
+pub mod box_id;
 pub mod box_value;
 pub mod register;
 
@@ -8,7 +9,7 @@ use super::json;
 use super::{
     digest32::blake2b256_hash,
     token::{TokenAmount, TokenId},
-    BoxId, TxId,
+    transaction::TxId,
 };
 use crate::{
     ergo_tree::ErgoTree,
@@ -19,6 +20,7 @@ use crate::{
         SerializationError, SigmaSerializable,
     },
 };
+use box_id::BoxId;
 use box_value::BoxValue;
 use indexmap::IndexSet;
 use register::NonMandatoryRegisters;
@@ -26,8 +28,8 @@ use register::NonMandatoryRegisters;
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "with-serde")]
 use std::convert::TryFrom;
+use std::convert::TryInto;
 use std::io;
-#[cfg(feature = "with-serde")]
 use thiserror::Error;
 
 /// Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency.
@@ -126,7 +128,7 @@ impl ErgoBox {
     ) -> ErgoBox {
         let box_with_zero_id = ErgoBox {
             box_id: BoxId::zero(),
-            value: box_candidate.value.clone(),
+            value: box_candidate.value,
             ergo_tree: box_candidate.ergo_tree.clone(),
             tokens: box_candidate.tokens.clone(),
             additional_registers: box_candidate.additional_registers.clone(),
@@ -147,6 +149,65 @@ impl ErgoBox {
     }
 }
 
+/// Assets that ErgoBox holds
+pub trait ErgoBoxAssets {
+    /// Box value
+    fn value(&self) -> BoxValue;
+    /// Tokens (ids and amounts)
+    fn tokens(&self) -> Vec<TokenAmount>;
+}
+
+/// Simple struct to hold ErgoBoxAssets values
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoBoxAssetsData {
+    /// Box value
+    pub value: BoxValue,
+    /// Tokens
+    pub tokens: Vec<TokenAmount>,
+}
+
+impl ErgoBoxAssets for ErgoBoxAssetsData {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<TokenAmount> {
+        self.tokens.clone()
+    }
+}
+
+impl ErgoBoxAssets for ErgoBoxCandidate {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<TokenAmount> {
+        self.tokens.clone()
+    }
+}
+
+impl ErgoBoxAssets for ErgoBox {
+    fn value(&self) -> BoxValue {
+        self.value
+    }
+
+    fn tokens(&self) -> Vec<TokenAmount> {
+        self.tokens.clone()
+    }
+}
+
+/// id of the ergo box
+pub trait ErgoBoxId {
+    /// Id of the ergo box
+    fn box_id(&self) -> BoxId;
+}
+
+impl ErgoBoxId for ErgoBox {
+    fn box_id(&self) -> BoxId {
+        self.box_id.clone()
+    }
+}
+
 /// Errors on parsing ErgoBox from JSON
 #[cfg(feature = "with-serde")]
 #[derive(Error, PartialEq, Eq, Debug, Clone)]
@@ -225,15 +286,45 @@ pub struct ErgoBoxCandidate {
     pub creation_height: u32,
 }
 
+/// ErgoBoxCandidate errors
+#[derive(Error, PartialEq, Eq, Clone, Debug)]
+pub enum ErgoBoxCandidateError {
+    /// Box value is too low
+    #[error("Box value is too low, minimum value for box size of {box_size_bytes} bytes is: {min_box_value:?} nanoERGs")]
+    BoxValueTooLow {
+        /// minimum box value for that box size
+        min_box_value: BoxValue,
+        /// box size in bytes
+        box_size_bytes: usize,
+    },
+}
+
 impl ErgoBoxCandidate {
     /// create box with value guarded by ErgoTree
-    pub fn new(value: BoxValue, ergo_tree: ErgoTree, creation_height: u32) -> ErgoBoxCandidate {
-        ErgoBoxCandidate {
+    pub fn new(
+        value: BoxValue,
+        ergo_tree: ErgoTree,
+        creation_height: u32,
+    ) -> Result<ErgoBoxCandidate, ErgoBoxCandidateError> {
+        let b = ErgoBoxCandidate {
             value,
             ergo_tree,
             tokens: vec![],
             additional_registers: NonMandatoryRegisters::empty(),
             creation_height,
+        };
+        let box_size_bytes = b.sigma_serialise_bytes().len();
+        // TODO: extract min value per byte as parameter. ErgoBoxCandidateBuilder?
+        let min_box_value: BoxValue = (box_size_bytes as i64 * BoxValue::MIN_VALUE_PER_BOX_BYTE)
+            .try_into()
+            .unwrap();
+        if value >= min_box_value {
+            Ok(b)
+        } else {
+            Err(ErgoBoxCandidateError::BoxValueTooLow {
+                min_box_value,
+                box_size_bytes,
+            })
         }
     }
 
@@ -292,11 +383,11 @@ mod tests {
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
     impl Arbitrary for ErgoBoxCandidate {
-        type Parameters = ();
+        type Parameters = super::box_value::tests::ArbBoxValueRange;
 
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
             (
-                any::<BoxValue>(),
+                any_with::<BoxValue>(args),
                 any::<ErgoTree>(),
                 vec(any::<TokenAmount>(), 0..10),
                 any::<u32>(),
@@ -317,10 +408,14 @@ mod tests {
     }
 
     impl Arbitrary for ErgoBox {
-        type Parameters = ();
+        type Parameters = super::box_value::tests::ArbBoxValueRange;
 
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (any::<ErgoBoxCandidate>(), any::<TxId>(), any::<u16>())
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (
+                any_with::<ErgoBoxCandidate>(args),
+                any::<TxId>(),
+                any::<u16>(),
+            )
                 .prop_map(|(box_candidate, tx_id, index)| {
                     Self::from_box_candidate(&box_candidate, tx_id, index)
                 })
diff --git a/sigma-tree/src/chain/box_id.rs b/sigma-tree/src/chain/ergo_box/box_id.rs
similarity index 97%
rename from sigma-tree/src/chain/box_id.rs
rename to sigma-tree/src/chain/ergo_box/box_id.rs
index a8fa72370..c19ca5299 100644
--- a/sigma-tree/src/chain/box_id.rs
+++ b/sigma-tree/src/chain/ergo_box/box_id.rs
@@ -4,7 +4,7 @@ use std::io;
 #[cfg(feature = "with-serde")]
 use serde::{Deserialize, Serialize};
 
-use super::digest32::Digest32;
+use super::super::digest32::Digest32;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
diff --git a/sigma-tree/src/chain/ergo_box/box_value.rs b/sigma-tree/src/chain/ergo_box/box_value.rs
index b29b16b86..e9a70b381 100644
--- a/sigma-tree/src/chain/ergo_box/box_value.rs
+++ b/sigma-tree/src/chain/ergo_box/box_value.rs
@@ -7,47 +7,104 @@ use crate::serialization::{
 use serde::{Deserialize, Serialize};
 use sigma_ser::vlq_encode;
 use std::{convert::TryFrom, io};
+use thiserror::Error;
 
-/// Box value
-#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+/// Box value with with bound checks
+#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
-pub struct BoxValue(u64);
+pub struct BoxValue(i64);
 
 impl BoxValue {
-    const MIN_RAW: u64 = 1;
-    const MAX_RAW: u64 = i64::MAX as u64;
+    /// Minimal box value per byte of the serialized box that was set on on launch
+    pub const MIN_VALUE_PER_BOX_BYTE: i64 = 360;
+    /// Minimal theoretical box size (smallest tree, no tokens, no registers, etc.)
+    const MIN_BOX_SIZE_BYTES: i64 = 30;
 
-    /// Minimal value
+    /// Mininal allowed box value
+    pub const MIN_RAW: i64 = BoxValue::MIN_VALUE_PER_BOX_BYTE * BoxValue::MIN_BOX_SIZE_BYTES;
+    /// Maximal allowed box value
+    pub const MAX_RAW: i64 = i64::MAX;
+
+    /// Minimal value, calculated from smallest possible box size and original value per byte requirement
     pub const MIN: BoxValue = BoxValue(BoxValue::MIN_RAW);
 
-    /// Create from u64 with bounds check
+    /// create from u64 with bounds check
     pub fn new(v: u64) -> Result<BoxValue, BoxValueError> {
         BoxValue::try_from(v)
     }
 
     /// Check if a value is in bounds
     pub fn within_bounds(v: u64) -> bool {
-        v >= BoxValue::MIN_RAW && v <= BoxValue::MAX_RAW
+        v >= BoxValue::MIN_RAW as u64 && v <= BoxValue::MAX_RAW as u64
+    }
+
+    /// Get the value as u64
+    pub fn as_u64(&self) -> u64 {
+        self.0 as u64
+    }
+
+    /// Get the value as i64
+    pub fn as_i64(&self) -> &i64 {
+        &self.0
     }
 
-    /// Get u64 value
-    pub fn value(&self) -> u64 {
+    /// Addition with overflow check
+    pub fn checked_add(&self, rhs: &Self) -> Result<Self, BoxValueError> {
         self.0
+            .checked_add(rhs.0)
+            .map(BoxValue)
+            .ok_or(BoxValueError::Overflow)
+    }
+
+    /// Subtraction with overflow and bounds check
+    pub fn checked_sub(&self, rhs: &Self) -> Result<Self, BoxValueError> {
+        let raw_i64 = self.0.checked_sub(rhs.0).ok_or(BoxValueError::Overflow)?;
+        if raw_i64 < BoxValue::MIN_RAW {
+            Err(BoxValueError::OutOfBounds)
+        } else {
+            Ok(BoxValue(raw_i64))
+        }
+    }
+
+    /// Multiplication with overflow check
+    pub fn checked_mul(&self, rhs: &Self) -> Result<Self, BoxValueError> {
+        self.0
+            .checked_mul(rhs.0)
+            .map(BoxValue)
+            .ok_or(BoxValueError::Overflow)
+    }
+
+    /// Multiplication with overflow check
+    pub fn checked_mul_u32(&self, rhs: u32) -> Result<Self, BoxValueError> {
+        self.0
+            .checked_mul(rhs as i64)
+            .map(BoxValue)
+            .ok_or(BoxValueError::Overflow)
     }
 }
 
-/// BoxValue errors
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub enum BoxValueError {
-    /// Value is out of bounds
-    OutOfBounds,
+impl PartialOrd for BoxValue {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        self.0.partial_cmp(other.as_i64())
+    }
 }
 
 impl TryFrom<u64> for BoxValue {
     type Error = BoxValueError;
     fn try_from(v: u64) -> Result<Self, Self::Error> {
         if BoxValue::within_bounds(v) {
-            Ok(BoxValue(v))
+            Ok(BoxValue(v as i64))
+        } else {
+            Err(BoxValueError::OutOfBounds)
+        }
+    }
+}
+
+impl TryFrom<i64> for BoxValue {
+    type Error = BoxValueError;
+    fn try_from(v: i64) -> Result<Self, Self::Error> {
+        if v >= BoxValue::MIN_RAW {
+            Ok(BoxValue(v as i64))
         } else {
             Err(BoxValueError::OutOfBounds)
         }
@@ -56,30 +113,140 @@ impl TryFrom<u64> for BoxValue {
 
 impl Into<u64> for BoxValue {
     fn into(self) -> u64 {
-        self.0
+        self.0 as u64
+    }
+}
+
+impl Into<i64> for BoxValue {
+    fn into(self) -> i64 {
+        // it's safe since upper bound is i64::MAX
+        self.0 as i64
     }
 }
 
 impl SigmaSerializable for BoxValue {
     fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
-        w.put_u64(self.0)
+        w.put_u64(self.0 as u64)
     }
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
         let v = r.get_u64()?;
-        Ok(BoxValue(v))
+        Ok(BoxValue::try_from(v)?)
     }
 }
 
+/// BoxValue errors
+#[derive(Error, Eq, PartialEq, Debug, Clone)]
+pub enum BoxValueError {
+    /// Value is out of bounds
+    #[error("Value is out of bounds")]
+    OutOfBounds,
+    /// Overflow
+    #[error("Overflow")]
+    Overflow,
+}
+
+impl From<BoxValueError> for SerializationError {
+    fn from(e: BoxValueError) -> Self {
+        SerializationError::ValueOutOfBounds(format!("{}", e))
+    }
+}
+
+/// Sums up all iterator's box values
+/// Returns Err on overflow
+pub fn checked_sum<I: Iterator<Item = BoxValue>>(mut iter: I) -> Result<BoxValue, BoxValueError> {
+    iter.try_fold(BoxValue(0), |acc, v| acc.checked_add(&v))
+        .map_or_else(Err, |v| {
+            if v.0 == 0 {
+                // input list was empty (sum is zero)
+                Err(BoxValueError::OutOfBounds)
+            } else {
+                Ok(v)
+            }
+        })
+}
+
 #[cfg(test)]
-mod tests {
+pub mod tests {
+    use std::convert::TryInto;
+    use std::ops::Range;
+
     use super::*;
-    use proptest::{arbitrary::Arbitrary, prelude::*};
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    pub struct ArbBoxValueRange(Range<i64>);
+
+    impl Default for ArbBoxValueRange {
+        fn default() -> Self {
+            ArbBoxValueRange(BoxValue::MIN_RAW..BoxValue::MAX_RAW)
+        }
+    }
+
+    impl Into<ArbBoxValueRange> for Range<i64> {
+        fn into(self) -> ArbBoxValueRange {
+            ArbBoxValueRange(self)
+        }
+    }
 
     impl Arbitrary for BoxValue {
-        type Parameters = ();
+        type Parameters = ArbBoxValueRange;
         type Strategy = BoxedStrategy<Self>;
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (1..i64::MAX).prop_map(|v| BoxValue(v as u64)).boxed()
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (args.0).prop_map(BoxValue).boxed()
+        }
+    }
+
+    #[test]
+    fn test_checked_add() {
+        let a = BoxValue::try_from(10000000u64)
+            .unwrap()
+            .checked_add(&10000000u64.try_into().unwrap())
+            .unwrap();
+        assert_eq!(a, 20000000u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn test_checked_add_overflow() {
+        assert!(BoxValue::try_from(BoxValue::MAX_RAW)
+            .unwrap()
+            .checked_add(&BoxValue::MIN)
+            .is_err())
+    }
+
+    #[test]
+    fn test_checked_sub() {
+        let a = BoxValue::try_from(10000000u64)
+            .unwrap()
+            .checked_sub(&5000000u64.try_into().unwrap())
+            .unwrap();
+        assert_eq!(a, 5000000u64.try_into().unwrap());
+    }
+
+    #[test]
+    fn test_checked_sub_overflow() {
+        assert!(BoxValue::MIN
+            .checked_sub(&BoxValue::MAX_RAW.try_into().unwrap())
+            .is_err())
+    }
+
+    #[test]
+    fn test_checked_sum_empty_input() {
+        let empty_input: Vec<BoxValue> = vec![];
+        assert!(checked_sum(empty_input.into_iter()).is_err());
+    }
+
+    #[test]
+    fn test_checked_sum_overflow() {
+        let input: Vec<BoxValue> = vec![BoxValue::MAX_RAW.try_into().unwrap(), BoxValue::MIN];
+        assert!(checked_sum(input.into_iter()).is_err());
+    }
+
+    proptest! {
+
+        #[test]
+        fn test_checked_sum(inputs in vec(any_with::<BoxValue>((9000..10000000).into()), 1..10)) {
+            let expected_sum: i64 = inputs.clone().into_iter().map(|v| *v.as_i64()).sum();
+            let checked_sum = checked_sum(inputs.into_iter()).unwrap();
+            assert_eq!(*checked_sum.as_i64(), expected_sum);
         }
     }
 }
diff --git a/sigma-tree/src/chain/ergo_state_context.rs b/sigma-tree/src/chain/ergo_state_context.rs
new file mode 100644
index 000000000..3764fd2d4
--- /dev/null
+++ b/sigma-tree/src/chain/ergo_state_context.rs
@@ -0,0 +1,12 @@
+//! Blockchain state
+
+/// Blockchain state (last headers, etc.)
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoStateContext();
+
+impl ErgoStateContext {
+    /// Empty(dummy) value
+    pub fn dummy() -> ErgoStateContext {
+        ErgoStateContext()
+    }
+}
diff --git a/sigma-tree/src/chain/input.rs b/sigma-tree/src/chain/input.rs
index 490a9a1cf..fbc38b496 100644
--- a/sigma-tree/src/chain/input.rs
+++ b/sigma-tree/src/chain/input.rs
@@ -1,7 +1,12 @@
 //! Transaction input
 use std::io;
 
-use super::{box_id::BoxId, prover_result::ProverResult, ProofBytes};
+use super::{
+    context_extension::ContextExtension,
+    ergo_box::box_id::BoxId,
+    ergo_box::ErgoBoxId,
+    prover_result::{ProofBytes, ProverResult},
+};
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
@@ -9,6 +14,25 @@ use crate::serialization::{
 #[cfg(feature = "with-serde")]
 use serde::{Deserialize, Serialize};
 
+/// Unsigned (without proofs) transaction input
+#[derive(PartialEq, Debug, Clone)]
+#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
+pub struct UnsignedInput {
+    /// id of the box to spent
+    pub box_id: BoxId,
+    /// user-defined variables to be put into context
+    pub extension: ContextExtension,
+}
+
+impl<T: ErgoBoxId> From<T> for UnsignedInput {
+    fn from(b: T) -> Self {
+        UnsignedInput {
+            box_id: b.box_id(),
+            extension: ContextExtension::empty(),
+        }
+    }
+}
+
 /// Fully signed transaction input
 #[derive(PartialEq, Debug, Clone)]
 #[cfg_attr(test, derive(proptest_derive::Arbitrary))]
diff --git a/sigma-tree/src/chain/json.rs b/sigma-tree/src/chain/json.rs
index 323c22758..3c724036b 100644
--- a/sigma-tree/src/chain/json.rs
+++ b/sigma-tree/src/chain/json.rs
@@ -39,7 +39,11 @@ pub mod ergo_tree {
 
 pub mod ergo_box {
     use crate::{
-        chain::{box_value::BoxValue, register::NonMandatoryRegisters, BoxId, TokenAmount, TxId},
+        chain::{
+            ergo_box::{box_id::BoxId, box_value::BoxValue, register::NonMandatoryRegisters},
+            token::TokenAmount,
+            transaction::TxId,
+        },
         ErgoTree,
     };
     use serde::Deserialize;
@@ -75,7 +79,7 @@ pub mod ergo_box {
 }
 
 pub mod transaction {
-    use crate::chain::{data_input::DataInput, ErgoBox, Input, TxId};
+    use crate::chain::{data_input::DataInput, ergo_box::ErgoBox, input::Input, transaction::TxId};
     use serde::{Deserialize, Serialize};
 
     #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -97,10 +101,12 @@ pub mod transaction {
 
 #[cfg(test)]
 mod tests {
+    use std::convert::TryInto;
+
     use super::super::ergo_box::*;
     use super::super::transaction::*;
     use super::*;
-    use crate::chain::ContextExtension;
+    use crate::chain::context_extension::ContextExtension;
     use proptest::prelude::*;
     use register::NonMandatoryRegisters;
 
@@ -159,4 +165,20 @@ mod tests {
         assert!(c.values.get(&1u8).is_some());
         assert!(c.values.get(&3u8).is_some());
     }
+
+    #[test]
+    fn parse_ergo_box() {
+        let box_json = r#"{
+          "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+          "value": 67500000000,
+          "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+          "assets": [],
+          "creationHeight": 284761,
+          "additionalRegisters": {},
+          "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+          "index": 1
+        }"#;
+        let b: ErgoBox = serde_json::from_str(box_json).unwrap();
+        assert_eq!(b.value, 67500000000u64.try_into().unwrap());
+    }
 }
diff --git a/sigma-tree/src/chain/transaction.rs b/sigma-tree/src/chain/transaction.rs
index 2225438b3..909981a15 100644
--- a/sigma-tree/src/chain/transaction.rs
+++ b/sigma-tree/src/chain/transaction.rs
@@ -1,14 +1,16 @@
 //! Ergo transaction
 
+pub mod unsigned;
+
 #[cfg(feature = "with-serde")]
 use super::json;
 use super::{
     data_input::DataInput,
     digest32::{blake2b256_hash, Digest32},
+    ergo_box::ErgoBox,
     ergo_box::ErgoBoxCandidate,
     input::Input,
     token::TokenId,
-    ErgoBox,
 };
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
@@ -255,11 +257,13 @@ impl TryFrom<json::transaction::TransactionJson> for Transaction {
 }
 
 #[cfg(test)]
-mod tests {
+pub mod tests {
 
     use super::*;
     use crate::serialization::sigma_serialize_roundtrip;
-    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    use proptest::prelude::*;
+    use proptest::{arbitrary::Arbitrary, collection::vec};
 
     impl Arbitrary for Transaction {
         type Parameters = ();
@@ -288,6 +292,7 @@ mod tests {
         fn tx_id_ser_roundtrip(v in any::<TxId>()) {
             prop_assert_eq![sigma_serialize_roundtrip(&v), v];
         }
+
     }
 
     #[test]
diff --git a/sigma-tree/src/chain/transaction/unsigned.rs b/sigma-tree/src/chain/transaction/unsigned.rs
new file mode 100644
index 000000000..615b3e6a8
--- /dev/null
+++ b/sigma-tree/src/chain/transaction/unsigned.rs
@@ -0,0 +1,108 @@
+//! Unsigned (without proofs) transaction
+
+use super::{
+    super::{
+        data_input::DataInput, digest32::blake2b256_hash, ergo_box::ErgoBoxCandidate, input::Input,
+    },
+    Transaction, TxId,
+};
+use crate::{
+    chain::{
+        input::UnsignedInput,
+        prover_result::{ProofBytes, ProverResult},
+    },
+    serialization::SigmaSerializable,
+};
+
+/// Unsigned (inputs without proofs) transaction
+#[derive(PartialEq, Debug, Clone)]
+pub struct UnsignedTransaction {
+    tx_id: TxId,
+    /// unsigned inputs, that will be spent by this transaction.
+    pub inputs: Vec<UnsignedInput>,
+    /// inputs, that are not going to be spent by transaction, but will be reachable from inputs
+    /// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
+    /// included in transaction cost and they do not contain spending proofs.
+    pub data_inputs: Vec<DataInput>,
+    /// box candidates to be created by this transaction
+    pub output_candidates: Vec<ErgoBoxCandidate>,
+}
+
+impl UnsignedTransaction {
+    /// Creates new transation
+    pub fn new(
+        inputs: Vec<UnsignedInput>,
+        data_inputs: Vec<DataInput>,
+        output_candidates: Vec<ErgoBoxCandidate>,
+    ) -> UnsignedTransaction {
+        let tx_to_sign = UnsignedTransaction {
+            tx_id: TxId::zero(),
+            inputs,
+            data_inputs,
+            output_candidates,
+        };
+        let tx_id = tx_to_sign.calc_tx_id();
+        UnsignedTransaction {
+            tx_id,
+            ..tx_to_sign
+        }
+    }
+
+    fn calc_tx_id(&self) -> TxId {
+        let bytes = self.bytes_to_sign();
+        TxId(blake2b256_hash(&bytes))
+    }
+
+    /// message to be signed by the [`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(),
+                },
+            })
+            .collect();
+        let tx = Transaction::new(
+            empty_proofs_input,
+            self.data_inputs.clone(),
+            self.output_candidates.clone(),
+        );
+        tx.sigma_serialise_bytes()
+    }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+
+    use proptest::prelude::*;
+    use proptest::{arbitrary::Arbitrary, collection::vec};
+
+    impl Arbitrary for UnsignedTransaction {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                vec(any::<UnsignedInput>(), 1..10),
+                vec(any::<DataInput>(), 0..10),
+                vec(any::<ErgoBoxCandidate>(), 1..10),
+            )
+                .prop_map(|(inputs, data_inputs, outputs)| Self::new(inputs, data_inputs, outputs))
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    proptest! {
+
+        #[test]
+        fn test_unsigned_tx_bytes_to_sign(v in any::<UnsignedTransaction>()) {
+            prop_assert!(!v.bytes_to_sign().is_empty());
+        }
+
+    }
+}
diff --git a/sigma-tree/src/constants.rs b/sigma-tree/src/constants.rs
index 8c732091c..1c741743c 100644
--- a/sigma-tree/src/constants.rs
+++ b/sigma-tree/src/constants.rs
@@ -1,1 +1,4 @@
 //! Constants specific to Ergo
+
+pub const MINERS_FEE_MAINNET_ADDRESS: &str =
+    "2iHkR7CWvD1R4j1yZg5bkeDRQavjAaVPeTDFGGLZduHyfWMuYpmhHocX8GJoaieTx78FntzJbCBVL6rf96ocJoZdmWBL2fci7NqWgAirppPQmZ7fN9V6z13Ay6brPriBKYqLp1bT2Fk4FkFLCfdPpe";
diff --git a/sigma-tree/src/ergo_tree.rs b/sigma-tree/src/ergo_tree.rs
index dad2da084..515c363c9 100644
--- a/sigma-tree/src/ergo_tree.rs
+++ b/sigma-tree/src/ergo_tree.rs
@@ -14,6 +14,7 @@ use crate::serialization::constant_store::ConstantStore;
 use sigma_ser::{peekable_reader::PeekableReader, vlq_encode};
 use std::io;
 use std::rc::Rc;
+use thiserror::Error;
 use vlq_encode::ReadSigmaVlqExt;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -60,11 +61,13 @@ pub struct ErgoTreeRootParsingError {
 }
 
 /// ErgoTree parsing (deserialization) error
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum ErgoTreeParsingError {
     /// Whole ErgoTree parsing (deserialization) error
+    #[error("Whole ErgoTree parsing (deserialization) error")]
     TreeParsingError(ErgoTreeConstantsParsingError),
     /// ErgoTree root expr parsing (deserialization) error
+    #[error("ErgoTree root expr parsing (deserialization) error")]
     RootParsingError(ErgoTreeRootParsingError),
 }
 
@@ -299,12 +302,12 @@ mod tests {
 
     #[test]
     fn test_constant_segregation_header_flag_support() {
-        let encoder = chain::AddressEncoder::new(chain::NetworkPrefix::Mainnet);
+        let encoder = chain::address::AddressEncoder::new(chain::address::NetworkPrefix::Mainnet);
         let address = encoder
             .parse_address_from_str("9hzP24a2q8KLPVCUk7gdMDXYc7vinmGuxmLp5KU7k9UwptgYBYV")
             .unwrap();
 
-        let contract = chain::Contract::pay_to_address(address).unwrap();
+        let contract = chain::contract::Contract::pay_to_address(&address).unwrap();
         let bytes = &contract.get_ergo_tree().sigma_serialise_bytes();
         assert_eq!(&bytes[..2], vec![0u8, 8u8].as_slice());
     }
diff --git a/sigma-tree/src/eval.rs b/sigma-tree/src/eval.rs
index 2f2323edc..69dc8fafe 100644
--- a/sigma-tree/src/eval.rs
+++ b/sigma-tree/src/eval.rs
@@ -1,3 +1,4 @@
+//! Interpreter
 use crate::{
     ast::{ops::BinOp, ops::NumOp, Constant, ConstantVal, Expr},
     sigma_protocol::sigma_boolean::SigmaBoolean,
@@ -5,33 +6,46 @@ use crate::{
 };
 
 use cost_accum::CostAccumulator;
+use thiserror::Error;
 use value::Value;
 
 mod cost_accum;
 mod costs;
 mod value;
 
+/// Environment vars for script interpreter
 pub struct Env();
 
 impl Env {
+    /// Empty environment
     pub fn empty() -> Env {
         Env()
     }
 }
 
-#[derive(PartialEq, Eq, Debug, Clone)]
+/// Interpreter errors
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum EvalError {
+    /// Only boolean or SigmaBoolean is a valid result expr type
+    #[error("Only boolean or SigmaBoolean is a valid result expr type")]
     InvalidResultType,
+    /// Unsupported Expr encountered during the evaluation
+    #[error("Unsupported Expr encountered during the evaluation")]
     // TODO: store unexpected expr
     UnexpectedExpr,
 }
 
+/// Result of ErgoTree reduction procedure (see `reduce_to_crypto`).
 pub struct ReductionResult {
+    /// value of SigmaProp type which represents a statement verifiable via sigma protocol.
     pub sigma_prop: SigmaBoolean,
+    /// estimated cost of contract execution
     pub cost: u64,
 }
 
+/// Interpreter
 pub trait Evaluator {
+    /// This method is used in both prover and verifier to compute SigmaBoolean value.
     fn reduce_to_crypto(&self, expr: &Expr, env: &Env) -> Result<ReductionResult, EvalError> {
         let mut ca = CostAccumulator::new(0, None);
         eval(expr, env, &mut ca).and_then(|v| match v {
diff --git a/sigma-tree/src/eval/value.rs b/sigma-tree/src/eval/value.rs
index f43372804..b89f660e6 100644
--- a/sigma-tree/src/eval/value.rs
+++ b/sigma-tree/src/eval/value.rs
@@ -1,4 +1,4 @@
-use crate::{chain::ErgoBox, sigma_protocol::sigma_boolean::SigmaBoolean};
+use crate::{chain::ergo_box::ErgoBox, sigma_protocol::sigma_boolean::SigmaBoolean};
 use std::ops::Add;
 
 #[allow(dead_code)]
diff --git a/sigma-tree/src/lib.rs b/sigma-tree/src/lib.rs
index 083b52e9e..8f3a54b17 100644
--- a/sigma-tree/src/lib.rs
+++ b/sigma-tree/src/lib.rs
@@ -15,13 +15,14 @@
 mod big_integer;
 mod constants;
 mod ergo_tree;
-mod eval;
 pub mod serialization;
 mod types;
 
 pub mod ast;
 pub mod chain;
+pub mod eval;
 pub mod sigma_protocol;
+pub mod wallet;
 
 pub use ergo_tree::*;
 // pub use serialization::serializable::*;
diff --git a/sigma-tree/src/serialization/ergo_box.rs b/sigma-tree/src/serialization/ergo_box.rs
index f5cadbb60..d54d416ba 100644
--- a/sigma-tree/src/serialization/ergo_box.rs
+++ b/sigma-tree/src/serialization/ergo_box.rs
@@ -5,8 +5,8 @@ use crate::serialization::{
 use crate::{
     ast::Constant,
     chain::{
-        box_value::BoxValue, register::NonMandatoryRegisters, ErgoBoxCandidate, TokenAmount,
-        TokenId,
+        ergo_box::{box_value::BoxValue, register::NonMandatoryRegisters, ErgoBoxCandidate},
+        token::{TokenAmount, TokenId},
     },
     ErgoTree,
 };
diff --git a/sigma-tree/src/serialization/serializable.rs b/sigma-tree/src/serialization/serializable.rs
index 1b662e482..c87c44004 100644
--- a/sigma-tree/src/serialization/serializable.rs
+++ b/sigma-tree/src/serialization/serializable.rs
@@ -36,6 +36,9 @@ pub enum SerializationError {
     /// Constant with given index not found in constant store
     #[error("Constant with index {0} not found in constant store")]
     ConstantForPlaceholderNotFound(u32),
+    /// Value out of bounds
+    #[error("Value out of bounds: {0}")]
+    ValueOutOfBounds(String),
 }
 
 impl From<vlq_encode::VlqEncodingError> for SerializationError {
diff --git a/sigma-tree/src/sigma_protocol/prover.rs b/sigma-tree/src/sigma_protocol/prover.rs
index a1a367710..ceee97d1f 100644
--- a/sigma-tree/src/sigma_protocol/prover.rs
+++ b/sigma-tree/src/sigma_protocol/prover.rs
@@ -9,27 +9,35 @@ use super::{
     UncheckedSigmaTree, UncheckedTree, UnprovenLeaf, UnprovenSchnorr, UnprovenTree,
 };
 use crate::{
-    chain::{ContextExtension, ProverResult},
+    chain::{context_extension::ContextExtension, prover_result::ProverResult},
     eval::{Env, EvalError, Evaluator},
     ErgoTree, ErgoTreeParsingError,
 };
+use thiserror::Error;
 
 /// Prover errors
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum ProverError {
     /// Failed to parse ErgoTree
+    #[error("Ergo tree error: {0}")]
     ErgoTreeError(ErgoTreeParsingError),
     /// Failed to evaluate ErgoTree
+    #[error("Evaluation error: {0}")]
     EvalError(EvalError),
     /// Script reduced to false
+    #[error("Script reduced to false")]
     ReducedToFalse,
     /// Failed on step2(prover does not have enough witnesses to perform the proof)
+    #[error("Failed on step2(prover does not have enough witnesses to perform the proof)")]
     TreeRootIsNotReal,
     /// Simulated leaf does not have challenge
+    #[error("Simulated leaf does not have challenge")]
     SimulatedLeafWithoutChallenge,
     /// Lacking challenge on step 9 for "real" unproven tree
+    #[error("Lacking challenge on step 9 for \"real\" unproven tree")]
     RealUnprovenTreeWithoutChallenge,
     /// Cannot find a secret for "real" unproven leaf
+    #[error("Cannot find a secret for \"real\" unproven leaf")]
     SecretNotFound,
 }
 
@@ -288,7 +296,7 @@ mod tests {
     use super::*;
     use crate::{
         ast::{Constant, ConstantVal, Expr},
-        chain::ProofBytes,
+        chain::prover_result::ProofBytes,
         sigma_protocol::DlogProverInput,
         types::SType,
     };
diff --git a/sigma-tree/src/sigma_protocol/sig_serializer.rs b/sigma-tree/src/sigma_protocol/sig_serializer.rs
index 3d917d79a..d70e71b48 100644
--- a/sigma-tree/src/sigma_protocol/sig_serializer.rs
+++ b/sigma-tree/src/sigma_protocol/sig_serializer.rs
@@ -6,7 +6,8 @@ use super::{
     Challenge, GroupSizedBytes, SigmaBoolean, SigmaProofOfKnowledgeTree, UncheckedSigmaTree,
     UncheckedTree,
 };
-use crate::chain::ProofBytes;
+
+use crate::chain::prover_result::ProofBytes;
 use k256::Scalar;
 use std::convert::{TryFrom, TryInto};
 
diff --git a/sigma-tree/src/sigma_protocol/verifier.rs b/sigma-tree/src/sigma_protocol/verifier.rs
index 5cd82aef0..0bf36247f 100644
--- a/sigma-tree/src/sigma_protocol/verifier.rs
+++ b/sigma-tree/src/sigma_protocol/verifier.rs
@@ -8,7 +8,7 @@ use super::{
     SigmaBoolean, UncheckedSigmaTree, UncheckedTree,
 };
 use crate::{
-    chain::ProofBytes,
+    chain::prover_result::ProofBytes,
     eval::{Env, EvalError, Evaluator},
     ErgoTree, ErgoTreeParsingError,
 };
@@ -155,7 +155,9 @@ mod tests {
     #[test]
     #[cfg(feature = "with-serde")]
     fn test_proof_from_mainnet() {
-        use crate::chain::{AddressEncoder, NetworkPrefix, Transaction};
+        use crate::chain::address::{AddressEncoder, NetworkPrefix};
+        use crate::chain::transaction::Transaction;
+
         let tx_json = r#"
          {
       "id": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
diff --git a/sigma-tree/src/wallet.rs b/sigma-tree/src/wallet.rs
new file mode 100644
index 000000000..3eaa78a18
--- /dev/null
+++ b/sigma-tree/src/wallet.rs
@@ -0,0 +1,69 @@
+//! Wallet-related features for Ergo
+
+pub mod box_selector;
+pub mod secret_key;
+pub mod signing;
+pub mod tx_builder;
+
+use secret_key::SecretKey;
+use signing::{sign_transaction, TxSigningError};
+use thiserror::Error;
+
+use crate::{
+    chain::{
+        ergo_box::ErgoBox, ergo_state_context::ErgoStateContext,
+        transaction::unsigned::UnsignedTransaction, transaction::Transaction,
+    },
+    sigma_protocol::prover::Prover,
+    sigma_protocol::prover::TestProver,
+    sigma_protocol::PrivateInput,
+};
+
+/// Wallet
+pub struct Wallet {
+    prover: Box<dyn Prover>,
+}
+
+/// Wallet errors
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+pub enum WalletError {
+    /// Error on tx signing
+    #[error("Transaction signing error: {0}")]
+    TxSigningError(TxSigningError),
+}
+
+impl From<TxSigningError> for WalletError {
+    fn from(e: TxSigningError) -> Self {
+        WalletError::TxSigningError(e)
+    }
+}
+
+impl Wallet {
+    /// Create Wallet from secrets
+    pub fn from_secrets(secrets: Vec<SecretKey>) -> Wallet {
+        let prover = TestProver {
+            secrets: secrets.into_iter().map(PrivateInput::from).collect(),
+        };
+        Wallet {
+            prover: Box::new(prover),
+        }
+    }
+
+    /// Signs a transaction
+    pub fn sign_transaction(
+        &self,
+        tx: UnsignedTransaction,
+        boxes_to_spend: &[ErgoBox],
+        _data_boxes: &[ErgoBox],
+        _state_context: &ErgoStateContext,
+    ) -> Result<Transaction, WalletError> {
+        sign_transaction(
+            self.prover.as_ref(),
+            tx,
+            boxes_to_spend,
+            _data_boxes,
+            _state_context,
+        )
+        .map_err(WalletError::from)
+    }
+}
diff --git a/sigma-tree/src/wallet/box_selector.rs b/sigma-tree/src/wallet/box_selector.rs
new file mode 100644
index 000000000..6979bcef5
--- /dev/null
+++ b/sigma-tree/src/wallet/box_selector.rs
@@ -0,0 +1,48 @@
+//! Box selection for transaction inputs
+
+pub mod simple;
+
+use crate::chain::ergo_box::box_value::BoxValueError;
+use crate::chain::ergo_box::ErgoBoxAssetsData;
+use crate::chain::{
+    ergo_box::{box_value::BoxValue, ErgoBoxAssets},
+    token::TokenAmount,
+};
+use thiserror::Error;
+
+/// Selected boxes (by [`BoxSelector`])
+pub struct BoxSelection<T: ErgoBoxAssets> {
+    /// selected boxes to spend
+    pub boxes: Vec<T>,
+    /// box assets with returning change amounts (to be put in tx outputs)
+    pub change_boxes: Vec<ErgoBoxAssetsData>,
+}
+
+/// Box selector
+pub trait BoxSelector<T: ErgoBoxAssets> {
+    /// Selects boxes out of the provided inputs to satisfy target balance and tokens
+    fn select(
+        &self,
+        inputs: Vec<T>,
+        target_balance: BoxValue,
+        target_tokens: &[TokenAmount],
+    ) -> Result<BoxSelection<T>, BoxSelectorError>;
+}
+
+/// Errors of BoxSelector
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+pub enum BoxSelectorError {
+    /// Not enough coins
+    #[error("Not enough coins({0} nanoERGs are missing)")]
+    NotEnoughCoins(u64),
+
+    /// BoxValue out of bounds
+    #[error("BoxValue out of bounds")]
+    BoxValueError(BoxValueError),
+}
+
+impl From<BoxValueError> for BoxSelectorError {
+    fn from(e: BoxValueError) -> Self {
+        BoxSelectorError::BoxValueError(e)
+    }
+}
diff --git a/sigma-tree/src/wallet/box_selector/simple.rs b/sigma-tree/src/wallet/box_selector/simple.rs
new file mode 100644
index 000000000..a7690f1c9
--- /dev/null
+++ b/sigma-tree/src/wallet/box_selector/simple.rs
@@ -0,0 +1,110 @@
+//! Box selector which selects all provided inputs
+
+use std::convert::TryInto;
+
+use crate::chain::ergo_box::box_value::BoxValue;
+use crate::chain::ergo_box::ErgoBoxAssets;
+use crate::chain::ergo_box::ErgoBoxAssetsData;
+use crate::chain::token::TokenAmount;
+
+use super::BoxSelectorError;
+use super::{BoxSelection, BoxSelector};
+
+/// Naive box selector, collects inputs until target balance is reached
+#[allow(dead_code)]
+pub struct SimpleBoxSelector {}
+
+impl SimpleBoxSelector {
+    /// Create new boxed instance
+    pub fn new() -> Box<Self> {
+        Box::new(SimpleBoxSelector {})
+    }
+}
+
+impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
+    /// Selects inputs to satisfy target balance and tokens.
+    /// `inputs` - available inputs (returns an error, if empty),
+    /// `target_balance` - coins (in nanoERGs) needed,
+    /// `target_tokens` - amount of tokens needed.
+    /// Returns selected inputs and box assets(value+tokens) with change.
+    fn select(
+        &self,
+        inputs: Vec<T>,
+        target_balance: BoxValue,
+        target_tokens: &[TokenAmount],
+    ) -> Result<BoxSelection<T>, BoxSelectorError> {
+        assert!(target_tokens.is_empty(), "tokens are not yet supported");
+        let mut selected_inputs: Vec<T> = vec![];
+        let mut unmet_target_balance: i64 = target_balance.into();
+        inputs.into_iter().for_each(|b| {
+            if unmet_target_balance > 0 {
+                let b_value: i64 = b.value().into();
+                unmet_target_balance -= b_value;
+                selected_inputs.push(b);
+            };
+        });
+        if unmet_target_balance > 0 {
+            return Err(BoxSelectorError::NotEnoughCoins(
+                unmet_target_balance.abs() as u64
+            ));
+        }
+        let change_boxes: Vec<ErgoBoxAssetsData> = if unmet_target_balance == 0 {
+            vec![]
+        } else {
+            let change_value: BoxValue = unmet_target_balance.abs().try_into()?;
+            vec![ErgoBoxAssetsData {
+                value: change_value,
+                tokens: vec![],
+            }]
+        };
+        Ok(BoxSelection {
+            boxes: selected_inputs,
+            change_boxes,
+        })
+    }
+}
+
+impl Default for SimpleBoxSelector {
+    fn default() -> Self {
+        SimpleBoxSelector {}
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::chain::ergo_box::box_value;
+    use crate::chain::ergo_box::ErgoBox;
+    use proptest::{collection::vec, prelude::*};
+
+    use super::*;
+
+    #[test]
+    fn test_empty_inputs() {
+        let s = SimpleBoxSelector::new();
+        let inputs: Vec<ErgoBox> = vec![];
+        let r = s.select(inputs, BoxValue::MIN, vec![].as_slice());
+        assert!(r.is_err());
+    }
+
+    proptest! {
+        #[test]
+        fn test_select(inputs in vec(any_with::<ErgoBox>((BoxValue::MIN_RAW * 2 .. BoxValue::MIN_RAW * 10).into()), 1..10)) {
+            let s = SimpleBoxSelector::new();
+            let all_inputs_val = box_value::checked_sum(inputs.iter().map(|b| b.value)).unwrap();
+
+            let balance_too_much = all_inputs_val.checked_add(&BoxValue::MIN).unwrap();
+            prop_assert!(s.select(inputs.clone(), balance_too_much, vec![].as_slice()).is_err());
+
+            let balance_exact = all_inputs_val;
+            let selection_exact = s.select(inputs.clone(), balance_exact, vec![].as_slice()).unwrap();
+            prop_assert!(selection_exact.change_boxes.is_empty());
+            prop_assert!(selection_exact.boxes == inputs);
+
+            let balance_less = all_inputs_val.checked_sub(&BoxValue::MIN).unwrap();
+            let selection_less = s.select(inputs.clone(), balance_less, vec![].as_slice()).unwrap();
+            let expected_change_box = ErgoBoxAssetsData {value: BoxValue::MIN, tokens: vec![]};
+            prop_assert!(selection_less.change_boxes == vec![expected_change_box]);
+            prop_assert!(selection_less.boxes == inputs);
+        }
+    }
+}
diff --git a/sigma-tree/src/wallet/secret_key.rs b/sigma-tree/src/wallet/secret_key.rs
new file mode 100644
index 000000000..77af243dd
--- /dev/null
+++ b/sigma-tree/src/wallet/secret_key.rs
@@ -0,0 +1,38 @@
+//! Secret types
+use crate::chain::address::Address;
+use crate::sigma_protocol::{DlogProverInput, PrivateInput};
+
+/// Types of secrets
+#[derive(PartialEq, Debug, Clone)]
+pub enum SecretKey {
+    /// Secret exponent of a group element, i.e. secret w such as h = g^^w, where g is group generator, h is a public key.
+    DlogSecretKey(DlogProverInput),
+}
+
+impl SecretKey {
+    /// Generates random DlogProverInput
+    pub fn random_dlog() -> SecretKey {
+        SecretKey::DlogSecretKey(DlogProverInput::random())
+    }
+
+    /// Address (encoded public image)
+    pub fn get_address_from_public_image(&self) -> Address {
+        match self {
+            SecretKey::DlogSecretKey(dpi) => Address::P2PK(dpi.public_image()),
+        }
+    }
+}
+
+impl From<SecretKey> for PrivateInput {
+    fn from(s: SecretKey) -> Self {
+        match s {
+            SecretKey::DlogSecretKey(dpi) => PrivateInput::DlogProverInput(dpi),
+        }
+    }
+}
+
+impl From<DlogProverInput> for SecretKey {
+    fn from(pi: DlogProverInput) -> Self {
+        SecretKey::DlogSecretKey(pi)
+    }
+}
diff --git a/sigma-tree/src/wallet/signing.rs b/sigma-tree/src/wallet/signing.rs
new file mode 100644
index 000000000..ba6886c80
--- /dev/null
+++ b/sigma-tree/src/wallet/signing.rs
@@ -0,0 +1,148 @@
+//! Transaction signing
+
+use crate::{
+    chain::{
+        ergo_box::ErgoBox,
+        ergo_state_context::ErgoStateContext,
+        input::Input,
+        transaction::{unsigned::UnsignedTransaction, Transaction},
+    },
+    eval::Env,
+    sigma_protocol::prover::{Prover, ProverError},
+};
+
+use thiserror::Error;
+
+/// Errors on transaction signing
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+pub enum TxSigningError {
+    /// error on proving an input
+    #[error("Prover error (tx input index {1}): {0}")]
+    ProverError(ProverError, usize),
+    /// failed to find an input in boxes_to_spend
+    #[error("Input box not found (index {0})")]
+    InputBoxNotFound(usize),
+}
+
+/// Signs a transaction (generating proofs for inputs)
+pub fn sign_transaction(
+    prover: &dyn Prover,
+    tx: UnsignedTransaction,
+    boxes_to_spend: &[ErgoBox],
+    _data_boxes: &[ErgoBox],
+    _state_context: &ErgoStateContext,
+) -> Result<Transaction, TxSigningError> {
+    let message_to_sign = tx.bytes_to_sign();
+    let mut signed_inputs: Vec<Input> = vec![];
+    boxes_to_spend
+        .iter()
+        .enumerate()
+        .try_for_each(|(idx, input_box)| {
+            if let Some(unsigned_input) = tx.inputs.get(idx) {
+                prover
+                    .prove(
+                        &input_box.ergo_tree,
+                        &Env::empty(),
+                        message_to_sign.as_slice(),
+                    )
+                    .map(|proof| {
+                        let input = Input {
+                            box_id: unsigned_input.box_id.clone(),
+                            spending_proof: proof,
+                        };
+                        signed_inputs.push(input);
+                    })
+                    .map_err(|e| TxSigningError::ProverError(e, idx))
+            } else {
+                Err(TxSigningError::InputBoxNotFound(idx))
+            }
+        })?;
+    Ok(Transaction::new(
+        signed_inputs,
+        tx.data_inputs,
+        tx.output_candidates,
+    ))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::collection::vec;
+    use proptest::prelude::*;
+
+    use crate::chain::ergo_box::box_value::BoxValue;
+    use crate::{
+        ast::{Constant, Expr},
+        chain::{
+            ergo_box::{register::NonMandatoryRegisters, ErgoBoxCandidate},
+            input::UnsignedInput,
+            transaction::TxId,
+        },
+        sigma_protocol::{
+            prover::TestProver,
+            verifier::{TestVerifier, Verifier, VerifierError},
+            DlogProverInput, PrivateInput,
+        },
+        types::SType,
+        ErgoTree,
+    };
+    use std::rc::Rc;
+
+    fn verify_tx_proofs(
+        tx: &Transaction,
+        boxes_to_spend: &[ErgoBox],
+    ) -> Result<bool, VerifierError> {
+        let verifier = TestVerifier;
+        let message = tx.bytes_to_sign();
+        boxes_to_spend
+            .iter()
+            .zip(tx.inputs.clone())
+            .try_fold(true, |acc, (b, input)| {
+                let res = verifier.verify(
+                    &b.ergo_tree,
+                    &Env::empty(),
+                    &input.spending_proof.proof,
+                    &message,
+                )?;
+                Ok(res.result && acc)
+            })
+    }
+
+    proptest! {
+
+        #[test]
+        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(Constant {
+                    tpe: SType::SSigmaProp,
+                    v: pk.into(),
+                })));
+                ErgoBox::new(BoxValue::MIN.checked_mul_u32(100).unwrap(),
+                             tree,
+                             vec![],
+                             NonMandatoryRegisters::empty(),
+                             0,
+                             TxId::zero(),
+                             0)
+            }).collect();
+            let prover = TestProver {
+                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(Constant {
+                    tpe: SType::SSigmaProp,
+                    v: secrets.get(0).unwrap().public_image().into(),
+            })));
+            let candidate = ErgoBoxCandidate::new(BoxValue::MIN.checked_mul_u32(100).unwrap(), ergo_tree, 0).unwrap();
+            let output_candidates = vec![candidate];
+            let tx = UnsignedTransaction::new(inputs, vec![], output_candidates);
+
+            let res = sign_transaction(Box::new(prover).as_ref(), tx, boxes_to_spend.as_slice(), vec![].as_slice(),
+                                       &ErgoStateContext::dummy());
+            let signed_tx = res.unwrap();
+            prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
+        }
+
+    }
+}
diff --git a/sigma-tree/src/wallet/tx_builder.rs b/sigma-tree/src/wallet/tx_builder.rs
new file mode 100644
index 000000000..05bdb65d1
--- /dev/null
+++ b/sigma-tree/src/wallet/tx_builder.rs
@@ -0,0 +1,263 @@
+//! Builder for an UnsignedTransaction
+
+use box_value::BoxValueError;
+use thiserror::Error;
+
+use crate::chain::address::Address;
+use crate::chain::address::AddressEncoder;
+use crate::chain::address::NetworkPrefix;
+use crate::chain::contract::Contract;
+use crate::chain::ergo_box::box_value;
+use crate::chain::ergo_box::ErgoBoxCandidateError;
+use crate::chain::{
+    ergo_box::ErgoBoxAssets,
+    ergo_box::ErgoBoxId,
+    ergo_box::{box_value::BoxValue, ErgoBoxCandidate},
+    input::UnsignedInput,
+    transaction::unsigned::UnsignedTransaction,
+};
+use crate::constants::MINERS_FEE_MAINNET_ADDRESS;
+use crate::serialization::SerializationError;
+
+use super::box_selector::{BoxSelection, BoxSelector, BoxSelectorError};
+
+/// Unsigned transaction builder
+pub struct TxBuilder<S: ErgoBoxAssets> {
+    box_selector: Box<dyn BoxSelector<S>>,
+    boxes_to_spend: Vec<S>,
+    output_candidates: Vec<ErgoBoxCandidate>,
+    current_height: u32,
+    fee_amount: BoxValue,
+    change_address: Address,
+    min_change_value: BoxValue,
+}
+
+impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
+    /// Creates new TxBuilder
+    /// `box_selector` - input box selection algorithm to choose inputs from `boxes_to_spend`,
+    /// `boxes_to_spend` - spendable boxes,
+    /// `output_candidates` - output boxes to be "created" in this transaction,
+    /// `current_height` - chain height that will be used in additionally created boxes (change, miner's fee, etc.),
+    /// `fee_amount` - miner's fee,
+    /// `change_address` - change (inputs - outputs) will be sent to this address,
+    /// `min_change_value` - minimal value of the change to be sent to `change_address`, value less than that
+    /// will be given to miners,
+    pub fn new(
+        box_selector: Box<dyn BoxSelector<S>>,
+        boxes_to_spend: Vec<S>,
+        output_candidates: Vec<ErgoBoxCandidate>,
+        current_height: u32,
+        fee_amount: BoxValue,
+        change_address: Address,
+        min_change_value: BoxValue,
+    ) -> Result<TxBuilder<S>, TxBuilderError> {
+        if boxes_to_spend.is_empty() {
+            return Err(TxBuilderError::InvalidArgs(
+                "boxes_to_spend is empty".to_string(),
+            ));
+        }
+        if output_candidates.is_empty() {
+            return Err(TxBuilderError::InvalidArgs(
+                "output_candidates is empty".to_string(),
+            ));
+        }
+        Ok(TxBuilder {
+            box_selector,
+            boxes_to_spend,
+            output_candidates,
+            current_height,
+            fee_amount,
+            change_address,
+            min_change_value,
+        })
+    }
+
+    /// Build the unsigned transaction
+    pub fn build(&self) -> Result<UnsignedTransaction, TxBuilderError> {
+        let total_output_value: BoxValue =
+            box_value::checked_sum(self.output_candidates.iter().map(|b| b.value))?
+                .checked_add(&self.fee_amount)?;
+        let selection: BoxSelection<S> = self.box_selector.select(
+            self.boxes_to_spend.clone(),
+            total_output_value,
+            vec![].as_slice(),
+        )?;
+        let mut output_candidates = self.output_candidates.clone();
+
+        let change_address_ergo_tree =
+            Contract::pay_to_address(&self.change_address)?.get_ergo_tree();
+        let change_boxes: Result<Vec<ErgoBoxCandidate>, ErgoBoxCandidateError> = selection
+            .change_boxes
+            .iter()
+            .filter(|b| b.value >= self.min_change_value)
+            .map(|b| {
+                ErgoBoxCandidate::new(
+                    b.value,
+                    change_address_ergo_tree.clone(),
+                    self.current_height,
+                )
+            })
+            .collect();
+        output_candidates.append(&mut change_boxes?);
+        // add miner's fee
+        let miner_fee_box = new_miner_fee_box(self.fee_amount, self.current_height)?;
+        output_candidates.push(miner_fee_box);
+        Ok(UnsignedTransaction::new(
+            selection
+                .boxes
+                .into_iter()
+                .map(UnsignedInput::from)
+                .collect(),
+            vec![],
+            output_candidates,
+        ))
+    }
+}
+
+/// Create a box with miner's contract and a given value
+pub fn new_miner_fee_box(
+    fee_amount: BoxValue,
+    creation_height: u32,
+) -> Result<ErgoBoxCandidate, ErgoBoxCandidateError> {
+    let address_encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+    let miner_fee_address = address_encoder
+        .parse_address_from_str(MINERS_FEE_MAINNET_ADDRESS)
+        .unwrap();
+    let ergo_tree = miner_fee_address.script().unwrap();
+    ErgoBoxCandidate::new(fee_amount, ergo_tree, creation_height)
+}
+
+/// Errors of TxBuilder
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+pub enum TxBuilderError {
+    /// Box selection error
+    #[error("Box selector error: {0}")]
+    BoxSelectorError(BoxSelectorError),
+    /// Box value error
+    #[error("Box value error")]
+    BoxValueError(BoxValueError),
+    /// Serialization error
+    #[error("Serialization error")]
+    SerializationError(SerializationError),
+    /// Invalid arguments
+    #[error("Invalid arguments: {0}")]
+    InvalidArgs(String),
+    /// ErgoBoxCandidate error
+    #[error("ErgoBoxCandidate error: {0}")]
+    ErgoBoxCandidateError(ErgoBoxCandidateError),
+}
+
+impl From<BoxSelectorError> for TxBuilderError {
+    fn from(e: BoxSelectorError) -> Self {
+        TxBuilderError::BoxSelectorError(e)
+    }
+}
+
+impl From<BoxValueError> for TxBuilderError {
+    fn from(e: BoxValueError) -> Self {
+        TxBuilderError::BoxValueError(e)
+    }
+}
+
+impl From<SerializationError> for TxBuilderError {
+    fn from(e: SerializationError) -> Self {
+        TxBuilderError::SerializationError(e)
+    }
+}
+
+impl From<ErgoBoxCandidateError> for TxBuilderError {
+    fn from(e: ErgoBoxCandidateError) -> Self {
+        TxBuilderError::ErgoBoxCandidateError(e)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use proptest::strategy::ValueTree;
+    use proptest::test_runner::TestRunner;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    use crate::chain::ergo_box::ErgoBox;
+    use crate::wallet::box_selector::simple::SimpleBoxSelector;
+
+    use super::*;
+
+    fn force_any_val<T: Arbitrary>() -> T {
+        let mut runner = TestRunner::default();
+        any::<T>().new_tree(&mut runner).unwrap().current()
+    }
+
+    #[test]
+    fn test_empty_inputs() {
+        let inputs: Vec<ErgoBox> = vec![];
+        let r = TxBuilder::new(
+            SimpleBoxSelector::new(),
+            inputs,
+            vec![force_any_val::<ErgoBoxCandidate>()],
+            1,
+            force_any_val::<BoxValue>(),
+            force_any_val::<Address>(),
+            BoxValue::MIN,
+        );
+        assert!(r.is_err());
+    }
+
+    #[test]
+    fn test_empty_outputs() {
+        let outputs: Vec<ErgoBoxCandidate> = vec![];
+        let r = TxBuilder::new(
+            SimpleBoxSelector::new(),
+            vec![force_any_val::<ErgoBox>()],
+            outputs,
+            1,
+            force_any_val::<BoxValue>(),
+            force_any_val::<Address>(),
+            BoxValue::MIN,
+        );
+        assert!(r.is_err());
+    }
+
+    proptest! {
+
+        #[test]
+        fn test_build_tx(inputs in vec(any_with::<ErgoBox>((BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
+                         outputs in vec(any_with::<ErgoBoxCandidate>((BoxValue::MIN_RAW * 1000 ..BoxValue::MIN_RAW * 2000).into()), 1..2),
+                         change_address in any::<Address>(),
+                         miners_fee in any_with::<BoxValue>((BoxValue::MIN_RAW * 1000..BoxValue::MIN_RAW * 2000).into())) {
+            let min_change_value = BoxValue::MIN;
+
+            let all_outputs = box_value::checked_sum(outputs.iter().map(|b| b.value)).unwrap()
+                                                                             .checked_add(&miners_fee)
+                                                                             .unwrap();
+            let all_inputs = box_value::checked_sum(inputs.iter().map(|b| b.value)).unwrap();
+
+            prop_assume!(all_outputs < all_inputs);
+
+            let tx_builder = TxBuilder::new(
+                SimpleBoxSelector::new(),
+                inputs.clone(),
+                outputs.clone(),
+                1,
+                miners_fee,
+                change_address.clone(),
+                min_change_value,
+            ).unwrap();
+            let tx = tx_builder.build().unwrap();
+            prop_assert!(outputs.into_iter().all(|i| tx.output_candidates.iter().any(|o| *o == i)),
+                         "tx.output_candidates is missing some outputs");
+            let tx_all_inputs_vals: Vec<BoxValue> = tx.inputs.iter()
+                                                             .map(|i| inputs.iter()
+                                                                  .find(|ib| ib.box_id() == i.box_id).unwrap().value)
+                                                             .collect();
+            let tx_all_inputs_sum = box_value::checked_sum(tx_all_inputs_vals.into_iter()).unwrap();
+            let expected_change = tx_all_inputs_sum.checked_sub(&all_outputs).unwrap();
+            prop_assert!(tx.output_candidates.iter().any(|b| {
+                b.value == expected_change && b.ergo_tree == change_address.script().unwrap()
+            }), "box with change {:?} is not found in outputs: {:?}", expected_change, tx.output_candidates);
+            prop_assert!(tx.output_candidates.iter().any(|b| {
+                b.value == miners_fee
+            }), "box with miner's fee {:?} is not found in outputs: {:?}", miners_fee, tx.output_candidates);
+        }
+    }
+}


Test patch:
diff --git a/bindings/ergo-wallet-lib-wasm/tests/test_transaction.js b/bindings/ergo-wallet-lib-wasm/tests/test_transaction.js
index fa2af28db..126cfce01 100644
--- a/bindings/ergo-wallet-lib-wasm/tests/test_transaction.js
+++ b/bindings/ergo-wallet-lib-wasm/tests/test_transaction.js
@@ -1,28 +1,56 @@
-import { expect } from 'chai';
+import { expect, assert } from 'chai';
 
 import {
-  Address, Wallet, UnspentBoxes, TxDataInputs, ErgoBoxCandidate, Contract,
-  TxOutputCandidates, ErgoStateContext
+  Address, Wallet, ErgoBox, ErgoBoxCandidate, Contract,
+  ErgoBoxes, ErgoBoxCandidates,
+  ErgoStateContext, TxBuilder, BoxValue, UnsignedTransaction, BoxSelector, SecretKey, TxId,
 } from '../pkg/ergo_wallet_lib_wasm';
 
-it('new signed transaction', async () => {
-
+it('TxBuilder test', async () => {
   const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
-  const unspent_boxes = UnspentBoxes.from_boxes([]);
-  const tx_data_inputs = TxDataInputs.from_boxes([]);
-  const send_change_to = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
-
-  let contract = Contract.pay_to_address(recipient);
-  let outbox = new ErgoBoxCandidate(1, 0, contract);
-  let tx_outputs = new TxOutputCandidates(outbox);
-  let dummy_ctx = ErgoStateContext.dummy();
-  let wallet = Wallet.from_mnemonic("", "");
-  expect(() => wallet.new_signed_transaction(dummy_ctx,
-    unspent_boxes,
-    tx_data_inputs,
-    tx_outputs,
-    send_change_to,
-    1,
-    1)).to.throw("Not yet implemented");
+  const unspent_boxes = ErgoBoxes.from_boxes_json([
+    {
+      "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+      "value": 67500000000,
+      "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+      "assets": [],
+      "creationHeight": 284761,
+      "additionalRegisters": {},
+      "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+      "index": 1
+    }
+  ]);
+  const contract = Contract.pay_to_address(recipient);
+  const outbox = new ErgoBoxCandidate(BoxValue.from_u32(10000000), 0, contract);
+  const tx_outputs = new ErgoBoxCandidates(outbox);
+  const fee = BoxValue.from_u32(1000000);
+  const change_address = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+  const min_change_value = BoxValue.MIN();
+  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value);
+  const tx = tx_builder.build();
+  assert(tx != null);
 });
 
+
+it('sign transaction', async () => {
+  const sk = SecretKey.random_dlog();
+  // simulate existing box guarded by the sk key
+  const input_contract = Contract.pay_to_address(sk.get_address());
+  const input_box = new ErgoBox(BoxValue.from_u32(1000000000), 0, input_contract, TxId.zero(), 0);
+  // create a transaction that spends the "simulated" box
+  const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+  const unspent_boxes = new ErgoBoxes(input_box);
+  const contract = Contract.pay_to_address(recipient);
+  const outbox = new ErgoBoxCandidate(BoxValue.from_u32(10000000), 0, contract);
+  const tx_outputs = new ErgoBoxCandidates(outbox);
+  const fee = BoxValue.from_u32(1000000);
+  const change_address = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+  const min_change_value = BoxValue.MIN();
+  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value);
+  const tx = tx_builder.build();
+  const tx_data_inputs = ErgoBoxes.from_boxes_json([]);
+  const dummy_ctx = ErgoStateContext.dummy();
+  const wallet = Wallet.from_secret(sk);
+  const signed_tx = wallet.sign_transaction(dummy_ctx, tx, unspent_boxes, tx_data_inputs);
+  assert(signed_tx != null);
+});
diff --git a/bindings/ergo-wallet-lib-wasm/tests/transaction.rs b/bindings/ergo-wallet-lib-wasm/tests/transaction.rs
index 3c343e78a..1bf2d8835 100644
--- a/bindings/ergo-wallet-lib-wasm/tests/transaction.rs
+++ b/bindings/ergo-wallet-lib-wasm/tests/transaction.rs
@@ -1,39 +1,75 @@
 //! Test suite for the Web and headless browsers.
 
-extern crate ergo_wallet_lib_wasm;
 extern crate wasm_bindgen_test;
-use ergo_wallet_lib_wasm::*;
+
+use std::{convert::TryInto, rc::Rc};
+
+use ergo_wallet_lib_wasm::{
+    address::Address, box_coll::ErgoBoxCandidates, box_coll::ErgoBoxes, box_selector::BoxSelector,
+    contract::Contract, ergo_box::BoxValue, ergo_box::ErgoBoxCandidate,
+    ergo_state_ctx::ErgoStateContext, secret_key::SecretKey, tx_builder::TxBuilder, wallet::Wallet,
+};
+use sigma_tree::{
+    ast::Expr, chain::ergo_box::register::NonMandatoryRegisters, chain::ergo_box::ErgoBox,
+    chain::transaction::TxId, sigma_protocol::sigma_boolean::SigmaProp,
+    sigma_protocol::DlogProverInput, ErgoTree,
+};
+use wasm_bindgen::JsValue;
 use wasm_bindgen_test::*;
 
 wasm_bindgen_test_configure!(run_in_browser);
 
 #[wasm_bindgen_test]
-fn test_signed_p2pk_transaction() {
-    let tx_inputs = UnspentBoxes::from_boxes(Box::new([]));
-    let tx_data_inputs = TxDataInputs::from_boxes(Box::new([]));
-    let send_change_to =
+fn test_sign_transaction() {
+    let pi = DlogProverInput::random();
+    let sk = SecretKey::from(sigma_tree::wallet::secret_key::SecretKey::from(pi.clone()));
+    let pk: SigmaProp = pi.public_image().into();
+    let tree = ErgoTree::from(Rc::new(Expr::Const(pk.into())));
+
+    let input_box = ErgoBox::new(
+        1000000000u64.try_into().unwrap(),
+        tree,
+        vec![],
+        NonMandatoryRegisters::empty(),
+        0,
+        TxId::zero(),
+        0,
+    );
+    let input_box_json_str = serde_json::to_string(&input_box).unwrap();
+    let box_json = JsValue::from_str(input_box_json_str.as_str());
+    let tx_inputs = ErgoBoxes::from_boxes_json(Box::new([box_json])).unwrap();
+    let recipient =
         Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
             .expect("failed");
-    let recipient =
+    let contract = Contract::pay_to_address(&recipient).expect("failed");
+    let outbox =
+        ErgoBoxCandidate::new(&BoxValue::from_u32(10000000).unwrap(), 0, &contract).unwrap();
+    let tx_outputs = ErgoBoxCandidates::new(&outbox);
+    let fee = BoxValue::from_u32(1000000).unwrap();
+    let change_address =
         Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
             .expect("failed");
-
-    let contract = Contract::pay_to_address(recipient).expect("failed");
-
-    let outbox = ErgoBoxCandidate::new(1, 0, contract);
-    let tx_outputs = TxOutputCandidates::new(outbox);
+    let min_change_value = BoxValue::MIN();
+    let tx_builder = TxBuilder::new(
+        BoxSelector::Simple,
+        &tx_inputs,
+        &tx_outputs,
+        0,
+        &fee,
+        &change_address,
+        &min_change_value,
+    )
+    .unwrap();
+    let tx = tx_builder.build().unwrap();
+    let wallet = Wallet::from_secret(&sk);
     let dummy_ctx = ErgoStateContext::dummy();
-    let wallet = Wallet::from_mnemonic("", "");
-    let res = wallet.new_signed_transaction(
-        dummy_ctx,
-        tx_inputs,
-        tx_data_inputs,
-        tx_outputs,
-        send_change_to,
-        1,
-        1,
+    let res = wallet.sign_transaction(
+        &dummy_ctx,
+        &tx,
+        &tx_inputs,
+        &ErgoBoxes::from_boxes_json(Box::new([])).unwrap(),
     );
-    assert!(res.is_err());
+    let _signed_tx = res.unwrap();
 }
 
 #[wasm_bindgen_test]
@@ -42,3 +78,44 @@ fn test_random() {
     let sk2 = SecretKey::random_dlog();
     assert_ne!(sk1, sk2);
 }
+
+#[wasm_bindgen_test]
+fn test_tx_builder() {
+    let box_json = JsValue::from_str(
+        r#"{
+          "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+          "value": 67500000000,
+          "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+          "assets": [],
+          "creationHeight": 284761,
+          "additionalRegisters": {},
+          "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+          "index": 1
+        }"#,
+    );
+    let tx_inputs = ErgoBoxes::from_boxes_json(Box::new([box_json])).unwrap();
+    let recipient =
+        Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
+            .expect("failed");
+    let contract = Contract::pay_to_address(&recipient).expect("failed");
+    let outbox =
+        ErgoBoxCandidate::new(&BoxValue::from_u32(10000000).unwrap(), 0, &contract).unwrap();
+    let tx_outputs = ErgoBoxCandidates::new(&outbox);
+    let fee = BoxValue::from_u32(1000000).unwrap();
+    let change_address =
+        Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
+            .expect("failed");
+    let min_change_value = BoxValue::MIN();
+    let tx_builder = TxBuilder::new(
+        BoxSelector::Simple,
+        &tx_inputs,
+        &tx_outputs,
+        0,
+        &fee,
+        &change_address,
+        &min_change_value,
+    )
+    .unwrap();
+    // assert!(tx_builder.is_ok());
+    let _tx = tx_builder.build().unwrap();
+}